Fixed #3523 -- Added list unpacking to for loops in templates. Thanks to SmileyChris and Honza Kral for their work.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@5443 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
83fe33e277
commit
16269c4d0a
|
@ -5,6 +5,7 @@ from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG
|
|||
from django.template import get_library, Library, InvalidTemplateLibrary
|
||||
from django.conf import settings
|
||||
import sys
|
||||
import re
|
||||
|
||||
register = Library()
|
||||
|
||||
|
@ -61,8 +62,8 @@ class FirstOfNode(Node):
|
|||
return ''
|
||||
|
||||
class ForNode(Node):
|
||||
def __init__(self, loopvar, sequence, reversed, nodelist_loop):
|
||||
self.loopvar, self.sequence = loopvar, sequence
|
||||
def __init__(self, loopvars, sequence, reversed, nodelist_loop):
|
||||
self.loopvars, self.sequence = loopvars, sequence
|
||||
self.reversed = reversed
|
||||
self.nodelist_loop = nodelist_loop
|
||||
|
||||
|
@ -72,7 +73,7 @@ class ForNode(Node):
|
|||
else:
|
||||
reversed = ''
|
||||
return "<For Node: for %s in %s, tail_len: %d%s>" % \
|
||||
(self.loopvar, self.sequence, len(self.nodelist_loop), reversed)
|
||||
(', '.join( self.loopvars ), self.sequence, len(self.nodelist_loop), reversed)
|
||||
|
||||
def __iter__(self):
|
||||
for node in self.nodelist_loop:
|
||||
|
@ -107,6 +108,7 @@ class ForNode(Node):
|
|||
for index in range(len(data)-1, -1, -1):
|
||||
yield data[index]
|
||||
values = reverse(values)
|
||||
unpack = len(self.loopvars) > 1
|
||||
for i, item in enumerate(values):
|
||||
context['forloop'] = {
|
||||
# shortcuts for current loop iteration number
|
||||
|
@ -120,9 +122,20 @@ class ForNode(Node):
|
|||
'last': (i == len_values - 1),
|
||||
'parentloop': parentloop,
|
||||
}
|
||||
context[self.loopvar] = item
|
||||
if unpack:
|
||||
# If there are multiple loop variables, unpack the item into them.
|
||||
context.update(dict(zip(self.loopvars, item)))
|
||||
else:
|
||||
context[self.loopvars[0]] = item
|
||||
for node in self.nodelist_loop:
|
||||
nodelist.append(node.render(context))
|
||||
if unpack:
|
||||
# The loop variables were pushed on to the context so pop them
|
||||
# off again. This is necessary because the tag lets the length
|
||||
# of loopvars differ to the length of each set of items and we
|
||||
# don't want to leave any vars from the previous loop on the
|
||||
# context.
|
||||
context.pop()
|
||||
context.pop()
|
||||
return nodelist.render(context)
|
||||
|
||||
|
@ -486,7 +499,7 @@ def do_filter(parser, token):
|
|||
nodelist = parser.parse(('endfilter',))
|
||||
parser.delete_first_token()
|
||||
return FilterNode(filter_expr, nodelist)
|
||||
filter = register.tag("filter", do_filter)
|
||||
do_filter = register.tag("filter", do_filter)
|
||||
|
||||
#@register.tag
|
||||
def firstof(parser, token):
|
||||
|
@ -530,9 +543,15 @@ def do_for(parser, token):
|
|||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
You can also loop over a list in reverse by using
|
||||
You can loop over a list in reverse by using
|
||||
``{% for obj in list reversed %}``.
|
||||
|
||||
You can also unpack multiple values from a two-dimensional array::
|
||||
|
||||
{% for key,value in dict.items %}
|
||||
{{ key }}: {{ value }}
|
||||
{% endfor %}
|
||||
|
||||
The for loop sets a number of variables available within the loop:
|
||||
|
||||
========================== ================================================
|
||||
|
@ -552,18 +571,23 @@ def do_for(parser, token):
|
|||
|
||||
"""
|
||||
bits = token.contents.split()
|
||||
if len(bits) == 5 and bits[4] != 'reversed':
|
||||
raise TemplateSyntaxError, "'for' statements with five words should end in 'reversed': %s" % token.contents
|
||||
if len(bits) not in (4, 5):
|
||||
raise TemplateSyntaxError, "'for' statements should have either four or five words: %s" % token.contents
|
||||
if bits[2] != 'in':
|
||||
raise TemplateSyntaxError, "'for' statement must contain 'in' as the second word: %s" % token.contents
|
||||
loopvar = bits[1]
|
||||
sequence = parser.compile_filter(bits[3])
|
||||
reversed = (len(bits) == 5)
|
||||
if len(bits) < 4:
|
||||
raise TemplateSyntaxError, "'for' statements should have at least four words: %s" % token.contents
|
||||
|
||||
reversed = bits[-1] == 'reversed'
|
||||
in_index = reversed and -3 or -2
|
||||
if bits[in_index] != 'in':
|
||||
raise TemplateSyntaxError, "'for' statements should use the format 'for x in y': %s" % token.contents
|
||||
|
||||
loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',')
|
||||
for var in loopvars:
|
||||
if not var or ' ' in var:
|
||||
raise TemplateSyntaxError, "'for' tag received an invalid argument: %s" % token.contents
|
||||
|
||||
sequence = parser.compile_filter(bits[in_index+1])
|
||||
nodelist_loop = parser.parse(('endfor',))
|
||||
parser.delete_first_token()
|
||||
return ForNode(loopvar, sequence, reversed, nodelist_loop)
|
||||
return ForNode(loopvars, sequence, reversed, nodelist_loop)
|
||||
do_for = register.tag("for", do_for)
|
||||
|
||||
def do_ifequal(parser, token, negate):
|
||||
|
|
|
@ -447,7 +447,7 @@ for
|
|||
~~~
|
||||
|
||||
Loop over each item in an array. For example, to display a list of athletes
|
||||
given ``athlete_list``::
|
||||
provided in ``athlete_list``::
|
||||
|
||||
<ul>
|
||||
{% for athlete in athlete_list %}
|
||||
|
@ -455,7 +455,25 @@ given ``athlete_list``::
|
|||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
You can also loop over a list in reverse by using ``{% for obj in list reversed %}``.
|
||||
You can loop over a list in reverse by using ``{% for obj in list reversed %}``.
|
||||
|
||||
**New in Django development version**
|
||||
If you need to loop over a list of lists, you can unpack the values
|
||||
in eachs sub-list into a set of known names. For example, if your context contains
|
||||
a list of (x,y) coordinates called ``points``, you could use the following
|
||||
to output the list of points::
|
||||
|
||||
{% for x, y in points %}
|
||||
There is a point at {{ x }},{{ y }}
|
||||
{% endfor %}
|
||||
|
||||
This can also be useful if you need to access the items in a dictionary.
|
||||
For example, if your context contained a dictionary ``data``, the following
|
||||
would display the keys and values of the dictionary::
|
||||
|
||||
{% for key, value in data.items %}
|
||||
{{ key }}: {{ value }}
|
||||
{% endfor %}
|
||||
|
||||
The for loop sets a number of variables available within the loop:
|
||||
|
||||
|
|
|
@ -289,6 +289,20 @@ class Templates(unittest.TestCase):
|
|||
'for-tag-vars02': ("{% for val in values %}{{ forloop.counter0 }}{% endfor %}", {"values": [6, 6, 6]}, "012"),
|
||||
'for-tag-vars03': ("{% for val in values %}{{ forloop.revcounter }}{% endfor %}", {"values": [6, 6, 6]}, "321"),
|
||||
'for-tag-vars04': ("{% for val in values %}{{ forloop.revcounter0 }}{% endfor %}", {"values": [6, 6, 6]}, "210"),
|
||||
'for-tag-unpack01': ("{% for key,value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"),
|
||||
'for-tag-unpack03': ("{% for key, value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"),
|
||||
'for-tag-unpack04': ("{% for key , value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"),
|
||||
'for-tag-unpack05': ("{% for key ,value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"),
|
||||
'for-tag-unpack06': ("{% for key value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, template.TemplateSyntaxError),
|
||||
'for-tag-unpack07': ("{% for key,,value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, template.TemplateSyntaxError),
|
||||
'for-tag-unpack08': ("{% for key,value, in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, template.TemplateSyntaxError),
|
||||
# Ensure that a single loopvar doesn't truncate the list in val.
|
||||
'for-tag-unpack09': ("{% for val in items %}{{ val.0 }}:{{ val.1 }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"),
|
||||
# Otherwise, silently truncate if the length of loopvars differs to the length of each set of items.
|
||||
'for-tag-unpack10': ("{% for x,y in items %}{{ x }}:{{ y }}/{% endfor %}", {"items": (('one', 1, 'carrot'), ('two', 2, 'orange'))}, "one:1/two:2/"),
|
||||
'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/")),
|
||||
'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/")),
|
||||
'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/")),
|
||||
|
||||
### IF TAG ################################################################
|
||||
'if-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"),
|
||||
|
|
Loading…
Reference in New Issue