Djangoを読む
はじめに †
テンプレートの解析まで読んだので最後、いよいよテンプレートのレンダリングです。呼び出しのトップはdjango.template.loaderのrender_to_string
1
2
3
4
5
6
7
8
9
10
11
|
-
|
|
|
!
| def render_to_string(template_name, context=None, request=None, using=None):
"""
Loads a template and renders it with a context. Returns a string.
template_name may be a string or a list of strings.
"""
if isinstance(template_name, (list, tuple)):
template = select_template(template_name, using=using)
else:
template = get_template(template_name, using=using)
return template.render(context, request)
|
templateの型は、とりあえずdjango.template.backends.django.Templateです。
Template移譲の終点探し †
とりあえずと言ったのは、templateと名のつくクラスが複数いるからです。まずはdjango.template.backends.django.Templateのrenderを見てみましょう。
1
2
3
4
5
6
|
| def render(self, context=None, request=None):
context = make_context(context, request, autoescape=self.backend.engine.autoescape)
try:
return self.template.render(context)
except TemplateDoesNotExist as exc:
reraise(exc, self.backend)
|
ここでいうself.templateはこう渡されている。
1
2
3
4
5
|
| def get_template(self, template_name):
try:
return Template(self.engine.get_template(template_name), self)
except TemplateDoesNotExist as exc:
reraise(exc, self)
|
たびたび見ているように、self.engineはdjango.template.engineのEngineクラス。
1
2
3
4
5
6
7
8
9
10
|
-
|
|
!
-
!
| def get_template(self, template_name):
"""
Returns a compiled Template object for the given template name,
handling template inheritance recursively.
"""
template, origin = self.find_template(template_name)
if not hasattr(template, 'render'):
template = Template(template, origin, template_name, engine=self)
return template
|
前回見た感じでは、find_templateの時点でコンパイルされたものが返ってきています。いずれにしろdjango.template.base.Templateが使用されます。
django.template.base.Template †
というわけでdjango.template.base.Templateのrenderメソッド
1
2
3
4
5
6
7
8
9
10
11
12
|
| def render(self, context):
"Display stage -- can be called many times"
context.render_context.push()
try:
if context.template is None:
with context.bind_template(self):
context.template_name = self.name
return self._render(context)
else:
return self._render(context)
finally:
context.render_context.pop()
|
contextでいろいろやっているのは見ててそんなにおもしろくないのでスルーします。context周りはdjango.template.contextに書かれています。
_renderはnodelist呼んでいるだけ。
1
2
|
| def _render(self, context):
return self.nodelist.render(context)
|
NodeListのrender
1
2
3
4
5
6
7
8
9
|
| def render(self, context):
bits = []
for node in self:
if isinstance(node, Node):
bit = node.render_annotated(context)
else:
bit = node
bits.append(force_text(bit))
return mark_safe(''.join(bits))
|
in selfとなっているのは、NodeListがlistクラスを継承しているからです。
render_annotatedというのは例外時にデバッグ情報出すようにしているだけのようなのでここからは各論行きます。
VariableNode †
変数を見てみましょう。ちなみに、Djangoテンプレートの変数は「.」指定されていた場合、以下のルールがあります。
- 変数を辞書とみなし、「.」以下を辞書のキーとしてアクセスしてみる
- 変数の属性としてアクセスしてみる
- リストのインデックスとしてアクセスしてみる
- いずれの場合も、得られたものがcallableなら引数なしで呼び出す
で、VariableNodeのrender
1
2
3
4
5
6
7
8
9
|
-
|
|
!
| def render(self, context):
try:
output = self.filter_expression.resolve(context)
except UnicodeDecodeError:
return ''
return render_value_in_context(output, context)
|
FilterExpressionクラスのresolveメソッド
1
2
3
4
5
6
7
8
9
10
|
-
!
-
!
| def resolve(self, context, ignore_failures=False):
if isinstance(self.var, Variable):
try:
obj = self.var.resolve(context)
except VariableDoesNotExist:
else:
obj = self.var
return obj
|
varはVariableオブジェクトなのでVariableクラスのresolveへ。
1
2
3
4
5
6
7
8
9
10
11
|
-
!
-
!
-
!
| def resolve(self, context):
"""Resolve this variable against a given context."""
if self.lookups is not None:
value = self._resolve_lookup(context)
else:
value = self.literal
if self.translate:
return value
|
前回見てなかったけど、lookupsはコンストラクタで設定されるようです(「.」でsplitしたタプル)。というわけで_resolve_lookupへ。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
-
|
!
-
!
-
!
-
!
| def _resolve_lookup(self, context):
current = context
try: for bit in self.lookups:
try: current = current[bit]
except (TypeError, AttributeError, KeyError, ValueError, IndexError):
try: if isinstance(current, BaseContext) and getattr(type(current), bit):
raise AttributeError
current = getattr(current, bit)
except (TypeError, AttributeError) as e:
if (isinstance(e, AttributeError) and
not isinstance(current, BaseContext) and bit in dir(current)):
raise
try: current = current[int(bit)]
except (IndexError, ValueError, KeyError, TypeError): raise VariableDoesNotExist("Failed lookup for key "
"[%s] in %r",
(bit, current)) if callable(current):
if getattr(current, 'do_not_call_in_templates', False):
pass
elif getattr(current, 'alters_data', False):
current = context.template.engine.string_if_invalid
else:
try: current = current()
except TypeError:
try:
inspect.getcallargs(current)
except TypeError: current = context.template.engine.string_if_invalid else:
raise
except Exception as e:
return current
|
ちょっと長いですが初めに挙げた順番で変数解決を行っています。
ちなみに、「.」付きじゃない普通の変数の場合どう動くんだ?という疑問に対しては、currentの初めがcontext(辞書オブジェクトとみなせる)なので先頭のdictionary lookupで指定された変数が見つかるようになっています。
django.template.defaulttags.IfNode †
次にタグの処理を見てみましょう。前回も確認したifに対応するIfNode。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
| def render(self, context):
for condition, nodelist in self.conditions_nodelists:
if condition is not None: try:
match = condition.eval(context)
except VariableDoesNotExist:
match = None
else: match = True
if match:
return nodelist.render(context)
return ''
|
conditionの中身がどうなっているかは前回さくっと省略しましたが、結局同じように変数解決まで進んで値が取り出されています。
おわりに †
以上、テンプレートのレンダリングについて見てきました。いろいろ省略したところはありましたがオブジェクトが出来上がってしまえば後は淡々と処理を行っていくだけなので今までに比べると簡単な印象です。
さて、これでようやく一年ぐらいかけて読んできた(読んでない期間も長かったですが)Djangoを一通り読み終わりました。
View(普通のMVCのController)、Model、Template(普通のMVCのTemplate)がそれぞれ切り離されているのでわかりやすい一方でModelは非常に複雑でした。Templateについてもフィルターやif条件のいろんなパターンについては触れませんでしたが地味にめんどくさいものになっています。
いろいろなケースをすべて対応するとなると結局ひとつの言語を実装することになるわけでどうしても複雑になるのかなという印象です。
そういえばそろそろ2.0リリースされそうですね(笑)