Custom filters & tests in Jinja
Custom Jinja filters and tests are easy to create because they are backed by regular Python methods. For custom Jinja filters a method should return the desired formatted value and for Jinja tests a method should contain the logic to return a boolean value.
Structure
The backing method for a custom
Jinja filter or test has arguments that correspond to the variable
itself -- as the first argument -- and any remaining values passed
by the filter or test as other method arguments (e.g.
variable|mycustomfilter("<div>")
backed by a
method like def
mycustomfilter(variable,htmltag="<p>"):
-- note
the htmltag
argument has a default value in case the
filter doesn't specify this value with a statement like
variable|mycustomfilter
).
The only Jinja specific logic you
need to consider when creating backing Python methods is related to
character safety in Jinja filters. By default, if a backing method
for a Jinja filter returns a string it's considered unsafe and is
therefore escaped (e.g. if the result of the filter returns
<div>
, Jinja renders the result as
<div>
) -- which is the same behavior
enforce by custom Django filters.
To mark the result as a safe
string, the backing Python method used by a Jinja filter must
return a jinja2.Markup
type, a process that's
illustrated in one of the sample filters in listing 4-23. Listing
4-23. illustrates various backing methods for custom Jinja filters
and tests.
Listing 4-23 Backing Python methods for Jinja custom filters and tests.
import jinja2 def customcoffee(value,arg="muted"): return jinja2.Markup('%s' % (arg,value)) import math def squarerootintext(value): return "The square root of %s is %s" % (value,math.sqrt(value)) def startswithvowel(value): if value.lower().startswith(("a", "e", "i", "o","u")): return True else: return False
The first method in listing 4-23
returns the value
argument wrapped in an HTML
<span>
tag and appends a CSS class with the
arg
argument -- note the arg
argument in
the method definition defaults to the muted
value in
case no value is provided. And also notice the
customcoffee
method returns a
jinja2.Markup
type, this is done so Jinja renders the
output as a safe string and interprets the
<span>
tag as HTML.
The second method in listing 4-23
calculates the square root of a given value and returns the
standard string "The square root of %s is %s"
where
the first %s
represents the passed in
value
and the second %s
the calculated
square root. The third method in listing 4-23 takes the
value
argument, transforms it to lower case and checks
if value
starts with a vowel, if it does it returns a
boolean True otherwise it returns a boolean False.
Installation and access
Once you create the backing
methods for custom Jinja filters and tests, you need to declare
them as part of the filters and/or tests variables on Jinja's
environment configuration, which is described in the next section.
Note that it's assumed the backing methods in listing 4-23 are
placed in a file/module named filters under the
coffeehouse.jinja
directory path.
To better illustrate the location
of the filters.py
file containing the custom Jinja
extension, listing 4-24 illustrates a directory structure with
additional Django project files for reference.
Listing 4-24 Directory structure and location of custom Jinja filters and tests
+---+-<PROJECT_DIR_coffeehouse> | +-asgi.py +-__init__.py +-settings.py +-urls.py +-wsgi.py | +-jinja-+ +-__init__.py +-env.py +-filters.py
Jinja filters and tests are set up as part of Jinja's environment configuration. Listing 4-25 illustrates a custom Jinja environment definition that sets a series of custom Jinja filters through the variable named filters and tests.
Listing 4-25 Custom Jinja environment with custom filters and tests
from jinja2.environment import Environment from coffeehouse.jinja.filters import customcoffee, squarerootintext, startswithvowel class JinjaEnvironment(Environment): def __init__(self,**kwargs): super(JinjaEnvironment, self).__init__(**kwargs) self.filters['customcoffee'] = customcoffee self.filters['squarerootintext'] = squarerootintext self.filters['startswithvowel'] = startswithvowel self.tests['startswithvowel'] = startswithvowel
As you can see in listing 4-25,
each backing Python method is first imported into the custom Jinja
environment. Next, to register custom Jinja filters you access
self.filters
and assign it a variable key name --
corresponding to the filter name -- along with the backing method
for the filter. And to register custom Jinja tests you access
self.tests
and assign it a variable key name --
corresponding to the test name -- along with the backing method for
the test.
An interesting aspect of listing
4-25 is the registration of startswithvowel
as both a
filter and test, which means the same backing method -- which
returns True or False -- can be used for both cases. This dual
registration allows startswithvowel
to either use a
pipe (i.e. as a filter {{variable|startwithvowel}}
to
output True or False verbatim) or the is
keyword in a
conditional (i.e. as a test {% if variable is startswithvowel
%}variable starts with vowel{% endif %}
).
Once a custom Jinja environment
is created, you need to set it up as part of Django's configuration
in the OPTIONS
variable of settings.py
, as illustrated in listing 4-26.
Listing 4-26 Configure custom Jinja environment in Django setttings.py
TEMPLATES = [ { 'BACKEND':'django.template.backends.jinja2.Jinja2', 'DIRS': [ PROJECT_DIR / 'jinjatemplates' ], 'APP_DIRS': True, 'OPTIONS': { 'environment': 'coffeehouse.jinja.env.JinjaEnvironment' } }, ]
In this case, you can see in
listing 4-26 the environment value corresponds to
coffeehouse.jinja.env.JinjaEnvironment
, where
JinjaEnvironment
is the class -- in listing 4-25 --
env
is the file/module name and
coffeehouse.jinja
is the directory path. To better
illustrate the location of the env.py
file take a look
at the directory structure in listing 4-24.
Once you finish this last registration step, all the declared custom Jinja filters and tests become available on all Jinja templates just like the regular built-in filters & tags described in the previous section.
Note The custom Jinja environment in listing 4-25 for custom Jinja filters and tests, is the same technique used in listing 4-11 to declare Jinja globals.