Chapter 13 · Templates

13.4 Variables and filters: FilterExpression and Variable

A variable tag such as {{ ship.name|upper }} is compiled into a FilterExpression: the value it starts from, a Variable or a constant, and the filters to apply. At render time Variable._resolve_lookup finds the value in the context, as a key, an attribute or an index, calling it if it can be called, and FilterExpression.resolve passes it through the filters, or decides what a missing variable becomes.

A template’s author writes {{ port.name }} without saying whether the port is a dictionary, an object or a list. The engine must find the value whichever it is, decide whether to call what it finds, and choose between failing and printing nothing. What the text settles is settled at compile time: the expression is cut into its parts, each filter’s function found and its arguments counted, each literal turned into its value. Only the lookup and the calls are left to each render. The same object serves more than {{ }}: Parser.compile_filter makes one, and the compile functions of {% if %}, {% for %}, {% url %} and others call it for the values they take.

The grammar

The grammar is one regular expression, filter_re. A constant is a string in double or single quotes, which may be wrapped in _() to mark it for translation (constant_string). A variable is a run of word characters, dots, and plus and minus signs. A filter is a |, with spaces allowed around it, a name, and optionally a : and one argument, a constant or a variable, with no space around the colon. filter_re is compiled from filter_raw_string, which takes the | and the : from FILTER_SEPARATOR and FILTER_ARGUMENT_SEPARATOR. So a filter is given the value and at most one argument.

A number has no branch of its own: 2 and -2 match as variables, so that, says a comment at constant_string, numbers keep their type when passed to filters. A constant argument goes through mark_safe as the filter runs, which would make a number a SafeString.

Compiling: FilterExpression.__init__

FilterExpression.__init__ takes the matches of filter_re in order, each beginning where the last ended. The first is the value: a constant is resolved at once and its value kept, a variable kept as a Variable, and FilterExpression.is_var says which. Each later match is a filter, whose name Parser.find_filter looks up among the builtins’ filters and those of the libraries loaded so far, and the expression keeps the function itself, so a filter not loaded is an error now, not at render. FilterExpression.args_check compares the count with the function’s signature, counting the value as one argument.

Each failure is a TemplateSyntaxError; from Engine.from_string, these gave:

{{ port name }}            Could not parse the remainder: ' name' from 'port name'
{{ port.name|uper }}       Invalid filter: 'uper'
{{ port.name|default }}    default requires 2 arguments, 1 provided

Literals and names: Variable.__init__

Variable.__init__ decides from the text alone what it holds. Text holding a dot, an "e" or an "E" is tried as a float, other text as an int, and text ending in a dot is no number; a number is kept in Variable.literal. Failing that, a _() around the text sets Variable.translate and is stripped. Text in quotes is then a string literal, its escaped quotes and backslashes undone and the result marked safe. Anything else is a name, split at the dots, VARIABLE_ATTRIBUTE_SEPARATOR, into Variable.lookups. A name is refused if it holds a + or a -, which the grammar admits for numbers, or if a part begins with an underscore, as the documentation says such attributes are generally private.

Variable.resolve returns the literal, or for a name what _resolve_lookup finds; for a variable marked for translation it returns a lazy translation of that, with Variable.message_context as its context if set. So a _("…") constant, resolved as the template compiles, is translated each time it renders. The {% translate %} tag sets translate on its variable at each render, and message_context when the tag is given a context (The libraries to load: static, i18n, l10n, tz and cache).

The lookup: Variable._resolve_lookup

Variable._resolve_lookup starts from the context and takes the parts of the name in turn, each applied to what the one before found. For each it tries three things, in this order, and keeps the first that works:

  1. A key, current[part], tried only when the object’s type defines __getitem__, so that a class whose __class_getitem__ would answer any key is not taken for a mapping. For the first part this is the context’s own lookup, from the top of its stack.
  2. An attribute, getattr(current, part). A context’s own attributes are never found this way: {{ push }} is a missing name, not a call of BaseContext.push.
  3. An index, current[int(part)], which serves a list or a dictionary with integer keys.

If all three fail, it raises VariableDoesNotExist, with the part and the object. One failure of the second step is not a miss: if getattr raises AttributeError or TypeError for a name that dir lists, the error came from inside the attribute, as from a property’s body, and is raised again.

After each step, what was found is called if it can be, unless it says otherwise:

The whole lookup is one try. An exception the steps above do not handle is logged on "django.template" at the debug level, naming the part that failed and the template the render began with; Django’s default logging drops that level. An exception with a true "silent_variable_failure" then gives "string_if_invalid", and any other goes on up. ObjectDoesNotExist sets it, so a Model.DoesNotExist raised in a lookup does not break the render.

Wherever the lookup gives "string_if_invalid", it is the option’s string as configured, %s and all, and the filters run on it: with the option set to "INVALID %s", {{ ship.sink|upper }}, its method carrying "alters_data", rendered "INVALID %S".

A missing variable: FilterExpression.resolve

FilterExpression.resolve catches VariableDoesNotExist from its variable and decides the value:

The option’s default is "", from Engine.__init__, and it is read at render time from the engine of Context.template. A port with no nme, rendered as {{ port.nme }} and as {{ port.nme|default:"none" }}:

the option alone with default
"" alone "" with default "none"
"INVALID %s" alone "INVALID port.nme" with default "INVALID port.nme"

Calling the filters

FilterExpression.resolve runs the filters in order, each given the value so far and its argument, a constant marked safe or a variable resolved in the context. Three flags, which Library.filter sets on the function (Libraries: Library, custom tags and {% load %}), change the call or its result. With "expects_localtime", an aware datetime is first put in the current time zone by template_localtime, as Context.use_tz, or else USE_TZ, allows. With "needs_autoescape", the function is passed Context.autoescape as its autoescape argument, to do its own escaping. With "is_safe", a result made from a safe value is marked safe in turn. What safe means is told in Autoescaping and safe strings.