Author: lukeplant
Date: 2009-12-09 16:40:36 -0600 (Wed, 09 Dec 2009)
New Revision: 11806

Added:
   django/trunk/django/template/smartif.py
   django/trunk/tests/regressiontests/templates/smartif.py
Modified:
   django/trunk/django/template/defaulttags.py
   django/trunk/docs/ref/templates/builtins.txt
   django/trunk/docs/releases/1.2.txt
   django/trunk/docs/topics/templates.txt
   django/trunk/tests/regressiontests/templates/tests.py
Log:
Implemented 'smart if' template tag, allowing filters and various operators to 
be used in the 'if' tag

Thanks to Chris Beaven for the initial patch, Fredrik Lundh for the basis
of the parser methodology and Russell Keith-Magee for code reviews.

There are some BACKWARDS INCOMPATIBILITIES in rare cases - in particular, if
you were using the keywords 'and', 'or' or 'not' as variable names within
the 'if' expression, which was previously allowed in some cases.



Modified: django/trunk/django/template/defaulttags.py
===================================================================
--- django/trunk/django/template/defaulttags.py 2009-12-09 17:23:16 UTC (rev 
11805)
+++ django/trunk/django/template/defaulttags.py 2009-12-09 22:40:36 UTC (rev 
11806)
@@ -11,6 +11,7 @@
 from django.template import Node, NodeList, Template, Context, Variable
 from django.template import TemplateSyntaxError, VariableDoesNotExist, 
BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, 
SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END
 from django.template import get_library, Library, InvalidTemplateLibrary
+from django.template.smartif import IfParser, Literal
 from django.conf import settings
 from django.utils.encoding import smart_str, smart_unicode
 from django.utils.itercompat import groupby
@@ -227,10 +228,9 @@
         return self.nodelist_false.render(context)
 
 class IfNode(Node):
-    def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
-        self.bool_exprs = bool_exprs
+    def __init__(self, var, nodelist_true, nodelist_false=None):
         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
-        self.link_type = link_type
+        self.var = var
 
     def __repr__(self):
         return "<If node>"
@@ -250,29 +250,11 @@
         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, True)
-                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, True)
-                except VariableDoesNotExist:
-                    value = None
-                if not ((value and not ifnot) or (ifnot and not value)):
-                    return self.nodelist_false.render(context)
+        if self.var.eval(context):
             return self.nodelist_true.render(context)
+        else:
+            return self.nodelist_false.render(context)
 
-    class LinkTypes:
-        and_ = 0,
-        or_ = 1
-
 class RegroupNode(Node):
     def __init__(self, target, expression, var_name):
         self.target, self.expression = target, expression
@@ -761,6 +743,27 @@
     return do_ifequal(parser, token, True)
 ifnotequal = register.tag(ifnotequal)
 
+class TemplateLiteral(Literal):
+    def __init__(self, value, text):
+        self.value = value
+        self.text = text # for better error messages
+
+    def display(self):
+        return self.text
+
+    def eval(self, context):
+        return self.value.resolve(context, ignore_failures=True)
+
+class TemplateIfParser(IfParser):
+    error_class = TemplateSyntaxError
+
+    def __init__(self, parser, *args, **kwargs):
+        self.template_parser = parser
+        return super(TemplateIfParser, self).__init__(*args, **kwargs)
+
+    def create_var(self, value):
+        return TemplateLiteral(self.template_parser.compile_filter(value), 
value)
+
 #...@register.tag(name="if")
 def do_if(parser, token):
     """
@@ -805,47 +808,21 @@
             There are some athletes and absolutely no coaches.
         {% endif %}
 
-    ``if`` tags do not allow ``and`` and ``or`` clauses with the same tag,
-    because the order of logic would be ambigous. For example, this is
-    invalid::
+    Comparison operators are also available, and the use of filters is also
+    allowed, for example:
 
-        {% if athlete_list and coach_list or cheerleader_list %}
+        {% if articles|length >= 5 %}...{% endif %}
 
-    If you need to combine ``and`` and ``or`` to do advanced logic, just use
-    nested if tags. For example::
+    Arguments and operators _must_ have a space between them, so
+    ``{% if 1>2 %}`` is not a valid if tag.
 
-        {% if athlete_list %}
-            {% if coach_list or cheerleader_list %}
-                We have athletes, and either coaches or cheerleaders!
-            {% endif %}
-        {% endif %}
+    All supported operators are: ``or``, ``and``, ``in``, ``==`` (or ``=``),
+    ``!=``, ``>``, ``>=``, ``<`` and ``<=``.
+
+    Operator precedence follows Python.
     """
