Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fixed #2026 -- Added support for 'and' in template 'if' tags, added d…

…ozens of unit tests and updated docs. Thanks, ckknight

git-svn-id: http://code.djangoproject.com/svn/django/trunk@3108 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit e5cd46d6d1ecf9a388d5b2b9e33f815803fcd1c4 1 parent 14c159d
Adrian Holovaty adrianholovaty authored
50 django/template/defaulttags.py
View
@@ -149,9 +149,10 @@ def render(self, context):
return self.nodelist_false.render(context)
class IfNode(Node):
- def __init__(self, bool_exprs, nodelist_true, nodelist_false):
+ def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
self.bool_exprs = bool_exprs
self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
+ self.link_type = link_type
def __repr__(self):
return "<If node>"
@@ -171,14 +172,28 @@ def get_nodes_by_type(self, nodetype):
return nodes
def render(self, context):
- for ifnot, bool_expr in self.bool_exprs:
- try:
- value = bool_expr.resolve(context)
- except VariableDoesNotExist:
- value = None
- if (value and not ifnot) or (ifnot and not value):
- return self.nodelist_true.render(context)
- return self.nodelist_false.render(context)
+ if self.link_type == IfNode.LinkTypes.or_:
+ for ifnot, bool_expr in self.bool_exprs:
+ try:
+ value = bool_expr.resolve(context)
+ except VariableDoesNotExist:
+ value = None
+ if (value and not ifnot) or (ifnot and not value):
+ return self.nodelist_true.render(context)
+ return self.nodelist_false.render(context)
+ else:
+ for ifnot, bool_expr in self.bool_exprs:
+ try:
+ value = bool_expr.resolve(context)
+ except VariableDoesNotExist:
+ value = None
+ if not ((value and not ifnot) or (ifnot and not value)):
+ return self.nodelist_false.render(context)
+ return self.nodelist_true.render(context)
+
+ class LinkTypes:
+ and_ = 0,
+ or_ = 1
class RegroupNode(Node):
def __init__(self, target, expression, var_name):
@@ -561,11 +576,22 @@ def do_if(parser, token):
if not bits:
raise TemplateSyntaxError, "'if' statement requires at least one argument"
# bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
- boolpairs = ' '.join(bits).split(' or ')
+ bitstr = ' '.join(bits)
+ boolpairs = bitstr.split(' and ')
boolvars = []
+ if len(boolpairs) == 1:
+ link_type = IfNode.LinkTypes.or_
+ boolpairs = bitstr.split(' or ')
+ else:
+ link_type = IfNode.LinkTypes.and_
+ if ' or ' in bitstr:
+ raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'"
for boolpair in boolpairs:
if ' ' in boolpair:
- not_, boolvar = boolpair.split()
+ try:
+ not_, boolvar = boolpair.split()
+ except ValueError:
+ raise TemplateSyntaxError, "'if' statement improperly formatted"
if not_ != 'not':
raise TemplateSyntaxError, "Expected 'not' in if statement"
boolvars.append((True, parser.compile_filter(boolvar)))
@@ -578,7 +604,7 @@ def do_if(parser, token):
parser.delete_first_token()
else:
nodelist_false = NodeList()
- return IfNode(boolvars, nodelist_true, nodelist_false)
+ return IfNode(boolvars, nodelist_true, nodelist_false, link_type)
do_if = register.tag("if", do_if)
#@register.tag
28 docs/templates.txt
View
@@ -454,8 +454,12 @@ displayed by the ``{{ athlete_list|length }}`` variable.
As you can see, the ``if`` tag can take an option ``{% else %}`` clause that
will be displayed if the test fails.
-``if`` tags may use ``or`` or ``not`` to test a number of variables or to negate
-a given variable::
+``if`` tags may use ``and``, ``or`` or ``not`` to test a number of variables or
+to negate a given variable::
+
+ {% if athlete_list and coach_list %}
+ Both athletes and coaches are available.
+ {% endif %}
{% if not athlete_list %}
There are no athletes.
@@ -468,16 +472,24 @@ a given variable::
{% if not athlete_list or coach_list %}
There are no athletes or there are some coaches (OK, so
writing English translations of boolean logic sounds
- stupid; it's not my fault).
+ stupid; it's not our fault).
+ {% endif %}
+
+ {% if athlete_list and not coach_list %}
+ There are some athletes and absolutely no coaches.
{% endif %}
-For simplicity, ``if`` tags do not allow ``and`` clauses; use nested ``if``
-tags instead::
+``if`` tags don't allow ``and`` and ``or`` clauses within the same tag, because
+the order of logic would be ambiguous. For example, this is invalid::
+
+ {% if athlete_list and coach_list or cheerleader_list %}
+
+If you need to combine ``and`` and ``or`` to do advanced logic, just use nested
+``if`` tags. For example::
{% if athlete_list %}
- {% if coach_list %}
- Number of athletes: {{ athlete_list|length }}.
- Number of coaches: {{ coach_list|length }}.
+ {% if coach_list or cheerleader_list %}
+ We have athletes, and either coaches or cheerleaders!
{% endif %}
{% endif %}
82 tests/othertests/templates.py
View
@@ -193,13 +193,11 @@ def method(self):
'exception04': ("{% extends 'inheritance17' %}{% block first %}{% echo 400 %}5678{% endblock %}", {}, template.TemplateSyntaxError),
### FILTER TAG ############################################################
- #'filterXX': ('', {}, ''),
'filter01': ('{% filter upper %}{% endfilter %}', {}, ''),
'filter02': ('{% filter upper %}django{% endfilter %}', {}, 'DJANGO'),
'filter03': ('{% filter upper|lower %}django{% endfilter %}', {}, 'django'),
### FIRSTOF TAG ###########################################################
- #'firstofXX': ('', {}, ''),
'firstof01': ('{% firstof a b c %}', {'a':0,'b':0,'c':0}, ''),
'firstof02': ('{% firstof a b c %}', {'a':1,'b':0,'c':0}, '1'),
'firstof03': ('{% firstof a b c %}', {'a':0,'b':2,'c':0}, '2'),
@@ -220,8 +218,79 @@ def method(self):
'if-tag02': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": False}, "no"),
'if-tag03': ("{% if foo %}yes{% else %}no{% endif %}", {}, "no"),
+ # AND
+ 'if-tag-and01': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'),
+ 'if-tag-and02': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'),
+ 'if-tag-and03': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'no'),
+ 'if-tag-and04': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'no'),
+ 'if-tag-and05': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': False}, 'no'),
+ 'if-tag-and06': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'bar': False}, 'no'),
+ 'if-tag-and07': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': True}, 'no'),
+ 'if-tag-and08': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'bar': True}, 'no'),
+
+ # OR
+ 'if-tag-or01': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'),
+ 'if-tag-or02': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'yes'),
+ 'if-tag-or03': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'),
+ 'if-tag-or04': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'no'),
+ 'if-tag-or05': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': False}, 'no'),
+ 'if-tag-or06': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'bar': False}, 'no'),
+ 'if-tag-or07': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': True}, 'yes'),
+ 'if-tag-or08': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'bar': True}, 'yes'),
+
+ # TODO: multiple ORs
+
+ # NOT
+ 'if-tag-not01': ("{% if not foo %}no{% else %}yes{% endif %}", {'foo': True}, 'yes'),
+ 'if-tag-not02': ("{% if not %}yes{% else %}no{% endif %}", {'foo': True}, 'no'),
+ 'if-tag-not03': ("{% if not %}yes{% else %}no{% endif %}", {'not': True}, 'yes'),
+ 'if-tag-not04': ("{% if not not %}no{% else %}yes{% endif %}", {'not': True}, 'yes'),
+ 'if-tag-not05': ("{% if not not %}no{% else %}yes{% endif %}", {}, 'no'),
+
+ 'if-tag-not06': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {}, 'no'),
+ 'if-tag-not07': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'),
+ 'if-tag-not08': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'yes'),
+ 'if-tag-not09': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'no'),
+ 'if-tag-not10': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'no'),
+
+ 'if-tag-not11': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {}, 'no'),
+ 'if-tag-not12': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'),
+ 'if-tag-not13': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'),
+ 'if-tag-not14': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'),
+ 'if-tag-not15': ("{% if not foo and bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'no'),
+
+ 'if-tag-not16': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {}, 'yes'),
+ 'if-tag-not17': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'),
+ 'if-tag-not18': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'yes'),
+ 'if-tag-not19': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'no'),
+ 'if-tag-not20': ("{% if foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'),
+
+ 'if-tag-not21': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {}, 'yes'),
+ 'if-tag-not22': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'),
+ 'if-tag-not23': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'),
+ 'if-tag-not24': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'),
+ 'if-tag-not25': ("{% if not foo or bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'),
+
+ 'if-tag-not26': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {}, 'yes'),
+ 'if-tag-not27': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'),
+ 'if-tag-not28': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'),
+ 'if-tag-not29': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'no'),
+ 'if-tag-not30': ("{% if not foo and not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'),
+
+ 'if-tag-not31': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {}, 'yes'),
+ 'if-tag-not32': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'),
+ 'if-tag-not33': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'yes'),
+ 'if-tag-not34': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'),
+ 'if-tag-not35': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'),
+
+ # AND and OR raises a TemplateSyntaxError
+ 'if-tag-error01': ("{% if foo or bar and baz %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, template.TemplateSyntaxError),
+ 'if-tag-error02': ("{% if foo and %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
+ 'if-tag-error03': ("{% if foo or %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
+ 'if-tag-error04': ("{% if not foo and %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
+ 'if-tag-error05': ("{% if not foo or %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
+
### IFCHANGED TAG #########################################################
- #'ifchangedXX': ('', {}, ''),
'ifchanged01': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,2,3) }, '123'),
'ifchanged02': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,1,3) }, '13'),
'ifchanged03': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,1,1) }, '1'),
@@ -388,7 +457,6 @@ def method(self):
"""),
### REGROUP TAG ###########################################################
- #'regroupXX': ('', {}, ''),
'regroup01': ('{% regroup data by bar as grouped %}' + \
'{% for group in grouped %}' + \
'{{ group.grouper }}:' + \
@@ -414,7 +482,6 @@ def method(self):
{}, ''),
### TEMPLATETAG TAG #######################################################
- #'templatetagXX': ('', {}, ''),
'templatetag01': ('{% templatetag openblock %}', {}, '{%'),
'templatetag02': ('{% templatetag closeblock %}', {}, '%}'),
'templatetag03': ('{% templatetag openvariable %}', {}, '{{'),
@@ -423,7 +490,6 @@ def method(self):
'templatetag06': ('{% templatetag foo %}', {}, template.TemplateSyntaxError),
### WIDTHRATIO TAG ########################################################
- #'widthratioXX': ('', {}, ''),
'widthratio01': ('{% widthratio a b 0 %}', {'a':50,'b':100}, '0'),
'widthratio02': ('{% widthratio a b 100 %}', {'a':0,'b':0}, ''),
'widthratio03': ('{% widthratio a b 100 %}', {'a':0,'b':100}, '0'),
@@ -440,11 +506,11 @@ def method(self):
'widthratio08': ('{% widthratio %}', {}, template.TemplateSyntaxError),
'widthratio09': ('{% widthratio a b %}', {'a':50,'b':100}, template.TemplateSyntaxError),
'widthratio10': ('{% widthratio a b 100.0 %}', {'a':50,'b':100}, template.TemplateSyntaxError),
-
+
### NOW TAG ########################################################
# Simple case
'now01' : ('{% now "j n Y"%}', {}, str(datetime.now().day) + ' ' + str(datetime.now().month) + ' ' + str(datetime.now().year)),
-
+
# Check parsing of escaped and special characters
'now02' : ('{% now "j "n" Y"%}', {}, template.TemplateSyntaxError),
# 'now03' : ('{% now "j \"n\" Y"%}', {}, str(datetime.now().day) + '"' + str(datetime.now().month) + '"' + str(datetime.now().year)),
Please sign in to comment.
Something went wrong with that request. Please try again.