Djangoを読む

はじめに

テンプレートの解析まで読んだので最後、いよいよテンプレートのレンダリングです。呼び出しのトップはdjango.template.loaderのrender_to_string

Everything is expanded.Everything is shortened.
  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を見てみましょう。

Everything is expanded.Everything is shortened.
  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はこう渡されている。

Everything is expanded.Everything is shortened.
  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クラス。

Everything is expanded.Everything is shortened.
  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 needs to be compiled
            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メソッド

Everything is expanded.Everything is shortened.
  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呼んでいるだけ。

Everything is expanded.Everything is shortened.
  1
  2
 
 
    def _render(self, context):
        return self.nodelist.render(context)

NodeListのrender

Everything is expanded.Everything is shortened.
  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

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 
 
 
 
-
|
|
!
 
    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メソッド

Everything is expanded.Everything is shortened.
  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へ。

Everything is expanded.Everything is shortened.
  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:
            # 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へ。

Everything is expanded.Everything is shortened.
  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:  # 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

次にタグの処理を見てみましょう。前回も確認したifに対応するIfNode。

Everything is expanded.Everything is shortened.
  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:           # 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の中身がどうなっているかは前回さくっと省略しましたが、結局同じように変数解決まで進んで値が取り出されています。

おわりに

以上、テンプレートのレンダリングについて見てきました。いろいろ省略したところはありましたがオブジェクトが出来上がってしまえば後は淡々と処理を行っていくだけなので今までに比べると簡単な印象です。

さて、これでようやく一年ぐらいかけて読んできた(読んでない期間も長かったですが)Djangoを一通り読み終わりました。 View(普通のMVCのController)、Model、Template(普通のMVCのTemplate)がそれぞれ切り離されているのでわかりやすい一方でModelは非常に複雑でした。Templateについてもフィルターやif条件のいろんなパターンについては触れませんでしたが地味にめんどくさいものになっています。 いろいろなケースをすべて対応するとなると結局ひとつの言語を実装することになるわけでどうしても複雑になるのかなという印象です。

そういえばそろそろ2.0リリースされそうですね(笑)


トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2017-10-22 (日) 21:50:39 (2371d)