Fixed #2026 -- Added support for 'and' in template 'if' tags, added dozens of unit tests and updated docs. Thanks, ckknight

git-svn-id: http://code.djangoproject.com/svn/django/trunk@3108 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Adrian Holovaty 2006-06-08 03:33:21 +00:00
parent 14c159d21d
commit e5cd46d6d1
3 changed files with 132 additions and 28 deletions

View File

@ -149,9 +149,10 @@ class IfEqualNode(Node):
return self.nodelist_false.render(context) return self.nodelist_false.render(context)
class IfNode(Node): 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.bool_exprs = bool_exprs
self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
self.link_type = link_type
def __repr__(self): def __repr__(self):
return "<If node>" return "<If node>"
@ -171,14 +172,28 @@ class IfNode(Node):
return nodes return nodes
def render(self, context): def render(self, context):
for ifnot, bool_expr in self.bool_exprs: if self.link_type == IfNode.LinkTypes.or_:
try: for ifnot, bool_expr in self.bool_exprs:
value = bool_expr.resolve(context) try:
except VariableDoesNotExist: value = bool_expr.resolve(context)
value = None except VariableDoesNotExist:
if (value and not ifnot) or (ifnot and not value): value = None
return self.nodelist_true.render(context) if (value and not ifnot) or (ifnot and not value):
return self.nodelist_false.render(context) 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): class RegroupNode(Node):
def __init__(self, target, expression, var_name): def __init__(self, target, expression, var_name):
@ -561,11 +576,22 @@ def do_if(parser, token):
if not bits: if not bits:
raise TemplateSyntaxError, "'if' statement requires at least one argument" raise TemplateSyntaxError, "'if' statement requires at least one argument"
# bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d'] # 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 = [] 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: for boolpair in boolpairs:
if ' ' in boolpair: if ' ' in boolpair:
not_, boolvar = boolpair.split() try:
not_, boolvar = boolpair.split()
except ValueError:
raise TemplateSyntaxError, "'if' statement improperly formatted"
if not_ != 'not': if not_ != 'not':
raise TemplateSyntaxError, "Expected 'not' in if statement" raise TemplateSyntaxError, "Expected 'not' in if statement"
boolvars.append((True, parser.compile_filter(boolvar))) boolvars.append((True, parser.compile_filter(boolvar)))
@ -578,7 +604,7 @@ def do_if(parser, token):
parser.delete_first_token() parser.delete_first_token()
else: else:
nodelist_false = NodeList() 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) do_if = register.tag("if", do_if)
#@register.tag #@register.tag

View File

@ -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 As you can see, the ``if`` tag can take an option ``{% else %}`` clause that
will be displayed if the test fails. will be displayed if the test fails.
``if`` tags may use ``or`` or ``not`` to test a number of variables or to negate ``if`` tags may use ``and``, ``or`` or ``not`` to test a number of variables or
a given variable:: to negate a given variable::
{% if athlete_list and coach_list %}
Both athletes and coaches are available.
{% endif %}
{% if not athlete_list %} {% if not athlete_list %}
There are no athletes. There are no athletes.
@ -468,16 +472,24 @@ a given variable::
{% if not athlete_list or coach_list %} {% if not athlete_list or coach_list %}
There are no athletes or there are some coaches (OK, so There are no athletes or there are some coaches (OK, so
writing English translations of boolean logic sounds writing English translations of boolean logic sounds
stupid; it's not my fault). stupid; it's not our fault).
{% endif %} {% endif %}
For simplicity, ``if`` tags do not allow ``and`` clauses; use nested ``if`` {% if athlete_list and not coach_list %}
tags instead:: 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 athlete_list %}
{% if coach_list %} {% if coach_list or cheerleader_list %}
Number of athletes: {{ athlete_list|length }}. We have athletes, and either coaches or cheerleaders!
Number of coaches: {{ coach_list|length }}.
{% endif %} {% endif %}
{% endif %} {% endif %}

View File

