====================================== テンプレートタグやフィルタを自作する ====================================== :revision-up-to: 17812 (1.4) Django テンプレートシステムには、いろいろな :doc:`組み込みタグとフィルタ ` が付属していて、アプリケーションのプレゼンテーショ ンロジックにまつわる問題を解決できます。とはいえ、コアのテンプレートタグプリミ ティブだけでは、要求を満たせない場合もあります。そういう場合のために、テンプ レートエンジンを拡張できます。Python で自作のタグやフィルタを書き、テンプレー ト上で :ttag:`{% load %}` タグを使って使えるのです。 コードの配置 ------------- カスタムのテンプレートタグやフィルタは Django のアプリケーション内に置きま す。既存のアプリケーションに関連があるのなら、そのアプリケーションにバンド ルすればよいでしょう。そうでなければ、コードを入れておくための新たなアプリ ケーションを作成します。 アプリケーション内には、 ``templatetags`` ディレクトリを置かねばなりません。 このディレクトリは、 ``models.py`` や ``views.py`` などと同じ階層に置きます。 ``templatetags`` がなければ、作成してください。ディレクトリ内に ``__init__.py`` を置いて、パッケージ化するのを忘れないでください。 カスタムのタグやフィルタは、 ``templatetags`` ディレクトリの下に置きます。 モジュールファイルの名前は、あとでタグをロードするときに使う名前にします。 ですから、他のアプリケーションのカスタムタグやフィルタと名前が衝突しないよ う、よく注意して名前を決めましょう。 ``poll_extras.py`` という名前のファイルに自作のタグ/フィルタを入れているの なら、アプリケーションのレイアウトは以下のようになるでしょう:: polls/ models.py templatetags/ __init__.py poll_extras.py views.py そして、テンプレートでは以下のようにしてロードします: .. code-block:: html+django {% load poll_extras %} あるアプリケーションのカスタムタグを :ttag:`{% load %}` で呼び出せるよう にするには、そのアプリケーションを :setting:`INSTALLED_APPS` にいれておかねば なりません。これは、一つのホストに複数の Django アプリケーションを置いたとき に、 Django アプリケーション同士が勝手に他のテンプレートライブラリにアクセスし ないようにするためのセキュリティ機能です。 ``templatetags`` パッケージには、いくつでもモジュールを入れられます。 :ttag:`{% load %}` 文はアプリケーションの名前ではなく、 Python モジュー ル名に対応したタグ/フィルタをロードするということを心に留めておいてください。 有効なタグライブラリを作るには、 ``register`` という名前のモジュールレベル変数 に ``template.Library`` のインスタンスを入れておかねばなりません。このインスタ ンスには、全てのタグとフィルタが登録されます。そこで、モジュールの一番上の方 で、以下のコードを実行しておいてください: .. code-block:: python from django import template register = template.Library() .. admonition:: 舞台裏 Django のデフォルトのフィルタやタグのソースコードには大量のサンプルが 収まっています。ソースコードはそれぞれ ``django/template/defaultfilters.py`` や ``django/template/defaulttags.py`` にあります。 :ttag:`load` タグについてもっと知りたければ、そちらのドキュメントを読んで ください。 カスタムのテンプレートフィルタ ------------------------------- カスタムフィルタは単なる Python の関数で、一つまたは二つの引数: * テンプレート変数 (入力) の値。文字列とは限りません。 * 引数の値。デフォルトを持たせたり、無視させたりできます。 を取ります。例えば、フィルタ ``{{ var|foo:"bar" }}`` では、フィルタ ``foo`` はテンプレート変数 ``var`` と引数 ``"bar"`` を受け取ります。 フィルタ関数は常に何かを返さねばなりません。フィルタ関数は例外を送出しては ならず、失敗するときは暗黙のうちに失敗せねばなりません。エラーが生じた場合、 フィルタ関数は元の入力そのままか空文字列のうち、分かりやすい方を返すように せねばなりません。 フィルタの定義の例を以下に示します: .. code-block:: python def cut(value, arg): """入力から arg の値を全て取り去る""" return value.replace(arg, '') このフィルタは以下のように使います: .. code-block:: html+django {{ somevariable|cut:"0" }} ほとんどのフィルタは引数を取りません。引数を取らない場合には関数から引数を なくして下さい。例えば: .. code-block:: python def lower(value): # Only one argument. """入力をすべて小文字にする""" return value.lower() カスタムフィルタを登録する ~~~~~~~~~~~~~~~~~~~~~~~~~~ フィルタ定義を書き終えたら、 Django テンプレート言語で使えるようにするため に ``Library`` インスタンスに登録する必要があります: .. code-block:: python register.filter('cut', cut) register.filter('lower', lower) ``Library.filter()`` は二つの引数を取ります: 1. フィルタの名前。文字列です。 2. コンパイル関数。(関数名の文字列ではなく) Python 関数です。 ``register.filter()`` をデコレータとして使うこともできます: .. code-block:: python @register.filter(name='cut') def cut(value, arg): return value.replace(arg, '') @register.filter def lower(value): return value.lower() 上の例の下の定義のようにして ``name`` 引数を省略すると、 Django は関数名をその ままフィルタ名として使います。 最後に ``register.filter()`` は ``is_safe`` と ``needs_autoescape`` の 2 つの キーワード引数をとることもできます。下の :ref:`フィルタと自動エスケープ ` で説明しています。  テンプレートフィルタに文字列だけを処理させる ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 第一引数に文字列しかとらないテンプレートフィルタを書きたければ、 ``stringfilter`` デコレータを使ってください。このフィルタは、フィルタ処理の 関数を呼び出す前に、第一引数のオブジェクトを文字列に変換します: .. code-block:: python from django import template from django.template.defaultfilters import stringfilter register = template.Library() @register.filter @stringfilter def lower(value): return value.lower() こうすれば、例えばフィルタに整数型を渡したとしても、(整数型に ``lower()`` がないために) ``AttributeError`` が送出されるようなことはありません。 .. _filters-auto-escaping: フィルタと自動エスケープ ~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 1.0 カスタムフィルタを書く場合、フィルタが Django の自動エスケープとどう関わっ ているかに少し配慮しておく必要があります。まず、テンプレートのコード内では、 3 種類の文字列が受け渡しされています: * **生の文字列** は、 Python ネイティブの ``str`` や ``unicode`` 型の オブジェクトです。出力時、自動エスケープが有効であればエスケープされ、 そうでなければ変更されません。 * **safe 文字列** は、出力時にさらなるエスケープ処理を行わなくてもよ い文字列です。safe 文字列は、必要なエスケープが済んでいる文字列です。 クライアント側でそのまま解釈されるべき生の HTML を含んだ文字列を表現 するのに使われます。 内部的には、これらの文字列は ``SafeString`` や ``SafeUnicode`` 型で表 現されています。 ``SafeString`` と ``SafeUnicode`` は ``SafeData`` を 共通の基底クラスに持つので、以下のようにすれば判別できます: .. code-block:: python if isinstance(value, SafeData): # "safe" 文字列を扱う処理をここに * **"エスケープが必要" であるとマークされている文字列** は、 ``autoescape`` ブロックの指定に関係なく、 *必ず* エスケープされます。 マークされている文字列は、自動エスケープの設定に関係なく、一度だけエ スケープ処理を受けます。 内部的には、エスケープ必要文字列は、 ``EscapeString`` や ``EscapeUnicode`` 型で表現されています。通常、これらの型のことを気に する必要はありません。 ``EscapeString`` や ``EscapeUnicode`` は、 ``escape`` フィルタの実装のために用意されています。 テンプレートフィルタのコードは、以下の二つのうちどちらかに落ち着きます: 1. フィルタコードの出力が、 HTML 出力として安全でない文字 (``<``, ``>``, ``'``, ``"`` or ``&``) を含まない場合。この場合、自動エスケー プの処理は全て Django に任せられます。必要なのは、フィルタ関数の ``is_safe`` 属性に ``True`` を設定しておくことだけです: .. code-block:: python @register.filter(is_safe=True) def myfilter(value): return value この属性を設定しておくと、このフィルタは「安全な」文字列を渡したとき には出力は「安全な」ままであり、「安全でない」文字列を渡した時には、 必要に応じて自動的にエスケープします。 ``is_safe`` は、「このフィルタは安全 (safe) であり、安全でない HTML を生成するおそれがない」という意味だと考えてください。 ``is_safe`` が必要なのは、通常の文字列操作で、 ``SafeData`` オブジェ クトを ``str`` や ``unicode`` オブジェクトに変換するものが数多くあ るからです。全てを try と catch で拾うのは難しいので、 Django はフィ ルタ処理が完了した時点でダメージを回復する戦略を取っています。 例えば、入力の末尾に ``xx`` を追加するようなフィルタを書くとします。 このフィルタは危険な HTML 文字を出力しないので、フィルタ関数を ``is_safe`` でマークしておきます: .. code-block:: python @register.filter(is_safe=True) def add_xx(value): return '%sxx' % value このフィルタをテンプレート上の自動エスケープが有効な部分で使うと、 Django は "safe" にマークされていない入力の時にフィルタの出力をエス ケープします。 デフォルトでは、 ``is_safe`` は ``False`` なので、 ``is_safe`` が 必要でなければ無視してかまいません。 フィルタを作成するときには、フィルタが本当に安全な文字を安全なままに 出力するのかをよく考えてください。例えば、フィルタが文字を *削除* す る場合、出力結果に、対応の取れていないタグや不完全なエンティティ表現 が意図せず混じる可能性があります。例えば、 ``>`` を入力から削ってし まうと、 ```` の出力は ```. このフラグがセットされると、もし最初の引数がタイムゾーン aware な datetime な ら Django はそれをフィルタに渡す前に :ref:`テンプレートでのタイムゾーン変換ルール ` に従っ て、適切なカレントタイムゾーンに変換します。 カスタムテンプレートタグを書く ------------------------------ タグはフィルタよりもやや複雑です。というのも、タグを使えば何でもできるから です。 テンプレートタグの概要 ~~~~~~~~~~~~~~~~~~~~~~ 前に、このドキュメントで、テンプレートシステムはコンパイルとレンダリングと いう二段階のプロセスで動作すると説明しました。カスタムテンプレートタグを定 義するには、コンパイルがどのように行われ、レンダリングがどのように行われる かを指定する必要があります。 テンプレートをコンパイルする時、Django は元のテンプレートテキストを「ノード (node)」に分割します。各ノードは ``django.template.Node`` のインスタンスで あり、 ``render()`` メソッドを持ちます。コンパイル済みのテンプレートは単な る ``Node`` オブジェクトの集まりに過ぎません。コンパイル済みのテンプレート オブジェクトに対して ``render()`` を呼び出すと、テンプレートは指定されたコ ンテキストを使ってノードリストの各 ``Node`` に対して ``render()`` を呼び出 します。戻り値は全て連結され、テンプレートのレンダリング結果になります。 従って、カスタムテンプレートタグを定義するには、元のテンプレートタグをどう やって ``Node`` (コンパイル関数: compilation function) に変換し、個々のノー ドの ``render()`` メソッドが何をするかを指定する必要があります。 コンパイル関数を書く ~~~~~~~~~~~~~~~~~~~~ テンプレートタグに到達するたびに、テンプレートパーザはタグの内容とパーザオ ブジェクト自体を引数にしてある Python 関数を呼び出します。この関数はタグの 内容に応じて ``Node`` インスタンスを返す役割を担っています。 例えば、 ``{% current_time %}`` というタグを書いてみましょう。このタグは現 在の日付/時刻を表示し、そのフォーマットはタグの引数に指定した :func:`~time.strftime` の文法に従うとします。何をおいてもタグの構文をまず決め ておくのがよいでしょう。我々の例では、タグは以下のように使われるものとしましょ う: .. code-block:: html+django

