23.7 What changed in templates
What changed in Django’s template language and in the helpers that build safe HTML, from 5.0 to this commit: template partials, {% querystring %}, {{ forloop, Library, the deprecated two dots in a variable, the scheme |urlize assumes, the create methods a template no longer calls, and format_html without arguments.
A template is compiled once into a tree of nodes, which renders with a context (Templates), and the changes fall at both ends. The parser gained a store a tag can fill for the compiled template, on which partials are built, and a warning for two dots in a variable; rendering gained {% querystring %} and a loop’s length, and stopped calling the methods of a queryset and of the user manager that create rows.
Template partials and the parser’s extra data
Since 6.0 a template can hold named fragments of itself. {% partialdef ship_row %} … {% endpartialdef %} defines one, which renders nothing where it stands unless the tag adds "inline"; {% partial ship_row %} renders it with the context at that point; and a name such as "fleet/, given to Engine and so to render_to_string and {% include %}, loads the fragment alone, a PartialTemplate. The store they rely on came in 5.1: Parser, a dictionary a compile function may fill, which Template keeps as Template once the parse succeeds; {% partialdef %} files each fragment there under "partials". Template partials: {% partialdef %} and {% partial %} tells how a partial is made, found and rendered.
Compiling: two dots, and tags made from functions
In {{ port..name }} the lookup’s middle part is an empty name, tried as a key, an attribute and an index like any other. The release notes call this normally a mistake, and since 6.1 Parser.parse warns with RemovedInDjango2028Warning as it compiles a variable tag whose variable holds two dots in a row; a comment there says the warning becomes an error when the deprecation ends. The check is made only when the expression starts from a Variable (FilterExpression), so a string literal such as {{ "a..b" }}, translated or not, does not warn, since a fix in 6.1.1. The arguments of filters and tags are not checked: {{ x|default: and {% if port..name %} compile without a warning. Variables and filters: FilterExpression and Variable tells the lookup.
Library (5.2) makes a tag with a body from a function: the compile function parses up to the end tag, and at each render the body, rendered in the same context, is passed as the function’s "content" argument. parse_bits, which matches a tag’s words against the function’s signature, no longer takes the takes_context flag (6.1): each decorator given takes_context checks for a first parameter "context" as it runs, and strikes it from the list it passes. Libraries: Library, custom tags and {% load %} tells both.
Rendering: {% querystring %}, the loop and "alters_data"
{% querystring %}, new in 5.1, writes a link’s query string from the request’s and the template’s values, so that a link to the next page keeps the rest of the query. Its positional arguments must be mappings, and with none the request’s HttpRequest.GET stands in; each mapping, then the keyword arguments, is applied in order to an empty QueryDict, where None removes a key. 6.0 made the result begin with "?" whatever it holds, and accepted several positional mappings: on a request for "/, {% querystring page=3 %} renders "?q=brig&page=3", and {% querystring q=None page=None %} renders "?". The built-in tags has the tag, and {{ forloop (6.0), the sequence’s length, which ForNode.render sets before the first item. {% csp_nonce_attr %} (6.1) is told in What changed in security: Content Security Policy and signing.
A callable a variable’s lookup finds is not called if it carries a true "alters_data": the value becomes the engine’s "string_if_invalid", unless a true "do_not_call_in_templates", tested first, keeps the callable itself. In 5.2 the flag was set on QuerySet.create, QuerySet, QuerySet, QuerySet, UserManager, UserManager and their coroutine twins, to prevent side effects when a template is rendered, say the release notes. A manager’s copy of a queryset’s method carries it too, being made with functools.wraps: given Port’s manager as ports, {{ ports renders the empty string, and the table stays empty.
Safe HTML and links
The function urlize, behind the filters |urlize and |urlizetrunc, links an address that begins "http://" or "https://", and a name with no scheme, such as "www or one ending in one of the seven original top-level domains. Such a name still gets "http://", with a RemovedInDjango2028Warning since 6.0, unless the transitional setting URLIZE_ASSUME_HTTPS is true, which gives it "https://". Django 2028 gives every such name "https://" and removes the setting, which 6.0 deprecated with the change. Setting it raises no warning: the settings object warns for the other transitional settings, not this one. What still warns: the deprecations and the releases that end them lists it.
| name | what changed | release | where now |
|---|---|---|---|
escapeseq |
what changed a new filter: each item through conditional_, for a list joined where autoescaping is off |
release 5.0 | where now escapeseq django/ |
format_html |
what changed given no arguments, it would escape nothing and return its string marked safe; deprecated, then a TypeError |
release 5.0, 6.0 | where now format_html django/ |
format_html_join |
what changed an item that is a mapping is passed to format_html as keyword arguments |
release 5.2 | where now format_html_join django/ |
Safe |
what changed returns NotImplemented for a right operand that is not a str, so the operand’s __radd__ is tried |
release 5.2 | where now Safe django/ |
Removed, and the engines’ checks
The length_is filter was removed in 5.1, and a template that uses it now fails to compile with a TemplateSyntaxError that says "Invalid filter: 'length_is'" (Parser, The built-in filters). Since 5.1 a template backend has BaseEngine.check, which returns no messages unless overridden, and the check check_templates calls it on every engine; DjangoTemplates reports a "string_if_invalid" that is not a string, "templates.E002", and a library name two modules use, "templates.W003" (check_templates, DjangoTemplates, Template engines: TEMPLATES and the backends).