Traversing multiple lists in django template in same for loop Traversing multiple lists in django template in same for loop django django

Traversing multiple lists in django template in same for loop


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 templateregister = 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 filterfor x in list_iter((list_a, list_b, list_c)):    print x

See the filter documentation


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.


Custom Template Tag

from django import templateregister = 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, kwargsclass 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.tagdef 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.