Heisenberg
Heisenberg

Reputation: 8816

How to remove the last item in jinja nested loop?

With a single loop, it's easy to write {% if not loop.last %}JOIN{% endif %} to remove JOIN from the last iteration of the loop.

With nested loop, we can access the outer loop, allowing us to check if we're in the final iterations of both loops.

Still, I have a 3-level nested, for which this solution is quite verbose:

{% set loop1 = loop %}
...
{% set loop2 = loop %}
...
{% if not loop1.last and loop2.last and loop.last %}JOIN{% endif %}

Is there a better way to skip the last iteration of a nested loop?

Upvotes: 4

Views: 3949

Answers (1)

okovko
okovko

Reputation: 1911

If you're willing to write a little Python, you can make it a clean one liner by writing a custom filter (this code has to run before you load any templates) or just adding all to your global Jinja2 environment:

from jinja2 import Environment, PackageLoader, select_autoescape, last

env = Environment(
    loader = PackageLoader('yourapplication', 'templates'),
    autoescape = select_autoescape(['html', 'xml'])
)

def between(token, *args):
    return token if all(x.last for x in args) else ''

env.globals['between'] = between
env.globals['all'] = all

Now you can do:

{{ 'JOIN'|between(loop1, loop2, loop) }}
# or
{% if not all([loop1, loop2, loop]|map('last')) %}JOIN{% endif %}

Otherwise, you can implement a Jinja2 macro that uses the special varargs variable to access all unnamed arguments passed to the macro as a Jinja2 list.

{% macro between(token) -%}
    {% if not varargs|map('last')|select|length == varargs|length -%}
        {{ token }}
    {%- endif %}
{%- endmacro %}

Note that the use of - in the example indicates that whitespace before {%- and after -%} will be removed when the macro expands. Now the usage:

{{ between(token = JOIN, loop1, loop2, loop) }}

You can keep an assortment of Jinja2 utility macros in a file and include it at the top of your templates like so: {% include 'util.jinja2' as 'util' %} and the usage becomes:

{{ util.between(token = JOIN, loop1, loop2, loop) }}

An alternative to making a macro collection and using it in your templates is to do it inline in your templates. Here's an idea for a one liner to do the trick:

{% set loops = [loop1, loop2, loop] %}
{% if not loops|map('last')|select|length == loops|length %}JOIN{% endif %}

Upvotes: 1

Related Questions