63

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?

djvg
  • 11,722
  • 5
  • 72
  • 103
Abhi
  • 2,298
  • 4
  • 29
  • 34
  • 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. – akaihola Jan 05 '11 at 08:15

8 Answers8

109

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.

J-a-n-u-s
  • 1,469
  • 17
  • 20
Mermoz
  • 14,898
  • 17
  • 60
  • 85
60

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%}
Marco
  • 1,196
  • 9
  • 11
28

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.

S.Lott
  • 384,516
  • 81
  • 508
  • 779
Johannes Charra
  • 29,455
  • 6
  • 42
  • 51
  • 1
    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 http://groups.google.com/group/django-users/browse_thread/thread/9160209ccfa94a30?pli=1 – Abhi Mar 10 '10 at 10:18
  • 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. – Johannes Charra Mar 10 '10 at 10:23
  • 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. – S.Lott Mar 10 '10 at 11:11
  • @Lott Care to elaborate with an example.? Did not get you as I am pretty new to python. – Abhi Mar 10 '10 at 14:23
  • @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. – S.Lott Mar 10 '10 at 15:08
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
Gabriel Grant
  • 5,415
  • 2
  • 32
  • 40
  • 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. – Thumbz Jun 27 '14 at 02:34
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 %}
jaboja
  • 2,178
  • 1
  • 21
  • 35
3

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}}

Sm Srikanth
  • 1,992
  • 1
  • 10
  • 10
2

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
}
bbrame
  • 18,031
  • 10
  • 35
  • 52
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.

djvg
  • 11,722
  • 5
  • 72
  • 103