diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index 18f1b9ab30..364a4608eb 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -149,9 +149,10 @@ class IfEqualNode(Node): 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 "" @@ -171,14 +172,28 @@ class IfNode(Node): 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 diff --git a/docs/templates.txt b/docs/templates.txt index c191b409f4..69251df8bb 100644 --- a/docs/templates.txt +++ b/docs/templates.txt @@ -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 %} -For simplicity, ``if`` tags do not allow ``and`` clauses; use nested ``if`` -tags instead:: + {% if athlete_list and not coach_list %} + There are some athletes and absolutely no coaches. + {% endif %} + +``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 %} diff --git a/tests/othertests/templates.py b/tests/othertests/templates.py index ed7105bb71..37495caf54 100644 --- a/tests/othertests/templates.py +++ b/tests/othertests/templates.py @@ -193,13 +193,11 @@ TEMPLATE_TESTS = { '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 @@ TEMPLATE_TESTS = { '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 @@ TEMPLATE_TESTS = { """), ### REGROUP TAG ########################################################### - #'regroupXX': ('', {}, ''), 'regroup01': ('{% regroup data by bar as grouped %}' + \ '{% for group in grouped %}' + \ '{{ group.grouper }}:' + \ @@ -414,7 +482,6 @@ TEMPLATE_TESTS = { {}, ''), ### TEMPLATETAG TAG ####################################################### - #'templatetagXX': ('', {}, ''), 'templatetag01': ('{% templatetag openblock %}', {}, '{%'), 'templatetag02': ('{% templatetag closeblock %}', {}, '%}'), 'templatetag03': ('{% templatetag openvariable %}', {}, '{{'), @@ -423,7 +490,6 @@ TEMPLATE_TESTS = { '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 @@ TEMPLATE_TESTS = { '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)),