自動化無しに生活無し

WEBとかAIとかLinux関係をひたすら書く備忘録系ブログ

Djangoで埋め込みカスタムテンプレートタグを実装する方法

thumbnail

Djangoでデータの値に応じて文字列を返したい場合は、カスタムテンプレートタグを使用すれば良い。下記記事は、ページ移動と検索を両立させるため、文字列を返す、カスタムテンプレートタグを実装している。

Djangoでページネーションを実装する方法【django.core.paginator】【パラメータ両立】

しかし、データの値に応じてHTMLタグを返却するには、上記の方法では成立しない。safeフィルタを使用すればHTMLタグを表示できるが、XSS脆弱性対策が課題になる。そこで、本記事ではHTMLタグを返却する埋め込みカスタムテンプレートタグ実装方法を解説する。

実装方法

まず、アプリのディレクトリ内にtemplatetagsディレクトリを作る。今回はアプリのディレクトリ名はdevlogとする

mkdir ./devlog/templatetags

続いて、templatetagsディレクトリ内にカスタムテンプレートタグの機能部に当たるPythonスクリプトを書く。今回はdiff_day.pyとする。

vi ./devlog/templatetags/diff_day.py

コードは以下。

from django import template
from django.utils import timezone
import datetime 

register = template.Library()

@register.inclusion_tag("devlog/left_day.html")
def left_day(deadline,start,progress):

    now         = datetime.datetime.utcnow().replace(tzinfo=timezone.utc)
    dead_diff   = deadline - now 
    start_diff  = now - start
    status      = ""

    if dead_diff.days >= 0 and start_diff.days >= 0:
        return { "left_day" : dead_diff.days }
    elif start_diff.days < 0:
        status  = "待機中"
    else:
        if progress >= 100:
            status  = "完了"
        else:
            status  = "期限切れ"

    return { "status": status }

上記コードは、left_dayという関数に、締め切り日時(deadline)、開始日時(start)、進捗状況(progress)の数値の3つの引数を与えている。このleft_dayをテンプレートであるHTMLから呼び出し、3つの引数を指定することで実行する。関数内では残り日数と進捗状況に応じて、status及びleft_dayが変化する。まだ始まっていない状態は『待機中』、締め切りを過ぎている状態で、なおかつ進捗100%であれば『完了』、100%未満であれば『期限切れ』をそれぞれstatusに与える。もし、既に始まっており、終わっていない状態であれば、残り日数に当たるleft_dayを返す。そして、関数はいずれも辞書型を返却する。

この辞書型の値の返却先はディレクティブである@register.inclusion_tag()で指定したテンプレートである。普段views.pyにてrender関数を使用してテンプレートのパスとcontextを指定してレンダリングしていた状況と考えればイメージしやすいだろう。

その、left_day.htmlが下記に当たる。

{% if status %}
<div class="project_header_center 
    {% if status == '完了' %}project_header_done
    {% elif status == '期限切れ' %}project_header_expire
    {% elif status == '待機中' %}project_header_wait
    {% endif %}">{{ status }}</div>
{% else %}
<div class="project_header_top">あと</div>
<div class="project_header_bottom {% if left_day < 3 %}left_day_danger{% endif %}" style="">{{ left_day }}日</div>
{% endif %}

普通のカスタムテンプレートタグ(simple_tag())には真似できない、複雑なHTMLと分岐が並ぶ。それぞれのステータスにおいて、別々のクラス名を与え、残り日数がある場合は、HTMLの構造を変えている。

そして、このカスタムテンプレートタグを呼び出すコードが下記。

{% load diff_day %}
<!--↑冒頭に設置-->

{% left_day content.deadline content.start content.progress %}

left_daydiff_day.pyにて定義した関数。続くcontent.deadline content.start content.progressこの3つは引数に値する。カスタムテンプレートタグで引数を指定するときはカンマ(,)は無しで。

さらに、settings.pyにてdiff_day.pyINSTALLED_APPSに代入し、実行可能な状態にさせる。

INSTALLED_APPS = [ 

    """省略"""

    'devlog.templatetags.diff_day',
]

埋め込みカスタムテンプレートタグが発動することで、下記のような複雑な表現が可能になるのだ。

埋め込みカスタムテンプレートタグが発動している

主な実装例

  • データの値によってHTMLを構造ごと変えたい場合
  • 文字列のみの返却ではテンプレートが煩雑になる場合
  • カスタムテンプレートタグで出力した文字列を元に、さらに分岐・繰り返し処理をしたい場合

埋め込みカスタムテンプレートタグは追加するたびにテンプレート(.html)とスクリプト(.py)の2つのファイルが増えていくので、みだりに追加しすぎるとどんどん煩雑になっていく。

返却する値が文字列だけで間に合う場合は、文字列を返すsimple_tag()の実装に留めたい。なお間違ってもsafeフィルタだけは使わないようにしたいところだ。

結論

Djangoはlaravelとは違ってテンプレートの自由度が限られているような気がする。故にカスタムテンプレートタグを使用しないと、詰む可能性がある。その場合、フロントの話なので、JSを使えばなんとかなりそうだが、それではユーザーの負担になるので、可能な限りサーバー側で処理が終わるようにしたい。

カスタムテンプレートタグは市販の教科書には殆ど掲載されていない内容なので、Djangoの公式ドキュメントも確認するべし。カスタムフィルタについても掲載されている。

https://docs.djangoproject.com/en/3.1/howto/custom-template-tags/#writing-custom-template-tags

スポンサーリンク

シェアボタン

Twitter LINEで送る Facebook はてなブログ