Jinja template configuration in Django

The first step to use Jinja in Django is to install the core package with the pip command: python3 -m pip install Jinja2. Note the package name is Jinja2 while the latest Jinja version is 3.0.2. This is an interesting naming convention, since Jinja 1 used the package name Jinja, Jinja 2 used the package name Jinja2 and when moving toward a version greater than 3 the Jinja package name remained Jinja2.

Next, you need to configure Jinja in a Django project inside the settings.py file. Listing 4-1 illustrates a basic Jinja configuration for Django.

Listing 4-1. Jinja configuration in Django settings.py

from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent
PROJECT_DIR = Path(__file__).resolve().parent

TEMPLATES = [
    { 
        'BACKEND':'django.template.backends.jinja2.Jinja2',
	'DIRS': [ PROJECT_DIR / 'jinjatemplates' ],
        'APP_DIRS': True,
        },
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

As you can see in listing 4-1, there are two configurations declared in the TEMPLATES variable: a dictionary for the Jinja template configuration with 'BACKEND':'django.template.backends.jinja2.Jinja2' and another dictionary with 'BACKEND': 'django.template.backends.django.DjangoTemplates' that represents the default Django template configuration. Since Django templates are still used by things like the Django admin and many third party packages, I highly recommended you use the base configuration in listing 4-1, as this guarantees all kinds of templates (i.e. Django and Jinja) are supported in a project, specially apps you have no control over which kind of templates they operate with.

The Jinja configuration in listing 4-1 is one of the most basic possible. In this case, the BACKEND variable uses the django.template.backends.jinja2.Jinja2 value to activate Jinja templates, and is followed immediately with the DIRS and APP_DIRS variables which tell Django where to locate Jinja templates.

Template search paths

The APP_DIRS variable permits the look-up of templates inside special app sub-directories named jinja2. This is helpful if you wish to contain Jinja templates to apps, but be aware the template search path is not aware of app namespaces. For example, if you have two apps that both rely on a template named index.html -- as illustrated in listing 4-2 -- and both app's have a method in views.py that returns control to the index.html template (e.g. render(request,'index.html')), both apps will use the index.html from the top-most declared app in INSTALLED_APPS, so one app won't use the expected index.html.

Listing 4-2. Django apps with jinja2 dirs with potential conflict and namespace qualification

# Templates directly under jinja2 folder can cause loading conflicts
+---+-<PROJECT_DIR_project_name_conflict>
    |
    +-asgi.py
    +-__init__.py
    +-settings.py
    +-urls.py
    +-wsgi.py
    |
    +-about(app)-+
    |            +-__init__.py
    |            +-models.py
    |            +-tests.py
    |            +-views.py
    |            +-jinja2-+
    |                     |
    |                     +-index.html
    +-stores(app)-+
                 +-__init__.py
                 +-models.py
                 +-tests.py
                 +-views.py
                 +-jinja2-+
                          |
                          +-index.html

# Templates classified with additional namespace avoid loading conflicts
+---+-<PROJECT_DIR_project_name_namespace>
    |
    +-asgi.py
    +-__init__.py
    +-settings.py
    +-urls.py
    +-wsgi.py
    |
    +-about(app)-+
    |            +-__init__.py
    |            +-models.py
    |            +-tests.py
    |            +-views.py
    |            +-jinja2-+
    |                     |
    |                     +-about-+
    |                             |
    |                             +-index.html
    +-stores(app)-+
                 +-__init__.py
                 +-models.py
                 +-tests.py
                 +-views.py
                 +-jinja2-+
                          |
                          +-stores-+
                                   |
                                   +-index.html

To fix this potential conflict, the recommended practice is to add an additional sub-folder to act as a namespace inside each jinja2 directory as illustrated in the second set of folders in listing 4-2. In this manner, you can then re-direct control to a template using this additional namespace sub-folder to avoid any ambiguity. So to send control to the about/index.html template you would declare render(request,'about/index.html') and to send control to the stores/index.html you would declare render(request,'about/index.html').

If you wish to disallow this behavior of allowing templates to be loaded from these internal app sub-folders, you can do so by setting APP_DIRS to FALSE.

A more common approach for Jinja templates is to have a single folder or various folders -- that live outside app structures -- to hold Jinja templates. Django first looks for a matching Jinja templates in the first DIRS value and then in jinja2 folders in apps -- if APP_DIRS is TRUE -- until it either finds a matching template or throws a TemplateDoesNotExist error.

For the case illustrated in listing 4-1, the only DIRS value relies on a directory named jinjatemplates relative to a path determined by the PROJECT_DIR variable. This variable technique is helpful when deploying a Django project across different machines, because the path is relative to the top-level Django project directory (i.e. where the settings.py and main urls.py file are) and adjusts dynamically irrespective of where a Django project is installed (e.g. /var/www/, /opt/website, C://website/).

Similar to the Django template OPTIONS variable, Jinja also supports a series of customizations through the OPTIONS variable. In the case of Jinja, the OPTIONS variable is a dictionary of key-values that correspond to Jinja environment initialization parameters[3]

By default, Django internally sets a series of Jinja environment initialization parameters to align Jinja's template behavior with that of Django templates. However, you can easily override these settings with the OPTIONS variable. The next sections describe these important settings.

Auto-escaping behavior

Django enables Jinja template auto-escaping by default, a behavior that's actually disabled in the Jinja engine in its out-of-the-box state. The crux of auto-escaping is that, on the one hand it errs on the side of precaution and security -- limiting the possibility to mangle output or introduce XSS (Cross-site scripting) vulnerabilities in HTML -- but on the other hand, it also introduces extra processing in the template engine that can cause performance problems.

By default, Django templates auto-escape all output from template variables -- < is converted to &lt; ,> is converted to &gt; ,' (single quote) is converted to &#39; ," (double quote) is converted to &quot; and & is converted to &amp -- unless you explicitly disable this behavior. Jinja in its out-of-the-box state doesn't auto-escape anything and you need to explicitly tell it when you want to auto-escape something.

Because the Jinja template integration for Django was done by Django designers, Jinja auto-escaping is enabled, to err on the side of security, just like it's for Django templates. However, you can disable Jinja auto-escaping with the autoescape parameter in OPTIONS as illustrated in listing 4-3.

Listing 4-3. Jinja disable autoescaping in Django

from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent
PROJECT_DIR = Path(__file__).resolve().parent

TEMPLATES = [
    { 
        'BACKEND':'django.template.backends.jinja2.Jinja2',
	'DIRS': [ PROJECT_DIR / 'jinjatemplates' ],
        'APP_DIRS': True,
        'OPTIONS': {
            'autoescape': False
        },
    }
]

As you can see in listing 4-3, autoescape is assigned False and with this change Jinja templates behave just as Jinja designers intended (i.e. you need to explicitly check where auto-escaping is necessary vs. the Django template way to check where auto-escaping isn't necessary).

Auto-reload template behavior & caching

In its out-of-the-box state, Jinja's template loader checks every time a template is requested to see if the source has changed, if it has changed Jinja reloads the template. This can be helpful in development where a template's source changes constantly, but can also translate into a performance hit in production where a template's source rarely changes and the check incurs in a delay.

By default, the Django framework Jinja integration takes a sensible approach and enables Jinja template auto-reloading based on the DEBUG variable in settings.py. If DEBUG=True -- a common setting in development -- Jinja template auto-reloading is set to True and if DEBUG=False -- a common setting in production -- Jinja template auto-reloading is set to False. Nevertheless, you can explicitly set Jinja's auto-loading behavior with the auto_reload parameter in OPTIONS.

The Jinja engine by default also caches up to four hundred templates. This means that when template four hundred and one is loaded, Jinja cleans out the least recently used template, the latter of which must be reloaded from its origin again if required at a later time. The Jinja cache limit can be adjusted with the cache_size parameter in OPTIONS (e.g. cache_size=1000 , to set a one thousand template cache). Setting cache_size to 0 (zero) disables caching and setting cache_size to -1 enables unlimited caching.

Another caching mechanism available in Jinja templates is byte-code caching. When you create Python source files (i.e. those with .py extensions) Python produces mirror like files with .pyc extensions that contain byte-code. Generating these byte-code files takes time, but they're a natural part of Python's run-time process. Jinja templates being based on Python also need to be turned into byte-code, but it's a process you can customize with the bytecode_cache parameter in OPTIONS.

The bytecode_cache parameter can be assigned either a custom byte-cache[4] or one of Jinja's built-in byte-code caches, which include support for standard file-system caching or more specialized caching with memcached.

Invalid template variables

You can set various behaviors when an invalid variable is encountered in Jinja templates. Django sets Jinja with two default behaviors, one for when DEBUG=True -- a common setting in development -- and the other for when DEBUG=False -- a common setting in production.

If DEBUG=True and an invalid variable is set in a Jinja template, Jinja uses the jinja2.DebugUndefined class to process it. The jinja2.DebugUndefined class outputs the variable verbatim for rendering (e.g. if the template has the {{foo}} statement and the variable doesn't exist in the context, Jinja outputs {{foo}}, making it easier to spot an invalid variable).

If DEBUG=False and an invalid variable is set in a Jinja template, Jinja uses the jinja2.Undefined class to process it. The jinja2.Undefined class outputs a blank space in the position of the variable for rendering (e.g. if the template has the {{bar}} statement and the variable doesn't exist in the context, Jinja outputs a blank space). It's worth mentioning this last behavior aligns with the default behavior of invalid variables in Django templates.

In addition to the jinja2.DebugUndefined and jinja2.Undefined classes, Jinja also supports the jinja2.StrictUndefined class. The jinja2.StrictUndefined class is used to generate an immediate error instead of proceeding with rendering, which is helpful for quicker diagnosis of invalid variables. However, be aware this last class changes its behavior based on the DEBUG variable, it either generates a stack error with the invalid variable name (i.e. when DEBUG=True) or it generates a standard HTTP 500 error page (i.e. when DEBUG=False).

Listing 4-4 illustrates how to configure a Jinja class to handle invalid variables through the OPTIONS parameter in settings.py.

Listing 4-4. Generate error for invalid variables in Jinja with jinja2.StrictUndefined

from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent
PROJECT_DIR = Path(__file__).resolve().parent

import jinja2

TEMPLATES = [
   { 
        'BACKEND':'django.template.backends.jinja2.Jinja2',
	'DIRS': [ PROJECT_DIR / 'jinjatemplates' ],
        'APP_DIRS': True,
        'OPTIONS': {
            'undefined':jinja2.StrictUndefined
        },
    }           
]

As you can see in listing 4-4, we first declare import jinja2 to gain access to Jinja's classes in settings.py. Next, we declare the undefined key inside the OPTIONS parameter and assign it the Jinja class to process invalid variables. In this case, we use the jinja2.StrictUndefined class to get errors when invalid templates variables are encountered, but you could equally use any of the other two Jinja classes to handle invalid variables (i.e. jinja2.DebugUndefined or jinja2.Undefined).

Template loaders

Jinja template loaders are Python classes that implement the actual logic required to search & load templates. Earlier in the section Template search paths, I described how Jinja searches for templates using the DIRS and APP_DIRS variables which are part of Django's template configuration. However, I intentionally omitted a deeper aspect associated with this template search process: each search mechanism is backed by a template loader.

In most circumstances, you won't need to deal with Jinja template loaders, since Jinja loaders are taken care of in the background by simply relying on the DIRS and APP_DIRS variables. But if you need to load Jinja templates from somewhere else than these locations (e.g. from an in-memory structure or a database) you can specify template loaders with the loader key inside the OPTIONS parameter.

Like Django template loaders, Jinja also offers the ability to create custom template loader[5], in addition to using built-in Jinja template loaders similar to those offered by Django template (e.g. loading templates from a Python dictionary).

Tip You can set custom values for any Jinja environment initialization parameter[6] in OPTIONS. The prior sections are just four of the most common Jinja template parameters, later sections describe other available OPTIONS.
Note OPTIONS is only intended for Jinja environment initialization parameters, other Jinja environment settings require configuring a separate Jinja environment class (e.g. Jinja globals, Jinja custom filters & tests and Jinja policies).
  1. https://jinja.palletsprojects.com/en/3.0.x/api/#jinja2.Environment     

  2. https://jinja.palletsprojects.com/en/3.0.x/api/#bytecode-cache     

  3. https://jinja.palletsprojects.com/en/3.0.x/api/#loaders     

  4. https://jinja.palletsprojects.com/en/3.0.x/api/#jinja2.Environment