diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
index 8182cd4d8c..06ad5c635a 100644
--- a/django/template/defaulttags.py
+++ b/django/template/defaulttags.py
@@ -83,10 +83,14 @@ class FirstOfNode(Node):
return u''
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.is_reversed = is_reversed
self.nodelist_loop = nodelist_loop
+ if nodelist_empty is None:
+ self.nodelist_empty = NodeList()
+ else:
+ self.nodelist_empty = nodelist_empty
def __repr__(self):
reversed_text = self.is_reversed and ' reversed' or ''
@@ -97,16 +101,18 @@ class ForNode(Node):
def __iter__(self):
for node in self.nodelist_loop:
yield node
+ for node in self.nodelist_empty:
+ yield node
def get_nodes_by_type(self, nodetype):
nodes = []
if isinstance(self, nodetype):
nodes.append(self)
nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))
+ nodes.extend(self.nodelist_empty.get_nodes_by_type(nodetype))
return nodes
def render(self, context):
- nodelist = NodeList()
if 'forloop' in context:
parentloop = context['forloop']
else:
@@ -121,6 +127,9 @@ class ForNode(Node):
if not hasattr(values, '__len__'):
values = list(values)
len_values = len(values)
+ if len_values < 1:
+ return self.nodelist_empty.render(context)
+ nodelist = NodeList()
if self.is_reversed:
values = reversed(values)
unpack = len(self.loopvars) > 1
@@ -610,6 +619,30 @@ def do_for(parser, token):
{{ key }}: {{ value }}
{% 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::
+
+
+ {% for athlete in athlete_list %}
+ - {{ athlete.name }}
+ {% empty %}
+ - Sorry, no athletes in this list.
+ {% endfor %}
+
+
+ The above is equivalent to -- but shorter, cleaner, and possibly faster
+ than -- the following::
+
+
+ {% if althete_list %}
+ {% for athlete in athlete_list %}
+ - {{ athlete.name }}
+ {% endfor %}
+ {% else %}
+ - Sorry, no athletes in this list.
+ {% endif %}
+
+
The for loop sets a number of variables available within the loop:
========================== ================================================
@@ -646,9 +679,14 @@ def do_for(parser, token):
" %s" % token.contents)
sequence = parser.compile_filter(bits[in_index+1])
- nodelist_loop = parser.parse(('endfor',))
- parser.delete_first_token()
- return ForNode(loopvars, sequence, is_reversed, nodelist_loop)
+ nodelist_loop = parser.parse(('default', 'endfor',))
+ token = parser.next_token()
+ 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)
def do_ifequal(parser, token, negate):
diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt
index a14f50adf0..f67f1a86cd 100644
--- a/docs/ref/templates/builtins.txt
+++ b/docs/ref/templates/builtins.txt
@@ -234,6 +234,35 @@ The for loop sets a number of variables available within the loop:
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::
+
+
+ {% for athlete in athlete_list %}
+ - {{ athlete.name }}
+ {% empty %}
+ - Sorry, no athlete in this list!
+ {% endfor %}
+
+
+The above is equivalent to -- but shorter, cleaner, and possibly faster
+than -- the following::
+
+
+ {% if althete_list %}
+ {% for athlete in athlete_list %}
+ - {{ athlete.name }}
+ {% endfor %}
+ {% else %}
+ - Sorry, no athletes in this list.
+ {% endif %}
+
+
.. templatetag:: if
if
diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
index cdf56ab56c..6e29666454 100644
--- a/tests/regressiontests/templates/tests.py
+++ b/tests/regressiontests/templates/tests.py
@@ -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-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-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-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"),