Python プログラマのための Django テンプレート言語ガイド
| revision-up-to: | 7203 (0.97pre SVN) |
|---|
このドキュメントでは、 Django のテンプレートシステムを技術的な側面、すなわ ちどのように動作し、どうやって拡張するかという観点から解説します。テンプレー ト言語の構文リファレンスを探しているのなら、 テンプレート作者のためのテンプレート言語ガイド を参照してください。
Django テンプレートシステムを他のアプリケーションの部品として使いたい場合、 すなわち Django フレームワークの他の部分は必要ない場合、このドキュメントの 後の方にある 設定 の節に必ず目を通して下さい。
基礎
テンプレート (template) とは、 Django テンプレート言語を使ってマークアッ プしたテキストドキュメントや Python 文字列です。テンプレートには ブロックタグ (block tag) や 変数 (variable) を入れられます。
ブロックタグ はテンプレート中で何らかの処理を行う部分を表すシンボルです。
とはいえ、この定義はいささか曖昧ですね。例えば、ブロックタグを使うと、何ら かのコンテンツを出力したり、制御構造 ("if" 文や "for" ループ) を記述したり、 データベースからコンテンツを取り出したり、他のテンプレートタグにアクセスし たりします。
ブロックタグは "{%" と "%}" で囲みます。
ブロックタグを使ったテンプレートの例を示します:
{% if is_logged_in %}Thanks for logging in!{% else %}Please log in.{% endif %}
変数 はテンプレート中で何らかの値を出力する部分を表すシンボルです。
変数は "{{" と "}}" で囲みます。
変数を使ったテンプレートの例を示します。:
My first name is {{ first_name }}. My last name is {{ last_name }}.
- コンテキスト (context) はテンプレートに渡される「変数名」から「変数の値」
- へのマッピングです。
テンプレートの レンダリング (rendering) では、コンテキストから値を取り 出して変数という「穴」に埋め、全てのブロックタグを実行します。
テンプレートシステムの利用
Python でテンプレートを使うには、以下の 2 段階のプロセスを踏みます:
- 生のテンプレートをコンパイルして Template オブジェクトを生成しま す。
- コンテキストを指定して、 Template オブジェクトの render() メ ソッドを呼び出します。
文字列のコンパイル
Template オブジェクトを作成するには、直接インスタンスを生成する方法が最 も簡単です。 Template クラスは django.template.Template にあります。 コンストラクタは引数に生のテンプレートコードを取ります:
>>> from django.template import Template
>>> t = Template("My name is {{ my_name }}.")
>>> print t
<django.template.Template instance>
背後では
このシステムは Template オブジェクトを生成するときに生のテンプレー トコードを一度しか解析しません。コンパイル後のテンプレートはパフォーマ ンス上の目的から "node" データ構造に保存されます。
テンプレートコードの解析自体もきわめて高速です。解析のほとんどは短い正 規表現を一つ呼び出すだけで終わります。
コンテキストのレンダリング
コンパイル済みの Template オブジェクトを作成したら、オブジェクトを使っ てコンテキストをレンダリングできるようになります。 Context クラスは django.template.Context にあります。コンテキストクラスのコンストラクタ は単一の (省略可能な) 引数として、変数名を変数の値に対応づける辞書をとりま す。コンテキストの値でテンプレートを「埋める」には、 Template オブジェ クトの render() メソッドを使います:
>>> from django.template import Context, Template
>>> t = Template("My name is {{ my_name }}.")
>>> c = Context({"my_name": "Adrian"})
>>> t.render(c)
"My name is Adrian."
>>> c = Context({"my_name": "Dolores"})
>>> t.render(c)
"My name is Dolores."
変数名には、任意の文字 (A-Z)、数字 (0-9)、アンダースコアまたはドットしか 使えません。
ドットはテンプレートレンダリングにおいて特殊な意味を持ちます。変数名中の ドットは、 値の照合 (lookup) を意味します。もっと詳しく言えば、テンプレー トシステムが変数名中にドットを見つけた場合、以下の順で値の照合を試みます:
- foo["bar"] のような辞書としての照合。
- foo.bar のような属性値の照合。
- foo.bar() のようなメソッド呼び出し。
- foo[bar] のようなリストのインデクス指定。
テンプレートシステムは最初に成功した照合結果を使います。いわば短絡的ロジッ ク (short-circuit logic) です。
いくつか例を示します:
>>> from django.template import Context, Template
>>> t = Template("My name is {{ person.first_name }}.")
>>> d = {"person": {"first_name": "Joe", "last_name": "Johnson"}}
>>> t.render(Context(d))
"My name is Joe."
>>> class PersonClass: pass
>>> p = PersonClass()
>>> p.first_name = "Ron"
>>> p.last_name = "Nasty"
>>> t.render(Context({"person": p}))
"My name is Ron."
>>> class PersonClass2:
... def first_name(self):
... return "Samantha"
>>> p = PersonClass2()
>>> t.render(Context({"person": p}))
"My name is Samantha."
>>> t = Template("The first stooge in the list is {{ stooges.0 }}.")
>>> c = Context({"stooges": ["Larry", "Curly", "Moe"]})
>>> t.render(c)
"The first stooge in the list is Larry."
メソッドの照合は他の照合よりもちょっと複雑です。いくつか心に留めておくべき ことを挙げておきます:
メソッドの照合中にメソッドが送出した例外に silent_variable_failure という属性があり、かつ値が True に設 定されている場合を除き、例外はそのまま伝播します。例外が silent_variable_failure という属性を持つ場合、変数は空文字列にレ ンダリングされます。例えば:
>>> t = Template("My name is {{ person.first_name }}.") >>> class PersonClass3: ... def first_name(self): ... raise AssertionError, "foo" >>> p = PersonClass3() >>> t.render(Context({"person": p})) Traceback (most recent call last): ... AssertionError: foo >>> class SilentAssertionError(Exception): ... silent_variable_failure = True >>> class PersonClass4: ... def first_name(self): ... raise SilentAssertionError >>> p = PersonClass4() >>> t.render(Context({"person": p})) "My name is ."全ての Django データベース API の DoesNotExist 例外の基底クラスで ある django.core.exceptions.ObjectDoesNotExist では silent_variable_failure = True になっているので注意してください。 つまり、 Django テンプレートと Django モデルオブジェクトを使っている 限り、 DoesNotExist 例外が出ても暗黙のうちに失敗するだけなのです。
メソッド呼び出しがうまくいくのは、メソッドに必須の引数が存在しない場 合だけです。必須の引数がある場合、メソッドは呼び出されず、次の照合タ イプ (リストのインデクス指定による照合) に移ります。
言うまでもないことですが、メソッドによっては副作用があります。 副作用のあるメソッドをテンプレートからアクセスできるようにしておくと、 間抜けな結果になったりセキュリティホールを作ったりしてしまうことがある ので注意して下さい。
分かりやすい例に、 Django モデルオブジェクトの delete() メソッド があります。テンプレートシステムでは、以下のような書き方を許すべきで はありません:
大事なデータを消しちゃいますよ。 {{ data.delete }}このような操作を防ぐには、メソッドに alters_data 関数属性を設定し ます。テンプレートシステムは、 alters_data=True の設定されたメソッ ドを実行しません。 Django モデルオブジェクトが動的に生成する delete() および save() メソッドには alters_data=True が自 動的に設定されます。 alters_data=True の例を以下に示します:
def sensitive_function(self): self.database_record.delete() sensitive_function.alters_data = True
無効な値の扱い
基本的に、変数が存在しなければ、 テンプレートシステムは TEMPLATE_STRING_IF_INVALID の設定値を挿入します。この値のデフォルトは '' (空文字列) です。
無効な値に対してフィルタが適用されるのは、 TEMPLATE_STRING_IF_INVALID の値が '' に設定されている場合だけです。 TEMPLATE_STRING_IF_INVALID が '' 以外の値になっていると、フィルタは無視されます。
ただし、 if, for, regroup といったテンプレートタグの中では、少 し違った挙動になります。これらのテンプレートタグ中では、無効な値は None であると解釈されます。また、これらのテンプレートタグ中のフィルタは、無効な 値に対しも常に適用されます。
TEMPLATE_STRING_IF_INVALID にフォーマット指示文字の '%s' が入ってい る場合、 '%s' は不正な変数の変数名で置き換えられます。
デバッグ専用です!
TEMPLATE_STRING_IF_INVALID はデバッグには便利ですが、「開発時のデフォ ルトの設定」としてオンにするのはよい考えではありません。
Admin サイトのテンプレートをはじめとする多くのテンプレートは、実在し ない値を呼び出しても何も出力しないという動作を前提に作られています。 '' 以外の値を TEMPLATE_STRING_IF_INVALID に指定した場合、こうし たテンプレートやサイトのレンダリングで問題が生じるかもしれません。
一般に、 TEMPLATE_STRING_IF_INVALID を有効にするのは特定のテンプレー トに対する問題を解決したいときだけにして、デバッグが終わったらすぐに無 効にしておくべきです。
コンテキストオブジェクトの操作
皆さんはほとんどの場合、全ての値を入れた辞書を Context() に渡して Context オブジェクトのインスタンスを生成していることでしょう。しかし、 Context オブジェクトはインスタンス化した後にも、通常の辞書と同じように 値を追加したり削除したりできます:
>>> c = Context({"foo": "bar"})
>>> c['foo']
'bar'
>>> del c['foo']
>>> c['foo']
''
>>> c['newvariable'] = 'hello'
>>> c['newvariable']
'hello'
Context オブジェクトは一種のスタックです。すなわち、 push() や pop() 操作を行えます。 pop() しすぎると、 django.template.ContextPopException を送出します:
>>> c = Context() >>> c['foo'] = 'first level' >>> c.push() >>> c['foo'] = 'second level' >>> c['foo'] 'second level' >>> c.pop() >>> c['foo'] 'first level' >>> c['foo'] = 'overwritten' >>> c['foo'] 'overwritten' >>> c.pop() Traceback (most recent call last): ... django.template.ContextPopException
Context のスタック的な利用は、後述するカスタムテンプレートタグで便利な ことがあります。
コンテキストのサブクラス: RequestContext
Django には django.template.RequestContext という特別なコンテキストクラ スがあります。このクラスは通常の django.template.Context とは少し異なる 部分があります。まず、 RequestContext は第一引数に HttpRequest オブジェクト をとります。例えば:
c = RequestContext(request, {
'foo': 'bar',
}
第二に、 RequestContext は TEMPLATE_CONTEXT_PROCESSORS の設定 に従っ ていくつかの値を自動的にコンテキストに入れます。
TEMPLATE_CONTEXT_PROCESSORS 設定は呼び出し可能オブジェクトのタプルになっ ています。個々の呼び出し可能オブジェクトは コンテキストプロセッサ と呼 ばれ、リクエストオブジェクトを引数にとって。何らかの値を要素に持った辞書を 返します。この辞書はコンテキストオブジェクトに統合されます。デフォルトでは、 TEMPLATE_CONTEXT_PROCESSORS は以下のようになっています:
("django.core.context_processors.auth",
"django.core.context_processors.debug",
"django.core.context_processors.i18n",
"django.core.context_processors.media")
各プロセッサはタプルに列挙した順に適用されます。従って、あるプロセッサが ある変数をコンテキストに入れた後、別のプロセッサが同じ名前の変数を入れれば、 後者が前者の内容を上書きします。デフォルトのプロセッサについては後で説明し ます。
RequestContext にはオプションの第三引数 processors に追加のプロセッ サのリストを指定できます。下記の例では、 RequestContext に ip_address 変数が入ります:
def ip_address_processor(request):
return {'ip_address': request.META['REMOTE_ADDR']}
def some_view(request):
# ...
c = RequestContext(request, {
'foo': 'bar',
}, [ip_address_processor])
return t.render(c)
Note
Django の render_to_response() ショートカットを使っていて、辞書オブジェ クトを渡してコンテキストの変数を追加している場合、テンプレートはデフォル トで (RequestContext ではなく) Context になります。テンプレートの レンダリングに RequestContext を使うには、 render_to_response() の 3 つめの引数に RequestContext インスタンスを指定します。コードは以 下のようになるでしょう:
def some_view(request):
# ...
return render_to_response('my_template.html',
my_data_dictionary,
context_instance=RequestContext(request))
各プロセッサの動作は以下のようになっています:
django.core.context_processors.auth
このプロセッサが TEMPLATE_CONTEXT_PROCESSORS にある場合、以下の 3 つの 変数が RequestContext に入ります:
user -- 現在ログインしているユーザを表す auth.User インスタン ス (クライアントがログインしていない場合には AnonymousUser インス タンス) です。 ユーザ認証のドキュメント を参照してください。
messages -- 現在ログインしているユーザ宛の (文字列の) メッセージ のリストです。舞台裏では、この変数を参照するたびに request.user.get_and_delete_messaages() を呼び出しています。 request.user.get_and_delete_messaages() は、ユーザ宛の全メッセー ジを集めて返し、 Message オブジェクトを削除します。
メッセージは user.message_set.create で設定します。詳しくは メッセージのドキュメント を参照してください。
perms -- 現在ログインしているユーザが有しているパーミッションを表 す、 django.core.context_processors.PermWrapper のインスタンスで す。 パーミッションのドキュメント を参照してください。
django.core.context_processors.debug
このプロセッサが TEMPLATE_CONTEXT_PROCESSORS にある場合、以下の 2 つの 変数が RequestContext に入ります。ただし、 DEBUG の設定が True で、リクエストの IP アドレス (request.META['REMOTE_ADDR']) が INTERNAL_IPS 設定に入っている場合だけです:
- debug -- True です。 DEBUG モードかどうかをテストするのに 使えます。
- sql_queries -- {'sql': ..., 'time': ...} 辞書のリストです。 このリストはリクエスト処理中に実行された SQL クエリと、それにかかった 時間を表しています。リストはクエリの実行順にならんでいます。
django.core.context_processors.i18n
このプロセッサが TEMPLATE_CONTEXT_PROCESSORS にある場合、以下の 2 つの 変数が RequestContext に入ります:
- LANGUAGES -- LANGUAGES 設定 の値です。
- LANGUAGE_CODE -- request.LANGUAGE_CODE が存在する場合、その値 です。値がなければ LANGUAGE_CODE 設定 の値になります。
詳しくは 国際化のドキュメント を参照してください。
django.core.context_processors.media
開発版の Django で新たに登場した機能です
TEMPLATE_CONTEXT_PROCESSORS にこのコンテキストプロセッサを入れると、 RequestContext に MEDIA_URL というコンテキスト変数が入ります。 MEDIA_URL には MEDIA_URL 設定 の値が入っています。
django.core.context_processors.request
このプロセッサが TEMPLATE_CONTEXT_PROCESSORS にある場合、変数 request が RequestContext に入ります。この変数は現在の HttpRequest オブジェクト を表現します。このプロセッサはデフォルトでは有 効でないので注意してください。利用したければ有効にせねばなりません。
コンテキストプロセッサを自作する
コンテキストプロセッサのインタフェースはとても簡単です。コンテキストプロセッ サは単なる Python の関数で、 HttpRequest オブジェクトを引数にとり、 テンプレートコンテキストに追加できるような辞書を返します。コンテキストプロ セッサは、 必ず 辞書型を返さねばなりません。
自分のコードベースに自作のコンテキストプロセッサを入れてもかまいません。 Django 側からは、自作のコンテキストプロセッサを TEMPLATE_CONTEXT_PROCESSORS 設定で指定するようにしてください。
テンプレートのロード
一般的には、低水準の Template API を自分で使うことはほとんどなく、テン プレートはファイルの形でファイルシステム上に保存することになるでしょう。 テンプレートディレクトリ に指定したディレクトリにテンプレートファイルを 保存してください。
Django はテンプレートローダの設定 (下記の Loader 型 を参照してください) に従って、いくつものテンプレートディレクトリを検索しますが、最も基本的なテ ンプレートディレクトリ指定方法は TEMPLATE_DIRS を使う方法です。
TEMPLATE_DIRS 設定
設定ファイル中で、 TEMPLATE_DIRS 設定を使ってどのディレクトリにテンプレー トが収められているかを指定してください。この値は文字列のリストかタプルで指 定します。文字列にはテンプレートディレクトリへの完全なパスを指定せねばなり ません。例えば:
TEMPLATE_DIRS = (
"/home/html/templates/lawrence.com",
"/home/html/templates/default",
)
Web サーバがテンプレートディレクトリの中身を読みだせる限り、テンプレートは どこにでも置けます。拡張子はなんでもよく、 .html でも .txt でも かまいませんし、拡張子を付けなくてもかまいません。
Windows を使っている場合でも、パスは Unix 形式のスラッシュを使った表記法で 指定せねばならないので注意して下さい。
Python API
Django でファイルからテンプレートを読み出すには二通りの方法があります:
- django.template.loader.get_template(template_name)
- get_template は、指定した名前のテンプレートに対応したコンパイル済み のテンプレート (Template オブジェクト) を返します。テンプレートが存 在しなければ django.template.TemplateDoesNotExist を送出します。
- django.template.loader.select_template(template_name_list)
- select_template は get_template と同じです。ただし、テンプレー ト名のリストを引数にとります。リストの中で最初に見つかったテンプレート を返します。
例えば get_template('story_detail.html') を呼び出した場合、前述のような TEMPLATE_DIRS の設定をしていれば、Django は以下のような順番でテンプレー トファイルを探します:
- /home/html/templates/lawrence.com/story_detail.html
- /home/html/templates/default/story_detail.html
select_template(['story_253_detail.html', 'story_detail.html']) なら、 以下の順で探します:
- /home/html/templates/lawrence.com/story_253_detail.html
- /home/html/templates/default/story_253_detail.html
- /home/html/templates/lawrence.com/story_detail.html
- /home/html/templates/default/story_detail.html
テンプレートが見つかった時点で Django は検索を停止します。
Tip
select_template() を使うと、極めて柔軟な「テンプレート構成機能 (templatability)」を実現できます。例えば、あるニュース記事を書いていて、 記事によってはカスタムのテンプレートを使いたいとします。 select_template(['story_%s_detail.html' % story.id, 'story_detail.html']) のようにすると、個々の記事にカスタムのテンプレートを使いながら、カスタ ムのテンプレートがない記事に対してはフォールバックのテンプレートを用意 できるというわけです。
サブディレクトリの使用
テンプレートディレクトリを複数のサブディレクトリで構成するのは可能ですし、 お勧めでもあります。慣習的には各 Django アプリケーション毎にサブディレク トリを作成します。必要に応じてさらにサブディレクトリを作成します。
自分の正気を保つためにもぜひテンプレートを階層化しましょう。一つのディレク トリのルートレベルに全てのテンプレートを保存するのは、だらしないことこの上 ありません。
サブディレクトリ下にあるテンプレートをロードするには、以下のようにスラッシュ を使って指定します:
get_template('news/story_detail.html')
この例の get_template() は、上の TEMPLATE_DIRS と同じ設定を使った場 合、以下の順番でテンプレートをロードしようと試みます:
- /home/html/templates/lawrence.com/news/story_detail.html
- /home/html/templates/default/news/story_detail.html
Loader 型
デフォルトでは、 Django はファイルシステムベースのテンプレートローダを使っ ていますが、 Django には他のテンプレートローダも付属しており、その他のデー タソースからテンプレートをロードできるようになっています。
ファイルシステムベース以外のテンプレートローダはデフォルトでは無効化されて いますが、 TEMPLATE_LOADERS 設定を編集して有効にできます。 TEMPLATE_LOADERS は文字列のタプルで、各文字列がテンプレートローダを表し ます。組み込みのテンプレートローダを以下に示します:
- django.template.loaders.filesystem.load_template_source
- TEMPLATE_DIRS の設定に従って、ファイルシステムからテンプレートをロー ドします。
- django.template.loaders.app_directories.load_template_source
ファイルシステム上の Django アプリケーションからテンプレートをロードし ます。このローダは INSTALLED_APPS の各アプリケーションについて templates サブディレクトリを探します。ディレクトリが存在すれば、 Django はその中からテンプレートを探します。
これはすなわち、個々のアプリケーションにテンプレートを保存しておけると いうことです。このローダを使うと、 Django アプリケーションをデフォルト のテンプレート付きで配りやすくなります。
例えば、以下のように設定します:
INSTALLED_APPS = ('myproject.polls', 'myproject.music')get_template('foo.html') は以下のディレクトリを順番に探します:
- /path/to/myproject/polls/templates/foo.html
- /path/to/myproject/music/templates/foo.html
このローダは最初に import した時に最適化処理を行います。すなわち、 INSTALLED_APPS パッケージリストの中で、 templates サブディレク トリを持つものをキャッシュします。
- django.template.loaders.eggs.load_template_source
- app_directories と同じですが、ファイルシステムではなく Python eggs からテンプレートをロードします。
Django はテンプレートローダを TEMPLATE_LOADERS 設定の順番に従って 試してゆき、マッチするテンプレートが見つかるまで探します。
render_to_string() ショートカット
テンプレートのロードとレンダの反復を減らすため、 Django は作業を自動化する ためのショートカットとして、 django.template.loader で render_to_string() を提供しています。このショートカットは、テンプレート をロードし、レンダして、結果を文字列として返します:
from django.template.loader import render_to_string
rendered = render_to_string('my_template.html', { 'foo': 'bar' })
render_to_string ショートカットは、必須の引数として、レンダリング時にロー ドするテンプレートの名前 template_name をとります。また、オプション引 数を二つとります:
dictionary
テンプレートのコンテキストとして使う値の入った辞書です。第二固定引
数として渡しても構いません。
context_instance
``Context`` またはサブクラス (例えば ``RequestContext``) のインスタ
ンスです。テンプレートのコンテキストとして使われます。第三固定引数
として渡してもかまいません。
render_to_response() ショートカットも参照してください。このショートカッ トは、結果を HttpResponse に入れて、ビューからレンダ内容を直接返せるよ うにしています。
テンプレートシステムの拡張
Django テンプレート言語にはデフォルトのタグやフィルタがいくつも組み込まれて いますが、自作のタグを書きたいと思うこともあるでしょう。もちろん簡単にでき ます。
まず、 templatetags パッケージを適当な Django アプリケーションパッケー ジ内に作ります。パッケージは models.py や views.py などと同じ階層 にせねばなりません。例えば:
polls/
models.py
templatetags/
views.py
templatetags パッケージには、二つのファイルを追加します。一つは __init__.py で、もう一つは自作のタグ/フィルタ定義の入ったファイルです。 後者のファイル名は、あとでタグをロードするときに使う名前にします。例えば poll_extras.py という名前のファイルに自作のタグ/フィルタを入れているの なら、テンプレートでは以下のようにしてロードします:
{% load poll_extras %}
{% load %} タグは INSTALLED_APPS 設定を探して、インストールされてい る Django アプリケーションに入っているテンプレートだけがロードされるように します。これはセキュリティ上の制約です: こうすることで、一つのコンピュータ にたくさんのテンプレートライブラリのコードを置きながら、個々の Django イン ストールがライブラリに安全にアクセスできるようにします。
特定のモデルやビューに結び付かないテンプレートライブラリを書く場合、 templatetags パッケージだけの入った Django アプリケーションパッケージを 作成しても問題ありません。
templatetags パッケージには、いくつでもモジュールを入れられます。 {% load %} 文はアプリケーションの名前ではなく、 Python モジュール名 に対応したタグ/フィルタをロードするということを心に留めておいてください。
Python モジュールを作成したら、実現したいフィルタやタグに応じて Python コー ドを少し書くだけです。
有効なタグライブラリを作るには、 register という名前のモジュールレベル 変数に template.Library のインスタンスを入れておく必要があります。 このインスタンスには、全てのタグとフィルタが登録されます。したがって、モジュー ルの一番上の方で、以下のようなコード:
from django import template register = template.Library()
を実行しておく必要があります。
舞台裏
Django のデフォルトのフィルタやタグのソースコードには大量のサンプルが 収められています。ソースコードはそれぞれ django/template/defaultfilters.py や django/template/defaulttags.py にあります。
カスタムのテンプレートフィルタ
カスタムフィルタは単なる Python の関数で、一つまたは二つの引数をとります:
- テンプレート変数 (入力) の値。文字列とは限りません。
- 引数の値。デフォルトを持たせたり、無視させたりできます。
を取ります。例えば、フィルタ {{ var|foo:"bar" }} では、フィルタ foo はテンプレート変数 var と引数 "bar" を受け取ります。
フィルタ関数は常に何かを返さねばなりません。フィルタ関数は例外を送出しては ならず、失敗するときは暗黙のうちに失敗せねばなりません。エラーが生じた場合、 フィルタ関数は元の入力そのままか空文字列のうち、分かりやすい方を返すように せねばなりません。
フィルタの定義の例を以下に示します:
def cut(value, arg):
"入力から arg の値を全て取り去る"
return value.replace(arg, '')
このフィルタの使用例は以下のようになります:
{{ somevariable|cut:"0" }}
ほとんどのフィルタは引数を取りません。引数を取らない場合には関数から引数を なくして下さい。例えば:
def lower(value): # Only one argument.
"入力をすべて小文字にする"
return value.lower()
テンプレートフィルタに文字列だけを処理させる
第一引数に文字列しかとらないテンプレートフィルタを書きたければ、 stringfilter デコレータを使ってください。このフィルタは、フィルタ処理の 関数を呼び出す前に、第一引数のオブジェクトを文字列に変換します:
from django.template.defaultfilters import stringfilter
@stringfilter
def lower(value):
return value.lower()
こうすれば、例えばフィルタに整数型を渡したとしても、(整数型に lower() がないために) AttributeError が送出されるようなことはありません。
カスタムフィルタを登録する
フィルタ定義を書き終えたら、 Django テンプレート言語で使えるようにするため に Library インスタンスに登録する必要があります:
register.filter('cut', cut)
register.filter('lower', lower)
Library.filter() は二つの引数を取ります:
- フィルタの名前。文字列です。
- コンパイル関数。(関数名の文字列ではなく) 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 は関数名を そのままフィルタ名として使います。
フィルタと自動エスケープ
カスタムフィルタを書く場合、フィルタが Django の自動エスケープとどう関わっ ているかに少し配慮しておく必要があります。
まず、テンプレートのコード内では、 3 種類の文字列が受け渡しされています:
生の文字列 とは、 Python ネイティブの str や unicode 型の オブジェクトです。出力時、自動エスケープが有効であればエスケープされ、 そうでなければ変更されません。
safe 文字列 とは、出力時にさらなるエスケープ処理を行わなくてもよ い文字列です。safe 文字列は、必要なエスケープが済んでいる文字列です。 クライアント側でそのまま解釈されるべき生の HTML を含んだ文字列を表現 するのに使われます。
内部的には、これらの文字列は SafeString や SafeUnicode 型で表 現されています。 SafeString と SafeUnicode は SafeData を 共通の基底クラスに持つので、以下のようにすれば判別できます:
if isinstance(value, SafeData): # "safe" 文字列を扱う処理をここに"エスケープが必要" であるとマークされている文字列 は、 autoescape ブロックの指定に関係なく、必ずエスケープされます。 マークされている文字列は、自動エスケープの設定に関係なく、一度だけエ スケープ処理を受けます。
内部的には、エスケープ必要文字列は、 EscapeString や EscapeUnicode 型で表現されています。通常、これらの型のことを気に する必要はありません。 EscapeString や EscapeUnicode は、 escape フィルタの実装のために用意されています。
テンプレートフィルタのコードは、以下の二つのうちどちらかに落ち着きます:
フィルタコードの出力が、 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 が 必要でなければ無視してかまいません。
フィルタを作成するときには、フィルタが本当に安全な文字を安全なままに 出力するのかをよく考えてください。例えば、フィルタが文字を 削除 す る場合、出力結果に、対応の取れていないタグや不完全なエンティティ表現 が意図せず混じる可能性があります。例えば、 > を入力から削ってし まうと、 <a> の出力は <a になってしまい、問題を起こさないよ うにするためには、出力をエスケープせねばならなくなってしまいます。同 様に、セミコロン (;) を除去すると、 & は & になっ てしまい、有効なエンティティ表現でないために、エスケープが必要になっ てしまいます。たいていのケースでは、このようなトリッキーなことは起こ りませんが、コードをレビューする際にはこの手のことが起こらないように よく確認してください。
もう一つは、フィルタのコードで必要なエスケープを行うというものです。 出力に新たな HTML マークアップを含めたい場合に必要です。この場合、 出力する HTML マークアップがそれ以上エスケープされないよう、出力を safe にする必要があるので、入力は自分で扱わねばなりません。
出力を safe 文字列としてマークするには、 django.utils.safestring.mark_safe() を使います。 ただし、注意してください。出力を safe であるとマークするだけでは十分 ではありません。出力が本当に safe である ことを保証せねばならず、 それは自動エスケープが有効か無効かで変わります。自動エスケープがオン でもオフでも使え、テンプレートの作者が簡単に扱えるフィルタを書くのが 理想です。
現在の自動エスケープ状態をフィルタに教えるには、関数の needs_autoescape 属性を True に設定します (この属性を設定 しない場合のデフォルト値は False です)。 needs_autoescape を True にすると、フィルタ関数には autoescape という追加の引 数が渡されます。自動エスケープが有効な状況でフィルタが呼び出されると、 autoescape には True が渡され、そうでない場合には False が渡されます。
例えば、文字列の先頭の文字を強調表示にするようなフィルタを書いてみま しょう:
from django.utils.html import conditional_escape from django.utils.safestring import mark_safe def initial_letter_filter(text, autoescape=None): first, other = text[0], text[1:] if autoescape: esc = conditional_escape else: esc = lambda x: x result = '<strong>%s</strong>%s' % (esc(first), esc(other)) return mark_safe(result) initial_letter_filter.needs_autoescape = Trueフィルタ関数の needs_autoescape 属性と autoescape キーワード 引数の存在によって、フィルタ関数は自身が呼び出されたときの自動エスケー プ状態を検知できます。上の例では、 autoescape を使って、入力デー タを django.utils.html.conditional_escape で処理するべきかどうか 判定しています。 (conditional_escape が不要の場合、 esc を アイデンティティ関数にしています) conditional_escape() は escape() に似ていますが、入力が SafeData インスタンスで ない 場合にのみエスケープを適用します。 conditional_escape() に SafeData を渡すと、データは変更されません。
最後に、上の例では、フィルタ結果を mark_safe で safe にマークし て、出力結果の HTML をこれ以上エスケープせず、最終的なテンプレート出 力に挿入させています。
このケースでは、 is_safe 属性について触れていません (実際には、 設定しても何の影響もありません)。自動エスケープの問題を手動で解決し て、 safe 文字列を返している場合、 is_safe 属性は何ら影響をもた ないのです。
カスタムテンプレートタグを書く
タグはフィルタよりもやや複雑です。というのも、タグは何でもできるからです。
テンプレートタグの概要
前に、このドキュメントで、テンプレートシステムはコンパイルとレンダリングと いう二段階のプロセスで動作すると説明しました。カスタムテンプレートタグを定 義するには、コンパイルがどのように行われ、レンダリングがどのように行われる かを指定する必要があります。
テンプレートをコンパイルする時、Django は元のテンプレートテキストを「ノード (node)」に分割します。各ノードは django.template.Node のインスタンスで あり、 render() メソッドを持ちます。コンパイル済みのテンプレートは単な る Node オブジェクトの集まりに過ぎません。コンパイル済みのテンプレート オブジェクトに対して render() を呼び出すと、テンプレートは指定されたコ ンテキストを使ってノードリストの各 Node に対して render() を呼び出 します。戻り値は全て連結され、テンプレートからの出力になります。
従って、カスタムテンプレートタグを定義するには、元のテンプレートタグをどう やって Node (コンパイル関数: compilation function) に変換し、個々のノー ドの render() メソッドが何をするかを指定する必要があります。
コンパイル関数を書く
テンプレートタグに到達するたびに、テンプレートパーザはタグの内容とパーザオ ブジェクト自体を引数にしてある Python 関数を呼び出します。この関数はタグの 内容に応じて Node インスタンスを返すようになっています。
例えば、 {% current_time %} というタグを書いてみましょう。このタグは現 在の日付/時刻を表示し、そのフォーマットはタグの引数に指定した strftime の文法 に従うとします。何をおいてもタグの構文をまず決めておくの がよいでしょう。我々の例では、タグは以下のように使われるものとしましょう:
<p>The time is {% current_time "%Y-%m-%d %I:%M %p" %}.</p>
以下の関数は、パラメタを取り出して、 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])
Notes:
- 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 を定義する必要がありま す:
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 やその他の例外を送出してはな りません。フィルタと同様、失敗は暗黙のうちに処理されるようにせねばな りません、
一つのテンプレートは、繰り返しパージングされることなく複数のコンテキストを レンダリングすることがあるので、このようなコンパイルとレンダリングの脱カッ プリングは究極的には効率的なテンプレートシステムを実現します。
自動エスケープについての注意点
開発版の Django で新たに登場した機能
テンプレートタグの出力は、自動エスケープフィルタで自動的に処理され ません 。とはいえ、テンプレートタグを書くときには、二つのことを心に留めて 置く必要があります。
まず、テンプレートタグの 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 %} ブロック内で使った場合の出力が期待とは違うものになっ てしまいます。
タグの登録
最後に、上の「カスタムテンプレートフィルタを書く」の節で説明したように、タ グをモジュールの Library インスタンスに登録します:
register.tag('current_time', do_current_time)
tag() メソッドは二つの引数をとります:
- テンプレートタグの名前、文字列です。省略すると、コンパイル関数の名前 を使います。
- コンパイル関数。 Python の関数です (関数名を表す文字列ではありません)。
フィルタの登録と同様、バージョン 2.4 以降の Python ではこの関数をデコレータ として使えます:
@register.tag(name="current_time")
def do_current_time(parser, token):
# ...
@register.tag
def shout(parser, token):
# ...
上の例の下の定義のようにして name 引数を省略すると、 Django は関数名を そのままフィルタ名として使います。
テンプレート変数をタグに渡す
token.split_contents() を使えば、テンプレートタグにいくつでも引数を渡せ ますが、引数はすべて文字列リテラルになってしまいます。そのため、テンプレー トタグの引数に動的なコンテンツ (テンプレート変数) を渡すには、もうひと工夫 必要です。先に挙げた例では、現在時刻を文字列にフォーマットして文字列で値を 返していましたが、今度は以下のように DateTimeField の値を渡してテンプレー トタグに日付と時刻をフォーマットさせたいとしましょう:
<p>この投稿が最後に更新された時刻:{% format_time blog_entry.date_updated "%Y-%m-%d %I:%M %p" %}</p>
まず、 token.split_contents() は以下の 3 つの値を返します:
- タグ名である format_time 。
- blog_entry.date_updated という 文字列 。
- フォーマット文字列 "%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 の値を解決して、 その値にしたがってフォーマット処理を行います。
Note
resolve_variable() は、渡された文字列の表す値をページの現在のコンテ キストから解決できなかった場合に VariableDoesNotExist 例外を送出し ます。
開発版の Django で新たに登場した機能
開発版の 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 例外が送出されます。
簡単なタグへのショートカット
多くのテンプレートタグは、文字列やテンプレート変数への参照を単一の引数とし て取り、入力引数や外部の何らかの情報に基づいて処理を行った後、文字列を返し ます。例えば、上で書いた 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(token):
...
- simple_tag ヘルパ関数については、以下の点に注意してください:
- ラップ対象の関数には単一の引数 (だけ) しか渡せません。
- 引数の個数チェックなどは関数の呼び出しの際に行われるので、わざわざ自 分でチェックしなくてもかまいません。
- 関数に渡される引数がクオートされている場合、クオートは自動的に取り去 られます。従って、関数は通常の文字列を受け取ることになります。
- 引数がテンプレート変数なら、関数に渡されるのは変数自体ではなく、 変数の現在値になります。
テンプレートタグが現在のコンテキストにアクセスしなくてもよいのなら、 入力値を引数に取るような関数を定義し、 simple_tag ヘルパ関数を使うのが 一番簡単です。
埋め込みタグ
テンプレートタグによくあるもう一つのタイプは、 別の テンプレートをレンダ して表示するものです。例えば、 Django の admin インタフェースではカスタムの テンプレートタグを使って「追加/変更」フォームページのボタンを表示していま す。これらのボタンはいつも同じように表示されていますが、リンクのターゲット は編集対象のオブジェクトによって変わります。従って、こうした処理は、小さな テンプレートを使って現在のオブジェクトに応じた値を埋めるのが最良の方法と言 えます。 (admin の submit_raw がそのタグです)。
こうしたタグのことを 「埋め込みタグ (inclusion tag)」 と呼びます。
埋め込みタグの書き方を説明するには、例を挙げるのがベストでしょう。 チュートリアル で作成した Poll オブジェクトを例に、ある Poll オ ブジェクトに対する選択肢のリストを出力するようなタグを書いてみましょう。 タグは以下のようになります:
{% show_results poll %}
例えば、出力は以下のようになります:
<ul> <li>First choice</li> <li>Second choice</li> <li>Third choice</li> </ul>
まず、引数を受け取って、対応するデータの辞書を生成するような関数を定義しま す。ここで重要なのは、辞書を返さねばならず、それ以外の複雑なデータを返して はならないということです。戻り値はテンプレートフラグメントをレンダするとき のテンプレートコンテキストに使われます。例を示します:
def show_results(poll):
choices = poll.choice_set.all()
return {'choices': choices}
次に、タグの出力をレンダする際に使うテンプレートを作成します。このテンプレー トはタグ固有のもので、テンプレートデザイナではなくタグの開発者が書くべきも のです。上の例に従えば、テンプレートはとても単純な形になります:
<ul>
{% for choice in choices %}
<li> {{ choice }} </li>
{% endfor %}
</ul>
さて、 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 テンプレートの中は以下のよう になります:
Jump directly to <a href="{{ link }}">{{ title }}</a>.
このカスタムタグを使う場合は常に、まずライブラリを呼び出しておき、下記のよ うに引数なしでタグを呼び出します:
{% jump_link %}
takes_context=True を使っている場合、テンプレートタグに引数を渡す必要は ありません。タグが自動的にコンテキストにアクセスします。
takes_context パラメタはデフォルトでは False です。この値を True に設定すると、タグの引数にはコンテキストオブジェクトが渡されます。この点だ けは前述の inclusion_tag の例と異なります。
コンテキスト中の変数を設定する
上の例は、単に値を出力するという単純なものでした。一般的な話として、テンプ レートタグが値を出力するだけでなく、テンプレート変数の値を設定できたりする ともっと柔軟性が増すでしょう。そうすれば、テンプレートの作者はテンプレート タグのつくり出す変数を再利用できるようになります。
コンテキスト中に変数を設定するには、 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() は空の文字列を返すだけです。
新しいバージョンのタグの使い方を示します:
{% current_time "%Y-%m-%d %I:%M %p" %}<p>The time is {{ current_time }}.</p>
ところで、 CurrentTimeNode2 には問題があります: 変数名 current_time がハードコードされているのです。これはすなわち、テンプレートの他の部分で {{ current_time }} を使わないようにせねばならないことを意味します。とい うのも、 {% current_time %} は変数の値を盲目的に上書きしてしまうからで す。より綺麗な解法は、出力用の変数名を定義させるというものです。例えば:
{% get_current_time "%Y-%m-%d %I:%M %p" as my_current_time %}
<p>The current time is {{ my_current_time }}.</p>
この機能を実現するには、コンパイル関数と 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 に渡すという点です。
他のブロックタグに到達するまでパージングする
テンプレートタグは違いに連携させられます。例えば、標準の {% 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 %} の間の内容は全て無視されます。
次のブロックタグまでパージングして、内容を保存する
前節の例では、 do_comment() は {% comment %} から {% endcomment %} までの全ての内容を無視しています。このような処理に代え て、ブロックタグ間のコードを使った処理も行えます。
例えば、 {% upper %} というカスタムのテンプレートタグを定義して、 {% endupper %} までの内容を大文字に変換できるようにします。
使い方はこのようになります:
{% 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 にあります。
テンプレートシステムをスタンドアロンモードに設定する
Note
この節は、テンプレートシステムを他のアプリケーションの出力用コンポーネ ントとして使ってみたいと考えている人のためのものです。テンプレートシス テムを Django の一部として使っている人には、この節の内容はあてはまりま せん。
通常、 Django は必要な全ての設定情報を、デフォルトの設定ファイル上の設定と 環境変数 DJANGO_SETTING_FILE に指定したファイル上の設定を合わせたものか らロードします。ただし、 Django の他の部分からテンプレートシステムを切り離 して使いたい場合は、環境変数を介して設定ファイルを扱うよりも、アプリケーショ ン内でテンプレートシステムを初期化したいでしょうから、環境変数を使う方法は あまり便利とはいえないでしょう。
この問題を解決するためには、 設定ファイル のドキュメントに書かれている手 動設定の方法を使う必要があります。テンプレートシステムの必要な部分を import して、何らかのテンプレート関連の関数を呼ぶ 前に 、指定したい設定を引数に して django.conf.settings.configure を呼び出して下さい。少なくとも TEMPLATE_DIRS (テンプレートローダを使いたい場合), DEFAULT_CHARSET (デフォルトの utf-8 で十分ですが), TEMPLATE_DEBUG などを設定するこ とになるでしょう。設定可能な変数は、 設定ファイルのドキュメント に書かれ ています。関係のある変数は TEMPLATE_ で始まっている設定です。