-    bits = token.contents.split()
-    del bits[0]
-    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']
-    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)))
-        else:
-            boolvars.append((False, parser.compile_filter(boolpair)))
+    bits = token.split_contents()[1:]
+    var = TemplateIfParser(parser, bits).parse()
     nodelist_true = parser.parse(('else', 'endif'))
     token = parser.next_token()
     if token.contents == 'else':
@@ -853,7 +830,7 @@
         parser.delete_first_token()
     else:
         nodelist_false = NodeList()
-    return IfNode(boolvars, nodelist_true, nodelist_false, link_type)
+    return IfNode(var, nodelist_true, nodelist_false)
 do_if = register.tag("if", do_if)
 
 #...@register.tag

Added: django/trunk/django/template/smartif.py
===================================================================
--- django/trunk/django/template/smartif.py                             (rev 0)
+++ django/trunk/django/template/smartif.py     2009-12-09 22:40:36 UTC (rev 
11806)
@@ -0,0 +1,192 @@
+"""
+Parser and utilities for the smart 'if' tag
+"""
+import operator
+
+# Using a simple top down parser, as described here:
+#    http://effbot.org/zone/simple-top-down-parsing.htm.
+# 'led' = left denotation
+# 'nud' = null denotation
+# 'bp' = binding power (left = lbp, right = rbp)
+
+class TokenBase(object):
+    """
+    Base class for operators and literals, mainly for debugging and for 
throwing
+    syntax errors.
+    """
+    id = None # node/token type name
+    value = None # used by literals
+    first = second = None # used by tree nodes
+
+    def nud(self, parser):
+        # Null denotation - called in prefix context
+        raise parser.error_class(
+            "Not expecting '%s' in this position in if tag." % self.id
+        )
+
+    def led(self, left, parser):
+        # Left denotation - called in infix context
+        raise parser.error_class(
+            "Not expecting '%s' as infix operator in if tag." % self.id
+        )
+
+    def display(self):
+        """
+        Returns what to display in error messages for this node
+        """
+        return self.id
+
+    def __repr__(self):
+        out = [str(x) for x in [self.id, self.first, self.second] if x is not 
None]
+        return "(" + " ".join(out) + ")"
+
+
+def infix(bp, func):
+    """
+    Creates an infix operator, given a binding power and a function that
+    evaluates the node
+    """
+    class Operator(TokenBase):
+        lbp = bp
+
+        def led(self, left, parser):
+            self.first = left
+            self.second = parser.expression(bp)
+            return self
+
+        def eval(self, context):
+            try:
+                return func(self.first.eval(context), 
self.second.eval(context))
+            except Exception:
+                # Templates shouldn't throw exceptions when rendering.  We are
+                # most likely to get exceptions for things like {% if foo in 
bar
+                # %} where 'bar' does not support 'in', so default to False
+                return False
+
+    return Operator
+
+
+def prefix(bp, func):
+    """
+    Creates a prefix operator, given a binding power and a function that
+    evaluates the node.
+    """
+    class Operator(TokenBase):
+        lbp = bp
+
+        def nud(self, parser):
+            self.first = parser.expression(bp)
+            self.second = None
+            return self
+
+        def eval(self, context):
+            try:
+                return func(self.first.eval(context))
+            except Exception:
+                return False
+
+    return Operator
+
+
+# Operator precedence follows Python.
+# NB - we can get slightly more accurate syntax error messages by not using the
+# same object for '==' and '='.
+
+OPERATORS = {
+    'or': infix(6, lambda x, y: x or y),
+    'and': infix(7, lambda x, y: x and y),
+    'not': prefix(8, operator.not_),
+    'in': infix(9, lambda x, y: x in y),
+    '=': infix(10, operator.eq),
+    '==': infix(10, operator.eq),
+    '!=': infix(10, operator.ne),
+    '>': infix(10, operator.gt),
+    '>=': infix(10, operator.ge),
+    '<': infix(10, operator.lt),
+    '<=': infix(10, operator.le),
+}
+
+# Assign 'id' to each:
+for key, op in OPERATORS.items():
+    op.id = key
+
+
+class Literal(TokenBase):
+    """
+    A basic self-resolvable object similar to a Django template variable.
+    """
+    # IfParser uses Literal in create_var, but TemplateIfParser overrides
+    # create_var so that a proper implementation that actually resolves
+    # variables, filters etc is used.
+    id = "literal"
+    lbp = 0
+
+    def __init__(self, value):
+        self.value = value
+
+    def display(self):
+        return repr(self.value)
+
+    def nud(self, parser):
+        return self
+
+    def eval(self, context):
+        return self.value
+
+    def __repr__(self):
+        return "(%s %r)" % (self.id, self.value)
+
+
+class EndToken(TokenBase):
+    lbp = 0
+
+    def nud(self, parser):
+        raise parser.error_class("Unexpected end of expression in if tag.")
+
+EndToken = EndToken()
+
+
+class IfParser(object):
+    error_class = ValueError
+
+    def __init__(self, tokens):
+        self.tokens = map(self.translate_tokens, tokens)
+        self.pos = 0
+        self.current_token = self.next()
+
+    def translate_tokens(self, token):
+        try:
+            op = OPERATORS[token]
+        except (KeyError, TypeError):
+            return self.create_var(token)
+        else:
+            return op()
+
+    def next(self):
+        if self.pos >= len(self.tokens):
+            return EndToken
+        else:
+            retval = self.tokens[self.pos]
+            self.pos += 1
+            return retval
+
+    def parse(self):
+        retval = self.expression()
+        # Check that we have exhausted all the tokens
+        if self.current_token is not EndToken:
+            raise self.error_class("Unused '%s' at end of if expression." %
+                                   self.current_token.display())
+        return retval
+
+    def expression(self, rbp=0):
+        t = self.current_token
+        self.current_token = self.next()
+        left = t.nud(self)
+        while rbp < self.current_token.lbp:
+            t = self.current_token
+            self.current_token = self.next()
+            left = t.led(left, self)
+        return left
+
+    def create_var(self, value):
+        return Literal(value)


