Templates and UI
The template system in this codebase provides a powerful engine for generating HTML dynamically. It is built around a central configuration engine, a sophisticated loading mechanism, and an extensible library for custom tags and filters.
Template Engine and Configuration
The Engine class in django/template/engine.py is the heart of the template system. It manages the configuration for template loaders, context processors, and built-in tags.
When an Engine is initialized, it sets up the default environment:
# django/template/engine.py
class Engine:
default_builtins = [
"django.template.defaulttags",
"django.template.defaultfilters",
"django.template.loader_tags",
]
def __init__(
self,
dirs=None,
app_dirs=False,
context_processors=None,
debug=False,
loaders=None,
# ... other options
):
# ... initialization logic
if loaders is None:
loaders = ["django.template.loaders.filesystem.Loader"]
if app_dirs:
loaders += ["django.template.loaders.app_directories.Loader"]
loaders = [("django.template.loaders.cached.Loader", loaders)]
The Engine is responsible for finding and compiling templates. It uses find_template to iterate through configured template_loaders and from_string or get_template to return a compiled Template object.
Loading and Rendering
The high-level API for interacting with templates is located in django/template/loader.py. These functions abstract away the underlying engine selection.
get_template and select_template
get_template(template_name) returns a compiled template object. If multiple engines are configured, it tries each one until it finds the template. select_template(template_name_list) works similarly but accepts a list of names, returning the first one that exists.
render_to_string
The most common way to generate HTML is render_to_string. It loads a template and renders it in one step:
# django/template/loader.py
def render_to_string(template_name, context=None, request=None, using=None):
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)
Core Objects: Template and Context
The rendering process involves two primary objects: the Template (the compiled source) and the Context (the data).
Template Compilation
A Template object in django/template/base.py represents a compiled template. During initialization, it uses a Lexer to tokenize the source and a Parser to produce a tree of Node objects. When render(context) is called, the template iterates through these nodes and joins their output.
Context Stack
The Context class in django/template/context.py acts as a stack-based container for variables. This allows for variable scoping, where pushing a new dictionary onto the stack creates a new scope.
# django/template/context.py
class BaseContext:
def push(self, *args, **kwargs):
# ... creates a new scope
return ContextDict(self, *dicts, **kwargs)
def pop(self):
# ... removes the current scope
if len(self.dicts) == 1:
raise ContextPopException
return self.dicts.pop()
RequestContext and Processors
RequestContext is a specialized Context that automatically populates itself using "context processors." These are functions that take a request object and return a dictionary of variables to be made available globally in the template (e.g., CSRF tokens).
# django/template/context.py
class RequestContext(Context):
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)
self.dicts[self._processors_index] = updates
# ...
Custom Tags and Filters
The system is highly extensible via the Library class in django/template/library.py. Developers can register custom logic that can be invoked within templates.
Filters
Filters transform values (e.g., {{ value|lower }}). They are registered using @register.filter:
# Example based on django/template/library.py
@register.filter
def lower(value):
return value.lower()
Simple Tags
Simple tags take arguments and return a string. They are registered using @register.simple_tag. They also support an as syntax to store the result in a context variable instead of outputting it directly.
Inclusion Tags
Inclusion tags render another template using data returned by a Python function. This is useful for creating reusable UI components:
# django/template/library.py
@register.inclusion_tag('results.html')
def show_results(poll):
choices = poll.choice_set.all()
return {'choices': choices}
Block Tags
The simple_block_tag allows for wrapping content between a start and end tag. The decorated function must accept a content argument which contains the rendered output of the block's body:
# django/template/library.py
@register.simple_block_tag
def upper_block(content):
return content.upper()
In a template, this would be used as {% upper_block %}some text{% endupper_block %}.
Safe Strings and Auto-escaping
To prevent XSS attacks, the template engine automatically escapes output. If a string is known to be safe HTML, it must be wrapped in django.utils.safestring.SafeString. The simple_tag helper handles conditional_escape automatically unless the output is already marked safe.
# django/template/library.py (SimpleNode.render)
if context.autoescape:
output = conditional_escape(output)
return output