Ticket #3523: for_tag.3.patch

File for_tag.3.patch, 7.1 KB (added by Chris Beaven, 17 years ago)

new and improved :)

  • django/template/defaulttags.py

     
    55from django.template import get_library, Library, InvalidTemplateLibrary
    66from django.conf import settings
    77import sys
     8import string
    89
    910register = Library()
    1011
     
    5859        return ''
    5960
    6061class ForNode(Node):
    61     def __init__(self, loopvar, sequence, reversed, nodelist_loop):
    62         self.loopvar, self.sequence = loopvar, sequence
     62    def __init__(self, loopvars, sequence, reversed, nodelist_loop):
     63        self.loopvars, self.sequence = loopvars, sequence
    6364        self.reversed = reversed
    6465        self.nodelist_loop = nodelist_loop
    6566
     67
    6668    def __repr__(self):
    6769        if self.reversed:
    6870            reversed = ' reversed'
    6971        else:
    7072            reversed = ''
    7173        return "<For Node: for %s in %s, tail_len: %d%s>" % \
    72             (self.loopvar, self.sequence, len(self.nodelist_loop), reversed)
     74            (', '.join( self.loopvars ), self.sequence, len(self.nodelist_loop), reversed)
    7375
    7476    def __iter__(self):
    7577        for node in self.nodelist_loop:
     
    104106                for index in range(len(data)-1, -1, -1):
    105107                    yield data[index]
    106108            values = reverse(values)
     109        unpack = len(self.loopvars) > 1
    107110        for i, item in enumerate(values):
    108111            context['forloop'] = {
    109112                # shortcuts for current loop iteration number
     
    117120                'last': (i == len_values - 1),
    118121                'parentloop': parentloop,
    119122            }
    120             context[self.loopvar] = item
     123            if unpack:
     124                # If multiple loop variables, unpack the item to them.
     125                context.update(dict(zip(self.loopvars, item)))
     126            else:
     127                context[self.loopvars[0]] = item
    121128            for node in self.nodelist_loop:
    122129                nodelist.append(node.render(context))
    123130        context.pop()
     
    438445    nodelist = parser.parse(('endfilter',))
    439446    parser.delete_first_token()
    440447    return FilterNode(filter_expr, nodelist)
    441 filter = register.tag("filter", do_filter)
     448do_filter = register.tag("filter", do_filter)
    442449
    443450#@register.tag
    444451def firstof(parser, token):
     
    482489        {% endfor %}
    483490        </ul>
    484491
    485     You can also loop over a list in reverse by using
     492    You can loop over a list in reverse by using
    486493    ``{% for obj in list reversed %}``.
     494   
     495    You can also unpack multiple values from a two-dimensional array::
     496   
     497        {% for key,value in dict.items %}
     498            {{ key }}: {{ value }}
     499        {% endfor %}
    487500
    488501    The for loop sets a number of variables available within the loop:
    489502
     
    504517
    505518    """
    506519    bits = token.contents.split()
    507     if len(bits) == 5 and bits[4] != 'reversed':
    508         raise TemplateSyntaxError, "'for' statements with five words should end in 'reversed': %s" % token.contents
    509     if len(bits) not in (4, 5):
    510         raise TemplateSyntaxError, "'for' statements should have either four or five words: %s" % token.contents
    511     if bits[2] != 'in':
    512         raise TemplateSyntaxError, "'for' statement must contain 'in' as the second word: %s" % token.contents
    513     loopvar = bits[1]
    514     sequence = parser.compile_filter(bits[3])
    515     reversed = (len(bits) == 5)
     520    if len(bits) < 4:
     521        raise TemplateSyntaxError, "'for' statements should have at least four words: %s" % token.contents
     522
     523    reversed = bits[-1] == 'reversed'
     524    in_index = reversed and -3 or -2
     525    if bits[in_index] != 'in':
     526        raise TemplateSyntaxError, "'for' statements should use the format 'for x in y': %s" % token.contents
     527
     528    loopvars = []
     529    for bit in bits[1:in_index]:
     530        loopvars.extend(bit.split(','))
     531    loopvars = map(string.strip, loopvars)
     532    loopvars = filter(None, loopvars)
     533
     534    sequence = parser.compile_filter(bits[in_index+1])
    516535    nodelist_loop = parser.parse(('endfor',))
    517536    parser.delete_first_token()
    518     return ForNode(loopvar, sequence, reversed, nodelist_loop)
     537    return ForNode(loopvars, sequence, reversed, nodelist_loop)
    519538do_for = register.tag("for", do_for)
    520539
    521540def do_ifequal(parser, token, negate):
  • docs/templates.txt

     
    435435~~~
    436436
    437437Loop over each item in an array.  For example, to display a list of athletes
    438 given ``athlete_list``::
     438provided in ``athlete_list``::
    439439
    440440    <ul>
    441441    {% for athlete in athlete_list %}
     
    443443    {% endfor %}
    444444    </ul>
    445445
    446 You can also loop over a list in reverse by using ``{% for obj in list reversed %}``.
     446You can loop over a list in reverse by using ``{% for obj in list reversed %}``.
    447447
     448For advanced use, you can unpack multiple values from a list of fixed length
     449lists. For example, if dict was a Python dictionary::
     450
     451    {% for key, value in dict.items %}
     452        {{ key }}: {{ value }}
     453    {% endfor %}
     454
    448455The for loop sets a number of variables available within the loop:
    449456
    450457    ==========================  ================================================
  • tests/regressiontests/templates/tests.py

     
    252252            'for-tag-vars02': ("{% for val in values %}{{ forloop.counter0 }}{% endfor %}", {"values": [6, 6, 6]}, "012"),
    253253            'for-tag-vars03': ("{% for val in values %}{{ forloop.revcounter }}{% endfor %}", {"values": [6, 6, 6]}, "321"),
    254254            'for-tag-vars04': ("{% for val in values %}{{ forloop.revcounter0 }}{% endfor %}", {"values": [6, 6, 6]}, "210"),
     255            'for-tag-unpack01': ("{% for key,value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"),
     256            'for-tag-unpack02': ("{% for key, value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"),
     257            'for-tag-unpack03': ("{% for key value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"),
     258            # Ensure that a single loopvar doesn't truncate the list in val.
     259            'for-tag-unpack04': ("{% for val in items %}{{ val.0 }}:{{ val.1 }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"),
     260            # Otherwise, silently truncate if the length of loopvars differs to the length of each set of items.
     261            'for-tag-unpack05': ("{% for x,y in items %}{{ x }}:{{ y }}/{% endfor %}", {"items": (('one', 1, 'carrot'), ('two', 2, 'orange'))}, "one:1/two:2/"),
     262            'for-tag-unpack06': ("{% for x,y,z in items %}{{ x }}:{{ y }},{{ z }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, ("one:1,/two:2,/", "one:1,INVALID/two:2,INVALID/")),
    255263
    256264            ### IF TAG ################################################################
    257265            'if-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"),
Back to Top