Property changes on: django/trunk/django/template/smartif.py
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: django/trunk/docs/ref/templates/builtins.txt
===================================================================
--- django/trunk/docs/ref/templates/builtins.txt        2009-12-09 17:23:16 UTC 
(rev 11805)
+++ django/trunk/docs/ref/templates/builtins.txt        2009-12-09 22:40:36 UTC 
(rev 11806)
@@ -313,6 +313,9 @@
 As you can see, the ``if`` tag can take an optional ``{% else %}`` clause that
 will be displayed if the test fails.
 
+Boolean operators
+^^^^^^^^^^^^^^^^^
+
 ``if`` tags may use ``and``, ``or`` or ``not`` to test a number of variables or
 to negate a given variable::
 
@@ -338,25 +341,154 @@
         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::
+.. versionchanged:: 1.2
 
+Use of both ``and`` and ``or`` clauses within the same tag is allowed, with
+``and`` having higher precedence than ``or`` e.g.::
+
     {% 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::
+will be interpreted like:
 
-    {% if athlete_list %}
-        {% if coach_list or cheerleader_list %}
-            We have athletes, and either coaches or cheerleaders!
-        {% endif %}
+.. code-block:: python
+
+    if (athlete_list and coach_list) or cheerleader_list
+
+Use of actual brackets in the ``if`` tag is invalid syntax.  If you need them 
to
+indicate precedence, you should use nested ``if`` tags.
+
+.. versionadded:: 1.2
+
+
+``if`` tags may also use the operators ``==``, ``!=``, ``<``, ``>``,
+``<=``, ``>=`` and ``in`` which work as follows:
+
+
+``==`` operator
+^^^^^^^^^^^^^^^
+
+Equality. Example::
+
+    {% if somevar == "x" %}
+      This appears if variable somevar equals the string "x"
     {% endif %}
 
-Multiple uses of the same logical operator are fine, as long as you use the
-same operator. For example, this is valid::
+``!=`` operator
+^^^^^^^^^^^^^^^
 
-    {% if athlete_list or coach_list or parent_list or teacher_list %}
+Inequality. Example::
 
+    {% if somevar != "x" %}
+      This appears if variable somevar does not equal the string "x",
+      or if somevar is not found in the context
+    {% endif %}
+
+``<`` operator
+^^^^^^^^^^^^^^
+
+Less than. Example::
+
+    {% if somevar < 100 %}
+      This appears if variable somevar is less than 100.
+    {% endif %}
+
+``>`` operator
+^^^^^^^^^^^^^^
+
+Greater than. Example::
+
+    {% if somevar > 0 %}
+      This appears if variable somevar is greater than 0.
+    {% endif %}
+
+``<=`` operator
+^^^^^^^^^^^^^^^
+
+Less than or equal to. Example::
+
+    {% if somevar <= 100 %}
+      This appears if variable somevar is less than 100 or equal to 100.
+    {% endif %}
+
+``>=`` operator
+^^^^^^^^^^^^^^^
+
+Greater than or equal to. Example::
+
+    {% if somevar >= 1 %}
+      This appears if variable somevar is greater than 1 or equal to 1.
+    {% endif %}
+
+``in`` operator
+^^^^^^^^^^^^^^^
+
+Contained within. This operator is supported by many Python containers to test
+whether the given value is in the container.  The following are some examples 
of
+how ``x in y`` will be interpreted::
+
+    {% if "bc" in "abcdef" %}
+      This appears since "bc" is a substring of "abcdef"
+    {% endif %}
+
+    {% if "hello" in greetings %}
+      If greetings is a list or set, one element of which is the string
+      "hello", this will appear.
+    {% endif %}
+
+    {% if user in users %}
+      If users is a QuerySet, this will appear if user is an
+      instance that belongs to the QuerySet.
+    {% endif %}
+
+
+The comparison operators cannot be 'chained' like in Python or in mathematical
+notation. For example, instead of using::
+
+    {% if a > b > c %}  (WRONG)
+
+you should use::
+
+    {% if a > b and b > c %}
+
+
+Filters
+^^^^^^^
+
+You can also use filters in the ``if`` expression. For example::
+
+    {% if messages|length >= 100 %}
+       You have lots of messages today!
+    {% endif %}
+
+Complex expressions
+^^^^^^^^^^^^^^^^^^^
+
+All of the above can be combined to form complex expressions. For such
+expressions, it can be important to know how the operators are grouped when the
+expression is evaluated - that is, the precedence rules.  The precedence of the
+operators, from lowest to highest, is as follows:
+
+ * ``or``
+ * ``and``
+ * ``not``
+ * ``in``
+ * ``==``, ``!=``, ``<``, ``>``,``<=``, ``>=``
+
+(This follows Python exactly). So, for example, the following complex if tag:
+
+    {% if a == b or c == d and e %}
+
+...will be interpreted as:
+
+.. code-block:: python
+
+    (a == b) or ((c == d) and e)
+
+If you need different precedence, you will need to use nested if tags. 
Sometimes
+that is better for clarity anyway, for the sake of those who do not know the
+precedence rules.
+
+
 .. templatetag:: ifchanged
 
 ifchanged
@@ -427,6 +559,9 @@
 ``False``.  If you need to test if something is true or false, use the ``if``
 tag instead.
 
+.. versionadded:: 1.2
+   An alternative to the ``ifequal`` tag is to use the :ttag:`if` tag and the 
``==`` operator.
+
 .. templatetag:: ifnotequal
 
 ifnotequal
@@ -434,6 +569,9 @@
 
 Just like ``ifequal``, except it tests that the two arguments are not equal.
 
+.. versionadded:: 1.2
+   An alternative to the ``ifnotequal`` tag is to use the :ttag:`if` tag and 
the ``!=`` operator.
+
 .. templatetag:: include
 
 include

Modified: django/trunk/docs/releases/1.2.txt
===================================================================
--- django/trunk/docs/releases/1.2.txt  2009-12-09 17:23:16 UTC (rev 11805)
+++ django/trunk/docs/releases/1.2.txt  2009-12-09 22:40:36 UTC (rev 11806)
@@ -42,6 +42,15 @@
  * All of the CSRF has moved from contrib to core (with backwards compatible
    imports in the old locations, which are deprecated).
 
+:ttag:`if` tag changes
+----------------------
+
+Due to new features in the :ttag:`if` template tag, it no longer accepts 'and',
+'or' and 'not' as valid **variable** names.  Previously that worked in some
+cases even though these strings were normally treated as keywords.  Now, the
+keyword status is always enforced, and template code like ``{% if not %}`` or
+``{% if and %}`` will throw a TemplateSyntaxError.
+
 ``LazyObject``
 --------------
 
@@ -196,3 +205,37 @@
 replaces the deprecated user message API and allows you to temporarily store
 messages in one request and retrieve them for display in a subsequent request
 (usually the next one).
+
+'Smart' if tag
+--------------
+
+The :ttag:`if` tag has been upgraded to be much more powerful.  First, support
+for comparison operators has been added. No longer will you have to type:
+
+.. code-block:: html+django
+
+    {% ifnotequal a b %}
+     ...
+    {% endifnotequal %}
+
+...as you can now do:
+
+.. code-block:: html+django
+
+    {% if a != b %}
+     ...
+    {% endif %}
+
+The operators supported are ``==``, ``!=``, ``<``, ``>``, ``<=``, ``>=`` and
+``in``, all of which work like the Python operators, in addition to ``and``,
+``or`` and ``not`` which were already supported.
+
+Also, filters may now be used in the ``if`` expression. For example:
+
+.. code-block:: html+django
+
+      <div
+        {% if user.email|lower == message.recipient|lower %}
+          class="highlight"
+        {% endif %}
+      >{{ message }}</div>

Modified: django/trunk/docs/topics/templates.txt
===================================================================
--- django/trunk/docs/topics/templates.txt      2009-12-09 17:23:16 UTC (rev 
11805)
+++ django/trunk/docs/topics/templates.txt      2009-12-09 22:40:36 UTC (rev 
11806)
@@ -187,8 +187,8 @@
                 <li>{{ athlete.name }}</li>
             {% endfor %}
             </ul>
-        
-    :ttag:`if` and :ttag:`else`
+
+    :ttag:`if` and ``else``
         Evaluates a variable, and if that variable is "true" the contents of 
the
         block are displayed::
 
@@ -200,20 +200,15 @@
 
         In the above, if ``athlete_list`` is not empty, the number of athletes
         will be displayed by the ``{{ athlete_list|length }}`` variable.
-        
-    :ttag:`ifequal` and :ttag:`ifnotequal`
-        Display some contents if two arguments are or are not equal. For 
example::
 
-            {% ifequal athlete.name coach.name %}
-                ...
-            {% endifequal %}
+        You can also use filters and various operators in the ``if`` tag::
 
-        Or::
+            {% if athlete_list|length > 1 %}
+               Team: {% for athlete in athlete_list %} ... {% endfor %}
+            {% else %}
+               Athlete: {{ athlete_list.0.name }}
+            {% endif %}
 
-            {% ifnotequal athlete.name "Joe" %}
-                ...
-            {% endifnotequal %}
-    
     :ttag:`block` and :ttag:`extends`
         Set up `template inheritance`_ (see below), a powerful way
         of cutting down on "boilerplate" in templates.

Added: django/trunk/tests/regressiontests/templates/smartif.py
===================================================================
--- django/trunk/tests/regressiontests/templates/smartif.py                     
        (rev 0)
+++ django/trunk/tests/regressiontests/templates/smartif.py     2009-12-09 
22:40:36 UTC (rev 11806)
@@ -0,0 +1,46 @@
+import unittest
+from django.template.smartif import IfParser, Literal
+
+class SmartIfTests(unittest.TestCase):
+
+    def assertCalcEqual(self, expected, tokens):
+        self.assertEqual(expected, IfParser(tokens).parse().eval({}))
+
+    # We only test things here that are difficult to test elsewhere
+    # Many other tests are found in the main tests for builtin template tags
+    # Test parsing via the printed parse tree
+    def test_not(self):
+        var = IfParser(["not", False]).parse()
+        self.assertEqual("(not (literal False))", repr(var))
+        self.assert_(var.eval({}))
+
+        self.assertFalse(IfParser(["not", True]).parse().eval({}))
+
+    def test_or(self):
+        var = IfParser([True, "or", False]).parse()
+        self.assertEqual("(or (literal True) (literal False))", repr(var))
+        self.assert_(var.eval({}))
+
+    def test_in(self):
+        list_ = [1,2,3]
+        self.assertCalcEqual(True, [1, 'in', list_])
+        self.assertCalcEqual(False, [1, 'in', None])
+        self.assertCalcEqual(False, [None, 'in', list_])
+
+    def test_precedence(self):
+        # (False and False) or True == True   <- we want this one, like Python
+        # False and (False or True) == False
+        self.assertCalcEqual(True, [False, 'and', False, 'or', True])
+
+        # True or (False and False) == True   <- we want this one, like Python
+        # (True or False) and False == False
+        self.assertCalcEqual(True, [True, 'or', False, 'and', False])
+
+        # (1 or 1) == 2  -> False
+        # 1 or (1 == 2)  -> True   <- we want this one
+        self.assertCalcEqual(True, [1, 'or', 1, '==', 2])
+
+        self.assertCalcEqual(True, [True, '==', True, 'or', True, '==', False])
+
+        self.assertEqual("(or (and (== (literal 1) (literal 2)) (literal 3)) 
(literal 4))",
+                         repr(IfParser([1, '==', 2, 'and', 3, 'or', 
4]).parse()))


