Chapter 1 · A request, end to end
1.5 The template is rendered
How render turns a template’s name and a dictionary into a string: the engine is made on first use from settings, its loaders find the file and compile it once, and rendering runs the context processors and then each node, which is where the queryset in the context is finally evaluated.
The view’s second line is render, a shortcut of two lines itself: it calls render_to_string with the template’s name, the context dictionary and the request, and wraps the string it gets in an HttpResponse. Everything between is the template system. This is the recording’s first request, where the engine and its loaders are made and the template compiled; the lines of the query the loop runs are left out, and A queryset becomes SQL has them.
render(request, "journal/entries.html", context)
get_template('journal/entries.html')
EngineHandler.__getitem__("django")
DjangoTemplates.__init__
get_installed_libraries
Engine.__init__
DjangoTemplates.get_template('journal/entries.html')
Engine.get_template('journal/entries.html')
Engine.find_template('journal/entries.html')
Engine.get_template_loaders
cached.Loader.__init__
Engine.get_template_loaders
filesystem.Loader.__init__
filesystem.Loader.__init__ (app_directories.Loader)
cached.Loader.get_template('journal/entries.html')
base.Loader.get_template('journal/entries.html') (cached.Loader)
filesystem.Loader.get_contents (app_directories.Loader, in admin/templates)
raises TemplateDoesNotExist
filesystem.Loader.get_contents (app_directories.Loader, in auth/templates)
raises TemplateDoesNotExist
filesystem.Loader.get_contents (app_directories.Loader, in journal/templates)
Template.__init__: the source is compiled
Lexer.tokenize
Parser.parse
do_for: {% for %} becomes a ForNode
Parser.parse
Template.render(context, request) (the backend's Template)
make_context
RequestContext.__init__
Template.render(context) (the engine's Template)
Engine.template_context_processors: the context processors are imported
context_processors.csrf
context_processors.request
auth.context_processors.auth
messages.context_processors.messages
VariableNode.render {{ year }}
conditional_escape
VariableNode.render {{ year }}
conditional_escape
ForNode.render {% for entry in entries %}
QuerySet.__len__
QuerySet._fetch_all
...
QuerySet.__iter__
QuerySet._fetch_all
VariableNode.render {{ entry.published|date:"j F" }}
defaultfilters.date(value, "j F")
conditional_escape
VariableNode.render {{ entry.title }}
conditional_escape
VariableNode.render {{ entry.published|date:"j F" }}
defaultfilters.date(value, "j F")
conditional_escape
VariableNode.render {{ entry.title }}
conditional_escape
HttpResponse.__init__(content)
response["Content-Type"] = "text/html; charset=utf-8"
Two classes in the recording are called Template. The engine’s, in django/, is the compiled template, which renders; the backend’s, in django/, is a thin wrapper around it that get_template returns and whose render makes the context.
The engine is made on first use
get_template asks each configured engine in turn for the template, and returns the first that has it; an engine that does not raises TemplateDoesNotExist, and the exceptions are chained onto the one raised when none has it. The engines are held by EngineHandler, one object made when django.template is imported and empty until then. Its first use reads settings, gives each entry an alias, by default the second-last part of its backend’s dotted path, django for Django’s own, and then makes each engine on demand: EngineHandler imports the backend class and instantiates it with the rest of the entry, and keeps the instance for the life of the process.
Making the Django backend is the costliest single step of this request. DjangoTemplates fills in the options a project left out, autoescaping on and debugging as settings.DEBUG has it, and then collects the template tag libraries: get_installed_libraries imports Django’s own django/ and the package named "templatetags" of every installed application, with every module in them, and keeps the modules that have a "register", which is why making the engine imports the admin’s tag libraries, for a page that uses none of them. It then makes the Engine, the object that holds the directories, the loaders, the libraries and the three builtin libraries every template may use without loading them.
The loaders are decided in Engine.__init__ too. A project that configures none gets the filesystem loader for its "DIRS", the application-directories loader as well when "APP_DIRS" is true, and both wrapped in the cached loader. That wrapping does not depend on settings.DEBUG: the cache is there in development too, and what the debug setting changes is the lexer used to compile, what a miss is cached as, and how much an exception raised while compiling or rendering is annotated with. Template engines: TEMPLATES and the backends has the engines in full.
Finding the file
Engine asks Engine, which tries each loader until one returns a template; the loaders themselves are made at the first call, by Engine, a property computed once by calling Engine; the cached loader’s constructor calls the same method for its children, which is the nesting the recording shows.
The cached loader keeps a dictionary from a template’s name, with the origins to skip when there are any, to the compiled template, or, for a template that does not exist, to the bare exception class when debugging is off and a copy of the exception when it is on; a name it has not seen it looks up with the method it inherits from the base loader. That lookup walks the origins the child loaders offer and opens each until one exists. The filesystem loader offers one origin per directory in "DIRS", none here. The application-directories loader offers one per installed application that has a templates directory, in the order of settings: the admin’s, the auth application’s, and then journal’s, which is why the recording tries three files and raises TemplateDoesNotExist twice before it reads one. The order of the applications is the order of precedence between templates of the same name, which is the mechanism by which a project overrides an application’s template: the project’s application comes first in the setting. Finding a template: Engine and the loaders has the loaders in full.
Compiled once
With the file’s text in hand, the base loader makes a Template of django/, and the constructor compiles it at once. Template runs two passes. Lexer.tokenize splits the source at every {% %}, {{ }} and {# #} into tokens of four kinds, text, variable, block and comment. Parser.parse then turns the tokens into a list of nodes: a text token becomes a TextNode; a variable token is compiled into a FilterExpression, which resolves the filter names to their functions now and keeps a constant argument like "j F" as a value, and is held by a VariableNode; a block token’s first word names a tag, and the compile function registered for that tag is called with the parser. The function for "for", do_for, parses the tag’s words, then calls the parser again to collect the body up to "endfor" or "empty", which is the inner Parser.parse in the recording, and returns a ForNode holding the loop variables, the sequence expression and the body.
The cached loader stores the compiled template, and the second request’s recording shows what that buys: get_template reaches the cached loader and stops. No directory is walked, no file is opened, nothing is compiled. The cache lasts as long as the process. Under the development server a change to a template file clears it instead of restarting the process. From text to nodes: the lexer and the parser has the compiling in full.
The context and its processors
The engine’s template is handed back inside the backend’s Template, whose render is what render_to_string calls. It makes the context and renders. make_context makes a RequestContext because a request was given, and pushes the view’s dictionary on top of it. A context is a stack of dictionaries, looked up from the top: the builtins True, False and None at the bottom, then an empty slot reserved for the context processors, then an empty dictionary for whatever is added later, then the view’s dictionary, so that what the view passed wins over what a processor adds.
The engine’s Template.render binds the template to the context, and binding is when the context processors run. The list is the engine’s Engine, computed once per engine: a builtin one first, then those the settings name, each imported by its dotted path. Each is called with the request and returns a dictionary, and the dictionaries are merged into the reserved slot. The builtin processor, csrf, adds a lazy "csrf_token" that is computed only if a template reads it. Of the three a default project configures, request adds the request; auth adds the request’s user as "user", which is still the lazy object the middleware set, and "perms"; messages adds the request’s messages and the level constants. None of them looks anything up: a template that reads "user" is what would. The context: Context, RequestContext and context processors has the stack and the processors in full.
Rendering the nodes
NodeList.render renders each node and joins the strings. A text node returns its text. A VariableNode resolves its expression and then formats the value for output.
Resolving is Variable: for each dotted part it tries a dictionary key, then an attribute, then an integer index, and if what it finds is callable it calls it, unless the callable is marked "do_not_call_in_templates" or "alters_data", which is how a model’s Model.delete cannot be called from a template. So "year" is found in the view’s dictionary, and "entry is an attribute of a model instance. A name that resolves to nothing becomes the engine’s Engine, which is empty by default: a template does not fail on a missing variable. The filters are then applied in order, date formatting the date by its argument. Variables and filters: FilterExpression and Variable has the lookup in full.
Formatting for output is render_value_in_context: a datetime is moved to the current time zone, a number is localised, which turns the year into the string "2026", and then, because autoescaping is on, the value passes through conditional_escape. That function escapes anything except a value that declares itself safe by having an __html__ method; a SafeString, which mark_safe makes and which the template system’s own output is, passes through untouched. This is autoescaping: not a pass over the output, but a test at each variable, with the filters consulted on the way, since a filter may declare its output safe, and with the {% autoescape %} tag able to turn the test off for a block (Autoescaping and safe strings).
ForNode.render is where the queryset meets the database. It pushes a dictionary onto the context, resolves the sequence expression to the queryset, and takes its length, because the "forloop" variables it maintains include "revcounter" and "last", which need the total before the first iteration; that len call is what sends the query, and A queryset becomes SQL follows it from there. For each item it then sets the loop variable in the pushed dictionary and renders the body’s nodes, and it pops the dictionary when the loop ends. The QuerySet in the recording, after the QuerySet.__len__, is the loop itself beginning, and it finds the results already fetched.
What comes out of NodeList.render is a SafeString, and that is what render_to_string returns to render, which makes the HttpResponse of it, with no content type given, so the response takes the default: The response returns to the server.