Using Jinja2 with Django

If you’ve worked with Django, one of the most popular Python-based web frameworks, then you are using or have used the framework’s own template engine DjangoTemplates. While most of the Django libraries and projects assume you use the default template engine, Django also has official support for a different template engine called Jinja2. And although the default template engine is completely fine most of the time, there are some advantages to using Jinja2 instead of DjangoTemplates, which I will explain in the course of this blog post.

What is Jinja2

Jinja2, like DjangoTemplates, is a templating language and a template engine. It is also based on the Django’s own templating language, which will no doubt be useful if you’re going to port templates from Django to Jinja2; most of the time you only need to make a few modifications to the template and sometimes none at all.

For example, the following template from Jinja2’s documentation works identically in both templating languages:

<title>{% block title %}{% endblock %}</title>
<ul>
{% for user in users %}
  <li><a href="{{ user.url }}">{{ user.username }}</a></li>
{% endfor %}
</ul>

Why use Jinja2 (instead of DjangoTemplates)

As I stated earlier, Django’s own templating language is in most cases adequate. However, Jinja2 has some advantages when compared to DjangoTemplates, which may be worth considering when starting a new project in Django.

  • Jinja2 is a general-purpose templating language

    While DjangoTemplates is used solely within Django, Jinja2 is a templating language that is also used with other web frameworks such as Flask. You can also use it with other Python applications, regardless of whether you’re using a web framework or not (the output doesn’t have to be HTML, either), making its potential use cases more diverse than Django’s.

  • Jinja2 is more flexible

    With Django, you have a limited subset of expressions you can use in templates to encourage separation of logic from views when it is not necessary. The most notable difference Jinja2 makes is that it allows more diverse Python expressions. For example, you can call an object’s methods as you wish in Jinja2, whereas in Django you would need the aid of a template tag or filter if you need to do something more expressive, like passing multiple arguments to the method. For example:

    <!-- Jinja2: this works! -->
    <p>WORKS: {{ user.get_username(all_caps=True) }}</p>
    <!-- Django: you can call it without args... -->
    <p>WORKS: {{ user.get_username }}</p>
    <!-- Django: ...but you can't call it with args! -->
    <p>WON'T WORK: {{ user.get_username(all_caps=True) }}</p>

    You can also use the methods provided by different Python modules as long as you remember to import them (more on that later). This also means that instead of coding a template tag that parses your parameters and returns a string, you can just import a plain old Python method and use it as-is.

    <p>{% if random.randint(1,2) == 1 %}We got 1!{% else %}We got 2!{% endif %}</p>

    Of course, just because you can use more logic in Jinja2 templates doesn’t mean it’s always necessary. Still, sometimes this may save you from writing a few template tags when you can import the relevant Python methods directly.

  • Jinja2 is (possibly) faster

    Since Jinja2 utilizes JIT compilation and is by default more lightweight than DjangoTemplates, it’s likely you could achieve a small performance improvement by porting your templates to Jinja2. This can depend on the specific view that is ported. In my tests, porting the frequently viewed paste view template in my pastebin application allowed around 45 requests per second with Jinja2 instead of 37 req/s with DjangoTemplates, amounting to a humble ~18% improvement.

    However, since no two templates are the same, there’s a chance you’ll achieve very negligible or no improvements at all. And as is the case with most web applications, your main bottleneck is most likely I/O bound, making this point moot in most cases.

How to use Jinja2 in Django

Okay, so I’ve explained what Jinja2 is and a little how it compares to Django’s own template engine, but I haven’t told you how to enable it yet. Thankfully, the recent versions of Django come bundled in with built-in Jinja2 support (you still have to install Jinja2, but it’s as easy as running pip install Jinja2). Once you’re ready, open up your project’s settings.py file and add the Jinja2 entry to the list of used template engines.

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.jinja2.Jinja2',
        'DIRS': [os.path.join(BASE_DIR, 'jinja2')],
        'APP_DIRS': True,
        'OPTIONS': {
            'environment': 'PROJECT_NAME.jinja2.environment',
            'extensions': [
                'jinja2.ext.with_', # this allows with keyword to be used in templates
            ],
        }
    },
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        'OPTIONS': {
            'context_processors': [
                ...
            ],
            'loaders': [
                ('django.template.loaders.cached.Loader', [
                    'django.template.loaders.filesystem.Loader',
                    'django.template.loaders.app_directories.Loader',
                ]),
            ],
            'debug': False,
        }
    },
]