Property changes on: django/trunk/tests/regressiontests/templates/smartif.py
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: django/trunk/tests/regressiontests/templates/tests.py
===================================================================
--- django/trunk/tests/regressiontests/templates/tests.py       2009-12-09 
17:23:16 UTC (rev 11805)
+++ django/trunk/tests/regressiontests/templates/tests.py       2009-12-09 
22:40:36 UTC (rev 11806)
@@ -24,6 +24,7 @@
 from custom import custom_filters
 from parser import filter_parsing, variable_parsing
 from unicode import unicode_tests
+from smartif import *
 
 try:
     from loaders import *
@@ -534,6 +535,27 @@
             'if-tag02': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": 
False}, "no"),
             'if-tag03': ("{% if foo %}yes{% else %}no{% endif %}", {}, "no"),
 
+            # Filters
+            'if-tag-filter01': ("{% if foo|length == 5 %}yes{% else %}no{% 
endif %}", {'foo': 'abcde'}, "yes"),
+            'if-tag-filter02': ("{% if foo|upper == 'ABC' %}yes{% else %}no{% 
endif %}", {}, "no"),
+
+            # Equality
+            'if-tag-eq01': ("{% if foo == bar %}yes{% else %}no{% endif %}", 
{}, "yes"),
+            'if-tag-eq02': ("{% if foo == bar %}yes{% else %}no{% endif %}", 
{'foo': 1}, "no"),
+            'if-tag-eq03': ("{% if foo == bar %}yes{% else %}no{% endif %}", 
{'foo': 1, 'bar': 1}, "yes"),
+            'if-tag-eq04': ("{% if foo == bar %}yes{% else %}no{% endif %}", 
{'foo': 1, 'bar': 2}, "no"),
+            'if-tag-eq05': ("{% if foo == '' %}yes{% else %}no{% endif %}", 
{}, "no"),
+
+            # Comparison
+            'if-tag-gt-01': ("{% if 2 > 1 %}yes{% else %}no{% endif %}", {}, 
"yes"),
+            'if-tag-gt-02': ("{% if 1 > 1 %}yes{% else %}no{% endif %}", {}, 
"no"),
+            'if-tag-gte-01': ("{% if 1 >= 1 %}yes{% else %}no{% endif %}", {}, 
"yes"),
+            'if-tag-gte-02': ("{% if 1 >= 2 %}yes{% else %}no{% endif %}", {}, 
"no"),
+            'if-tag-lt-01': ("{% if 1 < 2 %}yes{% else %}no{% endif %}", {}, 
"yes"),
+            'if-tag-lt-02': ("{% if 1 < 1 %}yes{% else %}no{% endif %}", {}, 
"no"),
+            'if-tag-lte-01': ("{% if 1 <= 1 %}yes{% else %}no{% endif %}", {}, 
"yes"),
+            'if-tag-lte-02': ("{% if 2 <= 1 %}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'),
@@ -554,14 +576,13 @@
             '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
+            # multiple ORs
+            'if-tag-or09': ("{% if foo or bar or baz %}yes{% else %}no{% endif 
%}", {'baz': True}, 'yes'),
 
             # 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-not02': ("{% if not not foo %}no{% else %}yes{% endif %}", 
{'foo': True}, 'no'),
+            # not03 to not05 removed, now TemplateSyntaxErrors
 
             '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'),
@@ -599,13 +620,22 @@
             '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),
+            # Various syntax errors
+            'if-tag-error01': ("{% if %}yes{% endif %}", {}, 
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),
+            'if-tag-error06': ("{% if abc def %}yes{% endif %}", {}, 
template.TemplateSyntaxError),
+            'if-tag-error07': ("{% if not %}yes{% endif %}", {}, 
template.TemplateSyntaxError),
+            'if-tag-error08': ("{% if and %}yes{% endif %}", {}, 
template.TemplateSyntaxError),
+            'if-tag-error09': ("{% if or %}yes{% endif %}", {}, 
template.TemplateSyntaxError),
+            'if-tag-error10': ("{% if == %}yes{% endif %}", {}, 
template.TemplateSyntaxError),
+            'if-tag-error11': ("{% if 1 == %}yes{% endif %}", {}, 
template.TemplateSyntaxError),
+            'if-tag-error12': ("{% if a not b %}yes{% endif %}", {}, 
template.TemplateSyntaxError),
 
+            # Additional, more precise parsing tests are in SmartIfTests
+
             ### IFCHANGED TAG 
#########################################################
             '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'),

--

You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to django-upda...@googlegroups.com.
To unsubscribe from this group, send email to 
django-updates+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en.


Reply via email to