タイトル: Template の定義方法
SEOタイトル: Django Template の定義方法完全ガイド — 継承と部分挿入
この本文には実行可能なタグ等が含まれるため、安全のためHTMLソースとして表示しています。本文データは変更していません。
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;background:#fffbea;border:1px solid #f4c430;margin:1em 0;width:100%"><tr><td style="background:#f4c430;color:#000;font-weight:bold;padding:8px">この記事の要点</td></tr><tr><td style="padding:10px"><ul><li>Django Template は <code>settings.py</code> の <code>TEMPLATES</code> 設定 + 各アプリの <code>templates/</code> ディレクトリで動く</li><li><strong>探索順</strong>: <code>DIRS</code> リスト → <code>APP_DIRS=True</code> ならインストール済アプリの <code>templates/</code></li><li><strong>継承</strong>: 親に <code>{% block content %}{% endblock %}</code>、子で <code>{% extends "base.html" %}</code> + <code>{% block content %}...{% endblock %}</code></li><li><strong>部分挿入</strong>: <code>{% include "header.html" %}</code> でファイルを差し込む</li><li><strong>HTML エスケープはデフォルト ON</strong>。<code>{{ var|safe }}</code> または <code>{% autoescape off %}</code> で解除(XSS 注意)</li><li>カスタムタグ/フィルタは <code>templatetags/</code> 配下で定義し、<code>{% load mytags %}</code> で読み込む</li></ul></td></tr></table>
<h2>settings.py — TEMPLATES 設定</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-python"># myproject/settings.py
import os
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')], # プロジェクト全体共通
'APP_DIRS': True, # 各アプリの templates/ も探索
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]</code></pre>
<h2>ディレクトリ配置</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-text">myproject/
├── manage.py
├── myproject/
│ └── settings.py
├── templates/ ← プロジェクト共通(DIRS で指定)
│ ├── base.html
│ └── 404.html
└── myapp/
├── views.py
└── templates/ ← アプリ別(APP_DIRS=True で自動)
└── myapp/ ← ネームスペース推奨
├── article_list.html
└── article_detail.html</code></pre>
<p>アプリの <code>templates/</code> 直下にファイルを置くと、他アプリの同名ファイルと衝突するため、<strong><code>templates/myapp/</code></strong> のようにアプリ名のサブディレクトリを挟むのが Django 標準のベストプラクティスです。</p>
<h2>ベーステンプレート (継承元)</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html">{# templates/base.html #}
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>{% block title %}MySite{% endblock %}</title>
{% block extra_head %}{% endblock %}
</head>
<body>
{% include "header.html" %}
<main>
{% block content %}{% endblock %}
</main>
{% include "footer.html" %}
{% block extra_js %}{% endblock %}
</body>
</html></code></pre>
<h2>子テンプレート (継承)</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html">{# templates/myapp/article_detail.html #}
{% extends "base.html" %}
{% load static %}
{% block title %}{{ article.title }} | MySite{% endblock %}
{% block extra_head %}
<link rel="stylesheet" href="{% static 'myapp/article.css' %}">
{% endblock %}
{% block content %}
<article>
<h1>{{ article.title }}</h1>
<p class="meta">
投稿者: {{ article.author.name }} / {{ article.created_at|date:"Y-m-d H:i" }}
</p>
<div class="body">
{{ article.body|linebreaks }}
</div>
<h2>コメント ({{ comments|length }})</h2>
{% for c in comments %}
<div class="comment">
<strong>{{ c.author }}</strong>: {{ c.body }}
</div>
{% empty %}
<p>コメントはまだありません。</p>
{% endfor %}
</article>
{% endblock %}</code></pre>
<h2>部分挿入 (include)</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html">{# templates/header.html #}
<header>
<a href="/">MySite</a>
<nav>
<a href="/articles/">記事</a>
{% if user.is_authenticated %}
<a href="/logout/">ログアウト ({{ user.username }})</a>
{% else %}
<a href="/login/">ログイン</a>
{% endif %}
</nav>
</header>
{# 呼び出し側 #}
{% include "header.html" %}
{# 変数を渡しながら include #}
{% include "card.html" with title="ようこそ" body=user.name only %}
{# only を付けると親コンテキストを渡さない #}</code></pre>
<h2>テンプレートタグ</h2>
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#e3f2fd"><th>タグ</th><th>意味</th></tr></thead>
<tbody>
<tr><td><code>{% if %}</code> / <code>{% elif %}</code> / <code>{% else %}</code></td><td>条件分岐</td></tr>
<tr><td><code>{% for x in xs %}</code> / <code>{% empty %}</code></td><td>ループ</td></tr>
<tr><td><code>{% url 'name' arg %}</code></td><td>URL 逆引き</td></tr>
<tr><td><code>{% csrf_token %}</code></td><td>CSRF トークン</td></tr>
<tr><td><code>{% block %}</code> / <code>{% extends %}</code></td><td>継承</td></tr>
<tr><td><code>{% include %}</code></td><td>部分挿入</td></tr>
<tr><td><code>{% load %}</code></td><td>カスタムタグライブラリ読込</td></tr>
<tr><td><code>{% with %}</code></td><td>一時変数</td></tr>
<tr><td><code>{% spaceless %}</code></td><td>タグ間の空白除去</td></tr>
<tr><td><code>{% autoescape on/off %}</code></td><td>HTML エスケープ切替</td></tr>
</tbody>
</table>
<h2>フィルタ (パイプ)</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html">{{ name|upper }} {# 大文字 #}
{{ body|truncatechars:100 }} {# 100 文字で省略 #}
{{ created_at|date:"Y-m-d H:i" }} {# 日付フォーマット #}
{{ price|floatformat:2 }} {# 小数2位 #}
{{ html|safe }} {# エスケープしない(XSS 注意) #}
{{ list|length }} {# 件数 #}
{{ value|default:"N/A" }} {# 値が偽なら N/A #}
{{ text|linebreaks }} {# 改行を <p><br> に #}
{{ user.name|escape }} {# 明示的にエスケープ #}</code></pre>
<h2>autoescape — XSS 対策</h2>
<p>Django テンプレートは<strong>デフォルトで全変数を HTML エスケープ</strong>します。<code><script>alert(1)</script></code> をそのまま埋めても文字列として表示されます。</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html">{# 通常: エスケープされる #}
{{ user_input }}
{# → <script>alert(1)</script> #}
{# 明示的にエスケープしない(XSS 注意) #}
{{ html_content|safe }}
{# ブロック単位で切替 #}
{% autoescape off %}
{{ trusted_html }}
{% endautoescape %}</code></pre>
<p><strong>safe</strong> や <code>autoescape off</code> はサニタイズ済みデータにのみ使うこと。ユーザー入力に直接使うと XSS の温床になります。</p>
<h2>カスタムタグ・フィルタ</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-text">myapp/
└── templatetags/
├── __init__.py
└── myapp_extras.py</code></pre><pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-python"># myapp/templatetags/myapp_extras.py
from django import template
register = template.Library()
@register.filter
def yen(value):
""" 1000000 → 1,000,000 円 """
try:
return f"{int(value):,} 円"
except (TypeError, ValueError):
return value
@register.simple_tag
def current_year():
import datetime
return datetime.date.today().year
@register.inclusion_tag('myapp/_pagination.html')
def show_pagination(page_obj):
return {'page_obj': page_obj}</code></pre><pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html">{# テンプレート側で読み込み #}
{% load myapp_extras %}
{{ order.amount|yen }} {# → 1,000,000 円 #}
© {% current_year %} MySite
{% show_pagination page_obj %}</code></pre>
<h2>テンプレート探索順</h2>
<ol>
<li><code>TEMPLATES.DIRS</code> リスト(順序通り)</li>
<li><code>APP_DIRS=True</code> なら <code>INSTALLED_APPS</code> の順に各アプリの <code>templates/</code></li>
</ol>
<p>先に見つかったものが使われます。サードパーティアプリのテンプレートを<strong>上書きしたい</strong>場合は、<code>DIRS</code> に書いたプロジェクト共通 <code>templates/</code> 配下に同じパスで配置すれば優先されます。</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-text"># 例: django.contrib.admin/templates/admin/index.html を上書きしたい
templates/
└── admin/
└── index.html ← これがあれば admin デフォルトを上書き</code></pre>
<h2>View からテンプレートを呼ぶ</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-python"># myapp/views.py
from django.shortcuts import render
from .models import Article
def article_detail(request, slug):
article = Article.objects.get(slug=slug)
return render(request, 'myapp/article_detail.html', {
'article': article,
'comments': article.comments.all(),
})
# CBV の場合
from django.views.generic import DetailView
class ArticleDetailView(DetailView):
model = Article
template_name = 'myapp/article_detail.html' # 省略時は myapp/article_detail.html が自動
context_object_name = 'article'</code></pre>
<h2>FAQ</h2>
<p><strong>Q: テンプレートが見つからない (TemplateDoesNotExist)</strong><br />
A: <code>DEBUG=True</code> 状態でエラー画面に「探したパス一覧」が出る。配置ミス・<code>APP_DIRS</code> 設定漏れ・<code>INSTALLED_APPS</code> 登録漏れを確認。</p>
<p><strong>Q: Jinja2 を使いたい</strong><br />
A: <code>TEMPLATES</code> の <code>BACKEND</code> を <code>django.template.backends.jinja2.Jinja2</code> に。Django タグ (<code>{% url %}</code> 等) は使えなくなる。</p>
<p><strong>Q: include vs extends の使い分け</strong><br />
A: <code>extends</code> は親レイアウトを継承して<strong>差し替える</strong>関係。<code>include</code> は同じ<strong>部品を埋め込む</strong>関係。ヘッダ・フッタ・カードは include、ページ全体は extends。</p>