The time is {% current_time "%Y-%m-%d %I:%M %p" %}.

以下の関数は、パラメタを取り出して、 ``Node`` オブジェクトを生成します: .. code-block:: python from django import template def do_current_time(parser, token): try: # split_contents() knows not to split quoted strings. tag_name, format_string = token.split_contents() except ValueError: raise template.TemplateSyntaxError("%r tag requires a single argument" % token.contents.split()[0]) if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")): raise template.TemplateSyntaxError("%r tag's argument should be in quotes" % tag_name) return CurrentTimeNode(format_string[1:-1]) 注意: * ``parser`` はテンプレートパーザオブジェクトです。上の例では特に必要で はありません。 * ``token.contents`` はタグの中身を表す文字列です。上の例では ``'current_time "%Y-%m-%d %I:%M %p"'`` です。 * ``token.split_contents()`` メソッドは、クオートされた文字列はそのまま にして、引数をスペースで分割します。 ``token.contents.split()`` の方 が直接的なように思えますが、クオート中の空白も含め、 *全ての* スペー スを区切りとみなすので、これは頑健な方法とはいえません。常に ``token.split_contents()`` を使った方がよいでしょう。 * この関数は、何らかの構文エラーが生じた場合、分かりやすいメッセージ付 きで ``django.template.TemplateSyntaxError`` を送出せねばなりません。 * ``TemplateSyntaxError`` 例外は ``tag_name`` 変数を使っています。 エラーメッセージにタグの名前をハードコードしてはなりません。なぜなら 関数とタグの名前がカップリングしてしまうからです。タグに引数があって もなくても、 ``token.contents.split()[0]`` は常にタグの名前と同じです。 * この関数は、タグの処理に必要な全ての情報を持った ``CurrentTimeNode`` を返します。この例の場合、タグには引数 ``"%Y-%m-%d %I:%M %p"`` が渡さ れただけです。テンプレートタグの先頭や末尾のクオートは ``format_string[1:-1]`` ではぎ取られています。 * パージング処理は極めて低水準で実現されています。 Django の開発者達は、 EBNF 文法のようなテクニックを使って、このパージングシステムの上に小さ なフレームワークを書く実験を重ねて来ましたが、その結果はテンプレート エンジンをひどく低速にしてしまいました。というわけで、低水準にしてい るのは、それが最も高速だからです。 レンダラを書く ~~~~~~~~~~~~~~ カスタムタグを書く二つめのステップは、 ``render()`` メソッドを持つ ``Node`` クラスの定義です。 上の例の続きとして話を勧めると、 ``CurrentTimeNode`` を定義する必要がありま す: .. code-block:: python from django import template import datetime class CurrentTimeNode(template.Node): def __init__(self, format_string): self.format_string = format_string def render(self, context): return datetime.datetime.now().strftime(self.format_string) 注意: * ``__init__()`` は ``do_current_time()`` から ``format_string`` を受け 取ります。 ``Node`` へのオプション/パラメタ/引数は、常に ``__init__()`` を介して渡すようにしてください。 * 実際の処理を実現するのは ``render()`` メソッドです。 * ``render()`` は ``TemplateSyntaxError`` やその他の例外を送出してはな りません。フィルタと同様、失敗は暗黙のうちに処理されるようにせねばな りません。 一つのテンプレートは、繰り返しパージングされることなく複数のコンテキストを レンダリングすることがあるので、このようなコンパイルとレンダリングの脱カッ プリングは究極的には効率的なテンプレートシステムを実現します。 自動エスケープの注意点 ~~~~~~~~~~~~~~~~~~~~~~~~~ テンプレートタグの出力は、自動エスケープフィルタで自動的に処理 *されません* 。とはいえ、テンプレートタグを書くときには、二つのことを心に留 めて置く必要があります。 まず、テンプレートタグの ``render()`` が結果をコンテキスト変数に保存する場 合 (文字列でそのまま返さない場合) には、必要なら ``mark_safe()`` を呼び出し ておく必要があります。その変数を最終的にレンダした時点で、変数は自動エスケー プの設定の影響を受けるので、自動エスケープから保護したい変数はそのようにマー クしておく必要があるのです。 また、テンプレートタグがサブレンダリングのために新たなコンテキストを生成す る場合、そのコンテキスト変数に ``autoescape`` 属性を設定してください。 ``Context`` クラスの ``__init__`` メソッドは、この目的のために、 ``autoescape`` というパラメタをとれるようになっています。例えば: .. code-block:: python def render(self, context): # ... new_context = Context({'var': obj}, autoescape=context.autoescape) # ... 新しいコンテキストで何かする ... このような状況はあまりありませんが、テンプレートタグ内で別のテンプレートを レンダする場合には便利です。例えば: .. code-block:: python def render(self, context): t = template.loader.get_template('small_fragment.html') return t.render(Context({'var': obj}, autoescape=context.autoescape)) 上の例で、 ``context.autoescape`` の値を渡し忘れると、出力の変数は *全て* 自動エスケープされてしまい、その結果、テンプレートタグを :ttag:`{% autoescape off %}` ブロック内で使った場合の出力が期待 とは違うものになってしまいます。 .. _template_tag_thread_safety: .. Thread-safety considerations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ スレッドセーフ性を考慮する ~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 1.2 .. Once a node is parsed, its ``render`` method may be called any number of times. Since Django is sometimes run in multi-threaded environments, a single node may be simultaneously rendering with different contexts in response to two separate requests. Therefore, it's important to make sure your template tags are thread safe. ノードがパージングされると、そのノードの ``render`` メソッドは何度も呼び出され る可能性があります。 Django はマルチスレッド環境で実行されることがあるので、 2 つの別のリクエストに対して 1 つのノードが異なるレスポンスコンテキストで同時に レンダリングされることがありえます。それ故、テンプレートタグをスレッドセーフに しておくことが大切です。 .. To make sure your template tags are thread safe, you should never store state information on the node itself. For example, Django provides a builtin :ttag:`cycle` template tag that cycles among a list of given strings each time it's rendered: テンプレートタグがスレッドセーフかどうかを確認するには、ノード自身に決して状態 情報を保存してはいけません。例えば、 Django は組込の :ttag:`cycle` テンプレー トタグを提供しています。これはレンダされるたびに与えられた文字列のリストを循環 させます: .. code-block:: html+django {% for o in some_list %} この投稿が最後に更新されたのは{% format_time blog_entry.date_updated "%Y-%m-%d %I:%M %p" %} です。

