Code

Ticket #3523: for_tag.4.patch

File for_tag.4.patch, 7.9 KB (added by SmileyChris, 7 years ago)

small bug fix and tighter format checking

  • django/template/defaulttags.py

     
    6161        return '' 
    6262 
    6363class ForNode(Node): 
    64     def __init__(self, loopvar, sequence, reversed, nodelist_loop): 
    65         self.loopvar, self.sequence = loopvar, sequence 
     64    def __init__(self, loopvars, sequence, reversed, nodelist_loop): 
     65        self.loopvars, self.sequence = loopvars, sequence 
    6666        self.reversed = reversed 
    6767        self.nodelist_loop = nodelist_loop 
    6868 
     
    7272        else: 
    7373            reversed = '' 
    7474        return "<For Node: for %s in %s, tail_len: %d%s>" % \ 
    75             (self.loopvar, self.sequence, len(self.nodelist_loop), reversed) 
     75            (', '.join( self.loopvars ), self.sequence, len(self.nodelist_loop), reversed) 
    7676 
    7777    def __iter__(self): 
    7878        for node in self.nodelist_loop: 
     
    107107                for index in range(len(data)-1, -1, -1): 
    108108                    yield data[index] 
    109109            values = reverse(values) 
     110        unpack = len(loopvars) > 1 
    110111        for i, item in enumerate(values): 
    111112            context['forloop'] = { 
    112113                # shortcuts for current loop iteration number 
     
    120121                'last': (i == len_values - 1), 
    121122                'parentloop': parentloop, 
    122123            } 
    123             context[self.loopvar] = item 
     124            if unpack: 
     125                # If multiple loop variables, unpack the item to them. 
     126                context.update(dict(zip(self.loopvars, item))) 
     127            else: 
     128                context[self.loopvars[0]] = item 
    124129            for node in self.nodelist_loop: 
    125130                nodelist.append(node.render(context)) 
     131            if unpack: 
     132                # The loop variables were pushed on to the context so pop them 
     133                # off again. This is necessary because the tag lets the length 
     134                # of loopvars differ to the length of each set of items and we 
     135                # don't want to leave any vars from this previous loop on the 
     136                # context. 
     137                context.pop() 
    126138        context.pop() 
    127139        return nodelist.render(context) 
    128140 
     
    486498    nodelist = parser.parse(('endfilter',)) 
    487499    parser.delete_first_token() 
    488500    return FilterNode(filter_expr, nodelist) 
    489 filter = register.tag("filter", do_filter) 
     501do_filter = register.tag("filter", do_filter) 
    490502 
    491503#@register.tag 
    492504def firstof(parser, token): 
     
    530542        {% endfor %} 
    531543        </ul> 
    532544 
    533     You can also loop over a list in reverse by using 
     545    You can loop over a list in reverse by using 
    534546    ``{% for obj in list reversed %}``. 
     547     
     548    You can also unpack multiple values from a two-dimensional array:: 
     549     
     550        {% for key,value in dict.items %} 
     551            {{ key }}: {{ value }} 
     552        {% endfor %} 
    535553 
    536554    The for loop sets a number of variables available within the loop: 
    537555 
     
    552570 
    553571    """ 
    554572    bits = token.contents.split() 
    555     if len(bits) == 5 and bits[4] != 'reversed': 
    556         raise TemplateSyntaxError, "'for' statements with five words should end in 'reversed': %s" % token.contents 
    557     if len(bits) not in (4, 5): 
    558         raise TemplateSyntaxError, "'for' statements should have either four or five words: %s" % token.contents 
    559     if bits[2] != 'in': 
    560         raise TemplateSyntaxError, "'for' statement must contain 'in' as the second word: %s" % token.contents 
    561     loopvar = bits[1] 
    562     sequence = parser.compile_filter(bits[3]) 
    563     reversed = (len(bits) == 5) 
     573    if len(bits) < 4: 
     574        raise TemplateSyntaxError, "'for' statements should have at least four words: %s" % token.contents 
     575 
     576    reversed = bits[-1] == 'reversed' 
     577    in_index = reversed and -3 or -2 
     578    if bits[in_index] != 'in': 
     579        raise TemplateSyntaxError, "'for' statements should use the format 'for x in y': %s" % token.contents 
     580 
     581    loopvars = ' '.join(bits[1:in_index]).replace(', ', ',').split(',') 
     582    for var in loopvars: 
     583        if not var or ' ' in var: 
     584            raise TemplateSyntaxError, "'for' tag received an invalid argument: %s" % token.contents 
     585 
     586    sequence = parser.compile_filter(bits[in_index+1]) 
    564587    nodelist_loop = parser.parse(('endfor',)) 
    565588    parser.delete_first_token() 
    566     return ForNode(loopvar, sequence, reversed, nodelist_loop) 
     589    return ForNode(loopvars, sequence, reversed, nodelist_loop) 
    567590do_for = register.tag("for", do_for) 
    568591 
    569592def do_ifequal(parser, token, negate): 
  • docs/templates.txt

     
    444444~~~ 
    445445 
    446446Loop over each item in an array.  For example, to display a list of athletes 
    447 given ``athlete_list``:: 
     447provided in ``athlete_list``:: 
    448448 
    449449    <ul> 
    450450    {% for athlete in athlete_list %} 
     
    452452    {% endfor %} 
    453453    </ul> 
    454454 
    455 You can also loop over a list in reverse by using ``{% for obj in list reversed %}``. 
     455You can loop over a list in reverse by using ``{% for obj in list reversed %}``. 
    456456 
     457For advanced use, you can unpack multiple values from a list of fixed length 
     458lists. For example, to display the keys and values of a Python dictionary:: 
     459 
     460    {% for key, value in dict.iteritems %} 
     461        {{ key }}: {{ value }} 
     462    {% endfor %} 
     463 
    457464The for loop sets a number of variables available within the loop: 
    458465 
    459466    ==========================  ================================================ 
  • tests/regressiontests/templates/tests.py

     
    289289            'for-tag-vars02': ("{% for val in values %}{{ forloop.counter0 }}{% endfor %}", {"values": [6, 6, 6]}, "012"), 
    290290            'for-tag-vars03': ("{% for val in values %}{{ forloop.revcounter }}{% endfor %}", {"values": [6, 6, 6]}, "321"), 
    291291            'for-tag-vars04': ("{% for val in values %}{{ forloop.revcounter0 }}{% endfor %}", {"values": [6, 6, 6]}, "210"), 
     292            'for-tag-unpack01': ("{% for key,value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"), 
     293            'for-tag-unpack02': ("{% for key, value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"), 
     294            'for-tag-unpack03': ("{% for key value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, template.TemplateSyntaxError), 
     295            'for-tag-unpack04': ("{% for key,,value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, template.TemplateSyntaxError), 
     296            'for-tag-unpack05': ("{% for key,value, in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, template.TemplateSyntaxError), 
     297            # Ensure that a single loopvar doesn't truncate the list in val. 
     298            'for-tag-unpack06': ("{% for val in items %}{{ val.0 }}:{{ val.1 }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"), 
     299            # Otherwise, silently truncate if the length of loopvars differs to the length of each set of items. 
     300            'for-tag-unpack07': ("{% for x,y in items %}{{ x }}:{{ y }}/{% endfor %}", {"items": (('one', 1, 'carrot'), ('two', 2, 'orange'))}, "one:1/two:2/"), 
     301            'for-tag-unpack08': ("{% 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/")), 
     302            'for-tag-unpack09': ("{% for x,y,z in items %}{{ x }}:{{ y }},{{ z }}/{% endfor %}", {"items": (('one', 1, 'carrot'), ('two', 2))}, ("one:1,carrot/two:2,/", "one:1,carrot/two:2,INVALID/")), 
    292303 
    293304            ### IF TAG ################################################################ 
    294305            'if-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"),