[[Djangoを読む]]
 
 #contents
 
 *はじめに [#b440516d]
 
 テンプレートの解析まで読んだので最後、いよいよテンプレートのレンダリングです。呼び出しのトップはdjango.template.loaderのrender_to_string
 
 #code(Python){{
 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移譲の終点探し [#v2225006]
 
 とりあえずと言ったのは、templateと名のつくクラスが複数いるからです。まずはdjango.template.backends.django.Templateのrenderを見てみましょう。
 
 #code(Python){{
     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はこう渡されている。
 
 #code(Python){{
     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クラス。
 
 #code(Python){{
     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 needs to be compiled
             template = Template(template, origin, template_name, engine=self)
         return template
 }}
 
 前回見た感じでは、find_templateの時点でコンパイルされたものが返ってきています。いずれにしろdjango.template.base.Templateが使用されます。
 
 *django.template.base.Template [#b2ebf392]
 
 というわけでdjango.template.base.Templateのrenderメソッド
 
 #code(Python){{
     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呼んでいるだけ。
 
 #code(Python){{
     def _render(self, context):
         return self.nodelist.render(context)
 }}
 
 NodeListのrender
 
 #code(Python){{
     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 [#q62e4ac0]
 
 変数を見てみましょう。ちなみに、Djangoテンプレートの変数は「.」指定されていた場合、以下のルールがあります。
 
 -変数を辞書とみなし、「.」以下を辞書のキーとしてアクセスしてみる
 -変数の属性としてアクセスしてみる
 -リストのインデックスとしてアクセスしてみる
 -いずれの場合も、得られたものがcallableなら引数なしで呼び出す
 
 で、VariableNodeのrender
 
 #code(Python){{
     def render(self, context):
         try:
             output = self.filter_expression.resolve(context)
         except UnicodeDecodeError:
             # Unicode conversion can fail sometimes for reasons out of our
             # control (e.g. exception rendering). In that case, we fail
             # quietly.
             return ''
         return render_value_in_context(output, context)
 }}
 
 FilterExpressionクラスのresolveメソッド
 
 #code(Python){{
     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へ。
 
 #code(Python){{
     def resolve(self, context):
         """Resolve this variable against a given context."""
         if self.lookups is not None:
             # We're dealing with a variable that needs to be resolved
             value = self._resolve_lookup(context)
         else:
             # We're dealing with a literal, so it's already been "resolved"
             value = self.literal
         if self.translate:
             # 省略
         return value
 }}
 
 前回見てなかったけど、lookupsはコンストラクタで設定されるようです(「.」でsplitしたタプル)。というわけで_resolve_lookupへ。
 
 #code(Python){{
     def _resolve_lookup(self, context):
         current = context
         try:  # catch-all for silent variable failures
             for bit in self.lookups:
                 try:  # dictionary lookup
                     current = current[bit]
                     # ValueError/IndexError are for numpy.array lookup on
                     # numpy < 1.9 and 1.9+ respectively
                 except (TypeError, AttributeError, KeyError, ValueError, IndexError):
                     try:  # attribute lookup
                         # Don't return class attributes if the class is the context:
                         if isinstance(current, BaseContext) and getattr(type(current), bit):
                             raise AttributeError
                         current = getattr(current, bit)
                     except (TypeError, AttributeError) as e:
                         # Reraise an AttributeError raised by a @property
                         if (isinstance(e, AttributeError) and
                                 not isinstance(current, BaseContext) and bit in dir(current)):
                             raise
                         try:  # list-index lookup
                             current = current[int(bit)]
                         except (IndexError,  # list index out of range
                                 ValueError,  # invalid literal for int()
                                 KeyError,    # current is a dict without `int(bit)` key
                                 TypeError):  # unsubscriptable object
                             raise VariableDoesNotExist("Failed lookup for key "
                                                        "[%s] in %r",
                                                        (bit, current))  # missing attribute
                 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:  # method call (assuming no args required)
                             current = current()
                         except TypeError:
                             try:
                                 inspect.getcallargs(current)
                             except TypeError:  # arguments *were* required
                                 current = context.template.engine.string_if_invalid  # invalid method call
                             else:
                                 raise
         except Exception as e:
             # 省略
 
         return current
 }}
 
 ちょっと長いですが初めに挙げた順番で変数解決を行っています。
 ちなみに、「.」付きじゃない普通の変数の場合どう動くんだ?という疑問に対しては、currentの初めがcontext(辞書オブジェクトとみなせる)なので先頭のdictionary lookupで指定された変数が見つかるようになっています。
 
 *django.template.defaulttags.IfNode [#f7dd39ca]
 
 次にタグの処理を見てみましょう。前回も確認したifに対応するIfNode。
 
 #code(Python){{
     def render(self, context):
         for condition, nodelist in self.conditions_nodelists:
 
             if condition is not None:           # if / elif clause
                 try:
                     match = condition.eval(context)
                 except VariableDoesNotExist:
                     match = None
             else:                               # else clause
                 match = True
 
             if match:
                 return nodelist.render(context)
 
         return ''
 }}
 
 conditionの中身がどうなっているかは前回さくっと省略しましたが、結局同じように変数解決まで進んで値が取り出されています。
 
 *おわりに [#r0a1b5fa]
 
 以上、テンプレートのレンダリングについて見てきました。いろいろ省略したところはありましたがオブジェクトが出来上がってしまえば後は淡々と処理を行っていくだけなので今までに比べると簡単な印象です。
 
 さて、これでようやく一年ぐらいかけて読んできた(読んでない期間も長かったですが)Djangoを一通り読み終わりました。
 View(普通のMVCのController)、Model、Template(普通のMVCのTemplate)がそれぞれ切り離されているのでわかりやすい一方でModelは非常に複雑でした。Templateについてもフィルターやif条件のいろんなパターンについては触れませんでしたが地味にめんどくさいものになっています。
 いろいろなケースをすべて対応するとなると結局ひとつの言語を実装することになるわけでどうしても複雑になるのかなという印象です。
 
 そういえばそろそろ2.0リリースされそうですね(笑)

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS