Django

Code

Changeset 5443

Show
Ignore:
Timestamp:
06/08/07 06:58:03 (1 year ago)
Author:
russellm
Message:

Fixed #3523 -- Added list unpacking to for loops in templates. Thanks to SmileyChris? and Honza Kral for their work.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/django/template/defaulttags.py

    r5392 r5443  
    66from django.conf import settings 
    77import sys 
     8import re 
    89 
    910register = Library() 
     
    6263 
    6364class ForNode(Node): 
    64     def __init__(self, loopvar, sequence, reversed, nodelist_loop): 
    65         self.loopvar, self.sequence = loopvar, sequence 
     65    def __init__(self, loopvars, sequence, reversed, nodelist_loop): 
     66        self.loopvars, self.sequence = loopvars, sequence 
    6667        self.reversed = reversed 
    6768        self.nodelist_loop = nodelist_loop 
     
    7374            reversed = '' 
    7475        return "<For Node: for %s in %s, tail_len: %d%s>" % \ 
    75             (self.loopvar, self.sequence, len(self.nodelist_loop), reversed) 
     76            (', '.join( self.loopvars ), self.sequence, len(self.nodelist_loop), reversed) 
    7677 
    7778    def __iter__(self): 
     
    108109                    yield data[index] 
    109110            values = reverse(values) 
     111        unpack = len(self.loopvars) > 1 
    110112        for i, item in enumerate(values): 
    111113            context['forloop'] = { 
     
    121123                'parentloop': parentloop, 
    122124            } 
    123             context[self.loopvar] = item 
     125            if unpack: 
     126                # If there are multiple loop variables, unpack the item into them. 
     127                context.update(dict(zip(self.loopvars, item))) 
     128            else: 
     129                context[self.loopvars[0]] = item 
    124130            for node in self.nodelist_loop: 
    125131                nodelist.append(node.render(context)) 
     132            if unpack: 
     133                # The loop variables were pushed on to the context so pop them 
     134                # off again. This is necessary because the tag lets the length 
     135                # of loopvars differ to the length of each set of items and we 
     136                # don't want to leave any vars from the previous loop on the 
     137                # context. 
     138                context.pop() 
    126139        context.pop() 
    127140        return nodelist.render(context) 
     
    487500    parser.delete_first_token() 
    488501    return FilterNode(filter_expr, nodelist) 
    489 filter = register.tag("filter", do_filter) 
     502do_filter = register.tag("filter", do_filter) 
    490503 
    491504#@register.tag 
     
    531544        </ul> 
    532545 
    533     You can also loop over a list in reverse by using 
     546    You can loop over a list in reverse by using 
    534547    ``{% for obj in list reversed %}``. 
     548     
     549    You can also unpack multiple values from a two-dimensional array:: 
     550     
     551        {% for key,value in dict.items %} 
     552            {{ key }}: {{ value }} 
     553        {% endfor %} 
    535554 
    536555    The for loop sets a number of variables available within the loop: 
     
    553572    """ 
    554573    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) 
     574    if len(bits) < 4: 
     575        raise TemplateSyntaxError, "'for' statements should have at least four words: %s" % token.contents 
     576 
     577    reversed = bits[-1] == 'reversed' 
     578    in_index = reversed and -3 or -2 
     579    if bits[in_index] != 'in': 
     580        raise TemplateSyntaxError, "'for' statements should use the format 'for x in y': %s" % token.contents 
     581 
     582    loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',') 
     583    for var in loopvars: 
     584        if not var or ' ' in var: 
     585            raise TemplateSyntaxError, "'for' tag received an invalid argument: %s" % token.contents 
     586 
     587    sequence = parser.compile_filter(bits[in_index+1]) 
    564588    nodelist_loop = parser.parse(('endfor',)) 
    565589    parser.delete_first_token() 
    566     return ForNode(loopvar, sequence, reversed, nodelist_loop) 
     590    return ForNode(loopvars, sequence, reversed, nodelist_loop) 
    567591do_for = register.tag("for", do_for) 
    568592 
  • django/trunk/docs/templates.txt

    r5436 r5443  
    448448 
    449449Loop over each item in an array.  For example, to display a list of athletes 
    450 given ``athlete_list``:: 
     450provided in ``athlete_list``:: 
    451451 
    452452    <ul> 
     
    456456    </ul> 
    457457 
    458 You can also loop over a list in reverse by using ``{% for obj in list reversed %}``. 
     458You can loop over a list in reverse by using ``{% for obj in list reversed %}``. 
     459 
     460**New in Django development version** 
     461If you need to loop over a list of lists, you can unpack the values 
     462in eachs sub-list into a set of known names. For example, if your context contains 
     463a list of (x,y) coordinates called ``points``, you could use the following 
     464to output the list of points::  
     465 
     466    {% for x, y in points %} 
     467        There is a point at {{ x }},{{ y }} 
     468    {% endfor %} 
     469     
     470This can also be useful if you need to access the items in a dictionary.  
     471For example, if your context contained a dictionary ``data``, the following 
     472would display the keys and values of the dictionary:: 
     473 
     474    {% for key, value in data.items %} 
     475        {{ key }}: {{ value }} 
     476    {% endfor %} 
    459477 
    460478The for loop sets a number of variables available within the loop: 
  • django/trunk/tests/regressiontests/templates/tests.py

    r5167 r5443  
    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-unpack03': ("{% for key, value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"), 
     294            'for-tag-unpack04': ("{% for key , value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"), 
     295            'for-tag-unpack05': ("{% for key ,value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"), 
     296            'for-tag-unpack06': ("{% for key value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, template.TemplateSyntaxError), 
     297            'for-tag-unpack07': ("{% for key,,value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, template.TemplateSyntaxError), 
     298            'for-tag-unpack08': ("{% for key,value, in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, template.TemplateSyntaxError), 
     299            # Ensure that a single loopvar doesn't truncate the list in val. 
     300            'for-tag-unpack09': ("{% for val in items %}{{ val.0 }}:{{ val.1 }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"), 
     301            # Otherwise, silently truncate if the length of loopvars differs to the length of each set of items. 
     302            'for-tag-unpack10': ("{% for x,y in items %}{{ x }}:{{ y }}/{% endfor %}", {"items": (('one', 1, 'carrot'), ('two', 2, 'orange'))}, "one:1/two:2/"), 
     303            'for-tag-unpack11': ("{% 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/")), 
     304            'for-tag-unpack12': ("{% 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/")), 
     305            'for-tag-unpack13': ("{% for x,y,z in items %}{{ x }}:{{ y }},{{ z }}/{% endfor %}", {"items": (('one', 1, 'carrot'), ('two', 2, 'cheese'))}, ("one:1,carrot/two:2,cheese/", "one:1,carrot/two:2,cheese/")), 
    292306 
    293307            ### IF TAG ################################################################