Chapter 13 · Templates

13.6 Rendering: nodes and the render context

Rendering walks a compiled template’s tree of nodes with a context and returns the output as one string. Each node renders its piece and NodeList.render joins them; VariableNode makes a value into text with render_value_in_context; and what a node must remember during the walk it keeps, ordinarily, in the RenderContext.

The cached loader, the default, hands the same compiled tree to every request for a template (Finding a template: Engine and the loaders), so a render must leave the tree as it found it. What belongs to one render lives in two objects made for it: the context, the values the template can see (The context: Context, RequestContext and context processors), and the render context, made with every Context, for the nodes’ own state.

Starting a render

Template.render is given a Context, prepares it, and walks the tree. It calls RenderContext.push_state with itself, which makes it the render context’s current template and pushes an empty level for its nodes. If no template is bound to the context yet, it binds itself with Context.bind_template, which is when a RequestContext runs its context processors, and sets Context.template_name to its name. Then Template._render renders the template’s NodeList, and the SafeString it returns is the output. The level and the binding are undone when the render ends, by an exception too.

So a render knows two templates. Context.template is the one the render began with, bound once: a template rendered from inside the render, by {% include %}, an inclusion tag or {% partial %}, finds the context bound and calls _render without binding. Nodes reach the engine through the bound template, for Engine.debug and Engine.string_if_invalid and to load templates, and Context.template_name names it in the debug log of a failed variable lookup. RenderContext.template is the template being rendered at the moment: the included one inside an {% include %}, and the parent once {% extends %} has handed over, even while the child’s blocks render within it.

Template._render is the walk alone. ExtendsNode.render pushes a parent’s state itself and calls the parent’s _render (Inheritance and inclusion: {% extends %}, {% block %} and {% include %}). The test environment replaces it, and PartialTemplate._render, with instrumented_test_render, which sends template_rendered and renders, so the signal comes for each parent and included template too.

Nodes and NodeList.render

A node is an instance of a subclass of Node and owes the walk one method: Node.render, given the context, returns the node’s output as a string. The base class’s method does nothing. Parser.extend_nodelist sets Node.token and the node’s origin on each node it adds because, its comment says, it cannot change the nodes’ __init__; the two tie a node to its place in the source.

A NodeList is a list of nodes, and NodeList.render calls Node.render_annotated on each and joins what they return. render_annotated calls Node.render; when an exception escapes and the engine’s "debug" option is on, it marks the exception with the node that raised it and, where it can, the template’s lines around that node’s token (Template errors and debug information). A TextNode returns its text from both methods, with no try.

The join is returned as a SafeString, which autoescaping leaves alone (Autoescaping and safe strings). Each piece is already final: a text node’s text is the author’s markup, a variable’s value has been escaped where it was due, and a tag’s node answers for its own output. Where rendered output becomes a value again, the mark keeps it from being escaped twice: the content a Library.simple_block_tag function is given, or a rendered template put into another’s context.

VariableNode and render_value_in_context

VariableNode.render asks the node’s FilterExpression for the value, looked up in the context and passed through the filters (Variables and filters: FilterExpression and Variable), and gives it to render_value_in_context, which makes it text in three steps:

  1. template_localtime converts an aware datetime to the current time zone when Context.use_tz is true, or, while it is None, when USE_TZ is; one that a filter of the tz library has converted is left as it is.
  2. localize formats a number, a date or a time for the current language, or, when Context.use_l10n is False, a number by str and a date or a time by the settings’ format. A string passes through.
  3. When Context.autoescape is true, a value that is not a string is made one with str, and conditional_escape escapes it unless it is safe. Otherwise it is only made a string.

{% localtime %} and {% localize %} set the first two flags for their bodies (The libraries to load: static, i18n, l10n, tz and cache), and the engine’s "autoescape" option and {% autoescape %} the third. As the third step tests the string a value becomes, an object whose __html__ returns markup is escaped all the same if its __str__ returns a plain string.

How a tag’s node renders

Between taking the context and returning a string, a tag’s node may resolve the FilterExpression objects its compile function made, render its own node lists, push the context for names it introduces and pop it after its body, as ForNode, WithNode and BlockNode do, and reach the engine through Context.template, as IncludeNode does. NodeList.render joins its output as it is, so a node that writes out a value has to escape it itself: CycleNode through render_value_in_context, SimpleNode through conditional_escape when autoescaping is on.

A node is shared, and two threads may render it at once. Its attributes are what compiling gave it, and a render leaves them alone: what a render learns goes into the context, if the template should see it, or into the render context, if only the node needs it; an IfChangedNode in a loop, whose memory is to end with the loop, keeps it in the loop’s "forloop" dictionary. The docstring of RenderContext calls it a safe place for state between calls of a node’s render method.

BlockNode.render shows the rule. {{ block.super }} needs the context its block renders in, and BlockNode.super looks for it on the block. In an inheritance chain the node takes the block that is to render in its place from the block context, makes a new node of its own class with that block’s name and node list, sets the context on the new one, and puts it in the context as "block". The comment there gives the reason: to store the context without thread-safety issues.

The render context

RenderContext, made by Context.__init__, is a BaseContext, a stack of dictionaries. A lookup reads only the top level, and writing, inherited from BaseContext, sets there too. Its docstring says why a fresh, empty level is pushed for each template: so that what is kept there is local to that template and does not affect the others.

RenderContext.push_state makes a template RenderContext.template and, unless isolated_context=False, pushes a level; on the way out it restores the previous template and pops. Only ExtendsNode.render passes isolated_context=False: a parent then renders on its child’s level, where {% extends %} left the block context and the chain’s history. What the built-in nodes keep there:

node key what it keeps
CycleNode key the node what it keeps an iterator over its values, made at its first render; {% resetcycle %} replaces it
IfChangedNode key the node, outside a loop what it keeps what it compared last
ExtendsNode key "extends_context" what it keeps the origins of the chain so far, given to Engine.find_template to skip, so that none is extended twice
ExtendsNode, BlockNode key "block_context" what it keeps the BlockContext: for each block name, the blocks of that name from the templates of the chain
IncludeNode key the node, in the bottom level, never popped what it keeps the templates it has loaded, by name, so that an {% include %} in a loop loads once
InclusionNode key the node what it keeps an inclusion tag’s template