PythonDjango

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 = [
    'app.apps.AppConfig',  # マイアプリケーション
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

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 = [
    'app.apps.AppConfig',  # マイアプリケーション
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.forms',  # 足す
]
...
...
FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'

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

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

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

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

Django標準のウィジェットを上書きするケースはそうそうないはずですが、それをしたい場合は、上でやったFORM_RENDERER = 'django.forms.renderers.TemplatesSetting'という設定が必要です。これをしないと、django.forms.templates内が真っ先に検索され、標準のウィジェットがそこで見つかるからです。

プロジェクト直下などにtemplatesを配置し、そこで上書きしたい場合は、上でやったFORM_RENDERER = 'django.forms.renderers.TemplatesSetting'といった設定さえしておけば、次の画像のように上書きすることができます。Django標準のウィジェットも、他アプリケーションのウィジェットも同様です。
TexttInputのテンプレートを上書き