Use and customize Jinja templates in Django

Problem

You want to know what are the advantages and disadvantages of using Jinja templates in Django projects. You also want to set up and customize Jinja templates in your Django projects.

Solution

Among the advantages of Jinja templates over Django templates are speed, flexibility and their similar syntax. Among the disadvantages of Jinja templates over Django templates are the large pool of third party apps (e.g. Django admin) that will still force you to use Django templates, as well as the different Jinja concepts you need to learn.

To use Jinja in Django, you'll first need to install the Jinja python package pip install Jinja2. Next, you have to configure Jinja in the TEMPLATES variable in settings.py and initialize the BACKEND':'django.template.backends.jinja2.Jinja2' dictionary. To configure where Django looks for Jinja templates you add directories to the DIRS list of the TEMPLATES variable. In addition, you can also add Jinja templates to Django apps in a special sub-folder named jinja2, this ability is enabled by the APP_DIRS option of the TEMPLATES variable.

You can set any Jinja environment initialization parameter as part of the OPTIONS variable in the Django Jinja configuration. This includes options like autoescaping behavior, auto-reload template behavior, as well as handling of invalid template variables.

How it works

In addition to Django's default template system, Django also supports Jinja templates. Jinja is a standalone template engine project -- available at http://jinja.pocoo.org/ -- that is very similar to Django's default template system. However, the adoption and growth behind Jinja in Django projects is in due part to the design limitations of Django templates which have changed little to nothing since Django's creation.

In case you're not 100% sure about using Jinja templates, I'll enumerate some of the main advantages and disadvantages of Jinja templates in Django projects.

Jinja template advantages

Jinja template disadvantages

Set up and configure Jinja in Django

The first step to use Jinja in Django is to install the core package with the command pip install Jinja2. Note the installation is for version 2 (i.e. Jinja2), which is the most recent version. While Jinja 1 is still available, Django does not offer built-in support for version 1, so place special attention that you install version 2.

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

Listing 1 - Jinja configuration in Django settings.py

import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))

TEMPLATES = [
    { 
        'BACKEND':'django.template.backends.jinja2.Jinja2',
        'DIRS': ['%s/jinjatemplates/'% (PROJECT_DIR),],
        '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',
            ],
        },
    },
]

You can see in listing 1, I kept the default Django template configuration in TEMPLATES. Since Django templates are still used in things like the Django admin and many third party packages, I highly recommended you also use the base configuration in listing 1 since it keeps other things you don't have template control over from breaking. Django can work with both template engines enabled and in the next recipe Transition to Jinja templates in Django projects I'll describe some of the main points you need to be aware if you're accustomed to Django templates.

The Jinja configuration in listing 1 is one of the most basic variations possible. In this case, I added the BACKEND variable which indicates the contiguous configurations refer to Jinja templates, as well as the DIRS and APP_DIRS variables which tell Django where to locate Jinja templates.

The APP_DIRS variable permits the look-up of templates inside the special app sub-directory named jinja2. This is helpful if you wish to contain an app's templates to the app's structure, but be aware the template search path is not aware of the app's namespace. For example, if you have two apps that both rely on a template named index.html -- as illustrated in listing 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 app's will use the index.html from the top-most declared app in INSTALLED_APPS, so in essence one app won't use the expected index.html.

Listing 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>
    |
    +-__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>
    |
    +-__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 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 template in the first DIRS value, then moves on to the second and so on, until it either finds a matching template or throws a TemplateDoesNotExist error.

For the case illustrated in listing 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/).

Jinja template OPTIONS in Django

Similar to the Django template OPTIONS variable -- viewable in listing 1 and described in detail in the Customize Django template configuration recipe -- 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. You can see a full list of Jinja environment initialization parameters here.

By default, Django internally sets a couple 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 some of these important settings.

Autoescaping behavior

Django enables Jinja template autoescaping by default, a behavior that's actually disabled in the Jinja engine in its out-of-the-box state. The crux of autoescaping 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 -- but on the other hand, it also introduces extra processing in the template engine that can cause performance problems.

By default, Django templates autoescape all output from template variables, unless explicitly told not to (e.g. < is converted to &lt;, > is converted to &gt; ). Where as Jinja in its out-of-the-box state doesn't autoescape anything, you need to explicitly tell it when you want to autoescape something.

Because the Jinja template integration for Django was done by Django designers, Jinja autoescaping 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 3.

Listing 3 - Jinja disable autoescaping in Django

import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))

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

As you can see in listing 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 autoescaping is necessary vs. the Django template way to check where autoescaping isn't necessary).

Auto-reload template behavior

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.

Django 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, if you wish to alter Django's Jinja template auto-reloading behavior, you can do so with the the auto_reload parameter in the OPTIONS variable.

Invalid template variables

You can set various behaviors when an invalid variable is encountered in a Jinja template. 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}}).

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).

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. Be aware that based on the DEBUG variable, this class either generates a stack error with the invalid variable name (i.e. when DEBUG=False) or generates a standard HTTP 500 error page (i.e. when DEBUG=True).

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

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

import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))

import jinja2

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

As you can see in listing 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).