13.1 Template engines: TEMPLATES and the backends
django, an EngineHandler, makes a template backend, a subclass of BaseEngine, for each entry of the TEMPLATES setting the first time it is asked for. get_template, select_template and render_to_string, of django, ask the backends in turn and gather their failures into one TemplateDoesNotExist. Django ships three: DjangoTemplates, which holds an Engine, one for Jinja2, and TemplateStrings.
A project may use more than one template language, and code that renders a page has only a template’s name and a dictionary. A backend is the contract between them: made from its entry of TEMPLATES, it returns a template for a name or raises TemplateDoesNotExist, and the template’s render takes a dictionary and, optionally, the request.
engines makes of two entries of TEMPLATES: under each name, a backend holding its language’s own object.The entries of TEMPLATES
EngineHandler reads the setting at its first use and keeps a dictionary from each entry’s name to the entry with its defaults filled in. Only "BACKEND", the dotted path of the backend’s class, is required. "DIRS" defaults to an empty list, "APP_DIRS" to false, "OPTIONS" to an empty dictionary, and "NAME" to the last part of the backend’s module path: "django" for DjangoTemplates. An entry whose "BACKEND" is missing or has no dot is refused with ImproperlyConfigured, as are two entries with one name, such as two DjangoTemplates entries without a "NAME".
engines, and a backend’s first use
engines["django"] returns the backend already made under that name, or finds the entry, copies it, takes "BACKEND" out, imports that class and calls it with the rest, and keeps the backend. A name with no entry raises InvalidTemplateEngineError, an ImproperlyConfigured. EngineHandler returns every backend in the setting’s order, making those not yet made.
get_template, select_template and render_to_string
get_template asks each backend for the name in the order of TEMPLATES and returns the first template it gets. select_template does the same for a list of names, asking every backend for the first name before any for the second, and refuses a single string with a TypeError. render_to_string calls select_template for a list or tuple and get_template otherwise, and renders the template with the dictionary and the request; django puts the result in a response (The shortcuts). Given using="jinja2", they ask only that backend; otherwise they take EngineHandler, which makes every backend not yet made.
BaseEngine: what a backend is
BaseEngine keeps "NAME", "DIRS" and "APP_DIRS" and refuses any other key with ImproperlyConfigured; Django’s backends take "OPTIONS" out first, so what is refused is a key such as "DIR" for "DIRS". A backend must provide BaseEngine and may provide BaseEngine. BaseEngine.check returns no messages; the system check check_templates calls it on every backend (The system checks).
BaseEngine and BaseEngine are there, the class’s comment says, to minimize code duplication and security issues in third-party backends. The first is the directories of "DIRS", then, with "APP_DIRS", each installed application’s subdirectory named by BaseEngine ("templates", "jinja2" and "template_strings" for Django’s three), which raises ImproperlyConfigured unless the backend overrides it. The second joins each directory with the name through safe_join, skipping a join that lands outside it, to avoid directory traversal attacks, its docstring says. DjangoTemplates uses neither, and leaves the search to its Engine’s loaders.
DjangoTemplates
DjangoTemplates turns an entry into an Engine. In a copy of "OPTIONS" it fills in "autoescape" as True, "file_charset" as "utf-8" and "debug" as settings.DEBUG is when the backend is made, where an Engine made directly has debug=False. It replaces "libraries" with every library of django and the installed applications, the option’s own added over them (Libraries: Library, custom tags and {% load %}). Each option goes to Engine as a keyword argument, so an unknown one is a TypeError, and one left out takes Engine’s default: for "loaders", the cached loader around the filesystem loader, which searches "DIRS", and, with "APP_DIRS", the application directories loader, which searches each application’s "templates" (Finding a template: Engine and the loaders). DjangoTemplates reports a "string_if_invalid" that is not a string, "templates.E002", and one library name given to two modules, "templates.W003".
DjangoTemplates wraps what Engine returns, a compiled Template or, for a name such as "fleet/, a PartialTemplate, in the backend’s Template, which keeps the backend and passes origin through. Its render has make_context make a Context of the dictionary, or a RequestContext given the request, with the engine’s "autoescape"; a Context passed instead is refused (The context: Context, RequestContext and context processors).
TemplateDoesNotExist across loaders and backends
A failed lookup should show every place searched, though no one object searched them all. TemplateDoesNotExist carries, besides its message, tried, pairs of an origin and why it did not serve; backend; and chain, other such exceptions. Engine raises one with every loader’s tried. DjangoTemplates raises it again through reraise, as a copy that names the backend, raised from the original, and the wrapper’s render does the same for one raised while rendering, as by {% include %}. get_template of django then raises a last one with each backend’s exception in chain. With the figure’s entries:
TemplateDoesNotExist('fleet/missing.html'), backend None, tried []
chain[0]: backend 'django', tried:
proj/templates/fleet/missing.html: Source does not exist
apps/fleet/templates/fleet/missing.html: Source does not exist
chain[1]: backend 'jinja2', tried []
copy_exception makes the copy: the same class, message, tried and chain, the backend it is given or else the original’s, and any debug data, but not the original’s traceback, context or cause, so that, its docstring says, it can be kept around; the cached loader keeps such copies when "debug" is on. The debug page lists chain by backend (The debug pages and ExceptionReporter).
Jinja2
The Jinja2 backend keeps for itself the options "environment", the dotted path of the environment’s class, "jinja2 unless given, and "context_processors". The rest go to the environment, with Django’s defaults: "loader", a FileSystemLoader over template_dirs; "autoescape", True; "auto_reload", settings.DEBUG; and "undefined", Jinja2’s DebugUndefined if settings.DEBUG is true and its Undefined if not. Jinja2 turns Jinja2’s TemplateNotFound into a TemplateDoesNotExist with no tried, and its syntax error into Django’s TemplateSyntaxError with debug data (Template errors and debug information).
Its template’s origin is the module’s own Origin, holding only the file name and the template’s name. Its render works on the dictionary it is given: with a request, it sets "request", "csrf_input", the hidden field for the CSRF token, marked safe, and "csrf_token", the last two lazy, so the token is fetched only if used; then it updates the dictionary with each context processor’s.
TemplateStrings
TemplateStrings shows how little the contract asks. It takes no options, reads the first file of iter_template_filenames that exists, and returns a template of the module’s own subclass of string.Template, whose render escapes every value with conditional_escape, adds "csrf_input" and "csrf_token" given a request, and leaves a placeholder with no value as written.
Engine.get_default
A Template made from a string alone asks Engine for an engine: the Engine of the first DjangoTemplates in engines, or ImproperlyConfigured if there is none. The answer is cached. Its docstring says it exists for historical APIs that rely on an implicitly configured engine.