Traversing multiple lists in django template in same for loop
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 }}
开发者_Go百科
{% endfor %}
Is something like this possible?
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
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 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.
精彩评论