Jinja extensions (like Django template library tags)
A Jinja extension is to Jinja templates, what a library is for programming languages: a re-usable set of features contained in a specific format to not have to continuously 'reinvent the wheel'.
Jinja itself includes various extensions that need to be enabled to be used. In addition, there are also various third party Jinja extensions that you can find helpful in certain situations (e.g. Jinja statements that emulate Django template tags). Table 1 contains a list of extensions including their technical name that's used to enable them.
Table 4-2. Jinja extensions with description and technical name
Extension functionality | Description | Technical name |
---|---|---|
{% break %} and {% continue %} statements | Offers the ability to break and continue in template loops, just like the standard break and continue Python keywords. | jinja2.ext.loopcontrols |
{% do %} statement | Offers the ability to evaluate an expression without producing output. | jinja2.ext.do |
{% trans %} statement | Offers the ability to apply i18n to templates (i.e. block gettext translations). | jinja2.ext.i18n |
{% debug %} statement | Offers the ability to use view the current context as well as the available filters and tests applied to a template. | jinja2.ext.debug |
* All extensions are included as part of Jinja, they require no additional installation, they just need to be enabled.
As you can see in table 4-2, the functionality provided by each extension varies and if you do an Internet search for 'Jinja2 extensions', you are sure to find a few more options that can save you time and work in various fronts.
To create a custom Jinja
extension you need to re-use the functionality provided by Jinja's
jinja2.ext.Extension
class, as well as use Jinja's API
to create the custom logic you're pursuing. Once you create a
custom Jinja extension and add it to your Django project, you must
also enable it with the extensions
key of the
OPTIONS
variable in settings.py
.
Enable Jinja extensions
Jinja extensions are set up as
part of Jinja's environment configuration, which in Django is
configured in the OPTIONS
variable of
settings.py
, as described in the previous section on
configuring Jinja templates in Django. Listing 4-27 illustrates a
sample Django configuration that enables a series of Jinja
extensions.
Listing 4-27. Jinja extension configuration in Django
TEMPLATES = [ { 'BACKEND':'django.template.backends.jinja2.Jinja2', 'DIRS': [ PROJECT_DIR / 'jinjatemplates' ], 'APP_DIRS': True, 'OPTIONS': { 'extensions': [ 'jinja2.ext.loopcontrols', 'jinja2.ext.debug', 'coffeehouse.jinja.extensions.DjangoNow', ], } } ]
As you can see in listing 4-27,
we use the Jinja extension's name -- as described in table 4-2 --
and add it to a list that's assigned to the extensions
key of the OPTIONS
variable, which itself is part of
Jinja's TEMPLATES
Django configuration in
settings.py
. Note that the third extension
coffeehouse.jinja.extensions.DjangoNow
in listing 4-27
is a custom Jinja extension that I'll create in the next and final
section of this chapter.
This is all that's necessary to enable a Jinja extension across all Jinja templates. Now that you know how to enable Jinja extensions, the next section explores how to create custom Jinja extensions.
Create Jinja extensions
Jinja has its own extension API which is thoroughly documented[7] and tackles all the possible cases you may need an extension for. I won't attempt to use all of the API's functionality, because it would be nearly impossible to do so in a single example, instead I'll focus on creating a practical extension and in the process illustrate the layout and deployment process for a custom Jinja extension.
In Django templates when you want
to output the current date or time, there's a tag named {%
now %}
for just this purpose, Jinja has no such statement,
so I'll create a Jinja extension to mimic the same behavior as the
Django template {% now %}
tag. The Jinja {% now
%}
statement will function just like the Django template
version and accept a format string, as well as the possibility to
use the as
keyword to define a variable with the
value.
Listing 4-28 illustrates the
source code for the custom Jinja extension that produces a Jinja
{% now %}
statement.
Listing 4-28. Jinja custom extension for Jinja {% now %} statement.
from jinja2 import lexer, nodes from jinja2.ext import Extension from django.utils import timezone from django.template.defaultfilters import date from django.conf import settings from datetime import datetime class DjangoNow(Extension): tags = set(['now']) def _now(self, date_format): tzinfo = timezone.get_current_timezone() if settings.USE_TZ else None formatted = date(datetime.now(tz=tzinfo),date_format) return formatted def parse(self, parser): lineno = next(parser.stream).lineno token = parser.stream.expect(lexer.TOKEN_STRING) date_format = nodes.Const(token.value) call = self.call_method('_now', [date_format], lineno=lineno) token = parser.stream.current if token.test('name:as'): next(parser.stream) as_var = parser.stream.expect(lexer.TOKEN_NAME) as_var = nodes.Name(as_var.value, 'store', lineno=as_var.lineno) return nodes.Assign(as_var, call, lineno=lineno) else: return nodes.Output([call], lineno=lineno)
After the various import
statements in listing 4-28, you can see we create the
DjangoNow
class that inherits its behavior from the
jinja2.ext.Extension
class, the last of which is part
of Jinja and used for all custom extensions. Next, you can see we
define the tags field with the set(['now'])
value
which is necessary to set up the statement/tag name. If you wanted
the custom statement/tag to be called {% mytimer %}
then you would declare tags = set(['mytimer'])
.
Next in listing 4-28 you can see
the _now
and parse
methods. The
_now
method performs the actual current date or time
calculation and checks the Django project's timezone configuration
in settings.py
-- a process that's just like Django's
{% now %}
tag. The parse
method
represents the entry point that executes the custom {% now
%}
statement/tag, where it uses the Jinja extension API to
analyze the input and depending on the {% now %}
declaration (e.g.{% now "F jS o" %}
, {% now "F
jS o" as today %}
) executes the _now
method and
returns a result.
Once you create the custom Jinja
extension, you need to declare it as part of the
extensions
variable on Jinja's environment
configuration, as illustrated in listing 4-27.
To better illustrate the location
of the extensions.py
file containing the custom Jinja
extension, listing 4-29 illustrates a directory structure with
additional Django project files for reference.
Listing 4-29 Directory structure and location of custom Jinja extension
+---+-<PROJECT_DIR_coffeehouse> | +-asgi.py +-__init__.py +-settings.py +-urls.py +-wsgi.py | +-jinja-+ +-__init__.py +-extensions.py
Note that based on the statement
to import the custom Jinja extension in listing 4-27 --
coffeehouse.jinja.extensions.DjangoNow
-- it's assumed
the DjangoNow
class in listing 4-28 is placed in a
file/module named extensions.py
under the
coffeehouse.jinja
directory path.