タイトル: setting.pyにおけるテンプレートの設定
SEOタイトル: Django settings.py TEMPLATES 設定完全ガイド(DIRS / APP_DIRS / context_processors)
この本文には実行可能なタグ等が含まれるため、安全のため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><strong>TEMPLATES は配列</strong>: 複数バックエンド(DjangoTemplates / Jinja2)を併用可能</li><li><strong>BACKEND</strong>: <code>django.template.backends.django.DjangoTemplates</code> が標準</li><li><strong>DIRS</strong>: プロジェクト共通テンプレートのディレクトリ。<code>BASE_DIR / "templates"</code></li><li><strong>APP_DIRS=True</strong>: 各アプリの <code>templates/<app>/</code> を自動探索</li><li><strong>OPTIONS.context_processors</strong>: 全テンプレートに自動注入される変数(<code>{{ user }}</code> 等)</li></ul></td></tr></table>
<h2>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
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,
'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>
<p>TEMPLATES は<strong>リストオブディクト</strong>です。1 要素 = 1 テンプレートエンジンの設定。複数エンジンを並べて先頭から探索する形になります。</p>
<h2>各キーの意味</h2>
<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>BACKEND</code></td><td>使用するテンプレートエンジンのクラス</td><td><code>django.template.backends.django.DjangoTemplates</code> / <code>django.template.backends.jinja2.Jinja2</code></td></tr>
<tr><td><code>NAME</code></td><td>このエンジンの識別名(省略時はクラス名から自動)</td><td><code>'django'</code></td></tr>
<tr><td><code>DIRS</code></td><td>テンプレートを探すディレクトリのリスト</td><td><code>[BASE_DIR / 'templates']</code></td></tr>
<tr><td><code>APP_DIRS</code></td><td>各 INSTALLED_APPS の <code>templates/</code> も探すか</td><td><code>True</code></td></tr>
<tr><td><code>OPTIONS</code></td><td>エンジン固有設定</td><td>context_processors, loaders, debug, autoescape ...</td></tr>
</tbody>
</table>
<h2>テンプレート探索順</h2>
<p>Django は次の順でテンプレートを探します:</p>
<ol>
<li><code>DIRS</code> に書かれたディレクトリを順番に</li>
<li><code>APP_DIRS=True</code> なら <code>INSTALLED_APPS</code> 順に各アプリの <code>templates/</code></li>
<li>見つかった最初のものを使う</li>
</ol>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-text">myproject/
├── templates/ ← DIRS で指定
│ ├── base.html
│ └── home.html
├── polls/
│ └── templates/
│ └── polls/ ← APP_DIRS=True で自動探索
│ ├── index.html
│ └── detail.html
└── blog/
└── templates/
└── blog/
└── post.html</code></pre>
<p><strong>アプリ名のサブディレクトリで包む</strong>のが定石(<code>polls/templates/polls/index.html</code>)。これは複数アプリで同名テンプレートが衝突しないようにするためです。</p>
<h2>DIRS の書き方(Pathlib)</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-python">from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
# 単一ディレクトリ
'DIRS': [BASE_DIR / 'templates'],
# 複数ディレクトリ
'DIRS': [
BASE_DIR / 'templates',
BASE_DIR / 'shared_templates',
'/var/www/global_templates',
],
# 旧形式(Django 3.0 以前)
import os
'DIRS': [os.path.join(BASE_DIR, 'templates')],</code></pre>
<h2>context_processors(全テンプレートに変数注入)</h2>
<p>すべてのテンプレートで自動的に使える変数を提供する関数群です:</p>
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#e8f5e9"><th>プロセッサ</th><th>提供する変数</th></tr></thead>
<tbody>
<tr><td><code>django.template.context_processors.debug</code></td><td><code>debug</code>, <code>sql_queries</code>(DEBUG=True 時のみ)</td></tr>
<tr><td><code>django.template.context_processors.request</code></td><td><code>request</code>(HttpRequest オブジェクト)</td></tr>
<tr><td><code>django.contrib.auth.context_processors.auth</code></td><td><code>user</code>, <code>perms</code></td></tr>
<tr><td><code>django.contrib.messages.context_processors.messages</code></td><td><code>messages</code></td></tr>
<tr><td><code>django.template.context_processors.media</code></td><td><code>MEDIA_URL</code></td></tr>
<tr><td><code>django.template.context_processors.static</code></td><td><code>STATIC_URL</code></td></tr>
<tr><td><code>django.template.context_processors.csrf</code></td><td><code>csrf_token</code></td></tr>
<tr><td><code>django.template.context_processors.tz</code></td><td><code>TIME_ZONE</code></td></tr>
<tr><td><code>django.template.context_processors.i18n</code></td><td><code>LANGUAGES</code>, <code>LANGUAGE_CODE</code></td></tr>
</tbody>
</table>
<h3>カスタム context_processor を作る</h3>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-python"># myproject/context_processors.py
def site_settings(request):
return {
'SITE_NAME': 'My Awesome Site',
'SUPPORT_EMAIL': 'support@example.com',
'UNREAD_COUNT': request.user.notifications.unread().count() if request.user.is_authenticated else 0,
}
# settings.py
TEMPLATES = [
{
...
'OPTIONS': {
'context_processors': [
...
'myproject.context_processors.site_settings',
],
},
},
]
# 以後、全テンプレートで {{ SITE_NAME }} が使える</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"># myapp/templatetags/__init__.py(空ファイル必須)
# myapp/templatetags/my_tags.py
from django import template
register = template.Library()
@register.filter
def upper_first(value):
return value[:1].upper() + value[1:]
@register.simple_tag
def greeting(name):
return f"Hello, {name}!"</code></pre><pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html">{# template.html #}
{% load my_tags %}
{{ name|upper_first }}
{% greeting "Alice" %}</code></pre>
<h2>Django Templates と Jinja2 の併用</h2>
<p>Django 1.8+ から複数バックエンドを登録できます。Jinja2 は速くて柔軟ですが、Django テンプレートが {{ user.is_authenticated }} などフレームワーク統合は強い:</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-python">TEMPLATES = [
{
'BACKEND': 'django.template.backends.jinja2.Jinja2',
'DIRS': [BASE_DIR / 'jinja2'],
'APP_DIRS': True,
'OPTIONS': {
'environment': 'myproject.jinja2.environment',
},
},
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [...],
},
},
]
# 探索順は先頭から。Jinja2 の jinja2/ に同名 .html があれば優先</code></pre>
<p>Jinja2 では <code>jinja2/</code> サブディレクトリが標準(Django Templates の <code>templates/</code> と区別するため)。</p>
<h2>OPTIONS の主要設定</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-python">TEMPLATES = [
{
...
'OPTIONS': {
'context_processors': [...],
# デフォルト True、False で disable
'debug': DEBUG,
# HTML エスケープを autoescape するか(デフォルト True)
'autoescape': True,
# 不明な変数を解決するときの値(デフォルトは空文字)
'string_if_invalid': 'INVALID: %s' if DEBUG else '',
# カスタムローダー(APP_DIRS=False のとき有効)
'loaders': [
('django.template.loaders.cached.Loader', [
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
]),
],
# カスタムタグライブラリ
'libraries': {
'my_tags': 'myapp.templatetags.my_tags',
},
# 組み込みタグライブラリの追加
'builtins': ['myapp.templatetags.my_tags'],
},
},
]</code></pre>
<h3>cached.Loader でパフォーマンス</h3>
<p>本番では cached.Loader を使うとテンプレートのパースをキャッシュして高速化。ただし <code>APP_DIRS</code> と<strong>排他</strong>なので明示的に <code>loaders</code> を書く必要があります:</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-python">TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
# 'APP_DIRS': True, # ← コメントアウト
'OPTIONS': {
'context_processors': [...],
'loaders': [
('django.template.loaders.cached.Loader', [
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
]),
],
},
},
]</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/base.html #}
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>{% block title %}{{ SITE_NAME }}{% endblock %}</title>
{% load static %}
<link rel="stylesheet" href="{% static 'css/main.css' %}">
</head>
<body>
{% include "partials/_header.html" %}
<main>
{% block content %}{% endblock %}
</main>
{% include "partials/_footer.html" %}
</body>
</html>
{# templates/polls/index.html #}
{% extends "base.html" %}
{% block title %}投票一覧 | {{ SITE_NAME }}{% endblock %}
{% block content %}
<h1>投票一覧</h1>
<ul>
{% for q in latest_questions %}
<li>{{ q.question_text }}</li>
{% endfor %}
</ul>
{% endblock %}</code></pre>
<h2>Bootstrap / Tailwind 連携</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html">{# Bootstrap CDN #}
{% load static %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
{# django-crispy-forms + crispy-bootstrap5 で美フォーム #}
# pip install django-crispy-forms crispy-bootstrap5
# settings.py
INSTALLED_APPS += ['crispy_forms', 'crispy_bootstrap5']
CRISPY_ALLOWED_TEMPLATE_PACKS = 'bootstrap5'
CRISPY_TEMPLATE_PACK = 'bootstrap5'
# template
{% load crispy_forms_tags %}
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-primary">Submit</button>
</form></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"># 本番では cached.Loader、開発では APP_DIRS=True
if DEBUG:
TEMPLATES[0]['APP_DIRS'] = True
TEMPLATES[0]['OPTIONS'].pop('loaders', None)
else:
TEMPLATES[0]['APP_DIRS'] = False
TEMPLATES[0]['OPTIONS']['loaders'] = [
('django.template.loaders.cached.Loader', [
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
]),
]
# string_if_invalid を開発のみ
if DEBUG:
TEMPLATES[0]['OPTIONS']['string_if_invalid'] = 'INVALID: %s'</code></pre>
<h2>トラブルシューティング</h2>
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#fff3e0"><th>症状</th><th>原因</th><th>対処</th></tr></thead>
<tbody>
<tr><td><code>TemplateDoesNotExist: polls/index.html</code></td><td>DIRS / APP_DIRS / アプリ登録漏れ</td><td>探索パスを <code>print(settings.TEMPLATES)</code> 等で確認</td></tr>
<tr><td><code>{{ request.user }}</code> が動かない</td><td>context_processors に request / auth が無い</td><td>2 つ追加</td></tr>
<tr><td><code>{% csrf_token %}</code> でエラー</td><td>context_processors の csrf が必要(標準で入る)</td><td>RequestContext を使う / render() を使う</td></tr>
<tr><td>cached.Loader を有効化したら変更が反映されない</td><td>サーバ再起動が必要</td><td>開発中は APP_DIRS=True、本番のみ cached</td></tr>
<tr><td>テンプレートタグが見つからない</td><td><code>templatetags/__init__.py</code> 無し</td><td>空ファイルでも置く</td></tr>
</tbody>
</table>
<h2>FAQ</h2>
<p><strong>Q: <code>BASE_DIR / 'templates'</code> と <code>os.path.join(BASE_DIR, 'templates')</code> どっちが良い?</strong><br />
A: 新規プロジェクトは Pathlib(前者)。Django 3.1+ で <code>startproject</code> も Pathlib に変わった。可読性も高い。</p>
<p><strong>Q: テンプレートを動的に追加したい</strong><br />
A: <code>DIRS</code> に動的にパスを追加するか、カスタムテンプレートローダーを実装。プラグイン的に登録するなら <code>libraries</code> + <code>builtins</code>。</p>
<p><strong>Q: 1 つのアプリで複数のテンプレートディレクトリを持ちたい</strong><br />
A: アプリ内に <code>templates/<app>/...</code> を作るのが標準。さらに分けたければ DIRS で追加。</p>