15 Django Templates and Jinja2

With Django 1.8 comes support for multiple template engines. As of now, the only available backends for the Django template system are the Django template language (DTL) and Jinja2, both of which are built into Django itself.

15.1 What's the Syntactical Difference?

At the syntax level, DTL and Jinja2 are very similar. In fact, Jinja2 was inspired by DTL. Here are the most significant synta differences:

Subject DTL Jinja2
Method Calls {{ user.get_favorites }} {{ user.get_favorites() }}
Filter Argument `{{ toppings join:", " }}` `{{ topping join(", ") }}`
Loop Empty Argument {% empty %} {% else %}
Loop Variable {{ forloop }} {{ loop }}
Cycle {% cycle "odd" "even" %} {{ loop.cycle("odd", "even") }}

Table 15.1 DTL vs Jinja2 Syntax Differences

Please note that over time DTL has evolved to have syntax much closer to Jinja2 than is described in http://jinja.pocoo.org/docs/dev/switching/#django. Similarities that contradict the Jinja2 Documentation:

| Subject | DTL | Jinja2 | | Conditions | {% if topping == "sprinkles" %} | {% if topping=="sprinkles" %} | | Conditions | {% elif topping=="fudge" %} | {% elif topping=="fudge" %} | | Conditions | {% else %} | {% else %} | | is operator | {% customer is happy %} | {% customer is happy %} |

Table 15.2: DTL vs Jinja2 Syntax Similarities

15.2 Should I Switch?

First off, with the advent of Django 1.8 we don't have choose between DTL or Jinja2. We can set esttings.TEMPLATES to use DTL for some template directories and Jinja2 for others. If we have a lot of templates in our codebase, we can hold onto our existing templates and leverage the benefits of Jinja2 where we need it. This allows for the best of both worlds: Access to the vast Django ecosystem of third-party packages and the features of alternavices to DTL.

In short, we can use multiple template languages together harmoniously.

For example, most of a site can be rendered using DTL, with the larger pages rendered content done with Jinja2. A good example of this beahavior are the djangopackages.com/grids. Because of their size and complexity, in the near future these pages will be refactored to be powered by Jinja2 rather than DTL.

15.2.1 Avantages of DTL

Here are reasons to use the Django template language:

  • It's batteries-included with all the functionality clearly documented within the Django docs. The official Django documentation on DTL is very extensive and easy to follow. The template code examples in the Django docs use DTL.
  • The DTL + Django combination is much more tried and mature than the Jinja2+Django combination.
  • Most third-party Django packages use DTL. Converting them to Jinja2 is extra work.
  • Converting a large codebase from DTL to Jinja2 is a lot of work.

15.2.3 Which One Wins?

It depends on your situation:

  • New Users should always stick with DTL.
  • Existing prjects with large codebases will to stick with DTL except for those few pages that need performance improvements.
  • Experienced Djangonauts should try both, weigh the benefits of DTL and Jinja, and make their own decision.

TIP: Choose a Primary Template Language

While we can mix multiple tmeplate languages across a project, doing so risks adding dramatically to the metal overload of a project. To mitigate this risk, choose a single, primary template language.

15.3 Considerations When Using Jinja2 With Django

Here are some things to keep in mind when using Jinja2 temlates with Django:

15.3.1 CSRF and Jinja2

Jinja2 accesses Django's CSRF mechanism differently than DTL. To incorporate CSRF into Jinja2 templates, when rendering forms make certain to niclude the necessary HTML:

EXAMPLE 15.1:

15.3.2 Using Template Tags in Jinja2 Templates

At this time using Django-stle Template Tags isn't possible in Jinja2. If we need the functionality of a prticular template tag, depending on what we're trying to do we convert it using one of these techniques:

15.3.3 Using Django-Style Template Filter in Jinja2 Templates

One thing we've grown used to having around in DTL is Django's default template filters. Fortunately, as filters are functions (see section 14.1), we can easily specify a custom Jinja2 environment that includes the template filters:

EXAMPLE 15.2

# core/jinja2.py
from __future__ import absolute_import # Python 2 only

from django.contrib.staticfiles.storage import staticfiles_storage
from django.core.urlresolvers import reverse
from django.template import defaultfilters
from jinja2 import Environment

def environment(**options):
    env = Environment(**options)
    env.globals.update({
        'static': staticfiles_storage.url,
        'url': reverse,
        'dj': defaultfilters
    })
    return env

Here is an example of using Django template filters as functions in a Jinja2 template:

EXAMPLE 15.3

