タイトル: Templateの使用準備
SEOタイトル: Django Template 設定と使い方完全ガイド(DIRS / extends / block / フィルター)
この本文には実行可能なタグ等が含まれるため、安全のため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 Language (DTL) を使うには <strong><code>settings.py</code> の TEMPLATES 設定</strong>が必須</li><li><strong><code>'DIRS': [BASE_DIR / 'templates']</code></strong> でプロジェクト共通テンプレ置き場を指定</li><li><strong><code>APP_DIRS: True</code></strong> で各アプリの <code><app>/templates/<app>/</code> を自動探索</li><li>基本文法: <strong><code>{{ variable }}</code></strong>(変数)/ <strong><code>{% tag %}</code></strong>(タグ)/ <strong><code>{{ x|filter }}</code></strong>(フィルター)</li><li>継承: <strong><code>{% extends 'base.html' %}</code></strong> + <strong><code>{% block content %}</code></strong> でレイアウト共通化</li></ul></td></tr></table>
<h2>Django Template の全体像</h2>
<p>Django Template Language (DTL) は Django 標準のテンプレートエンジン。HTML に <code>{% tag %}</code> や <code>{{ variable }}</code> を埋め込み、ビューから渡されたコンテキストで描画します。Jinja2 も切り替え可能ですが、DTL がデフォルトで完備されているので本記事は DTL を扱います。</p>
<h2>ステップ 1: settings.py の TEMPLATES 設定</h2>
<p>プロジェクト作成時にデフォルトで生成されていますが、<strong>DIRS</strong> を埋めるのが最初の作業です:</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-python"># mysite/settings.py
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [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>
<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>BACKEND</td><td>テンプレートエンジン本体(DTL or Jinja2)</td></tr>
<tr><td>DIRS</td><td>共通テンプレディレクトリ(リスト順に探索)</td></tr>
<tr><td>APP_DIRS</td><td>True で <code><app>/templates/</code> を自動探索</td></tr>
<tr><td>context_processors</td><td>全テンプレに自動注入する変数群</td></tr>
</tbody>
</table>
<h2>ステップ 2: ディレクトリ構成</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-text">mysite/
├── manage.py
├── mysite/
│ ├── settings.py
│ └── urls.py
├── templates/ ← BASE_DIR / 'templates'(共通)
│ ├── base.html
│ └── partials/
│ ├── header.html
│ └── footer.html
└── blog/ ← アプリ
├── views.py
└── templates/ ← APP_DIRS で自動探索
└── blog/ ← ★名前衝突回避のためアプリ名でネスト
├── index.html
└── detail.html</code></pre>
<p><strong>重要</strong>: アプリの templates ディレクトリは <code>blog/templates/blog/</code> のように<strong>アプリ名でネスト</strong>するのが慣例。複数アプリで <code>index.html</code> が衝突するのを防ぎます。</p>
<h2>ステップ 3: ビューからテンプレートを呼ぶ</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-python"># blog/views.py
from django.shortcuts import render
def index(request):
context = {
'posts': Post.objects.all(),
'site_name': 'My Blog',
}
return render(request, 'blog/index.html', context)
# ↑ APP_DIRS により blog/templates/blog/index.html を解決</code></pre>
<h2>DTL の基本文法</h2>
<h3>変数表示 <code>{{ ... }}</code></h3>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><p>{{ post.title }}</p>
<p>{{ user.username }}</p>
<p>{{ posts.0.title }}</p> {# リストのインデックス #}
<p>{{ post.created_at|date:"Y-m-d" }}</p> {# フィルター適用 #}</code></pre>
<h3>タグ <code>{% ... %}</code></h3>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html">{% if user.is_authenticated %}
<p>こんにちは {{ user.username }} さん</p>
{% else %}
<a href="{% url 'login' %}">ログイン</a>
{% endif %}
{% for post in posts %}
<li>{{ post.title }} ({{ forloop.counter }}/{{ posts|length }})</li>
{% empty %}
<li>記事はまだありません</li>
{% endfor %}
{% url 'blog:detail' post.pk %} {# URL 解決 #}
{% csrf_token %} {# CSRF トークン #}
{% load humanize %} {# テンプレタグライブラリ読込 #}</code></pre>
<h3>主要フィルター一覧</h3>
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#e3f2fd"><th>フィルター</th><th>例</th><th>結果</th></tr></thead>
<tbody>
<tr><td><code>date</code></td><td><code>{{ d|date:"Y-m-d H:i" }}</code></td><td>2026-06-10 12:34</td></tr>
<tr><td><code>length</code></td><td><code>{{ list|length }}</code></td><td>要素数</td></tr>
<tr><td><code>default</code></td><td><code>{{ name|default:"匿名" }}</code></td><td>空なら "匿名"</td></tr>
<tr><td><code>truncatechars</code></td><td><code>{{ body|truncatechars:50 }}</code></td><td>50文字 + ...</td></tr>
<tr><td><code>safe</code></td><td><code>{{ html|safe }}</code></td><td>エスケープ無効化(★危険、入力検証必須)</td></tr>
<tr><td><code>linebreaks</code></td><td><code>{{ body|linebreaks }}</code></td><td>改行を <br> / <p> に</td></tr>
<tr><td><code>floatformat</code></td><td><code>{{ price|floatformat:2 }}</code></td><td>1234.50</td></tr>
<tr><td><code>upper / lower</code></td><td><code>{{ s|upper }}</code></td><td>大文字化</td></tr>
</tbody>
</table>
<h2>テンプレート継承 (extends / block)</h2>
<p>共通レイアウトを <code>base.html</code> に定義し、各ページで上書きします:</p>
<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 #}
{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>{% block title %}{{ site_name }}{% endblock %}</title>
<link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
{% include 'partials/header.html' %}
<main>
{% block content %}{% endblock %}
</main>
{% include 'partials/footer.html' %}
</body>
</html></code></pre><pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html">{# blog/templates/blog/index.html #}
{% extends 'base.html' %}
{% block title %}記事一覧 | {{ block.super }}{% endblock %}
{% block content %}
<h1>記事一覧</h1>
<ul>
{% for post in posts %}
<li><a href="{% url 'blog:detail' post.pk %}">{{ post.title }}</a></li>
{% endfor %}
</ul>
{% 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">{# 引数渡し #}
{% include 'partials/card.html' with post=post show_author=True %}
{# 親のコンテキスト引き継がない(独立) #}
{% include 'partials/card.html' with post=post only %}</code></pre>
<h2>{% load static %} と静的ファイル</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html">{% load static %}
<link rel="stylesheet" href="{% static 'blog/css/post.css' %}">
<img src="{% static 'blog/img/logo.png' %}" alt="logo">
<script src="{% static 'blog/js/main.js' %}"></script></code></pre><pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-python"># settings.py
STATIC_URL = '/static/'
STATICFILES_DIRS = [BASE_DIR / 'static'] # 開発時の追加探索パス
STATIC_ROOT = BASE_DIR / 'staticfiles' # collectstatic の出力先(本番)</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-python"># settings.py(本番のみ。DEBUG=True ならコメントアウト)
TEMPLATES = [
{
# ...
'APP_DIRS': False, # cached.Loader 使用時は False
'OPTIONS': {
'loaders': [
('django.template.loaders.cached.Loader', [
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
]),
],
'context_processors': [...],
},
},
]</code></pre>
<h2>FAQ</h2>
<p><strong>Q: テンプレートが見つからないと言われる</strong><br />
A: <code>blog/templates/blog/index.html</code> のように<strong>アプリ名でネスト</strong>しているか確認。また INSTALLED_APPS に登録され <code>APP_DIRS: True</code> である必要があります。</p>
<p><strong>Q: Jinja2 を使いたい</strong><br />
A: TEMPLATES に <code>BACKEND: 'django.template.backends.jinja2.Jinja2'</code> を追加。DTL と並行利用も可。</p>
<p><strong>Q: テンプレートで Python 関数を直接呼びたい</strong><br />
A: 不可(意図的制限)。<strong>カスタムテンプレートタグ / フィルター</strong>を <code>templatetags/</code> に作って <code>{% load %}</code> して使います。</p>