.. _howto-custom-template-tags:
======================================
テンプレートタグやフィルタを自作する
======================================
:revision-up-to: 8961 (1.0)
はじめに
============
Django テンプレートシステムには、広範な :ref:`組み込みタグとフィルタ
` が付属していて、アプリケーションのプレゼンテーショ
ンロジックに纏わる問題を解決できます。とはいえ、コアのテンプレートタグ
プリミティブだけでは、要求を満たせない場合もあります。そういう場合のために、
テンプレートエンジンを拡張できます。Python で自作のタグやフィルタを書き、
テンプレート上で ``{% 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 %}
あるアプリケーションのカスタムタグを ``{% load %}`` で呼び出せるようにする
には、そのアプリケーションを :setting:`INSTALLED_APPS` にいれておかねばなり
ません。これはセキュリティ上の制約です: こうすることで、一つのコンピュータ
にたくさんのテンプレートライブラリのコードを置きながら、個々の Django イン
ストールがライブラリに安全にアクセスできるようにします。
``templatetags`` パッケージには、いくつでもモジュールを入れられます。
``{% load %}`` 文はアプリケーションの名前ではなく、 Python モジュール名
に対応したタグ/フィルタをロードするということを心に留めておいてください。
有効なタグライブラリを作るには、 ``register`` という名前のモジュールレベル
変数に ``template.Library`` のインスタンスを入れておかねばなりません。
このインスタンスには、全てのタグとフィルタが登録されます。そこで、モジュー
ルの一番上の方で、以下のコード::
from django import template
register = template.Library()
を実行しておいてください。
.. admonition:: 舞台裏
:class: admonition-behind-the-scenes
Django のデフォルトのフィルタやタグのソースコードには大量のサンプルが
収められています。ソースコードはそれぞれ
``django/template/defaultfilters.py`` や
``django/template/defaulttags.py`` にあります。
.. _Writing custom template filters:
カスタムのテンプレートフィルタ
-------------------------------
カスタムフィルタは単なる Python の関数で、一つまたは二つの引数をとります:
* テンプレート変数 (入力) の値。文字列とは限りません。
* 引数の値。デフォルトを持たせたり、無視させたりできます。
を取ります。例えば、フィルタ ``{{ var|foo:"bar" }}`` では、フィルタ ``foo``
はテンプレート変数 ``var`` と引数 ``"bar"`` を受け取ります。
フィルタ関数は常に何かを返さねばなりません。フィルタ関数は例外を送出しては
ならず、失敗するときは暗黙のうちに失敗せねばなりません。エラーが生じた場合、
フィルタ関数は元の入力そのままか空文字列のうち、分かりやすい方を返すように
せねばなりません。
フィルタの定義の例を以下に示します::
def cut(value, arg):
"入力から arg の値を全て取り去る"
return value.replace(arg, '')
このフィルタは以下のように使います:
.. code-block:: html+django
{{ somevariable|cut:"0" }}
ほとんどのフィルタは引数を取りません。引数を取らない場合には関数から引数を
なくして下さい。例えば::
def lower(value): # Only one argument.
"入力をすべて小文字にする"
return value.lower()
.. _Template filters that expect strings:
テンプレートフィルタに文字列だけを処理させる
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
第一引数に文字列しかとらないテンプレートフィルタを書きたければ、
``stringfilter`` デコレータを使ってください。このフィルタは、フィルタ処理の
関数を呼び出す前に、第一引数のオブジェクトを文字列に変換します::
from django.template.defaultfilters import stringfilter
@stringfilter
def lower(value):
return value.lower()
こうすれば、例えばフィルタに整数型を渡したとしても、(整数型に ``lower()``
がないために) ``AttributeError`` が送出されるようなことはありません。
.. _Registering custom filters:
カスタムフィルタを登録する
~~~~~~~~~~~~~~~~~~~~~~~~~~
フィルタ定義を書き終えたら、 Django テンプレート言語で使えるようにするため
に ``Library`` インスタンスに登録する必要があります::
register.filter('cut', cut)
register.filter('lower', lower)
``Library.filter()`` は二つの引数を取ります:
1. フィルタの名前。文字列です。
2. コンパイル関数。(関数名の文字列ではなく) Python 関数です。
バージョン 2.4 以上の Python を使っているのなら、 ``register.filter()``
デコレータを使えます::
@register.filter(name='cut')
@stringfilter
def cut(value, arg):
return value.replace(arg, '')
@register.filter
@stringfilter
def lower(value):
return value.lower()
上の例の下の定義のようにして ``name`` 引数を省略すると、 Django は関数名を
そのままフィルタ名として使います。
.. _Filters and auto-escaping:
フィルタと自動エスケープ
~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 1.0
カスタムフィルタを書く場合、フィルタが Django の自動エスケープとどう関わっ
ているかに少し配慮しておく必要があります。まず、テンプレートのコード内では、
3 種類の文字列が受け渡しされています:
* **生の文字列** は、 Python ネイティブの ``str`` や ``unicode`` 型の
オブジェクトです。出力時、自動エスケープが有効であればエスケープされ、
そうでなければ変更されません。
* **safe 文字列** は、出力時にさらなるエスケープ処理を行わなくてもよ
い文字列です。safe 文字列は、必要なエスケープが済んでいる文字列です。
クライアント側でそのまま解釈されるべき生の HTML を含んだ文字列を表現
するのに使われます。
内部的には、これらの文字列は ``SafeString`` や ``SafeUnicode`` 型で表
現されています。 ``SafeString`` と ``SafeUnicode`` は ``SafeData`` を
共通の基底クラスに持つので、以下のようにすれば判別できます::
if isinstance(value, SafeData):
# "safe" 文字列を扱う処理をここに
* **"エスケープが必要" であるとマークされている文字列** は、
``autoescape`` ブロックの指定に関係なく、必ずエスケープされます。
マークされている文字列は、自動エスケープの設定に関係なく、一度だけエ
スケープ処理を受けます。
内部的には、エスケープ必要文字列は、 ``EscapeString`` や
``EscapeUnicode`` 型で表現されています。通常、これらの型のことを気に
する必要はありません。 ``EscapeString`` や ``EscapeUnicode`` は、
``escape`` フィルタの実装のために用意されています。
テンプレートフィルタのコードは、以下の二つのうちどちらかに落ち着きます:
1. フィルタコードの出力が、 HTML 出力として安全でない文字 (``<``,
``>``, ``'``, ``"`` or ``&``) を含まない場合。この場合、自動エスケー
プの処理は全て Django に任せられます。必要なのは、フィルタ関数の
``is_safe`` 属性に ``True`` を設定しておくことだけです::
@register.filter
def myfilter(value):
return value
myfilter.is_safe = True
この属性を設定しておくと、このフィルタは「安全な」文字列を渡したとき
には出力は「安全な」ままであり、「安全でない」文字列を渡した時には、
必要に応じて自動的にエスケープします。
``is_safe`` は、「このフィルタは安全 (safe) であり、安全でない HTML
を生成する危険性がない」という意味だと考えてください。
``is_safe`` が必要なのは、通常の文字列操作で、 ``SafeData`` オブジェ
クトを ``str`` や ``unicode`` オブジェクトに変換するものが数多くあ
るためです。全てを try と catch で拾うのは難しいので、 Django はフィ
ルタ処理が完了した時点でダメージを回復する戦略を取っています。
例えば、入力の末尾に ``xx`` を追加するようなフィルタを書くとします。
このフィルタは危険な HTML 文字を出力しないので、フィルタ関数を
``is_safe`` でマークしておきます::
@register.filter
def add_xx(value):
return '%sxx' % value
add_xx.is_safe = True
このフィルタをテンプレート上の自動エスケープが有効な部分で使うと、
Django は "safe" にマークされていない入力の時にフィルタの出力をエス
ケープします。
デフォルトでは、 ``is_safe`` は ``False`` なので、 ``is_safe`` が
必要でなければ無視してかまいません。
フィルタを作成するときには、フィルタが本当に安全な文字を安全なままに
出力するのかをよく考えてください。例えば、フィルタが文字を *削除* す
る場合、出力結果に、対応の取れていないタグや不完全なエンティティ表現
が意図せず混じる可能性があります。例えば、 ``>`` を入力から削ってし
まうと、 ```` の出力は ``The time is {% current_time "%Y-%m-%d %I:%M %p" %}.
.. _`strftime syntax`: http://www.python.org/doc/current/lib/module-time.html#l2h-1941
.. _`strftime の文法`: http://www.python.jp/doc/release/lib/module-time.html#l2h-1915
以下の関数は、パラメタを取り出して、 ``Node`` オブジェクトを生成します::
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 文法のようなテクニックを使って、このパージングシステムの上に小さ
なフレームワークを書く実験を重ねて来ましたが、その結果はテンプレート
エンジンをひどく低速にしてしまいました。というわけで、低水準にしてい
るのは、それが最も高速だからです。
.. _Writing the renderer:
レンダラを書く
~~~~~~~~~~~~~~
カスタムタグを書く二つめのステップは、 ``render()`` メソッドを持つ ``Node``
クラスの定義です。
上の例の続きとして話を勧めると、 ``CurrentTimeNode`` を定義する必要がありま
す::
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`` やその他の例外を送出してはな
りません。フィルタと同様、失敗は暗黙のうちに処理されるようにせねばな
りません、
一つのテンプレートは、繰り返しパージングされることなく複数のコンテキストを
レンダリングすることがあるので、このようなコンパイルとレンダリングの脱カッ
プリングは究極的には効率的なテンプレートシステムを実現します。
.. _Auto-escaping considerations:
自動エスケープについての注意点
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 1.0
テンプレートタグの出力は、自動エスケープフィルタで自動的に処理され
*ません* 。とはいえ、テンプレートタグを書くときには、二つのことを心に留めて
置く必要があります。
まず、テンプレートタグの ``render()`` が結果をコンテキスト変数に保存する場
合 (文字列でそのまま返さない場合) には、必要なら ``mark_safe()`` を呼び出し
ておく必要があります。その変数を最終的にレンダした時点で、変数は自動エスケー
プの設定の影響を受けるので、自動エスケープから保護したい変数はそのようにマー
クしておく必要があるのです。
また、テンプレートタグがサブレンダリングのために新たなコンテキストを生成す
る場合、そのコンテキスト変数に ``autoescape`` 属性を設定してください。
``Context`` クラスの ``__init__`` メソッドは、この目的のために、
``autoescape`` というパラメタをとれるようになっています。例えば::
def render(self, context):
# ...
new_context = Context({'var': obj}, autoescape=context.autoescape)
# ... 新しいコンテキストで何かする ...
このような状況はあまりありませんが、テンプレートタグ内で別のテンプレートを
レンダする場合には便利です。例えば::
def render(self, context):
t = template.loader.get_template('small_fragment.html')
return t.render(Context({'var': obj}, autoescape=context.autoescape))
上の例で、 ``context.autoescape`` の値を渡し忘れると、出力の変数は
*全て* 自動エスケープされてしまい、その結果、テンプレートタグを
``{% autoescape off %}`` ブロック内で使った場合の出力が期待とは違うものになっ
てしまいます。
.. _Registering the tag:
タグの登録
~~~~~~~~~~
最後に、上の「カスタムテンプレートフィルタを書く」の節で説明したように、タ
グをモジュールの ``Library`` インスタンスに登録します::
register.tag('current_time', do_current_time)
``tag()`` メソッドは二つの引数をとります:
1. テンプレートタグの名前、文字列です。省略すると、コンパイル関数の名前
を使います。
2. コンパイル関数。 Python の関数です (関数名を表す文字列ではありません)。
フィルタの登録と同様、バージョン 2.4 以降の Python ではこの関数をデコレータ
として使えます::
@register.tag(name="current_time")
def do_current_time(parser, token):
# ...
@register.tag
def shout(parser, token):
# ...
上の例の下の定義のようにして ``name`` 引数を省略すると、 Django は関数名を
そのままフィルタ名として使います。
.. _Passing template variables to the tag:
テンプレート変数をタグに渡す
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``token.split_contents()`` を使えば、テンプレートタグにいくつでも引数を渡せ
ますが、引数はすべて文字列リテラルになってしまいます。そのため、テンプレー
トタグの引数に動的なコンテンツ (テンプレート変数) を渡すには、もうひと工夫
必要です。
先に挙げた例では、現在時刻を文字列にフォーマットして文字列で値を返していま
したが、今度は以下のように ``DateTimeField`` の値を渡してテンプレートタグに
日付と時刻をフォーマットさせたいとしましょう:
.. code-block:: html+django
この投稿が最後に更新されたのは{% 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()`` は、
文字列リテラルの先頭と末尾にあるクオート文字を除去しません。
今度は、タグの定義は以下のようになります::
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`` の ``resolve_varialbe()`` で実現します。
``resolve_variable()`` には、変数名と ``render`` メソッドを介して渡される現
在のコンテキストを指定します::
from django import template
from django.template import resolve_variable
import datetime
class FormatTimeNode(template.Node):
def __init__(self, date_to_be_formatted, format_string):
self.date_to_be_formatted = date_to_be_formatted
self.format_string = format_string
def render(self, context):
try:
actual_date = resolve_variable(self.date_to_be_formatted, context)
return actual_date.strftime(self.format_string)
except template.VariableDoesNotExist:
return ''
これで、 ``resolve_variable`` は ``blog_entry.date_updated`` の値を解決して、
その値にしたがってフォーマット処理を行います。
.. versionadded:: 1.0
バージョン 1.0 の Django から、変数の解決方法が変更されました。
``template.resolve_variable()`` はまだ使えますが、撤廃され、
``template.Variable`` に取って代わられました。たいていは、このクラスを
使った方が、 ``template.resolve_variable`` より高速です。
``Variable`` クラスを使うには、解決したい名前を指定してインスタンスを生
成し、 ``variable.resolve(context)`` を呼び出します。つまり、開発版では、
上の例を以下のように書き換える必要があります::
.. parsed-literal::
class FormatTimeNode(template.Node):
def __init__(self, date_to_be_formatted, format_string):
self.date_to_be_formatted = **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`` 例外が送出されます。
.. _Shortcut for simple tags:
簡単なタグへのショートカット
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
多くのテンプレートタグは、文字列やテンプレート変数への参照を単一の引数とし
て取り、入力引数や外部の何らかの情報に基づいて処理を行った後、文字列を返し
ます。例えば、上で書いた ``current_time`` タグがその例で、フォーマット文字
列を指定して、時刻を文字列の形で返すようになっています。
この種のタグを簡単に作成できるようにするため、 Django では ``simple_tag``
というヘルパー関数を提供しています。この関数は ``django.template.Library``
のメソッドで、何らかの関数を引数にとり、その関数を ``render`` メソッドにラッ
プし、これまでに述べてきたいくつかのお作法を適用した後、テンプレートシステ
ムにタグを登録します。
``simple_tag`` を使うと、上の ``current_time`` 関数は以下のように書けます::
def current_time(format_string):
return datetime.datetime.now().strftime(format_string)
register.simple_tag(current_time)
Python 2.4 からは、デコレータ構文も使えます::
@register.simple_tag
def current_time(format_string):
...
``simple_tag`` ヘルパ関数については、以下の点に注意してください:
* 引数の個数チェックなどは関数の呼び出しの際に行われるので、わざわざ自
分でチェックしなくてもかまいません。
* 関数に渡される引数がクオートされている場合、クオートは自動的に取り去
られます。従って、関数は通常の文字列を受け取ることになります。
* 引数がテンプレート変数なら、関数に渡されるのは変数自体ではなく、
変数の現在値になります。
テンプレートタグが現在のコンテキストにアクセスしなくてもよいのなら、
入力値を引数に取るような関数を定義し、 ``simple_tag`` ヘルパ関数を使うのが
一番簡単です。
.. _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
まず、引数を受け取って、対応するデータの辞書を生成するような関数を定義しま
す。ここで重要なのは、辞書を返さねばならず、それ以外の複雑なデータを返して
はならないということです。戻り値はテンプレートフラグメントをレンダするとき
のテンプレートコンテキストに使われます。例を示します::
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`` だとすると、タグの登録は以下
のようにして行います::
# Here, register is a django.template.Library instance, as before
register.inclusion_tag('results.html')(show_results)
ここでも、 Python 2.4 のデコレータ構文を使えます。従って、関数を定義する時
点で下記のようにも書けます::
@register.inclusion_tag('results.html')
def show_results(poll):
...
時として、埋め込みタグが大量の引数を取るようになっていて、テンプレートの作
者が全ての引数やその順番を管理しきれなくなるような場合があります。この問題
を解決するために、 Django では埋め込みタグに対して ``takes_context`` オプショ
ンを提供しています。テンプレートタグの作成時に ``takes_context`` を指定する
と、タグは必須の引数を取らなくなり、タグのラップしている Python 関数は単一
の引数を取るようになります。この引数には、タグが呼び出されたときのテンプレー
トコンテキストが入ります。
例えば、メインページに戻るためのリンクに使う ``home_link`` および
``home_title`` という変数の入ったコンテキストの下で使う埋め込みタグを書いて
いるとしましょう。タグの Python 関数は以下のようになります::
# 最初の引数は *必ず* "context" と *呼ばねばなりません*
def jump_link(context):
return {
'link': context['home_link'],
'title': context['home_title'],
}
# Register the custom tag as an inclusion tag with 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`` の例と異なります。
.. _Setting a variable in the context:
コンテキスト中の変数を設定する
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
上の例は、単に値を出力するという単純なものでした。一般的な話として、テンプ
レートタグが値を出力するだけでなく、テンプレート変数の値を設定できたりする
ともっと柔軟性が増すでしょう。そうすれば、テンプレートの作者はテンプレート
タグのつくり出す変数を再利用できるようになります。
コンテキスト中に変数を設定するには、 ``render()`` メソッドで渡されるコンテ
キストオブジェクトを辞書として代入するだけです。先程の ``CurrentTimeNode``
を変更して、値を出力するのではなく ``current_time`` というテンプレート変数
を設定した例を示します::
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" %}The time is {{ current_time }}.
ところで、 ``CurrentTimeNode2`` には問題があります: 変数名 ``current_time``
がハードコードされているのです。これはすなわち、テンプレートの他の部分で
``{{ current_time }}`` を使わないようにせねばならないことを意味します。とい
うのも、 ``{% current_time %}`` は変数の値を盲目的に上書きしてしまうからで
す。より綺麗な解法は、出力用の変数名を定義させるというものです:
.. code-block:: html+django
{% get_current_time "%Y-%M-%d %I:%M %p" as my_current_time %}
The current time is {{ my_current_time }}.
この機能を実現するには、コンパイル関数と ``Node`` の両方をリファクタして、
以下のようにせねばなりません::
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):
# This version uses a regular expression to parse tag contents.
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`` に渡すという点です。
.. _Parsing until another block tag:
他のブロックタグに到達するまでパージングする
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
テンプレートタグは違いに連携させられます。例えば、標準の ``{% comment %}``
というタグは、 ``{% endcomment %}`` までの全ての内容を出力しないようにしま
す。このようなテンプレートタグを作るには、コンパイル関数中で
``parser.parse()`` を使います。
標準の ``{% comment %}`` タグの実装方法を示します::
def do_comment(parser, token):
nodelist = parser.parse(('endcomment',))
parser.delete_first_token()
return CommentNode()
class CommentNode(template.Node):
def render(self, context):
return ''
``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 %}`` の間の内容は全て無視されます。
.. _`Parsing until another block tag, and saving contents`:
次のブロックタグまでパージングして、内容を保存する
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
前節の例では、 ``do_comment()`` は ``{% comment %}`` から
``{% endcomment %}`` までの全ての内容を無視しています。このような処理に代え
て、ブロックタグ間のコードを使った処理も行えます。
例えば、 ``{% upper %}`` というカスタムのテンプレートタグを定義して、
``{% endupper %}`` までの内容を大文字に変換できるようにします。
使い方はこのようになります:
.. code-block:: html+django
{% upper %}This will appear in uppercase, {{ your_name }}.{% endupper %}
これmでの例と同様、 ``parser.parse()`` を使います。ただし、今回は得られた
``nodelist`` を ``Node`` に渡します::
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)`` が、新たに導入
されたコンセプトを表しています。
複雑なレンダリングについて他にも例を見たければ、 ``{% if %}`` や
``{% for %}``, ``{% ifequal %}`` , ``{% ifchanged %}`` のソースを参照してく
ださい。ソースは ``django/template/defaulttags.py`` にあります。