@ -193,13 +193,11 @@ TEMPLATE_TESTS = {
'exception04': ("{% extends 'inheritance17' %}{% block first %}{% echo 400 %}5678{% endblock %}", {}, template.TemplateSyntaxError), 'exception04': ("{% extends 'inheritance17' %}{% block first %}{% echo 400 %}5678{% endblock %}", {}, template.TemplateSyntaxError),
### FILTER TAG ############################################################ ### FILTER TAG ############################################################
#'filterXX': ('', {}, ''),
'filter01': ('{% filter upper %}{% endfilter %}', {}, ''), 'filter01': ('{% filter upper %}{% endfilter %}', {}, ''),
'filter02': ('{% filter upper %}django{% endfilter %}', {}, 'DJANGO'), 'filter02': ('{% filter upper %}django{% endfilter %}', {}, 'DJANGO'),
'filter03': ('{% filter upper|lower %}django{% endfilter %}', {}, 'django'), 'filter03': ('{% filter upper|lower %}django{% endfilter %}', {}, 'django'),
### FIRSTOF TAG ########################################################### ### FIRSTOF TAG ###########################################################
#'firstofXX': ('', {}, ''),
'firstof01': ('{% firstof a b c %}', {'a':0,'b':0,'c':0}, ''), 'firstof01': ('{% firstof a b c %}', {'a':0,'b':0,'c':0}, ''),
'firstof02': ('{% firstof a b c %}', {'a':1,'b':0,'c':0}, '1'), 'firstof02': ('{% firstof a b c %}', {'a':1,'b':0,'c':0}, '1'),
'firstof03': ('{% firstof a b c %}', {'a':0,'b':2,'c':0}, '2'), '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-tag02': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": False}, "no"),
'if-tag03': ("{% if foo %}yes{% else %}no{% endif %}", {}, "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 ######################################################### ### IFCHANGED TAG #########################################################
#'ifchangedXX': ('', {}, ''),
'ifchanged01': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,2,3) }, '123'), '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'), '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'), 'ifchanged03': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', { 'num': (1,1,1) }, '1'),
@ -388,7 +457,6 @@ TEMPLATE_TESTS = {
"""), """),
### REGROUP TAG ########################################################### ### REGROUP TAG ###########################################################
#'regroupXX': ('', {}, ''),
'regroup01': ('{% regroup data by bar as grouped %}' + \ 'regroup01': ('{% regroup data by bar as grouped %}' + \
'{% for group in grouped %}' + \ '{% for group in grouped %}' + \
'{{ group.grouper }}:' + \ '{{ group.grouper }}:' + \
@ -414,7 +482,6 @@ TEMPLATE_TESTS = {
{}, ''), {}, ''),
### TEMPLATETAG TAG ####################################################### ### TEMPLATETAG TAG #######################################################
#'templatetagXX': ('', {}, ''),
'templatetag01': ('{% templatetag openblock %}', {}, '{%'), 'templatetag01': ('{% templatetag openblock %}', {}, '{%'),
'templatetag02': ('{% templatetag closeblock %}', {}, '%}'), 'templatetag02': ('{% templatetag closeblock %}', {}, '%}'),
'templatetag03': ('{% templatetag openvariable %}', {}, '{{'), 'templatetag03': ('{% templatetag openvariable %}', {}, '{{'),
@ -423,7 +490,6 @@ TEMPLATE_TESTS = {
'templatetag06': ('{% templatetag foo %}', {}, template.TemplateSyntaxError), 'templatetag06': ('{% templatetag foo %}', {}, template.TemplateSyntaxError),
### WIDTHRATIO TAG ######################################################## ### WIDTHRATIO TAG ########################################################
#'widthratioXX': ('', {}, ''),
'widthratio01': ('{% widthratio a b 0 %}', {'a':50,'b':100}, '0'), 'widthratio01': ('{% widthratio a b 0 %}', {'a':50,'b':100}, '0'),
'widthratio02': ('{% widthratio a b 100 %}', {'a':0,'b':0}, ''), 'widthratio02': ('{% widthratio a b 100 %}', {'a':0,'b':0}, ''),
'widthratio03': ('{% widthratio a b 100 %}', {'a':0,'b':100}, '0'), 'widthratio03': ('{% widthratio a b 100 %}', {'a':0,'b':100}, '0'),
@ -440,11 +506,11 @@ TEMPLATE_TESTS = {
'widthratio08': ('{% widthratio %}', {}, template.TemplateSyntaxError), 'widthratio08': ('{% widthratio %}', {}, template.TemplateSyntaxError),
'widthratio09': ('{% widthratio a b %}', {'a':50,'b':100}, template.TemplateSyntaxError), 'widthratio09': ('{% widthratio a b %}', {'a':50,'b':100}, template.TemplateSyntaxError),
'widthratio10': ('{% widthratio a b 100.0 %}', {'a':50,'b':100}, template.TemplateSyntaxError), 'widthratio10': ('{% widthratio a b 100.0 %}', {'a':50,'b':100}, template.TemplateSyntaxError),
### NOW TAG ######################################################## ### NOW TAG ########################################################
# Simple case # Simple case
'now01' : ('{% now "j n Y"%}', {}, str(datetime.now().day) + ' ' + str(datetime.now().month) + ' ' + str(datetime.now().year)), 'now01' : ('{% now "j n Y"%}', {}, str(datetime.now().day) + ' ' + str(datetime.now().month) + ' ' + str(datetime.now().year)),
# Check parsing of escaped and special characters # Check parsing of escaped and special characters
'now02' : ('{% now "j "n" Y"%}', {}, template.TemplateSyntaxError), 'now02' : ('{% now "j "n" Y"%}', {}, template.TemplateSyntaxError),
# 'now03' : ('{% now "j \"n\" Y"%}', {}, str(datetime.now().day) + '"' + str(datetime.now().month) + '"' + str(datetime.now().year)), # 'now03' : ('{% now "j \"n\" Y"%}', {}, str(datetime.now().day) + '"' + str(datetime.now().month) + '"' + str(datetime.now().year)),