Traversing multiple lists in django template in same for loop

11,508

Solution 1

You have two options:

1. You define your objects so that you can access the items like parameters

for x in list:
    {{x.item1}}, {{x.item2}}, {{x.item3}}

Note that you have to make up the list by combining the three lists:

lst = [{'item1': t[0], 'item2': t[1], 'item3':t[2]} for t in zip(list_a, list_b, list_c)]

2. You define your own filter

from django import template

register = template.Library()

@register.filter(name='list_iter')
def list_iter(lists):
    list_a, list_b, list_c = lists

    for x, y, z in zip(list_a, list_b, list_c):
        yield (x, y, z)

# test the filter
for x in list_iter((list_a, list_b, list_c)):
    print x

See the filter documentation

Solution 2

Abusing django templates:

{% for x in list_a %}
{% with forloop.counter|cut:" " as index %}
  {{ x }},
  {{ list_b|slice:index|last }},
  {{ list_c|slice:index|last }} <br/>
{% endwith %}
{% endfor %}

But NEVER do that!!! just use zip in Your views.

Solution 3

Custom Template Tag

from django import template

register = template.Library()

def parse_tokens(parser, bits):
    """
    Parse a tag bits (split tokens) and return a list on kwargs (from bits of the  fu=bar) and a list of arguments.
    """

    kwargs = {}
    args = []
    for bit in bits[1:]:
        try:
            try:
                pair = bit.split('=')
                kwargs[str(pair[0])] = parser.compile_filter(pair[1])
            except IndexError:
                args.append(parser.compile_filter(bit))
        except TypeError:
            raise template.TemplateSyntaxError('Bad argument "%s" for tag "%s"' % (bit, bits[0]))

    return args, kwargs

class ZipLongestNode(template.Node):
    """
    Zip multiple lists into one using the longest to determine the size

    Usage: {% zip_longest list1 list2 <list3...> as items %}
    """
    def __init__(self, *args, **kwargs):
        self.lists = args
        self.varname = kwargs['varname']

    def render(self, context):
        lists = [e.resolve(context) for e in self.lists]

        if self.varname is not None:
            context[self.varname] = [i for i in map(lambda *a: a, *lists)]
        return ''

@register.tag
def zip_longest(parser, token):
    bits = token.contents.split()
    varname = None
    if bits[-2] == 'as':
        varname = bits[-1]
        del bits[-2:]
    else:
        # raise exception
        pass
    args, kwargs = parse_tokens(parser, bits)

    if varname:
        kwargs['varname'] = varname

    return ZipLongestNode(*args, **kwargs)

Usage:

{% zip_longest list1 list2 as items %}

This lets you pass 2 or more lists to a tag then iterate over the items variable. If you use more than two lists then you'll need loop again unfortunately. However with two lists I've used the first and last filters inside the loop like this:

{% for item in items %}
    {% with item|first as one %}
    {% with item|last as two %}
    <p>{{ one }}</p>
    <p>{{ two }}</p>
    {% endwith %}
    {% endwith %}
{% endfor %}

However, having built all of this, it might be better to do this in a view!

Python's Itertools

You should also consider Python's itertools, which has the izip_longest method that takes two or more lists. It returns the lists as one using the longest list to determine the size (if you want it to concatenate to the shortest list then look no further than izip). You can choose what to fill empty values with using the fillvalue keyword, but by default this is None.

Both izip_longest and izip return an iterator instead of a list, so you could see some performance gain on larger sites.

It's important to to note that izip_longest might hit the db slightly more than necessary depending on how it determines the length of each list (performing a count() would be an extra call to the db). However I haven't managed to reliably test this and it would only matter once you had to scale up.

Share:
11,508
demos
Author by

demos

Updated on June 06, 2022

Comments

  • demos
    demos almost 2 years

    I want to traverse multiple lists within a django template in the same for loop. How do i do it?

    some thinking link this:

    {% for item1, item2, item3 in list1, list2 list3 %}
    
    {{ item1 }}, {{ item2 }}, {{ item3 }}
    
    {% endfor %}
    

    Is something like this possible?

  • demos
    demos almost 14 years
    Thanks a ton! perfect solution.
  • demos
    demos almost 14 years
    Nice hack! :) Might come in handy some day.
  • webtweakers
    webtweakers about 10 years
    How would you use solution 2 in a template?
  • Himanshu dua
    Himanshu dua about 9 years
    Good hack Very helpful for me