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

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: Comparison operations, loops, Python & filter operations, spacing & special characters and template structures.

Comparison operations

Listing 4-14. 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 a condition.

Loops

Listing 4-15 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 4-1 illustrates the {% for %} statement variables.

Table 4-1. Jinja {% for %} statement variables

Variable Description
loop.index The current iteration of the loop (1-indexed)
loop.index0 The current iteration of the loop (0-indexed)
loop.revindex The number of iterations from the end of the loop (1-indexed)
loop.revindex0 The number of iterations from the end of the loop (0-indexed)
loop.first True if it's the first time through the loop
loop.last True if it's the last time through the loop
loop.length The number of items in the sequence.
loop.cycle A helper function to cycle between a list of sequences.
loop.depth Indicates how deep in a recursive loop the rendering currently is, starts at level 1
loop.depth0 Indicates how deep in a recursive loop the rendering currently is, starts at level 0
Neste Jinja Loops: Use reference variable and cycle variable

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. However, Jinja templates don't have this variable as you can see in table 4-1. A solution in Jinja templates is to define a reference variable with {% set %} before entering the child loop to gain access to the parent loop, as illustrated in the following snippet

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

Another nested loop feature in Jinja templates is cycle, which does not exist in Django templates (as a variable at least, it does exist as a tag). The primary use of cycle is to define CSS classes so each iteration receives a different CSS class and upon rendering each iteration is displayed in a different color. The following snippet illustrates the use of the cycle variable.

{% for drink in drinks %} 
   <li class="{{ loop.cycle('odd','even') }}">{{ drink.name }}</li>
{% endfor %}

Note cycle can iterate sequentially over any number of strings or variables (e.g. {{ loop.cycle('red' 'white' 'blue') }}).

Listing 4-16. Jinja {% for %} statement with recursive keyword

# Dictionary definition
coffees={
    'espresso': 
         {'nothing else':'Espresso',
          'water': 'Americano', 
          'steamed milk': {'more steamed milk than milk foam': 'Latte', 
                           'chocolate syrup': {'Whipped cream': 'Mocha'}
           }, 
           'more milk foam than 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
          more steamed milk than milk foam YOU GET: Latte
          chocolate syrup
               Whipped cream YOU GET: Mocha
     more milk foam than steamed milk YOU GET: Capuccino
     nothing else YOU GET: Espresso
Note {% break %} and {% continue %} require enabling the built-in jinja2.ext.loopcontrols extension. See the second to last section in this chapter on how to enable Jinja extensions for more details.

Listing 4-17 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 4-18 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 and filter operations

Note {% break %} and {% continue %} require enabling the built-in jinja2.ext.loopcontrols extension. See the second to last section in this chapter on how to enable Jinja extensions for more details.
Note {% with %} requires enabling the built-in jinja2.ext.with_ extension. See the second to last section in this chapter on how to enable Jinja extensions for more details.

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 4-1 illustrates the default rendering of a template snippet in Jinja.

Figure 4-1 Default space rendering in Jinja template

As you can see in figure 4-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 4-2 and figure 4-3.

Figure 4-2. Space rendering in Jinja template with single -

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

As you can see in figure 4-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 in figure 4-2, the - symbol has no impact because there's no new line associated with the statement. In figure 4-3 you can see there's 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 4-3, 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 prior section on setting up Jinja template configuration in Django.

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

Figure 4-4. Space rendering in Jinja template with trim_blocks

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

As you can see in figures 4-4 and 4-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.

Tip You can output special Jinja template characters individually by quoting them as part of a hard-coded string variable (e.g. to output {{ use {{ '{{' }}) vs. using a {% raw %} statement.
Note {% autoescape %} requires enabling the built-in jinja2.ext.autoescape extension. See the second to last section in this chapter on how to enable Jinja extensions for more details.

Template structures