Django、テンプレートの探索順序

概要

Djangoのテンプレートが探される順序を説明していきます。通常のテンプレートのほか、フォームウィジェットのテンプレートにも触れていきます。

通常のテンプレート

まずはrender()get_template()、クラスベースビューの内部で呼ばれる場合の探索順です。

この場合の探索順は、settings.pyTEMPLATESの設定によります。デフォルトでは、プロジェクトを作ると次のようになっています。

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        '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',
            ],
        },
    },
]

テンプレートの探索順において重要なのは、DIRSAPP_DIRSの2つです。

DIRS

最初に探索されるのが、このDIRSに指定したパスです。空だった場合は探索されません。

例えば'DIRS': [os.path.join(BASE_DIR, 'templates')]とした場合...これはプロジェクト直下、manage.pyと同じ階層のtemplatesディレクトリを表しますが、そこが真っ先に探索されます。
プロジェクト直下にtemplatesがある例

APP_DIRS

'APP_DIRS': Trueとしていた場合は、DIRSパスの次にアプリケーションディレクトリ内のtemplatesが探索されるようになります。
アプリケーション内のtemplates

アプリケーションというのは上の画像でいうappだけではありません。具体的には、settings.pyINSTALLED_APPSに指定されたものがそうです。

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app.apps.AppConfig',  # マイアプリケーション
]

adminやauthの中にもtemplatesディレクトリがあり、それも当然探索されます。また、サードパーティ製のDjangoアプリケーションをpipしてINSTALLED_APPSに足した場合も同様に、その中にあるtemplatesが探索されます。

そのため、APP_DIRSをFalseにしてしまうと管理画面やサードパーティ製Djangoアプリのテンプレートが見つからなくなります。Trueのままにしておきましょう。

テンプレートの上書き

ここまでの話がわかると、上書きも簡単に行えます。DIRSのパスが優先的に探索されるので、上書きしたければそちらに同名のファイルを置くことになります。

次の画像は'DIRS': [os.path.join(BASE_DIR, 'templates')]として、プロジェクト直下にtemplatesを作り、adminのlogin.htmlを上書きしている例です。
adminのlogin.htmlを上書き

pipでインストールしたDjangoアプリケーションのテンプレートも同様に上書きできます。

アプリケーション内で上書きする

APP_DIRSでの読み込みにも順番があるので、やろうと思えばappアプリケーション内のtemplates内にadmin/login.htmlを配置すれば上書きすることはできます。その場合はINSTALLED_APPSでappをadminよりも上にする必要があります。

フォームのテンプレート

フォームのウィジェットは、それがどういうHTMLになるかをテンプレートで作成しています。

次のコードは、Djangoのソースコードを抜粋したものです。TextInputは<input type="text"な要素になりますが、それは'django/forms/widgets/text.html'というテンプレートで作られています。

class TextInput(Input):
    input_type = 'text'
    template_name = 'django/forms/widgets/text.html'


class NumberInput(Input):
    input_type = 'number'
    template_name = 'django/forms/widgets/number.html'


class EmailInput(Input):
    input_type = 'email'
    template_name = 'django/forms/widgets/email.html'

難しいことをしようとすると、独自のウィジェットクラスを作ることもあります。

class SuggestTagWidget(forms.SelectMultiple):
    """タグをサジェストして登録するタイプのウィジェット"""
    template_name = 'app/widgets/suggest_tag.html'

ややこしいのは、ここで指定しているテンプレートの探索順序が通常のものと違うということです。

具体的には、django.forms.templates内をまず探索し、それから各アプリケーション内のtemplates内を探索します。通常のテンプレートにあったDIRSは探索しません。

独自のウィジェットクラスを作った場合、ウィジェットのテンプレート(上で言うapp/widgets/suggest_tag.html)をアプリケーション内templatesの中に配置すれば読み込んではくれますが、アプリケーション内にtemplatesを配置しない主義の方も多くいます。

そういった場合は次の方法が使えます。settings.pyに次のように書きます。

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.forms',  # 足す
    'app.apps.AppConfig',  # マイアプリケーション
]
...
...
FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'

FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'によって、通常のテンプレートと同様に探索してくれるようになります。

INSTALLED_APPSにdjango.formsを何故足しているかというと、'django/forms/widgets/text.html'のようなDjango標準のウィジェットテンプレートを見つけるためです。

ウィジェットテンプレートの上書き

サードパーティ製アプリケーションのウィジェットテンプレートならば、各アプリケーション内のtemplates内に同名のディレクトリ・ファイルを作れば上書きできます。通常のテンプレート上書きと同様に、INSTALLED_APPSの順番に注意してください。

一般的には、プロジェクト直下などにtemplatesを配置し、そこで上書きする方が管理は楽です。

そのためには、上でやったFORM_RENDERER = 'django.forms.renderers.TemplatesSetting'といった設定を忘れないようにしておきましょう。それさえしておけば、次の画像のようにDjango標準のウィジェットテンプレートでさえ上書きすることができます。
TexttInputのテンプレートを上書き

同様にして、サードパーティ製のアプリケーションのウィジェットテンプレートも上書きできます。