Skip to main content

Managing Template Context

In this codebase, template context management is built on a stack-based architecture that allows for variable shadowing, scoping, and efficient state tracking during the rendering process. This system is implemented through a hierarchy of classes in django/template/context.py.

The Context Stack Architecture

The foundation of context management is the BaseContext class. It maintains a list of dictionaries in self.dicts. When you look up a variable, the context searches this stack from the top (the last added dictionary) down to the bottom.

Variable Lookup and Shadowing

Because lookups happen from the top down, variables in higher levels of the stack "shadow" variables with the same name in lower levels. The bottom-most dictionary (self.dicts[0]) always contains built-in constants like True, False, and None.

# From django/template/context.py
class BaseContext:
def _reset_dicts(self, value=None):
builtins = {"True": True, "False": False, "None": None}
self.dicts = [builtins]
# ... adds initial values if provided

Scoping with Push and Pop

The push() and pop() methods allow you to manage scopes. This is frequently used by template tags to create local variables that don't leak into the global context.

A common pattern is using push() as a context manager, as seen in tests/template_tests/test_context.py:

def test_context(self):
c = Context({"a": 1, "b": "xyzzy"})
with c.push():
c["a"] = 2
# Inside this block, 'a' is 2
self.assertEqual(c["a"], 2)
# Outside the block, the top dict is popped, and 'a' is 1 again
self.assertEqual(c["a"], 1)

The WithNode in django/template/defaulttags.py uses this to implement the {% with %} tag:

def render(self, context):
values = {key: val.resolve(context) for key, val in self.extra_context.items()}
with context.push(**values):
return self.nodelist.render(context)

Standard Template Context

The Context class inherits from BaseContext and adds metadata required for rendering, such as autoescape settings and localization preferences (use_l10n, use_tz). It also initializes a RenderContext for internal node state.

RequestContext and Context Processors

RequestContext is a specialized subclass designed to work with an HttpRequest. Its primary purpose is to automatically populate the context with data from "context processors"—callables that return a dictionary of variables (e.g., the current user or site settings).

The Binding Mechanism

Unlike a standard Context, a RequestContext does not run its processors immediately upon instantiation. Instead, it waits until it is bound to a template via the bind_template context manager. This is typically handled by the template engine during the render() call.

# From django/template/context.py
@contextmanager
def bind_template(self, template):
# ...
processors = template.engine.template_context_processors + self._processors
updates = {}
for processor in processors:
context = processor(self.request)
updates.update(context)

# The processors' data is inserted at a specific index in the stack
self.dicts[self._processors_index] = updates
try:
yield
finally:
# Clean up after rendering
self.dicts[self._processors_index] = {}

Stack Structure in RequestContext

As demonstrated in tests/template_tests/test_context.py, a RequestContext stack typically contains four layers:

  1. Builtins: True, False, None.
  2. Supplied Context: Data passed during initialization.
  3. Context Processors: Data populated during bind_template.
  4. Local Overrides: An empty dict for any modifications made after initialization.

Internal State with RenderContext

While Context is for data passed to the template, RenderContext is used by template nodes to store internal state safely.

Isolated Scoping

RenderContext differs from BaseContext in its lookup logic. While BaseContext searches the entire stack, RenderContext lookups are restricted to the topmost dictionary. This ensures that variables are strictly local to a specific template and do not bleed into included or extended templates.

Usage in Template Tags

The CycleNode in django/template/defaulttags.py uses RenderContext to keep track of the current iteration state across multiple renders of the same node:

def render(self, context):
if self not in context.render_context:
# First time the node is rendered in this template
context.render_context[self] = itertools_cycle(self.cyclevars)

cycle_iter = context.render_context[self]
# ...

Advanced Context Manipulation

Modifying Parent Scopes

The set_upward(key, value) method allows you to modify a variable in the highest level of the stack where it already exists. If the key is not found in any level, it defaults to setting it in the current (topmost) context.

Flattening

If you need to export the entire context as a single dictionary (e.g., for debugging or passing to another system), the flatten() method merges all dictionaries in the stack into one, respecting the shadowing rules.

# From django/template/context.py
def flatten(self):
flat = {}
for d in self.dicts:
flat.update(d)
return flat