4.

Django settings.pyのTEMPLATES設定|DIRS・APP_DIRS・context_processors

編集
この記事の要点
  • TEMPLATES は配列: 複数バックエンド(DjangoTemplates / Jinja2)を併用可能
  • BACKEND: django.template.backends.django.DjangoTemplates が標準
  • DIRS: プロジェクト共通テンプレートのディレクトリ。BASE_DIR / "templates"
  • APP_DIRS=True: 各アプリの templates/<app>/ を自動探索
  • OPTIONS.context_processors: 全テンプレートに自動注入される変数({{ user }} 等)

TEMPLATES の基本構造

# 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',
            ],
        },
    },
]

TEMPLATES はリストオブディクトです。1 要素 = 1 テンプレートエンジンの設定。複数エンジンを並べて先頭から探索する形になります。

各キーの意味

キー意味
BACKEND使用するテンプレートエンジンのクラスdjango.template.backends.django.DjangoTemplates / django.template.backends.jinja2.Jinja2
NAMEこのエンジンの識別名(省略時はクラス名から自動)'django'
DIRSテンプレートを探すディレクトリのリスト[BASE_DIR / 'templates']
APP_DIRS各 INSTALLED_APPS の templates/ も探すかTrue
OPTIONSエンジン固有設定context_processors, loaders, debug, autoescape ...

テンプレート探索順

Django は次の順でテンプレートを探します:

  1. DIRS に書かれたディレクトリを順番に
  2. APP_DIRS=True なら INSTALLED_APPS 順に各アプリの templates/
  3. 見つかった最初のものを使う
myproject/
├── templates/             ← DIRS で指定
│   ├── base.html
│   └── home.html
├── polls/
│   └── templates/
│       └── polls/         ← APP_DIRS=True で自動探索
│           ├── index.html
│           └── detail.html
└── blog/
    └── templates/
        └── blog/
            └── post.html

アプリ名のサブディレクトリで包むのが定石(polls/templates/polls/index.html)。これは複数アプリで同名テンプレートが衝突しないようにするためです。

DIRS の書き方(Pathlib)

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')],

context_processors(全テンプレートに変数注入)

すべてのテンプレートで自動的に使える変数を提供する関数群です:

プロセッサ提供する変数
django.template.context_processors.debugdebug, sql_queries(DEBUG=True 時のみ)
django.template.context_processors.requestrequest(HttpRequest オブジェクト)
django.contrib.auth.context_processors.authuser, perms
django.contrib.messages.context_processors.messagesmessages
django.template.context_processors.mediaMEDIA_URL
django.template.context_processors.staticSTATIC_URL
django.template.context_processors.csrfcsrf_token
django.template.context_processors.tzTIME_ZONE
django.template.context_processors.i18nLANGUAGES, LANGUAGE_CODE

カスタム context_processor を作る

# 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 }} が使える

カスタムタグ・フィルタの登録

# 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}!"
{# template.html #}
{% load my_tags %}

{{ name|upper_first }}
{% greeting "Alice" %}

Django Templates と Jinja2 の併用

Django 1.8+ から複数バックエンドを登録できます。Jinja2 は速くて柔軟ですが、Django テンプレートが {{ user.is_authenticated }} などフレームワーク統合は強い:

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 があれば優先

Jinja2 では jinja2/ サブディレクトリが標準(Django Templates の templates/ と区別するため)。

OPTIONS の主要設定

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'],
        },
    },
]

cached.Loader でパフォーマンス

本番では cached.Loader を使うとテンプレートのパースをキャッシュして高速化。ただし APP_DIRS排他なので明示的に loaders を書く必要があります:

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',
                ]),
            ],
        },
    },
]

テンプレート継承パターン

{# 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 %}

Bootstrap / Tailwind 連携

{# 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>

環境別 設定の切替

# 本番では 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'

トラブルシューティング

症状原因対処
TemplateDoesNotExist: polls/index.htmlDIRS / APP_DIRS / アプリ登録漏れ探索パスを print(settings.TEMPLATES) 等で確認
{{ request.user }} が動かないcontext_processors に request / auth が無い2 つ追加
{% csrf_token %} でエラーcontext_processors の csrf が必要(標準で入る)RequestContext を使う / render() を使う
cached.Loader を有効化したら変更が反映されないサーバ再起動が必要開発中は APP_DIRS=True、本番のみ cached
テンプレートタグが見つからないtemplatetags/__init__.py 無し空ファイルでも置く

FAQ

Q: BASE_DIR / 'templates'os.path.join(BASE_DIR, 'templates') どっちが良い?
A: 新規プロジェクトは Pathlib(前者)。Django 3.1+ で startproject も Pathlib に変わった。可読性も高い。

Q: テンプレートを動的に追加したい
A: DIRS に動的にパスを追加するか、カスタムテンプレートローダーを実装。プラグイン的に登録するなら libraries + builtins

Q: 1 つのアプリで複数のテンプレートディレクトリを持ちたい
A: アプリ内に templates/<app>/... を作るのが標準。さらに分けたければ DIRS で追加。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. Templateの使用準備
  2. Template の定義方法
  3. テンプレートの作成と共通化
  4. setting.pyにおけるテンプレートの設定
  5. テンプレートの名前の重複について
  6. 静的ファイルの読み込み
  7. if文
  8. テンプレートで定数を使用する方法
  9. aタグのhrefの記載方法

最近更新/作成されたページ