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:
parent
14c159d21d
commit
e5cd46d6d1
|
@ -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 "<If node>"
|
||||
|
@ -171,6 +172,7 @@ class IfNode(Node):
|
|||
return nodes
|
||||
|
||||
def render(self, context):
|
||||
if self.link_type == IfNode.LinkTypes.or_:
|
||||
for ifnot, bool_expr in self.bool_exprs:
|
||||
try:
|
||||
value = bool_expr.resolve(context)
|
||||
|
@ -179,6 +181,19 @@ class IfNode(Node):
|
|||
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:
|
||||
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
|
||||
|
|
|
@ -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 %}
|
||||
|
||||
|
|
|
@ -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'),
|
||||
|
|
Loading…
Reference in New Issue