Fixed #6398: added an optional `{% empty %}` clause to the `{% for %}` template tag. The contents of this clause are rendered if the list iterated over turns out to be empty. Thanks, Jannis Leidel.

Astute readers will notice that the patch originally called this `default`; after consideration I decided that `empty` is a very slightly better color for this particular bikeshed.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@9530 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Jacob Kaplan-Moss 2008-11-24 22:01:48 +00:00
parent 136c4f8549
commit 4aa97f5c18
3 changed files with 75 additions and 5 deletions

View File

@ -83,10 +83,14 @@ class FirstOfNode(Node):
return u'' return u''
class ForNode(Node): class ForNode(Node):
def __init__(self, loopvars, sequence, is_reversed, nodelist_loop): def __init__(self, loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty=None):
self.loopvars, self.sequence = loopvars, sequence self.loopvars, self.sequence = loopvars, sequence
self.is_reversed = is_reversed self.is_reversed = is_reversed
self.nodelist_loop = nodelist_loop self.nodelist_loop = nodelist_loop
if nodelist_empty is None:
self.nodelist_empty = NodeList()
else:
self.nodelist_empty = nodelist_empty
def __repr__(self): def __repr__(self):
reversed_text = self.is_reversed and ' reversed' or '' reversed_text = self.is_reversed and ' reversed' or ''
@ -97,16 +101,18 @@ class ForNode(Node):
def __iter__(self): def __iter__(self):
for node in self.nodelist_loop: for node in self.nodelist_loop:
yield node yield node
for node in self.nodelist_empty:
yield node
def get_nodes_by_type(self, nodetype): def get_nodes_by_type(self, nodetype):
nodes = [] nodes = []
if isinstance(self, nodetype): if isinstance(self, nodetype):
nodes.append(self) nodes.append(self)
nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype)) nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))
nodes.extend(self.nodelist_empty.get_nodes_by_type(nodetype))
return nodes return nodes
def render(self, context): def render(self, context):
nodelist = NodeList()
if 'forloop' in context: if 'forloop' in context:
parentloop = context['forloop'] parentloop = context['forloop']
else: else:
@ -121,6 +127,9 @@ class ForNode(Node):
if not hasattr(values, '__len__'): if not hasattr(values, '__len__'):
values = list(values) values = list(values)
len_values = len(values) len_values = len(values)
if len_values < 1:
return self.nodelist_empty.render(context)
nodelist = NodeList()
if self.is_reversed: if self.is_reversed:
values = reversed(values) values = reversed(values)
unpack = len(self.loopvars) > 1 unpack = len(self.loopvars) > 1
@ -610,6 +619,30 @@ def do_for(parser, token):
{{ key }}: {{ value }} {{ key }}: {{ value }}
{% endfor %} {% endfor %}
The ``for`` tag can take an optional ``{% empty %}`` clause that will
be displayed if the given array is empty or could not be found::
<ul>
{% for athlete in athlete_list %}
<li>{{ athlete.name }}</li>
{% empty %}
<li>Sorry, no athletes in this list.</li>
{% endfor %}
<ul>
The above is equivalent to -- but shorter, cleaner, and possibly faster
than -- the following::
<ul>
{% if althete_list %}
{% for athlete in athlete_list %}
<li>{{ athlete.name }}</li>
{% endfor %}
{% else %}
<li>Sorry, no athletes in this list.</li>
{% endif %}
</ul>
The for loop sets a number of variables available within the loop: The for loop sets a number of variables available within the loop:
========================== ================================================ ========================== ================================================
@ -646,9 +679,14 @@ def do_for(parser, token):
" %s" % token.contents) " %s" % token.contents)
sequence = parser.compile_filter(bits[in_index+1]) sequence = parser.compile_filter(bits[in_index+1])
nodelist_loop = parser.parse(('endfor',)) nodelist_loop = parser.parse(('default', 'endfor',))
parser.delete_first_token() token = parser.next_token()
return ForNode(loopvars, sequence, is_reversed, nodelist_loop) if token.contents == 'default':
nodelist_default = parser.parse(('endfor',))
parser.delete_first_token()
else:
nodelist_default = None
return ForNode(loopvars, sequence, is_reversed, nodelist_loop, nodelist_default)
do_for = register.tag("for", do_for) do_for = register.tag("for", do_for)
def do_ifequal(parser, token, negate): def do_ifequal(parser, token, negate):

View File

@ -234,6 +234,35 @@ The for loop sets a number of variables available within the loop:
current one current one
========================== ================================================ ========================== ================================================
for ... empty
^^^^^^^^^^^^^
.. versionadded:: 1.1
The ``for`` tag can take an optional ``{% empty %}`` clause that will be
displayed if the given array is empty or could not be found::
<ul>
{% for athlete in athlete_list %}
<li>{{ athlete.name }}</li>
{% empty %}
<li>Sorry, no athlete in this list!</li>
{% endfor %}
<ul>
The above is equivalent to -- but shorter, cleaner, and possibly faster
than -- the following::
<ul>
{% if althete_list %}
{% for athlete in athlete_list %}
<li>{{ athlete.name }}</li>
{% endfor %}
{% else %}
<li>Sorry, no athletes in this list.</li>
{% endif %}
</ul>
.. templatetag:: if .. templatetag:: if
if if

View File

@ -484,6 +484,9 @@ class Templates(unittest.TestCase):
'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-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-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/")), '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/")),
'for-tag-default01': ("{% for val in values %}{{ val }}{% default %}default text{% endfor %}", {"values": [1, 2, 3]}, "123"),
'for-tag-default02': ("{% for val in values %}{{ val }}{% default %}values array empty{% endfor %}", {"values": []}, "values array empty"),
'for-tag-default03': ("{% for val in values %}{{ val }}{% default %}values array not found{% endfor %}", {}, "values array not found"),
### IF TAG ################################################################ ### IF TAG ################################################################
'if-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"), 'if-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"),