まず、 ``token.split_contents()`` は以下の 3 つの値を返します: 1. タグ名である ``format_time`` 。 2. ``blog_entry.date_updated`` という *文字列* 。 3. フォーマット文字列 "%Y-%m-%d %I:%M %p" 。 ``split_contents()`` は、 文字列リテラルの先頭と末尾にあるクオート文字を除去しません。 今度は、タグの定義は以下のようになります: .. code-block:: python from django import template def do_format_time(parser, token): try: # split_contents() はクオート付き文字列を分解しない tag_name, date_to_be_formatted, format_string = token.split_contents() except ValueError: raise template.TemplateSyntaxError("%r tag requires exactly two arguments" % token.contents.split()[0]) if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")): raise template.TemplateSyntaxError("%r tag's argument should be in quotes" % tag_name) return FormatTimeNode(date_to_be_formatted, format_string[1:-1]) レンダラの方も変更して、 ``blog_entry`` オブジェクトの ``date_updated`` プ ロパティの実際の中身を取り出せるようにせねばなりません。これは ``django.template`` の ``Variable()`` クラスで実現します。 ``Variable`` クラスの使い方は、単に解決されるべき変数名を渡してインスタンスを 作り、それから ``variable.resolve(context)`` を呼びます。従って: .. code-block:: python class FormatTimeNode(template.Node): def __init__(self, date_to_be_formatted, format_string): self.date_to_be_formatted = template.Variable(date_to_be_formatted) self.format_string = format_string def render(self, context): try: actual_date = self.date_to_be_formatted.resolve(context) return actual_date.strftime(self.format_string) except template.VariableDoesNotExist: return '' 現在のページのコンテキストに、 ``Variable`` に渡した名前がなかった場合、 名前解決の際に ``VariableDoesNotExist`` 例外が送出されます。 .. _howto-custom-template-tags-simple-tags: 簡単なタグ ~~~~~~~~~~ 多くのテンプレートタグは、文字列やテンプレート変数への参照をいくつか引数と して取り、入力引数や外部の何らかの情報に基づいて処理を行った後、文字列を返 します。例えば、上で書いた ``current_time`` タグがその例で、フォーマット文 字列を指定して、時刻を文字列の形で返すようになっています。 この種のタグを簡単に作成できるようにするため、 Django では ``simple_tag`` というヘルパー関数を提供しています。この関数は ``django.template.Library`` のメソッドで、何らかの関数を引数にとり、その関数を ``render`` メソッドにラッ プし、これまでに述べてきたいくつかのお作法を適用した後、テンプレートシステ ムにタグを登録します。 ``simple_tag`` を使うと、上の ``current_time`` 関数は以下のように書けます: .. code-block:: python def current_time(format_string): return datetime.datetime.now().strftime(format_string) register.simple_tag(current_time) デコレータ構文も使えます: .. code-block:: python @register.simple_tag def current_time(format_string): ... ``simple_tag`` ヘルパ関数については、以下の点に注意してください: * 引数の個数チェックなどは関数の呼び出しの際に行われるので、わざわざ自 分でチェックしなくてもかまいません。 * 関数に渡される引数がクオートされている場合、クオートは自動的に取り去 られます。従って、関数は通常の文字列を受け取ることになります。 * 引数がテンプレート変数なら、関数に渡されるのは変数自体ではなく、 変数の現在値になります。 .. versionadded:: 1.3 .. If your template tag needs to access the current context, you can use the ``takes_context`` argument when registering your tag: .. code-block:: python # The first argument *must* be called "context" here. def current_time(context, format_string): timezone = context['timezone'] return your_get_current_time_method(timezone, format_string) register.simple_tag(takes_context=True)(current_time) もしテンプレートタグが現在のコンテキストにアクセスする必要があるなら、タグを登 録する時に ``takes_context`` 引数を使うことができます: .. code-block:: python # ここで最初の引数は "context" と呼ばれなければ *いけません* def current_time(context, format_string): timezone = context['timezone'] return your_get_current_time_method(timezone, format_string) register.simple_tag(takes_context=True)(current_time) .. Or, using decorator syntax: あるいはデコレータ構文を使います: .. code-block:: python @register.simple_tag(takes_context=True) def current_time(context, format_string): timezone = context['timezone'] return your_get_current_time_method(timezone, format_string) .. For more information on how the ``takes_context`` option works, see the section on :ref:`inclusion tags`. ``takes_context`` オプションの動きについてもっと詳しく知りたければ :ref:`埋込タグ` の章を読んでくださ い。 .. versionadded:: 1.4 .. If you need to rename your tag, you can provide a custom name for it: タグの名前を変更したければ、カスタムの名前を与えることができます: .. code-block:: python register.simple_tag(lambda x: x - 1, name='minusone') @register.simple_tag(name='minustwo') def some_function(value): return value - 2 .. versionadded:: 1.4 .. ``simple_tag`` functions may accept any number of positional or keyword arguments. For example: ``simple_tag`` 関数は任意の位置指定引数 (positional arguments) またはキーワー ド引数を受け取ります。例: .. code-block:: python @register.simple_tag def my_tag(a, b, *args, **kwargs): warning = kwargs['warning'] profile = kwargs['profile'] ... return ... .. Then in the template any number of arguments, separated by spaces, may be passed to the template tag. Like in Python, the values for keyword arguments are set using the equal sign ("``=``") and must be provided after the positional arguments. For example: すると、テンプレートでは、任意の数の引数を空白区切りでテンプレートタグに渡すこ とができます。 Python でそうなのと同じく、キーワード引数の値はイコール記号 ("``=``") を使って、位置指定引数の後に渡される必要があります。例: .. code-block:: html+django {% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %} .. _howto-custom-template-tags-inclusion-tags: 埋め込みタグ ~~~~~~~~~~~~ テンプレートタグによくあるもう一つのタイプは、 *別の* テンプレートをレンダ して表示するものです。例えば、 Django の admin インタフェースではカスタムの テンプレートタグを使って「追加/変更」フォームページのボタンを表示していま す。これらのボタンはいつも同じように表示されていますが、リンクのターゲット は編集対象のオブジェクトによって変わります。従って、こうした処理は、小さな テンプレートを使って現在のオブジェクトに応じた値を埋めるのが最良の方法と言 えます。 (admin の ``submit_raw`` がそのタグです)。 こうしたタグのことを 「埋め込みタグ (inclusion tag)」 と呼びます。 埋め込みタグの書き方を説明するには、例を挙げるのがベストでしょう。 :ref:`チュートリアル ` で作成した ``Poll`` オブジェクトを 例に、ある ``Poll`` オブジェクトに対する選択肢のリストを出力するようなタグ を書いてみましょう。タグは以下のようになります: .. code-block:: html+django {% show_results poll %} 出力は以下のようになります: .. code-block:: html
  • First choice
  • Second choice
  • Third choice
