Djangoのテンプレートローダーを自作する

Djangoフレームワークのテンプレートエンジンは、『テンプレートをロードする処理』を自分で実装した処理に差し替えるための設定が用意されています。

django.template.loaders.base.Loader を継承したクラスを実装し、 settings.pyTEMPLATES を設定すると、処理を差し替えることができます。

※この記事の内容は、DjangoCongressJP 2023で発表した内容の一部をまとめなおしたものになります。

Djangoテンプレートエンジンを使いこなそう! - DjangoCongressJP 2023 @tokibito

アプリの用意

まずは、変更の比較のためにテンプレートを単純にレンダリングするビューを含むアプリを用意します。

python manage.py startapp myapp

myappのアプリをstartappで追加したら、 settings.pyINSTALLED_APPS に追加して、有効にしておきます。

myapp/views.py

from django.shortcuts import render

def my_view(request):
    return render(request, 'spam.html', {'name': 'egg'})

myapp/templates/spam.html

Hello {{ name }}

myproject/urls.py

from django.contrib import admin
from django.urls import path
from myapp import views

urlpatterns = [
    path('', views.my_view),
    path('admin/', admin.site.urls),
]

デフォルトのテンプレートローダーの動作を確認

この状態でrunserverを起動し、 http://127.0.0.1:8000/ にアクセスすると、myapp/templates/spam.html` が使われる表示となります。

テンプレートローダーを自作する

今回は、データベースからテンプレートデータをロードするようなローダーを作成してみます。

models.py

テンプレートデータを保存しておくモデルです。 name にテンプレート名(テンプレートパス)を入れておき、 source にテンプレートの内容を入れておく想定です。

from django.db import models

class Template(models.Model):
    name = models.CharField(max_length=255)
    source = models.TextField()

    def __str__(self):
        return f"{self.name}"

makemigrationsとmigrateでテーブルを作っておきます。

python manage.py makemigrations myapp
python manage.py migrate myapp

myapp/admin.py

Django adminからTemplateのモデルを編集できるように、admin.pyを書き換えます。

from django.contrib import admin
from .models import Template

admin.site.register(Template)

myapp/db_loader.py

テンプレートローダーのクラスです。

from django.template.loaders.base import Loader as BaseLoader
from django.template import Origin
from .models import Template

class DatabaseOrigin(Origin):
    def __init__(self, name, template_name=None, loader=None, object=None):
        super().__init__(name, template_name, loader)
        self.object = object


class Loader(BaseLoader):
    def get_contents(self, origin):
        return origin.object.source

    def get_template_sources(self, template_name):
        # データベースからテンプレートデータを取得
        template = Template.objects.filter(name=template_name).first()
        if not template:
            return []
        # テンプレートのデータをカスタムのOriginクラスでラップして返す。
        origin = DatabaseOrigin(
            name=template_name,
            template_name=template_name,
            loader=self,
            object=template)
        return [origin]

これでテンプレートローダーは完成です。

自作したテンプレートローダーを有効にする

作成したテンプレートローダーを有効にしてみます。 settings.pyTEMPLATES 設定を書き換えます。

APP_DIRS を削除(またはコメントアウト)して、 loaders キーを追加し、作成したテンプレートローダーと app_directories.Loader を設定します。

settings.py(抜粋)

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        # 'APP_DIRS': True,  # APP_DIRSは、loaders設定を入れる場合には、設定できない。
        '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',
            ],
            'loaders': [
                # 複数のテンプレートローダーが設定されている場合、前にあるほうから、順番にテンプレートを探す
                (
                    # 自作したテンプレートローダー
                    'myapp.db_loader.Loader',
                ),
                (
                    # Django adminなどが動作するように、app_directories.Loaderを有効にしておく
                    'django.template.loaders.app_directories.Loader',
                ),
            ]
        }
    },
]

動かしてみる

この状態で http://127.0.0.1:8000 にアクセスすると、データベースにはテンプレートデータがまだ無いので、ファイルから読み込まれたテンプレートが使用され、挙動に変化はありません。

Django adminからnameを spam.html としたテンプレートデータを追加します。

そうすると、今度は自作したテンプレートローダー側が読み取った、データベース上のデータがテンプレートとして使用されます。

データベース上のテンプレートデータを利用するテンプレートローダーを作成することができました。

サンプルコード一式

github.com

参考