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:
- Convert the functionality into a function.
- Create a Jinja2 Extension. See http://www.2scoops.co/wirtting-jinja2-extensions/
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
- http://www.2scoops.co/1.8-jinja2-templates/ Django's documentation on using Jinja2.
- http://jinja.pocoo.org
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.