まず、引数を受け取って、対応するデータの辞書を生成するような関数を定義しま す。ここで重要なのは、辞書を返さねばならず、それ以外の複雑なデータを返して はならないということです。戻り値はテンプレートフラグメントをレンダするとき のテンプレートコンテキストに使われます。例を示します: .. code-block:: python def show_results(poll): choices = poll.choice_set.all() return {'choices': choices} 次に、タグの出力をレンダする際に使うテンプレートを作成します。このテンプレー トはタグ固有のもので、テンプレートデザイナではなくタグの開発者が書くべきも のです。上の例に従えば、テンプレートはとても単純な形になります: .. code-block:: html+django
    {% for choice in choices %}
  • {{ choice }}
  • {% endfor %}
さて、 ``Library`` オブジェクトの ``inclusion_tag()`` メソッドを呼び出して、 埋め込みタグを作成し、登録しましょう。上のテンプレートがテンプレートローダ の検索対象ディレクトリ下にある ``result.html`` だとすると、タグの登録は以下 のようにして行います: .. code-block:: python # Here, register is a django.template.Library instance, as before register.inclusion_tag('results.html')(show_results) .. versionchanged:: 1.4 .. Alternatively it is possible to register the inclusion tag using a :class:`django.template.Template` instance: :class:`django.template.Template` インスタンスを使って埋込タグを登録するこ とも可能です: .. code-block:: python from django.template.loader import get_template t = get_template('results.html') register.inclusion_tag(t)(show_results) ここでもデコレータ構文を使えます。従って、関数を定義する時点で下記のようにも書 けます: .. code-block:: python @register.inclusion_tag('results.html') def show_results(poll): ... 時として、埋め込みタグが大量の引数を取るようになっていて、テンプレートの作 者が全ての引数やその順番を管理しきれなくなるような場合があります。この問題 を解決するために、 Django では埋め込みタグに対して ``takes_context`` オプショ ンを提供しています。テンプレートタグの作成時に ``takes_context`` を指定する と、タグは必須の引数を取らなくなり、タグのラップしている Python 関数は単一 の引数を取るようになります。この引数には、タグが呼び出されたときのテンプレー トコンテキストが入ります。 例えば、メインページに戻るためのリンクに使う ``home_link`` および ``home_title`` という変数の入ったコンテキストの下で使う埋め込みタグを書いて いるとしましょう。タグの Python 関数は以下のようになります: .. code-block:: python # 最初の引数は *必ず* "context" と *呼ばねばなりません* def jump_link(context): return { 'link': context['home_link'], 'title': context['home_title'], } # 定義したカスタムタグを takes_context=True の埋め込みタグとして登録する register.inclusion_tag('link.html', takes_context=True)(jump_link) (関数の最初のパラメタは *必ず* ``context`` という名前に *せねばならない* の で注意してください。) ``register.inclusion_tag()`` の行で、 ``takes_context=True`` を指定し、テン プレートの名前を指定しています。 ``link.html`` テンプレートの中は以下のよう になります: .. code-block:: html+django Jump directly to
{{ title }}. このカスタムタグを使う場合は常に、まずライブラリを呼び出しておき、下記のよ うに引数なしでタグを呼び出します: .. code-block:: html+django {% jump_link %} ``takes_context=True`` を使っている場合、テンプレートタグに引数を渡す必要は ありません。タグが自動的にコンテキストにアクセスします。 ``takes_context`` パラメタはデフォルトでは ``False`` です。この値を ``True`` に設定すると、タグの引数にはコンテキストオブジェクトが渡されます。この点だ けは前述の ``inclusion_tag`` の例と異なります。 .. versionadded:: 1.4 .. ``inclusion_tag`` functions may accept any number of positional or keyword arguments. For example: ``inclusion_tag`` 関数は任意の数の位置指定引数またはキーワード引数を受け取るこ とができます。例: .. code-block:: python @register.inclusion_tag('my_template.html') def my_tag(a, b, *args, **kwargs): warning = kwargs['warning'] profile = kwargs['profile'] ... return ... .. Then in the template any number of arguments, separated by spaces, may be passed to the template tag. Like in Python, the values for keyword arguments are set using the equal sign ("``=``") and must be provided after the positional arguments. For example: すると、テンプレートでは、任意の数の引数を空白区切りでテンプレートタグに渡すこ とができます。 Python でそうなのと同じく、キーワード引数の値はイコール記号 ("``=``") を使って、位置指定引数の後に渡される必要があります。例: .. code-block:: html+django {% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile %} コンテキスト中の変数を設定する ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 上の例は、単に値を出力するという単純なものでした。一般的な話として、テンプ レートタグが値を出力するだけでなく、テンプレート変数の値を設定できたりする ともっと柔軟性が増すでしょう。そうすれば、テンプレートの作者はテンプレート タグのつくり出す変数を再利用できるようになります。 コンテキスト中に変数を設定するには、 ``render()`` メソッドで渡されるコンテ キストオブジェクトを辞書として代入するだけです。先程の ``CurrentTimeNode`` を変更して、値を出力するのではなく ``current_time`` というテンプレート変数 を設定した例を示します: .. code-block:: python class CurrentTimeNode2(template.Node): def __init__(self, format_string): self.format_string = format_string def render(self, context): context['current_time'] = datetime.datetime.now().strftime(self.format_string) return '' ``render()`` は空文字列を返していることに注意して下さい。 ``render()`` は常に文字列を出力せねばなりません。全てのテンプレートタグが変数の設定 しかしなければ、 ``render()`` は空の文字列を返すだけです。 新しいバージョンのタグの使い方を示します: .. code-block:: html+django {% current_time "%Y-%M-%d %I:%M %p" %}