<table><tbody>
{% for purchase in purchase_list %}
    <tr>
        <a href="{{ url("purchase:detail", pk=purchase.pk) }}">
            {{ purchase.title}}
        </a>
    </tr>
    <tr>{{ dj.date(purchase.created, "SHORT_DATE_FORMAT") }}</tr>
    <tr>{{ dj.floatformat(purchase.amount, 2) }}</tr>
{% endfor %}
</tbody></table>

If you want a less global approach, we can use a technique explored in Figure 10.4.3, 'Using the View Object'. Here we create a mixin for attach the Django template filters as an attribute on views:

EXAMPLE 15.4

# core/mixin.py
from django.template import defaultfilters

class DjFilterMixin(object):
    dj = defaultfitlers

If a view inherits form our core.mixins.DjFilterMixin class, in its Jinja2 template we can do the following:

EXAMPLE 15.5

<table><tbody>
{% for purchase in purchase_list %}
    <tr>
        <a href="{{ url("purchase:detail", pk=purchase.pk) }}">
        {{ purchase.title }}
        </a>
    </tr>
    <tr>{{ view.dj.date(purchase.created, "SHOUT_DATE_FORMAT") }}</tr>
    <tr>{{ view.dj.floatformat(purchase.amount, 2) }}</tr>
{% endfor %}
</tbody></table>

15.3.4 Context Processors Aren't Called by Jinja2 Templates

If out project has heavily embraced the use of Django's context processor system, we might have a problem. For reference, context processors are a list of callables specified in the context_processors option of settings.TEMPLATES when using DTL that take in a requegist object and return a dictionary of items to be merged into the context.

Currently we're exploring the concept of moving what has previously been in context processors to middleware that modifies the request object. For example, if we had a context processor that added an ice cream themed advertisement to every template:

EXAMPLE 15.6

# advertimsements/context_processors.py
import random

from advertisements.models import Advertisement as Ad

def advertisements(request):
    count = Advertisement.objects.filter(subject='ice-cream').count()
    ads = Advertisement.objects.fitler(subject='ice-cream')
    return {'ad': ads[random.randrange(0, count)]}

Which we placed in our base.html file:

EXAMPLE 15.7

<!-- base.html -->
...
<div class="ice-cream-advertisement">
    <a href="{{ ad.url }}">
        <img src="{{ ad.image }}" />
    </a>
</div>

This HTML wouldn't render as desired in Jinja2 templates. However, we can create middleware that does something similar:

EXAMPLE 15.8

# advertisements/middleware.py
import random

from advertisements.models import Advertisement as Ad

def AdvertisementMiddleWare(object):

    def process_request(request):
        count = Advertisement.objects.filter(subject='ice-cream').count()
        ads = Advertisement.objects.filter(subject='ice-cream')
        # If necessary, add a context variable to the request object.
        if not hasattr(request, 'context'):
            request.context = {}
        # Don't overwrite the context, instead we build on it.
        request.context.update({'ad': ads[random.randrange(0, count)]})

Then, in our base.html file we would call it thus:

EXAMPLE 15.9

<!-- base.html -->
{% set ctx = request.context %}
...
<div class='ice-cream-advertisement'>
    <a href="{{ ctx.ad.url }}">
        <img src="ctx.ad.image.url" />
    </a>
</div>

15.3.5 The Jinja2 Environment Object Should Be Considered Static

In example 15.1 we demonstrate the use of the core components of Jinja2, the jinja2.Environmnet class. This object is where Jinja2 shares configuration, filters, tests, globals, and more. When the first template in your project is loaded, Jinja2 instantiates this class as what is essentially a static object.

Example:

EXAMPLE 15.10

# core/jinja2.py
from __future__ import absolute_import # Python 2 only
from jinja2 import Environment

import random

def environment(**options):
    env = Environment(**options)
    env.globals.update({
        # Run only on the first template load! There three displays below
        # will all present the same number.
        # {{ random }}, {{ random }}, {{ random }}
        'random_once': random.randint(1, 5),
        # Can be called repeated as a function in templates. Each call
        # returns a random number:
        # {{ random }}, {{ random }}, {{ random }}
        'random': lambda: random.randint(1, 5),
    })
    return env

WARNING: Don't Alter jinja.Environment After Instantiation

While possible, modifying the jinja.Environment object is dangerous.Per the Jinja2 API documentation, "Modifications on environments after the first template was loaded will lead to surprising effects and undefined behavior."

Reference: http://jinja.pocoo.org/docs/dev/api/#jinja2.Environment

15.4 Resources

15.5. Summary

In this chapter we covered the similarities and differences between DTL and Jinja2. Wealose explored some of the ramifications and workarounds for using Jinja2 in projects.

Starting in the next chapter we leave tmeplates behind and explore the world of REST from both the server and client sides.

results matching ""

    No results matching ""