Use built-in statements/tags and functions on Jinja templates (like Django template tags)

Problem

You want to do elaborate operations on Jinja templates, such as: Logical operations to present 'x' or 'y' content, loop over data to create layout lists, add template comments, among other things.

Solution

Use one of Jinja's many built-in statements/tags or functions. Jinja offers built-in statements/tags and functions for several purposes that are available by default on all Jinja templates.

How it works

Jinja offers several built-in statements/tags that offer immediate access to elaborate operations on Jinja templates. I'll classify each of these built-in statements/tags and functions into sections so it's easier to identify them, note I'll add the reference (Function) to indicate it's referring to a Jinja function. The categories I'll use are: Logical operations, Loops, Python code, Spacing and special characters, Template structures.

Logical operations

Listing 1 - Jinja {% if %} statement with {% elif %} and {% else %}

{% if drinks %}             {% if drinks %}              {% if drinks %}
  We have drinks!                We have drinks              We have drinks 
{% endif %}                 {% else %}                   {% elif drinks_on_sale %}
                                No drinks,sorry              We have drinks on sale!
                            {% endif %}                  {% else %}
                      	                                     No drinks, sorry 
                                                         {% endif %}

*Note a variable must both exist and not be empty to match a condition
 a variable that just exists and is empty does not match. 

Loops

Listing 3 - Jinja {% for %} statement and {% for %} with {% else %}

<ul>                                  <ul>
{% for drink in drinks %}              {% for storeid,store in stores %}
 <li>{{ drink.name }}</li>               <li><a href="/stores/{{storeid}}/">{{store.name}}</a></li>
{% else %}                             {% endfor %}
 <li>No drinks, sorry</li>            </ul>
{% endfor %}
</ul>

The {% for %} statement also generates a series of variables to manage the iteration process, such as an iteration counter, a first iteration flag and a last iteration flag. Table 1 illustrates the {% for %} statement variables.

Table 1 - Jinja {% for %} statement variables
VariableDescription
loop.indexThe current iteration of the loop (1-indexed)
loop.index0The current iteration of the loop (0-indexed)
loop.revindexThe number of iterations from the end of the loop (1-indexed)
loop.revindex0The number of iterations from the end of the loop (0-indexed)
loop.firstTrue if it's the first time through the loop
loop.lastTrue if it's the last time through the loop
loop.lengthThe number of items in the sequence.
loop.cycleA helper function to cycle between a list of sequences.
loop.depthIndicates how deep in a recursive loop the rendering currently is, starts at level 1
loop.depth0Indicates how deep in a recursive loop the rendering currently is, starts at level 0
Note Define a variable to access a parent loop

On certain occasions you may need to nest multiple {% for %} statements and access parent loop items. In Django templates, this is easy because there's a variable for just this purpose. Jinja though doesn't have this variable as you can see in table 1. However, a Jinja solution is to define a reference variable with {% set %} before entering the child loop to gain access to the parent loop. The following code snippet illustrates this mechanism:


<ul>
{% for chapter in chapters %}
  {% set chapterloop = loop %}
  {% for section in chapter %}
    <li> {{ chapterloop.index }}.{{ loop.index }}">{{ section }}</li>
  {% endfor %}
{% endfor %}
</ul>

In listing 4 you can see the cycle method that's part of the {% for %} statement and which iterates over a given set of strings or variables. One of the primary uses of the cycle method is to define CSS classes so each iteration receives a different CSS class and upon rendering each iteration is displayed in a different color. Note that the cycle can iterate sequentially over any number of strings or variables (e.g. {{ loop.cycle('red' 'white' 'blue') }}).

Listing 4 - Jinja {% for %} statement and loop.cycle function

{% for drink in drinks %} 
 <li class="{{ loop.cycle('odd', 'even') }}">{{ drink.name }}</li>
{% endfor %}
Listing 5 - Jinja {% for %} statement with recursive keyword

# Dictionary definition
coffees={
    'espresso': 
         {'nothing else':'Espresso',
          'water': 'Americano', 
          'steamed milk': {'milk foam < steamed milk': 'Latte', 
                           'chocolate syrup': {'Whipped cream': 'Mocha'}
           }, 
           'milk foam > steamed milk': 'Capuccino'
     }
}

# Template definition with for and recursive
{% for ingredient,result in coffees.iteritems() recursive %}
    <li>{{ ingredient }}
    {% if result is mapping %}
        <ul>{{ loop(result.iteritems()) }}</ul>
    {% else %} 
         YOU GET:  {{ result }}
    {% endif %}</li>
{% endfor %}


