Commit e5cd46d6 authored by Adrian Holovaty's avatar Adrian Holovaty
Browse files

Fixed #2026 -- Added support for 'and' in template 'if' tags, added dozens of...

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
parent 14c159d2
Loading
Loading
Loading
Loading
+38 −12
Original line number Diff line number Diff line
@@ -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
+20 −8
Original line number Diff line number Diff line
@@ -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 %}

+74 −8
Original line number Diff line number Diff line
@@ -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'),