Code

Ticket #3523: for_tag.3.patch

File for_tag.3.patch, 7.1 KB (added by SmileyChris, 7 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"),