59

How can I break out of a for loop in jinja2?

my code is like this:

<a href="#">
{% for page in pages if page.tags['foo'] == bar %}
{{page.title}}
{% break %}
{% endfor %}
</a>

I have more than one page that has this condition and I want to end the loop, once the condition has been met.

Taxellool
  • 4,063
  • 4
  • 21
  • 38
  • 12
    there is an extension for that : [extensions/#loop-controls](http://jinja.pocoo.org/docs/extensions/#loop-controls) – sb32134 Jul 09 '14 at 08:30

3 Answers3

75

You can't use break, you'd filter instead. From the Jinja2 documentation on {% for %}:

Unlike in Python it’s not possible to break or continue in a loop. You can however filter the sequence during iteration which allows you to skip items. The following example skips all the users which are hidden:

{% for user in users if not user.hidden %}
    <li>{{ user.username|e }}</li>
{% endfor %}

In your case, however, you appear to only need the first element; just filter and pick the first:

{{ (pages|selectattr('tags.foo', 'eq', bar)|first).title }}

This filters the list using the selectattr() filter, the result of which is passed to the first filter.

The selectattr() filter produces an iterator, so using first here will only iterate over the input up to the first matching element, and no further.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    This is not always a good approach. For example, I have one case where there's a class that pulls data from somewhere else and delivers it (through an iterator) on request. If I want to show only the first 25 elements, forcing my code to iterate through all, say, 500 and just filter them out from the display is highly inefficient. – Canuck Mar 20 '15 at 21:16
  • 1
    @Canuck: it is up to your view to then provide the template with a smaller dataset. – Martijn Pieters Mar 20 '15 at 21:20
  • 3
    @Canuck: the `|first` filter on the other hand will ensure that not the whole dataset is iterated over; `selectattr()` uses iteration, it doesn't produce a whole new list, so `first` only will require iteration *up to the first matching element*. – Martijn Pieters Mar 20 '15 at 21:21
  • 1
    @Canuck: I'm not sure that this deserved downvoting however; your scenario is quite different from the one in the question here. If you have a large dataset and you need only the first 25 elements, either have whatever calls the template limit that dataset, or try using `slice()` and using only the first batch. – Martijn Pieters Mar 20 '15 at 21:27
  • This one doesn't work in Django??? At least in my case it complains that `for` format should be `for x in y` only – holms Mar 10 '18 at 01:28
  • @holms: Jinja is not the same template engine as the Django template engine. – Martijn Pieters Mar 10 '18 at 08:22
  • {{ (pages|selectattr('tags.foo', 'eq', bar)|first).title }} What if there is no element found with condition, will this fail? when trying to access 'title'? Do we need a condition before accessing the title attribute? – Satyam Raikar Aug 14 '20 at 08:22
  • @SatyamRaikar no, because unless you explicitly have disabled this Jinja2 will produce an [*undefined* object that renders as an empty string](https://jinja.palletsprojects.com/en/2.11.x/templates/#variables) when the select fails or there is no title attribute. – Martijn Pieters Aug 14 '20 at 08:48
25

Break and Continue can be added to Jinja2 using the loop controls extension. Jinja Loop Control Just add the extension to the jinja environment.

jinja_env = Environment(extensions=['jinja2.ext.loopcontrols'])

as per sb32134 comment

oneklc
  • 2,659
  • 1
  • 20
  • 13
13

But if you for some reason need a loop you can check the loop index inside for-loop block using "loop.first":

{% for dict in list_of_dict %} 
    {% for key, value in dict.items() if loop.first %}
      <th>{{ key }}</th>
    {% endfor %} 
{% endfor %}