if, for and whitespace control

Jinja · Essentials · 12 min

Lists and conditional sections are the bread and butter of every template.

{% if %} works like Python's, with elif and else, closed by {% endif %}.

{% for item in items %} loops over any iterable, closed by {% endfor %}. Inside the loop, the special `loop` variable gives:

  • loop.index (from 1), loop.index0 (from 0), loop.revindex
  • loop.first, loop.last, loop.length
  • loop.cycle("odd", "even") for alternating values, loop.previtem, loop.nextitem, loop.changed(value)

Loop over dicts with {% for key, value in d.items() %}. Add a filter inline, {% for u in users if u.active %}, so loop.length counts only matching items. An `else` block runs when the loop produced no iterations: perfect for "No results".

There is no break or continue unless you enable the jinja2.ext.loopcontrols extension. Also note that variables set inside a loop do not survive after it; to accumulate, use {% set ns = namespace(total=0) %} and {% set ns.total = ns.total + x %}.

Whitespace control. Tags leave their line breaks in the output, which matters for text formats. Add - inside a delimiter to strip whitespace on that side: {%- if x %} strips before, {{ value -}} after. Or set trim_blocks=True (remove the first newline after a block tag) and lstrip_blocks=True (strip spaces before a block tag) on the Environment, which is the usual choice for YAML and config files.

Example

from jinja2 import Environment

env = Environment(trim_blocks=True, lstrip_blocks=True)
t = env.from_string("""{% set ns = namespace(total=0) %}
{% for u in users if u.active %}
  {{ loop.index }}/{{ loop.length }} {{ u.name }}{{ " (first)" if loop.first }}
  {% set ns.total = ns.total + u.score %}
{% else %}
  No active users.
{% endfor %}
Total: {{ ns.total }}
{% for k, v in limits.items() %}{{ k }}={{ v }}{{ "," if not loop.last }}{% endfor %}""")

print(t.render(users=[{"name": "Ann", "active": True, "score": 3},
                      {"name": "Bo", "active": False, "score": 9},
                      {"name": "Cy", "active": True, "score": 4}],
               limits={"cpu": 2, "mem": "1Gi"}))
#   1/2 Ann (first)
#   2/2 Cy
# Total: 7
# cpu=2,mem=1Gi

Remember

  • loop.index, loop.first/last and loop.cycle are available in every for loop
  • for ... else handles empty lists; for x in xs if cond filters
  • Use namespace() to carry values out of a loop
  • - markers or trim_blocks/lstrip_blocks control whitespace

Watch out for

  • Setting a variable inside a loop and expecting the change to be visible after it.

Check yourself

  1. A template does {% set count = 0 %}{% for x in xs %}{% set count = count + 1 %}{% endfor %}{{ count }} with three items. What prints?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: jinja.palletsprojects.com/en/stable/templates/#list-of-control-structures