# Output
espresso
     water YOU GET: Americano
     steamed milk
          milk foam < steamed milk YOU GET: Latte
          chocolate syrup
               Whipped cream YOU GET: Mocha
     milk foam > steamed milk YOU GET: Capuccino
     nothing else YOU GET: Espresso
Listing 6 - Jinja cycler function

{% set row_class = cycler('white','lightgrey','grey') %}

<ul>
{% for item in items %}
  <li class="{{ row_class.next() }}">{{ item }}</li>
{% endfor %}
{% for otheritem in moreitems %}
  <li class="{{ row_class.next() }}">{{ otheritem }}</li>
{% endfor %}

# Output
<ul>
  <li class="white">Item 1</li>
  <li class="lightgrey">Item 2 </li>
  <li class="grey">Item 3 </li>
  <li class="white">Item 4</li>
  <li class="lightgrey">Item 5</li>
  <li class="grey">Other item 1</li>
  <li class="white">Other item 2</li>
</ul>

Listing 7 - Jinja joiner function

{% set slash_joiner = joiner("/ ") %}

User: {% if username %} {{ slash_joiner() }}
    {{username}}
{% endif %}
{% if alias %} {{ slash_joiner() }}
    {{alias}}
{% endif %}
{% if nickname %} {{ slash_joiner() }}
    {{nickname}}
{% endif %}


# Output
# If all variables are defined
User: username / alias / nickname
# If only nickname is defined
User: nickname
# If only username and alias is defined 
User: username / alias 
# Etc, the joiner function avoids any unnecessary preceding slash because it doesn't print anything the first time its called

Python code

Spacing and special characters

By default, Jinja keeps all spacing (e.g. tabs, spaces, newlines) unchanged from how they are defined in a template. Figure 1 illustrates the default rendering of a template snippet in Jinja.

Default space rendering in Jinja template
Figure 1.- Default space rendering in Jinja template

As you can see in Figure 1, the spacing before, after and by the {% for %} and {% if %} statements themselves is generated as is. While this spacing is natural, it can be beneficial to create more compact outputs with templates that handle a lot of data. The minus sign - appended to either the start or end of a statement (e.g. {%- <statement> -%}) tells Jinja to strip the new line that follows it. This is best illustrated with the examples presented in Figure 2 and Figure 3.

Space rendering in Jinja template with -
Figure 2.- Space rendering in Jinja template with -
Space rendering in Jinja template with double -
Figure 3.- Space rendering in Jinja template with double -

As you can see in figure 2, the - symbol before closing the {% for %} statement makes Jinja eliminate the new line after each iteration. In the case of the {% if %} statement also is figure 2, the - symbol has no impact because there's no new line associated with the statement. In figure 3 you can see we added an additional - symbol at the start of the {% endfor %} statement which makes Jinja eliminate the new line before the start of each iteration. In the case of the {% if %} statement also is figure 2, the additional - symbol has no impact because there's no new line associated with the statement.

Because adding - symbols to every Jinja statement can become tiresome, you can configure Jinja so that by default it uses this behavior (i.e. just as if you added -). To alter Jinja's default spacing behavior, you can use two Jinja environment parameters : trim_blocks and lstrip_blocks, both of which default to False. Note that in Django you set up Jinja environment parameters as part of the OPTIONS variable in settings.py, as described in the recipe Use and customize Jinja templates in Django

Figure 4 illustrates the rendering of a code snippet when trim_blocks is set to True, where as figure 5 illustrates the rendering of a code snippet when both trim_blocks and lstrip_blocks are set to True.

Space rendering in Jinja template with trim_blocks True
Figure 4.- Space rendering in Jinja template with trim_blocks True
Space rendering in Jinja template with both trim_blocks and lstrip_blocks set to True
Figure 5.- Space rendering in Jinja template with both trim_blocks and lstrip_blocks set to True

As you can see in figures 4 and 5, the rendering produced by changing the trim_blocks and lstrip_blocks Jinja environment variables is very similar to that of using - symbols to start and end Jinja statements. It's worth mentioning that if you set lstrip_blocks to True and want to omit its behavior for certain sections, you can do so by adding the plus sign + to either the start or end of a statement -- just like you use the minus sign - to achieve its opposite behavior.

Template structures