You’ll also need to create the jinja2.py file into the same directory as the settings.py; this will contain the environment we’ll create for the template engine.

from __future__ import absolute_import  # Python 2 only
  
from django.contrib.staticfiles.storage import staticfiles_storage
from django.core.urlresolvers import reverse
  
from jinja2 import Environment
  
def environment(**options):
    env = Environment(**options)
    env.globals.update({
        'url': reverse,
    })
    return env

Simply said, the method creates a Jinja2 environment with all the settings and our app-related methods we need; in this example we’ve imported the reverse method from django.core.urlresolvers. Using it in the template is as easy as {{ url("front_page") }}. However, you’ve probably become accustomed to {% url "front_page" %} instead. There is a link to a library in the later section of this blog post that works as a zero-effort replacement that doesn’t require you to change the syntax for every url template tag call.

Now, if you try starting the development server, you’ll notice that the site will continue using your existing templates. This is normal, we simply told our project to give priority to the Jinja2 templates: if it finds Jinja2 templates, it will try loading them; if not, it wil fallback to our original Django templates. This also means you don’t need to port all your templates from Django to Jinja2, but instead you can instead work your way up or simply port the templates where it makes sense to.

As for the location of the Jinja2 templates, whereas the normal Django template engine looks for templates in paths such as

../PROJECT_NAME/APP_1/templates
../PROJECT_NAME/APP_2/templates
../PROJECT_NAME/templates

the example configuration for Jinja2 in our app will look for templates in

../PROJECT_NAME/APP_1/jinja2
../PROJECT_NAME/APP_2/jinja2
../PROJECT_NAME/jinja2

So, simply create a new directory called jinja2 into the apps where you wish to use Jinja2 templates. Aside from that, the routes and the filenames should be identical.  Jinja2 in Django checklist and gotchas

Since the syntax between the two languages are almost identical, porting a template from DjangoTemplates to Jinja2 is not as much about rewriting the templates as it is about changing some of Django’s own tags to use your own imported methods or Jinja2’s own methods instead. With that in mind, here are some things you’ll need to remember when porting your templates from DjangoTemplates to Jinja2.

  • jinja2-django-tags contains Jinja2 implementations of the most used Django template tags (eg. csrf_token, static, url). After installing the library (very easy if you’re using pip), using it is as easy as adding jdj_tags.extensions.DjangoCompat to the list of extensions used by Jinja2 in settings.py. No need to touch the original calls at all, they’ll work out of the box!

  • When accessing the details of the current user in templates, use {{ request.user.username }} instead of {{ user.username }}. Although DjangoTemplates allows you to use both methods, with Jinja2 you’ll have to explicitly access all of the properties through the request context.

  • Use brackets if you’re calling a method. DjangoTemplates will determine whether a property is a primitive or a method and either print the primitive as-is or the method’s result; in Jinja2 you’ll have to be explicit. For example {{ request.user.get_username() }} will not work in Jinja2 without the brackets.

  • Use @contextfunction decorator if your global method needs to access the request context. For example, if you want to create a method that prints the currently logged-in user’s name, you can write it as such:

    from jinja2 import Environment
    from jinja2.utils import contextfunction
    
    @contextfunction
    def print_username(context):
        return context['request'].user.username

  • Add jinja2.ext.with_ to the list of Jinja2 extensions if you need to use the with keyword.

  • load tags you would use to load template tags in DjangoTemplates will cause Jinja2 to throw an error; simply remove those calls from the templates.

  • Many Jinja2 idioms are identical to their Django counterparts, but not all. Here’s a list of some common cases where the used method differs:

    Django Jinja2
    Include a template + with keyword {% include "comment_form.html" with style="blocky" %} {% with style="blocky" %}{% include "comment_form.html" %}{% endwith %}
    Format a float {{ a_float|floatformat }} {{ "%.2f" % a_float }}
    Truncate a string {{ long_text|truncatechars:64 }} {{ long_text|truncate(64) }}

In closing

Depending on the application you’re creating, moving from the default template engine to Jinja2 can either be really easy and fast, or somewhat time-consuming. However, if you’re about to start working on a new Django application, choosing Jinja2 instead of DjangoTemplates could save you a lot of time when it comes to customizing the template engine, as it allows custom methods to be added more easily and allows most of the Python syntax to be used inside the templates: a very powerful feature if used right. Truthfully, it is also a matter of personal choice and if DjangoTemplates is already working well for you, there’s probably no need to toss it aside for a Jinja2-based implementation.