時刻は {{ current_time }} です。

.. .. admonition:: Variable scope in context Any variable set in the context will only be available in the same ``block`` of the template in which it was assigned. This behavior is intentional; it provides a scope for variables so that they don't conflict with context in other blocks. .. admonition:: コンテキストにおける変数のスコープ コンテキストにセットされるいかなる変数も、でそれが定義されたのと同じテンプ レートブロック (``block``) の中でしか使うことができません。この挙動は意図 的なもので、変数のスコープを提供しています。そのため異なるブロックのコンテ キストが変数が衝突することがありません。 ところで、 ``CurrentTimeNode2`` には問題があります: 変数名 ``current_time`` がハードコードされているのです。これはすなわち、テンプレートの他の部分で ``{{ current_time }}`` を使わないようにせねばならないことを意味します。とい うのも、 ``{% current_time %}`` は変数の値を盲目的に上書きしてしまうからで す。より綺麗な解法は、出力用の変数名を定義させるというものです: .. code-block:: html+django {% get_current_time "%Y-%M-%d %I:%M %p" as my_current_time %}

時刻は {{ my_current_time }} です。

この機能を実現するには、コンパイル関数と ``Node`` の両方をリファクタして、 以下のようにせねばなりません: .. code-block:: python class CurrentTimeNode3(template.Node): def __init__(self, format_string, var_name): self.format_string = format_string self.var_name = var_name def render(self, context): context[self.var_name] = datetime.datetime.now().strftime(self.format_string) return '' import re def do_current_time(parser, token): # このバージョンはタグのコンテンツを解析するのに正規表現を使っています try: # Splitting by None == splitting by spaces. tag_name, arg = token.contents.split(None, 1) except ValueError: raise template.TemplateSyntaxError("%r tag requires arguments" % token.contents.split()[0]) m = re.search(r'(.*?) as (\w+)', arg) if not m: raise template.TemplateSyntaxError("%r tag had invalid arguments" % tag_name) format_string, var_name = m.groups() if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")): raise template.TemplateSyntaxError("%r tag's argument should be in quotes" % tag_name) return CurrentTimeNode3(format_string[1:-1], var_name) ここでの違いは、 ``do_current_tume()`` がフォーマット文字列と変数名を取り、 ``CurrentTimeNode3`` に渡すという点です。 Finally, if you only need to have a simple syntax for your custom context-updating template tag, you might want to consider using an :ref:`assignment tag `. .. _howto-custom-template-tags-assignment-tags: .. Assignment tags ~~~~~~~~~~~~~~~ Assignment タグ ~~~~~~~~~~~~~~~ .. versionadded:: 1.4 .. To ease the creation of tags setting a variable in the context, Django provides a helper function, ``assignment_tag``. This function works the same way as :ref:`simple_tag`, except that it stores the tag's result in a specified context variable instead of directly outputting it. コンテキストに変数をセットするタグを簡単に作れるように、 Django は ``assignment_tag`` ヘルパー関数を提供しています。この関数は :ref:`simple_tag` と同じように動作しま す。ただしこちらは指定したコンテキスト変数でのタグの結果を、直接出力する代わり に保存します。 .. Our earlier ``current_time`` function could thus be written like this: 少し前に示した ``current_time`` 関数はこのように書くことができます: .. code-block:: python def get_current_time(format_string): return datetime.datetime.now().strftime(format_string) register.assignment_tag(get_current_time) .. The decorator syntax also works: デコレータ構文も使えます: .. code-block:: python @register.assignment_tag def get_current_time(format_string): ... .. You may then store the result in a template variable using the ``as`` argument followed by the variable name, and output it yourself where you see fit: ``as`` 引数の後ろに変数名を続けることで、この結果をテンプレート変数に保存し、 ちょうどいい場所で自分で出力することができます: .. code-block:: html+django {% get_current_time "%Y-%m-%d %I:%M %p" as the_time %}

