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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.