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.