The time is {{ the_time }}.

.. If your template tag needs to access the current context, you can use the ``takes_context`` argument when registering your tag: .. code-block:: python # The first argument *must* be called "context" here. def get_current_time(context, format_string): timezone = context['timezone'] return your_get_current_time_method(timezone, format_string) register.assignment_tag(takes_context=True)(get_current_time) テンプレートタグが現在のコンテキストにアクセスする必要がある場合、 タグを登録する時に ``takes_context`` 引数を使うことができます: .. code-block:: python # ここで最初の引数は "context" でなければ *いけません* def get_current_time(context, format_string): timezone = context['timezone'] return your_get_current_time_method(timezone, format_string) register.assignment_tag(takes_context=True)(get_current_time) .. Or, using decorator syntax: デコレータ構文を使うこともできます: .. code-block:: python @register.assignment_tag(takes_context=True) def get_current_time(context, format_string): timezone = context['timezone'] return your_get_current_time_method(timezone, format_string) .. For more information on how the ``takes_context`` option works, see the section on :ref:`inclusion tags`. ``takes_context`` オプションの動きについてもっと詳しく知りたければ :ref:`埋込タグ` の章を読んでくださ い。 .. ``assignment_tag`` functions may accept any number of positional or keyword arguments. For example: ``assignment_tag`` 関数は任意の数の位置指定引数またはキーワード引数を受け取る ことができます。例: .. code-block:: python @register.assignment_tag def my_tag(a, b, *args, **kwargs): warning = kwargs['warning'] profile = kwargs['profile'] ... return ... .. Then in the template any number of arguments, separated by spaces, may be passed to the template tag. Like in Python, the values for keyword arguments are set using the equal sign ("``=``") and must be provided after the positional arguments. For example: すると、テンプレートでは、任意の数の引数を空白区切りでテンプレートタグに渡すこ とができます。 Python でそうなのと同じく、キーワード引数の値はイコール記号 ("``=``") を使って、位置指定引数の後に渡される必要があります。例: .. code-block:: html+django {% my_tag 123 "abcd" book.title warning=message|lower profile=user.profile as the_result %} 他のブロックタグに到達するまでパージングする ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ テンプレートタグは違いに連携させられます。例えば、標準の :ttag:`{% comment %}` というタグは、 ``{% endcomment %}`` までの全ての内容を出力しない %ようにします。このようなテンプレートタグを作るには、コンパイル関数中で %``parser.parse()`` を使います。 簡単な ``{% comment %}`` タグの実装方法を示します: .. code-block:: python def do_comment(parser, token): nodelist = parser.parse(('endcomment',)) parser.delete_first_token() return CommentNode() class CommentNode(template.Node): def render(self, context): return '' .. .. note:: The actual implementation of :ttag:`{% comment %}` is slightly different in that it allows broken template tags to appear between ``{% comment %}`` and ``{% endcomment %}``. It does so by calling ``parser.skip_past('endcomment')`` instead of ``parser.parse(('endcomment',))`` followed by ``parser.delete_first_token()``, thus avoiding the generation of a node list. .. note:: :ttag:`{% comment %}` の現実の実装はこれとは少し違っていて、 ``{% comment %}`` と ``{% endcomment %}`` の間に壊れたテンプレートタグが現 れることを許容します。 ``parser.delete_first_token()`` の前で ``parser.parse(('endcomment',))`` の代わりに ``parser.skip_past('endcomment')`` を呼び、ノードリストの生成を避けること によってそれを行っています。 ``parser.parse()`` は「そこまで読み進める」ブロックタグからなるタプルを引数 にとります。 ``parser.parse()`` は ``django.template.NodeList`` のインスタ ンスを返します。このインスタンスは、タプルに指定したブロックタグのいずれか に到達する「より以前」に、パーザが出会った全ての ``Node`` オブジェクトから なるリストです。 上の例の ``"nodelist = parser.parse(('endcomment',))"`` では、 ``nodelist`` は ``{% comment %}`` から ``{% endcomment %}`` までの全てのノードからなる リストになります。ただし、 ``{% comment %}`` や ``{% endcomment %}`` 自体は除きます。 ``parser.parse()`` の呼び出し直後では、パーザはまだ ``{% endcomment %}`` タグを「消費」していないので、コードから明示的に ``parser.delete_first_token()`` を呼び出してやる必要があります。 ``CommentNode.render()`` は単に空文字列を返します。その結果、 ``{% comment %}`` と ``{% endcomment %}`` の間の内容は全て無視されます。 次のブロックタグまでパージングして、内容を保存する ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 前節の例では、 ``do_comment()`` は ``{% comment %}`` から ``{% endcomment %}`` までの全ての内容を無視しています。このような処理に代え て、ブロックタグ間のコードを使った処理も行えます。 例えば、 ``{% upper %}`` というカスタムのテンプレートタグを定義して、 ``{% endupper %}`` までの内容を大文字に変換できるようにします。 使い方はこのようになります: .. code-block:: html+django {% upper %}This will appear in uppercase, {{ your_name }}.{% endupper %} これまでの例と同様、 ``parser.parse()`` を使います。ただし、今回は得られた ``nodelist`` を ``Node`` に渡します: .. code-block:: python def do_upper(parser, token): nodelist = parser.parse(('endupper',)) parser.delete_first_token() return UpperNode(nodelist) class UpperNode(template.Node): def __init__(self, nodelist): self.nodelist = nodelist def render(self, context): output = self.nodelist.render(context) return output.upper() ``UpperNode.render()`` の ``self.nodelist.render(context)`` が、新たに導入 されたコンセプトを表しています。 複雑なレンダリングについて他にも例を見たければ、 :ttag:`{% if %}` や :ttag:`{% for %}`, :ttag:`{% ifequal %}`, :ttag:`{% ifchanged %}` のソースを参照してください。ソースは ``django/template/defaulttags.py`` にあります。