T.H/ 2023年 5月 31日/ 技術

はじめに

こんにちは。T.H.です。
今回は、Djangoのカスタムフィルターとカスタムタグについてです。
作成方法についてはそこそこに、直感的に分かりにくい制約があるのでそのあたりの解説です。
制約そのものについてはテンプレートタグ/テンプレートフィルタと変わらないかとは思いますが、自作した場合に引っ掛かるポイントになるかと思います。
実験していた環境がDjango3.2と少し古いものになりますので、現在の挙動が違っていたら申し訳ありません。

カスタムフィルターとカスタムタグとは

テンプレートファイル内で使用する、{xxxx|upper}のような形式のものをテンプレートフィルタ、{% url xxxx %}のような形式のものをテンプレートタグ、と呼びます。
いずれも自作して定義することができ、それらをカスタムフィルター、カスタムタグとよびます。
フィルタとタグの機能は似通っていますが、タグは引数の数に制限なし、フィルターは引数2個までという制約があります。

作り方

少し手短に説明します。

    templatetags/
        __init__.py
        custom_tags.py

のようにtemplatetagsフォルダにカスタムタグ用の.pyファイルを作成し、

from django import template
register = template.Library()

# カスタムフィルタ1
@register.filter
def sum_filter(x, y):
    return x + y

# カスタムフィルタ2
@register.filter
def multiply_filter(x, y):
    return x * y

# カスタムタグ1
@register.simple_tag
def sum_tag(x, y):
    return x + y

# カスタムタグ2
@register.simple_tag
def multiply_tag(x, y):
    return x * y

とすることで、カスタムフィルタ/タグの作成は完了です。
また、@register.simple_tag`以外にも`@register.inclusion_tag というものがあります。こちらは今回の説明の範囲外といたしますが、ざっくりいいますと、タグの箇所に別のテンプレートを埋め込むことができるものです。

これらを呼び出すには、テンプレートファイル内に以下のように記述します。

{% load custom_tags %}
~~ 中略 ~~
<span>足し算フィルタ:{{ num_x|sum_filter:num_y }}</span>
<span>足し算タグ:{% sum_tag num_x num_y %}</span>

{% load custom_tags %}
の記述が面倒な場合は、設定ファイル(manage.pyのDJANGO_SETTINGS_MODULE指定ファイル)のTEMPLATESのOPTIONSに'builtins'を追加することで毎回loadタグを記載せずに済みます。

TEMPLATES = [
    {
        'OPTIONS': {
            'builtins': ['templatetags.custom_tags'],
        },
    },
]

少し駆け足で説明いたしました。作成方法を説明しているサイトはたくさんありますので、詳細を知りたい方はそれらを参考にされるとよいかと思います。

使用に関しての制約、注意事項

タグの中にタグを書くことはできない

下記のような記述はいずれもエラーとなります。

<span>掛け算の結果を足し算:{% sum_tag multiply_tag num_x num_y num_z %}</span>
<span>掛け算の結果を足し算:{% sum_tag {% multiply_tag num_x num_y %} num_z %}</span>

タグ同士で階層構造を作ることは出来ませんので、欲しい機能ごとにタグを作成する必要があります。
複雑なことをやろうとするとカスタムタグの数が大変なことになるのでフロント側の処理をタグで頑張るのは余り推奨できません。素直に別の手段にした方がよさそうです。

タグの中にフィルタを書くことはできる

タグの中にタグを書くことは出来ませんが、タグの中にフィルタを書くことは出来ます。

<span>足し算の結果を足し算:{% sum_tag num_x num_y|sum_filter:num_z %}</span>

この場合はsum_filterの処理結果をsum_tagの引数として受け取ることができます。
それぞれの引数の書き方に癖があることも相まってやや見にくくなってきました。
なお、引数がスペースで区切られる都合上、num_y|sum_filter:num_zの中にスペースが入ると引数の数が合わなくなるためエラーとなります。

フィルタを入れ子にした場合、左から順に処理される

タグと異なり、フィルタを入れ子にすることは可能です。ですが、引数を階層的に処理することが出来ず、単純に左から順に処理していきます。

<span>掛け算の結果を足し算:{{ num_x|multiply_filter:num_y|sum_filter:num_z }}</span>

この場合、先にnum_x|multiply_filter:num_yの処理が行われ、次にmultiply_filterの結果を元にsum_filterの処理が行われます。
先述した通り、タグの場合は引数に書いたフィルタの処理が先に行われるため、混乱しやすく分かりにくい記述になります。

回避策

上記の問題があるため基本的にはタグ、フィルタの入れ子構造はあまり使用しない方が良いように思います。

  • タグやフィルタの入れ子は避け、withタグを利用して見通しをよくする
  • 入れ子にならないよう別のタグを作成する
  • view.py、Middleware、JavaScriptに持っていくことですっきりした形にならないか検討する

など、回避策を検討してそれでも無理なら…、というかんじでしょうか。

最後に

カスタムフィルタ/タグの作成方法の情報は簡単に手に入るのですが、今回のような妙な癖に言及している記事があまり見当たらなかったため、記事とさせていただきました。参考になれば幸いです。

About T.H

North Torch株式会社 プログラマ 技術的な経歴は.NETアプリケーションが一番長い。 その他はまだまだ勉強中。