69

I want to do the below list iteration in django templates:

foo = ['foo', 'bar']; moo = ['moo', 'loo']; for (a, b) in zip(foo, moo): print a, b 

django code:

{% for a, b in zip(foo, moo) %} {{ a }} {{ b }} {% endfor %} 

I get the below error when I try this:

File "/base/python_lib/versions/third_party/django-0.96/django/template/defaulttags.py", line 538, in do_for raise TemplateSyntaxError, "'for' statements should have either four or five words: %s" % token.contents 

How do I accomplish this?

1
  • One tricky use case is when you have a master list ['I', 'you', 'he'] and sublists = [['me' ,'you', 'him'], ['my', 'your', 'his'], ['mine', 'yours', 'his']]. If you want to iterate each of the sublists together with master, you'd have to zip every one of them in the view. Commented Jan 5, 2011 at 8:15

8 Answers 8

117

You can use zip in your view:

mylist = zip(list1, list2) context = { 'mylist': mylist, } return render(request, 'template.html', context) 

and in your template use

{% for item1, item2 in mylist %} 

to iterate through both lists.

This should work with all version of Django.

Sign up to request clarification or add additional context in comments.

3 Comments

Don't use "list" as a variable name, since this clobbers the built-in.
Anti-pattern detected! Use jinja and dont mix your view and template logic
This is a nice and quick solution. Please don't use your comments to confuse people, especially beginners.
64

Simply define zip as a template filter:

@register.filter(name='zip') def zip_lists(a, b): return zip(a, b) 

Then, in your template:

{%for a, b in first_list|zip:second_list %} {{a}} {{b}} {%endfor%} 

4 Comments

This is the nicest solution since I don't have to change the context. Thanks.
Just what I needed for rendering model formsets in a table.
what about if 2 lists have different sizes?
If 2 lists have not the same size the remaining items of the larger one is ignored. you can use litertools.zip_longest(a, b)
29

It's possible to do

{% for ab in mylist %} {{ab.0}} {{ab.1}} {% endfor %} 

but you cannot make a call to zip within the for structure. You'll have to store the zipped list in another variable first, then iterate over it.

5 Comments

syntax a, b is not possible in django template version 0.96 which google app engine uses I guess because I get an error when I try to use the above syntax. Instead do {%for item in mylist%} and use item.0 and item.1. Got this from groups.google.com/group/django-users/browse_thread/thread/…
Ok. Then my answer might even be wrong after all. I just knew that tuple unpacking is possible in for loops an concluded that the error must come from the zip call. Didn't test it, though - sorry.
Fixed. Bottom line is simple: you cannot do any computing in the template; you must do ALL your computations in the view functions. Also, using zip is a poor choice; a namedtuple is a far better idea because it makes the template more sensible.
@Lott Care to elaborate with an example.? Did not get you as I am pretty new to python.
@Abhi: Since your question has no details, it's difficult to fabricate an example that might be helpful. I could guess randomly what you're trying to do. Instead, start by using namedtuple instead of zip. If you still have questions -- well -- post a question.
9

I built django-multiforloop to solve this problem. From the README:

With django-multiforloop installed, rendering this template

{% for x in x_list; y in y_list %} {{ x }}:{{ y }} {% endfor %} 

with this context

context = { "x_list": ('one', 1, 'carrot'), "y_list": ('two', 2, 'orange') } 

will output

one:two 1:2 carrot:orange 

1 Comment

Cool app.. but I couldn't get it to work :/ Also, I happen to need something which will loop over the small array multiple times.
4

In views.py:

foo = ['foo', 'bar'] moo = ['moo', 'loo'] zipped_list = zip(foo,moo) return render(request,"template.html",{"context":zipped_list} 

In template.html:

{% for f,m in context%} {{f}}{{m}} {% endfor %} 

If f is a queryset returned from database then access it by {{f.required_attribute_name}}

Comments

3

You can make the foo objects properties of the moo objects on the server side.

for f, b in zip(foo, bar): f.foosBar = b context = { "foo": foo } 

This is especially clean when the second list are properties of the first (which is typically the case).

users = User.objects.all() for user in users: user.bestFriend = findBestFriendForUser(user) context = { "users": users } 

Comments

3

Here is modified {% for %} templatetag which allows iterating several lists at once izip-ing them before:

import re from itertools import izip from django import template from django.template.base import TemplateSyntaxError from django.template.defaulttags import ForNode register = template.Library() class ZipExpression(object): def __init__(self, var): self.var = var def resolve(self, *args, **kwargs): return izip(*( f.resolve(*args, **kwargs) for f in self.var )) @register.tag('for') def do_for(parser, token): """ For tag with ziping multiple iterables. """ bits = token.contents.split() if len(bits) < 4: raise TemplateSyntaxError("'foreach' statements should have at least" " four words: %s" % token.contents) is_reversed = False try: in_index = bits.index('in') sequence = bits[in_index+1:] if sequence[-1] == 'reversed': is_reversed = True sequence.pop() if not sequence or 'in' in sequence: raise ValueError sequence = re.split(r' *, *', ' '.join(sequence)) except ValueError: raise TemplateSyntaxError( "'foreach' statements should use the format" " 'foreach a,b,(...) in x,y,(...)': %s" % token.contents) loopvars = re.split(r' *, *', ' '.join(bits[1:in_index])) for var in loopvars: if not var or ' ' in var: raise TemplateSyntaxError("'foreach' tag received an invalid" " argumewnt: %s" % token.contents) if len(sequence) > 1: sequence = ZipExpression(map(parser.compile_filter, sequence)) else: sequence = parser.compile_filter(sequence[0]) nodelist_loop = parser.parse(('empty', 'endfor',)) token = parser.next_token() if token.contents == 'empty': nodelist_empty = parser.parse(('endfor',)) parser.delete_first_token() else: nodelist_empty = None return ForNode( loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty) 

Just save it as templatetag library and import it in your template. It will override build-in {% for %} tag (don't worry it is backward compatible with it).

Example usage:

{% for a,b in foo, moo %} {{ a }} {{ b }} {% endfor %} 

Comments

0

@marco's approach, using zip in a custom template filter, works well for the OP's case with two lists.

However, a template filter only supports two arguments, so, if you want to combine more than two lists, you would need to resort to filter chaining.

As an alternative, you could create a simple_tag, which supports any number of arguments.

For example:

@register.simple_tag(name='zip') def zip_many(*args): return zip(*args) 

This can be used in a template as follows:

{% zip a b c as abc_zipped %} {% for x, y, z in abc_zipped %} ... {% endfor %} 

where a, b, and c are lists.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.