Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Implemented 'smart if' template tag, allowing filters and various ope…

…rators 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.



git-svn-id: http://code.djangoproject.com/svn/django/trunk@11806 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 2c2f5aee4d44836779fcd74c7782368914f9cfd1 1 parent 25020dd
Luke Plant authored December 09, 2009
101  django/template/defaulttags.py
@@ -11,6 +11,7 @@
11 11
 from django.template import Node, NodeList, Template, Context, Variable
12 12
 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
13 13
 from django.template import get_library, Library, InvalidTemplateLibrary
  14
+from django.template.smartif import IfParser, Literal
14 15
 from django.conf import settings
15 16
 from django.utils.encoding import smart_str, smart_unicode
16 17
 from django.utils.itercompat import groupby
@@ -227,10 +228,9 @@ def render(self, context):
227 228
         return self.nodelist_false.render(context)
228 229
 
229 230
 class IfNode(Node):
230  
-    def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
231  
-        self.bool_exprs = bool_exprs
  231
+    def __init__(self, var, nodelist_true, nodelist_false=None):
232 232
         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
233  
-        self.link_type = link_type
  233
+        self.var = var
234 234
 
235 235
     def __repr__(self):
236 236
         return "<If node>"
@@ -250,28 +250,10 @@ def get_nodes_by_type(self, nodetype):
250 250
         return nodes
251 251
 
252 252
     def render(self, context):
253  
-        if self.link_type == IfNode.LinkTypes.or_:
254  
-            for ifnot, bool_expr in self.bool_exprs:
255  
-                try:
256  
-                    value = bool_expr.resolve(context, True)
257  
-                except VariableDoesNotExist:
258  
-                    value = None
259  
-                if (value and not ifnot) or (ifnot and not value):
260  
-                    return self.nodelist_true.render(context)
261  
-            return self.nodelist_false.render(context)
262  
-        else:
263  
-            for ifnot, bool_expr in self.bool_exprs:
264  
-                try:
265  
-                    value = bool_expr.resolve(context, True)
266  
-                except VariableDoesNotExist:
267  
-                    value = None
268  
-                if not ((value and not ifnot) or (ifnot and not value)):
269  
-                    return self.nodelist_false.render(context)
  253
+        if self.var.eval(context):
270 254
             return self.nodelist_true.render(context)
271  
-
272  
-    class LinkTypes:
273  
-        and_ = 0,
274  
-        or_ = 1
  255
+        else:
  256
+            return self.nodelist_false.render(context)
275 257
 
276 258
 class RegroupNode(Node):
277 259
     def __init__(self, target, expression, var_name):
@@ -761,6 +743,27 @@ def ifnotequal(parser, token):
761 743
     return do_ifequal(parser, token, True)
762 744
 ifnotequal = register.tag(ifnotequal)
763 745
 
  746
+class TemplateLiteral(Literal):
  747
+    def __init__(self, value, text):
  748
+        self.value = value
  749
+        self.text = text # for better error messages
  750
+
  751
+    def display(self):
  752
+        return self.text
  753
+
  754
+    def eval(self, context):
  755
+        return self.value.resolve(context, ignore_failures=True)
  756
+
  757
+class TemplateIfParser(IfParser):
  758
+    error_class = TemplateSyntaxError
  759
+
  760
+    def __init__(self, parser, *args, **kwargs):
  761
+        self.template_parser = parser
  762
+        return super(TemplateIfParser, self).__init__(*args, **kwargs)
  763
+
  764
+    def create_var(self, value):
  765
+        return TemplateLiteral(self.template_parser.compile_filter(value), value)
  766
+
764 767
 #@register.tag(name="if")
765 768
 def do_if(parser, token):
766 769
     """
@@ -805,47 +808,21 @@ def do_if(parser, token):
805 808
             There are some athletes and absolutely no coaches.
806 809
         {% endif %}
807 810
 
808  
-    ``if`` tags do not allow ``and`` and ``or`` clauses with the same tag,
809  
-    because the order of logic would be ambigous. For example, this is
810  
-    invalid::
  811
+    Comparison operators are also available, and the use of filters is also
  812
+    allowed, for example:
811 813
 
812  
-        {% if athlete_list and coach_list or cheerleader_list %}
  814
+        {% if articles|length >= 5 %}...{% endif %}
813 815
 
814  
-    If you need to combine ``and`` and ``or`` to do advanced logic, just use
815  
-    nested if tags. For example::
  816
+    Arguments and operators _must_ have a space between them, so
  817
+    ``{% if 1>2 %}`` is not a valid if tag.
816 818
 
817  
-        {% if athlete_list %}
818  
-            {% if coach_list or cheerleader_list %}
819  
-                We have athletes, and either coaches or cheerleaders!
820  
-            {% endif %}
821  
-        {% endif %}
  819
+    All supported operators are: ``or``, ``and``, ``in``, ``==`` (or ``=``),
  820
+    ``!=``, ``>``, ``>=``, ``<`` and ``<=``.
  821
+
  822
+    Operator precedence follows Python.
822 823
     """
823  
-    bits = token.contents.split()
824  
-    del bits[0]
825  
-    if not bits:
826  
-        raise TemplateSyntaxError("'if' statement requires at least one argument")
827  
-    # Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
828  
-    bitstr = ' '.join(bits)
829  
-    boolpairs = bitstr.split(' and ')
830  
-    boolvars = []
831  
-    if len(boolpairs) == 1:
832  
-        link_type = IfNode.LinkTypes.or_
833  
-        boolpairs = bitstr.split(' or ')
834  
-    else:
835  
-        link_type = IfNode.LinkTypes.and_
836  
-        if ' or ' in bitstr:
837  
-            raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'"
838  
-    for boolpair in boolpairs:
839  
-        if ' ' in boolpair:
840  
-            try:
841  
-                not_, boolvar = boolpair.split()
842  
-            except ValueError:
843  
-                raise TemplateSyntaxError, "'if' statement improperly formatted"
844  
-            if not_ != 'not':
845  
-                raise TemplateSyntaxError, "Expected 'not' in if statement"
846  
-            boolvars.append((True, parser.compile_filter(boolvar)))
847  
-        else:
848  
-            boolvars.append((False, parser.compile_filter(boolpair)))
  824
+    bits = token.split_contents()[1:]
  825
+    var = TemplateIfParser(parser, bits).parse()
849 826
     nodelist_true = parser.parse(('else', 'endif'))
850 827
     token = parser.next_token()
851 828
     if token.contents == 'else':
@@ -853,7 +830,7 @@ def do_if(parser, token):
853 830
         parser.delete_first_token()
854 831
     else:
855 832
         nodelist_false = NodeList()
856  
-    return IfNode(boolvars, nodelist_true, nodelist_false, link_type)
  833
+    return IfNode(var, nodelist_true, nodelist_false)
857 834
 do_if = register.tag("if", do_if)
858 835
 
859 836
 #@register.tag
192  django/template/smartif.py
... ...
@@ -0,0 +1,192 @@
  1
+"""
  2
+Parser and utilities for the smart 'if' tag
  3
+"""
  4
+import operator
  5
+
  6
+# Using a simple top down parser, as described here:
  7
+#    http://effbot.org/zone/simple-top-down-parsing.htm.
  8
+# 'led' = left denotation
  9
+# 'nud' = null denotation
  10
+# 'bp' = binding power (left = lbp, right = rbp)
  11
+
  12
+class TokenBase(object):
  13
+    """
  14
+    Base class for operators and literals, mainly for debugging and for throwing
  15
+    syntax errors.
  16
+    """
  17
+    id = None # node/token type name
  18
+    value = None # used by literals
  19
+    first = second = None # used by tree nodes
  20
+
  21
+    def nud(self, parser):
  22
+        # Null denotation - called in prefix context
  23
+        raise parser.error_class(
  24
+            "Not expecting '%s' in this position in if tag." % self.id
  25
+        )
  26
+
  27
+    def led(self, left, parser):
  28
+        # Left denotation - called in infix context
  29
+        raise parser.error_class(
  30
+            "Not expecting '%s' as infix operator in if tag." % self.id
  31
+        )
  32
+
  33
+    def display(self):
  34
+        """
  35
+        Returns what to display in error messages for this node
  36
+        """
  37
+        return self.id
  38
+
  39
+    def __repr__(self):
  40
+        out = [str(x) for x in [self.id, self.first, self.second] if x is not None]
  41
+        return "(" + " ".join(out) + ")"
  42
+
  43
+
  44
+def infix(bp, func):
  45
+    """
  46
+    Creates an infix operator, given a binding power and a function that
  47
+    evaluates the node
  48
+    """
  49
+    class Operator(TokenBase):
  50
+        lbp = bp
  51
+
  52
+        def led(self, left, parser):
  53
+            self.first = left
  54
+            self.second = parser.expression(bp)
  55
+            return self
  56
+
  57
+        def eval(self, context):
  58
+            try:
  59
+                return func(self.first.eval(context), self.second.eval(context))
  60
+            except Exception:
  61
+                # Templates shouldn't throw exceptions when rendering.  We are
  62
+                # most likely to get exceptions for things like {% if foo in bar
  63
+                # %} where 'bar' does not support 'in', so default to False
  64
+                return False
  65
+
  66
+    return Operator
  67
+
  68
+
  69
+def prefix(bp, func):
  70
+    """
  71
+    Creates a prefix operator, given a binding power and a function that
  72
+    evaluates the node.
  73
+    """
  74
+    class Operator(TokenBase):
  75
+        lbp = bp
  76
+
  77
+        def nud(self, parser):
  78
+            self.first = parser.expression(bp)
  79
+            self.second = None
  80
+            return self
  81
+
  82
+        def eval(self, context):
  83
+            try:
  84
+                return func(self.first.eval(context))
  85
+            except Exception:
  86
+                return False
  87
+
  88
+    return Operator
  89
+
  90
+
  91
+# Operator precedence follows Python.
  92
+# NB - we can get slightly more accurate syntax error messages by not using the
  93
+# same object for '==' and '='.
  94
+
  95
+OPERATORS = {
  96
+    'or': infix(6, lambda x, y: x or y),
  97
+    'and': infix(7, lambda x, y: x and y),
  98
+    'not': prefix(8, operator.not_),
  99
+    'in': infix(9, lambda x, y: x in y),
  100
+    '=': infix(10, operator.eq),
  101
+    '==': infix(10, operator.eq),
  102
+    '!=': infix(10, operator.ne),
  103
+    '>': infix(10, operator.gt),
  104
+    '>=': infix(10, operator.ge),
  105
+    '<': infix(10, operator.lt),
  106
+    '<=': infix(10, operator.le),
  107
+}
  108
+
  109
+# Assign 'id' to each:
  110
+for key, op in OPERATORS.items():
  111
+    op.id = key
  112
+
  113
+
  114
+class Literal(TokenBase):
  115
+    """
  116
+    A basic self-resolvable object similar to a Django template variable.
  117
+    """
  118
+    # IfParser uses Literal in create_var, but TemplateIfParser overrides
  119
+    # create_var so that a proper implementation that actually resolves
  120
+    # variables, filters etc is used.
  121
+    id = "literal"
  122
+    lbp = 0
  123
+
  124
+    def __init__(self, value):
  125
+        self.value = value
  126
+
  127
+    def display(self):
  128
+        return repr(self.value)
  129
+
  130
+    def nud(self, parser):
  131
+        return self
  132
+
  133
+    def eval(self, context):
  134
+        return self.value
  135
+
  136
+    def __repr__(self):
  137
+        return "(%s %r)" % (self.id, self.value)
  138
+
  139
+
  140
+class EndToken(TokenBase):
  141
+    lbp = 0
  142
+
  143
+    def nud(self, parser):
  144
+        raise parser.error_class("Unexpected end of expression in if tag.")
  145
+
  146
+EndToken = EndToken()
  147
+
  148
+
  149
+class IfParser(object):
  150
+    error_class = ValueError
  151
+
  152
+    def __init__(self, tokens):
  153
+        self.tokens = map(self.translate_tokens, tokens)
  154
+        self.pos = 0
  155
+        self.current_token = self.next()
  156
+
  157
+    def translate_tokens(self, token):
  158
+        try:
  159
+            op = OPERATORS[token]
  160
+        except (KeyError, TypeError):
  161
+            return self.create_var(token)
  162
+        else:
  163
+            return op()
  164
+
  165
+    def next(self):
  166
+        if self.pos >= len(self.tokens):
  167
+            return EndToken
  168
+        else:
  169
+            retval = self.tokens[self.pos]
  170
+            self.pos += 1
  171
+            return retval
  172
+
  173
+    def parse(self):
  174
+        retval = self.expression()
  175
+        # Check that we have exhausted all the tokens
  176
+        if self.current_token is not EndToken:
  177
+            raise self.error_class("Unused '%s' at end of if expression." %
  178
+                                   self.current_token.display())
  179
+        return retval
  180
+
  181
+    def expression(self, rbp=0):
  182
+        t = self.current_token
  183
+        self.current_token = self.next()
  184
+        left = t.nud(self)
  185
+        while rbp < self.current_token.lbp:
  186
+            t = self.current_token
  187
+            self.current_token = self.next()
  188
+            left = t.led(left, self)
  189
+        return left
  190
+
  191
+    def create_var(self, value):
  192
+        return Literal(value)
160  docs/ref/templates/builtins.txt
@@ -313,6 +313,9 @@ displayed by the ``{{ athlete_list|length }}`` variable.
313 313
 As you can see, the ``if`` tag can take an optional ``{% else %}`` clause that
314 314
 will be displayed if the test fails.
315 315
 
  316
+Boolean operators
  317
+^^^^^^^^^^^^^^^^^
  318
+
316 319
 ``if`` tags may use ``and``, ``or`` or ``not`` to test a number of variables or
317 320
 to negate a given variable::
318 321
 
@@ -338,24 +341,153 @@ to negate a given variable::
338 341
         There are some athletes and absolutely no coaches.
339 342
     {% endif %}
340 343
 
341  
-``if`` tags don't allow ``and`` and ``or`` clauses within the same tag, because
342  
-the order of logic would be ambiguous. For example, this is invalid::
  344
+.. versionchanged:: 1.2
  345
+
  346
+Use of both ``and`` and ``or`` clauses within the same tag is allowed, with
  347
+``and`` having higher precedence than ``or`` e.g.::
343 348
 
344 349
     {% if athlete_list and coach_list or cheerleader_list %}
345 350
 
346  
-If you need to combine ``and`` and ``or`` to do advanced logic, just use nested
347  
-``if`` tags. For example::
  351
+will be interpreted like:
348 352
 
349  
-    {% if athlete_list %}
350  
-        {% if coach_list or cheerleader_list %}
351  
-            We have athletes, and either coaches or cheerleaders!
352  
-        {% endif %}
  353
+.. code-block:: python
  354
+
  355
+    if (athlete_list and coach_list) or cheerleader_list
  356
+
  357
+Use of actual brackets in the ``if`` tag is invalid syntax.  If you need them to
  358
+indicate precedence, you should use nested ``if`` tags.
  359
+
  360
+.. versionadded:: 1.2
  361
+
  362
+
  363
+``if`` tags may also use the operators ``==``, ``!=``, ``<``, ``>``,
  364
+``<=``, ``>=`` and ``in`` which work as follows:
  365
+
  366
+
  367
+``==`` operator
  368
+^^^^^^^^^^^^^^^
  369
+
  370
+Equality. Example::
  371
+
  372
+    {% if somevar == "x" %}
  373
+      This appears if variable somevar equals the string "x"
  374
+    {% endif %}
  375
+
  376
+``!=`` operator
  377
+^^^^^^^^^^^^^^^
  378
+
  379
+Inequality. Example::
  380
+
  381
+    {% if somevar != "x" %}
  382
+      This appears if variable somevar does not equal the string "x",
  383
+      or if somevar is not found in the context
  384
+    {% endif %}
  385
+
  386
+``<`` operator
  387
+^^^^^^^^^^^^^^
  388
+
  389
+Less than. Example::
  390
+
  391
+    {% if somevar < 100 %}
  392
+      This appears if variable somevar is less than 100.
  393
+    {% endif %}
  394
+
  395
+``>`` operator
  396
+^^^^^^^^^^^^^^
  397
+
  398
+Greater than. Example::
  399
+
  400
+    {% if somevar > 0 %}
  401
+      This appears if variable somevar is greater than 0.
  402
+    {% endif %}
  403
+
  404
+``<=`` operator
  405
+^^^^^^^^^^^^^^^
  406
+
  407
+Less than or equal to. Example::
  408
+
  409
+    {% if somevar <= 100 %}
  410
+      This appears if variable somevar is less than 100 or equal to 100.
  411
+    {% endif %}
  412
+
  413
+``>=`` operator
  414
+^^^^^^^^^^^^^^^
  415
+
  416
+Greater than or equal to. Example::
  417
+
  418
+    {% if somevar >= 1 %}
  419
+      This appears if variable somevar is greater than 1 or equal to 1.
  420
+    {% endif %}
  421
+
  422
+``in`` operator
  423
+^^^^^^^^^^^^^^^
  424
+
  425
+Contained within. This operator is supported by many Python containers to test
  426
+whether the given value is in the container.  The following are some examples of
  427
+how ``x in y`` will be interpreted::
  428
+
  429
+    {% if "bc" in "abcdef" %}
  430
+      This appears since "bc" is a substring of "abcdef"
  431
+    {% endif %}
  432
+
  433
+    {% if "hello" in greetings %}
  434
+      If greetings is a list or set, one element of which is the string
  435
+      "hello", this will appear.
353 436
     {% endif %}
354 437
 
355  
-Multiple uses of the same logical operator are fine, as long as you use the
356  
-same operator. For example, this is valid::
  438
+    {% if user in users %}
  439
+      If users is a QuerySet, this will appear if user is an
  440
+      instance that belongs to the QuerySet.
  441
+    {% endif %}
  442
+
  443
+
  444
+The comparison operators cannot be 'chained' like in Python or in mathematical
  445
+notation. For example, instead of using::
  446
+
  447
+    {% if a > b > c %}  (WRONG)
  448
+
  449
+you should use::
  450
+
  451
+    {% if a > b and b > c %}
  452
+
  453
+
  454
+Filters
  455
+^^^^^^^
  456
+
  457
+You can also use filters in the ``if`` expression. For example::
  458
+
  459
+    {% if messages|length >= 100 %}
  460
+       You have lots of messages today!
  461
+    {% endif %}
  462
+
  463
+Complex expressions
  464
+^^^^^^^^^^^^^^^^^^^
  465
+
  466
+All of the above can be combined to form complex expressions. For such
  467
+expressions, it can be important to know how the operators are grouped when the
  468
+expression is evaluated - that is, the precedence rules.  The precedence of the
  469
+operators, from lowest to highest, is as follows:
  470
+
  471
+ * ``or``
  472
+ * ``and``
  473
+ * ``not``
  474
+ * ``in``
  475
+ * ``==``, ``!=``, ``<``, ``>``,``<=``, ``>=``
  476
+
  477
+(This follows Python exactly). So, for example, the following complex if tag:
  478
+
  479
+    {% if a == b or c == d and e %}
  480
+
  481
+...will be interpreted as:
  482
+
  483
+.. code-block:: python
  484
+
  485
+    (a == b) or ((c == d) and e)
  486
+
  487
+If you need different precedence, you will need to use nested if tags. Sometimes
  488
+that is better for clarity anyway, for the sake of those who do not know the
  489
+precedence rules.
357 490
 
358  
-    {% if athlete_list or coach_list or parent_list or teacher_list %}
359 491
 
360 492
 .. templatetag:: ifchanged
361 493
 
@@ -427,6 +559,9 @@ You cannot check for equality with Python objects such as ``True`` or
427 559
 ``False``.  If you need to test if something is true or false, use the ``if``
428 560
 tag instead.
429 561
 
  562
+.. versionadded:: 1.2
  563
+   An alternative to the ``ifequal`` tag is to use the :ttag:`if` tag and the ``==`` operator.
  564
+
430 565
 .. templatetag:: ifnotequal
431 566
 
432 567
 ifnotequal
@@ -434,6 +569,9 @@ ifnotequal
434 569
 
435 570
 Just like ``ifequal``, except it tests that the two arguments are not equal.
436 571
 
  572
+.. versionadded:: 1.2
  573
+   An alternative to the ``ifnotequal`` tag is to use the :ttag:`if` tag and the ``!=`` operator.
  574
+
437 575
 .. templatetag:: include
438 576
 
439 577
 include
43  docs/releases/1.2.txt
@@ -42,6 +42,15 @@ changes that developers must be aware of:
42 42
  * All of the CSRF has moved from contrib to core (with backwards compatible
43 43
    imports in the old locations, which are deprecated).
44 44
 
  45
+:ttag:`if` tag changes
  46
+----------------------
  47
+
  48
+Due to new features in the :ttag:`if` template tag, it no longer accepts 'and',
  49
+'or' and 'not' as valid **variable** names.  Previously that worked in some
  50
+cases even though these strings were normally treated as keywords.  Now, the
  51
+keyword status is always enforced, and template code like ``{% if not %}`` or
  52
+``{% if and %}`` will throw a TemplateSyntaxError.
  53
+
45 54
 ``LazyObject``
46 55
 --------------
47 56
 
@@ -196,3 +205,37 @@ messaging, for both anonymous and authenticated clients. The messages framework
196 205
 replaces the deprecated user message API and allows you to temporarily store
197 206
 messages in one request and retrieve them for display in a subsequent request
198 207
 (usually the next one).
  208
+
  209
+'Smart' if tag
  210
+--------------
  211
+
  212
+The :ttag:`if` tag has been upgraded to be much more powerful.  First, support
  213
+for comparison operators has been added. No longer will you have to type:
  214
+
  215
+.. code-block:: html+django
  216
+
  217
+    {% ifnotequal a b %}
  218
+     ...
  219
+    {% endifnotequal %}
  220
+
  221
+...as you can now do:
  222
+
  223
+.. code-block:: html+django
  224
+
  225
+    {% if a != b %}
  226
+     ...
  227
+    {% endif %}
  228
+
  229
+The operators supported are ``==``, ``!=``, ``<``, ``>``, ``<=``, ``>=`` and
  230
+``in``, all of which work like the Python operators, in addition to ``and``,
  231
+``or`` and ``not`` which were already supported.
  232
+
  233
+Also, filters may now be used in the ``if`` expression. For example:
  234
+
  235
+.. code-block:: html+django
  236
+
  237
+      <div
  238
+        {% if user.email|lower == message.recipient|lower %}
  239
+          class="highlight"
  240
+        {% endif %}
  241
+      >{{ message }}</div>
21  docs/topics/templates.txt
@@ -187,8 +187,8 @@ tags:
187 187
                 <li>{{ athlete.name }}</li>
188 188
             {% endfor %}
189 189
             </ul>
190  
-        
191  
-    :ttag:`if` and :ttag:`else`
  190
+
  191
+    :ttag:`if` and ``else``
192 192
         Evaluates a variable, and if that variable is "true" the contents of the
193 193
         block are displayed::
194 194
 
@@ -200,20 +200,15 @@ tags:
200 200
 
201 201
         In the above, if ``athlete_list`` is not empty, the number of athletes
202 202
         will be displayed by the ``{{ athlete_list|length }}`` variable.
203  
-        
204  
-    :ttag:`ifequal` and :ttag:`ifnotequal`
205  
-        Display some contents if two arguments are or are not equal. For example::
206 203
 
207  
-            {% ifequal athlete.name coach.name %}
208  
-                ...
209  
-            {% endifequal %}
  204
+        You can also use filters and various operators in the ``if`` tag::
210 205
 
211  
-        Or::
  206
+            {% if athlete_list|length > 1 %}
  207
+               Team: {% for athlete in athlete_list %} ... {% endfor %}
  208
+            {% else %}
  209
+               Athlete: {{ athlete_list.0.name }}
  210
+            {% endif %}
212 211
 
213  
-            {% ifnotequal athlete.name "Joe" %}
214  
-                ...
215  
-            {% endifnotequal %}
216  
-    
217 212
     :ttag:`block` and :ttag:`extends`
218 213
         Set up `template inheritance`_ (see below), a powerful way
219 214
         of cutting down on "boilerplate" in templates.
46  tests/regressiontests/templates/smartif.py
... ...
@@ -0,0 +1,46 @@
  1
+import unittest
  2
+from django.template.smartif import IfParser, Literal
  3
+
  4
+class SmartIfTests(unittest.TestCase):
  5
+
  6
+    def assertCalcEqual(self, expected, tokens):
  7
+        self.assertEqual(expected, IfParser(tokens).parse().eval({}))
  8
+
  9
+    # We only test things here that are difficult to test elsewhere
  10
+    # Many other tests are found in the main tests for builtin template tags
  11
+    # Test parsing via the printed parse tree
  12
+    def test_not(self):
  13
+        var = IfParser(["not", False]).parse()
  14
+        self.assertEqual("(not (literal False))", repr(var))
  15
+        self.assert_(var.eval({}))
  16
+
  17
+        self.assertFalse(IfParser(["not", True]).parse().eval({}))
  18
+
  19
+    def test_or(self):
  20
+        var = IfParser([True, "or", False]).parse()
  21
+        self.assertEqual("(or (literal True) (literal False))", repr(var))
  22
+        self.assert_(var.eval({}))
  23
+
  24
+    def test_in(self):
  25
+        list_ = [1,2,3]
  26
+        self.assertCalcEqual(True, [1, 'in', list_])
  27
+        self.assertCalcEqual(False, [1, 'in', None])
  28
+        self.assertCalcEqual(False, [None, 'in', list_])
  29
+
  30
+    def test_precedence(self):
  31
+        # (False and False) or True == True   <- we want this one, like Python
  32
+        # False and (False or True) == False
  33
+        self.assertCalcEqual(True, [False, 'and', False, 'or', True])
  34
+
  35
+        # True or (False and False) == True   <- we want this one, like Python
  36
+        # (True or False) and False == False
  37
+        self.assertCalcEqual(True, [True, 'or', False, 'and', False])
  38
+
  39
+        # (1 or 1) == 2  -> False
  40
+        # 1 or (1 == 2)  -> True   <- we want this one
  41
+        self.assertCalcEqual(True, [1, 'or', 1, '==', 2])
  42
+
  43
+        self.assertCalcEqual(True, [True, '==', True, 'or', True, '==', False])
  44
+
  45
+        self.assertEqual("(or (and (== (literal 1) (literal 2)) (literal 3)) (literal 4))",
  46
+                         repr(IfParser([1, '==', 2, 'and', 3, 'or', 4]).parse()))
44  tests/regressiontests/templates/tests.py
@@ -24,6 +24,7 @@
24 24
 from custom import custom_filters
25 25
 from parser import filter_parsing, variable_parsing
26 26
 from unicode import unicode_tests
  27
+from smartif import *
27 28
 
28 29
 try:
29 30
     from loaders import *
@@ -534,6 +535,27 @@ def get_template_tests(self):
534 535
             'if-tag02': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": False}, "no"),
535 536
             'if-tag03': ("{% if foo %}yes{% else %}no{% endif %}", {}, "no"),
536 537
 
  538
+            # Filters
  539
+            'if-tag-filter01': ("{% if foo|length == 5 %}yes{% else %}no{% endif %}", {'foo': 'abcde'}, "yes"),
  540
+            'if-tag-filter02': ("{% if foo|upper == 'ABC' %}yes{% else %}no{% endif %}", {}, "no"),
  541
+
  542
+            # Equality
  543
+            'if-tag-eq01': ("{% if foo == bar %}yes{% else %}no{% endif %}", {}, "yes"),
  544
+            'if-tag-eq02': ("{% if foo == bar %}yes{% else %}no{% endif %}", {'foo': 1}, "no"),
  545
+            'if-tag-eq03': ("{% if foo == bar %}yes{% else %}no{% endif %}", {'foo': 1, 'bar': 1}, "yes"),
  546
+            'if-tag-eq04': ("{% if foo == bar %}yes{% else %}no{% endif %}", {'foo': 1, 'bar': 2}, "no"),
  547
+            'if-tag-eq05': ("{% if foo == '' %}yes{% else %}no{% endif %}", {}, "no"),
  548
+
  549
+            # Comparison
  550
+            'if-tag-gt-01': ("{% if 2 > 1 %}yes{% else %}no{% endif %}", {}, "yes"),
  551
+            'if-tag-gt-02': ("{% if 1 > 1 %}yes{% else %}no{% endif %}", {}, "no"),
  552
+            'if-tag-gte-01': ("{% if 1 >= 1 %}yes{% else %}no{% endif %}", {}, "yes"),
  553
+            'if-tag-gte-02': ("{% if 1 >= 2 %}yes{% else %}no{% endif %}", {}, "no"),
  554
+            'if-tag-lt-01': ("{% if 1 < 2 %}yes{% else %}no{% endif %}", {}, "yes"),
  555
+            'if-tag-lt-02': ("{% if 1 < 1 %}yes{% else %}no{% endif %}", {}, "no"),
  556
+            'if-tag-lte-01': ("{% if 1 <= 1 %}yes{% else %}no{% endif %}", {}, "yes"),
  557
+            'if-tag-lte-02': ("{% if 2 <= 1 %}yes{% else %}no{% endif %}", {}, "no"),
  558
+
537 559
             # AND
538 560
             'if-tag-and01': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'),
539 561
             'if-tag-and02': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'),
@@ -554,14 +576,13 @@ def get_template_tests(self):
554 576
             'if-tag-or07': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': True}, 'yes'),
555 577
             'if-tag-or08': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'bar': True}, 'yes'),
556 578
 
557  
-            # TODO: multiple ORs
  579
+            # multiple ORs
  580
+            'if-tag-or09': ("{% if foo or bar or baz %}yes{% else %}no{% endif %}", {'baz': True}, 'yes'),
558 581
 
559 582
             # NOT
560 583
             'if-tag-not01': ("{% if not foo %}no{% else %}yes{% endif %}", {'foo': True}, 'yes'),
561  
-            'if-tag-not02': ("{% if not %}yes{% else %}no{% endif %}", {'foo': True}, 'no'),
562  
-            'if-tag-not03': ("{% if not %}yes{% else %}no{% endif %}", {'not': True}, 'yes'),
563  
-            'if-tag-not04': ("{% if not not %}no{% else %}yes{% endif %}", {'not': True}, 'yes'),
564  
-            'if-tag-not05': ("{% if not not %}no{% else %}yes{% endif %}", {}, 'no'),
  584
+            'if-tag-not02': ("{% if not not foo %}no{% else %}yes{% endif %}", {'foo': True}, 'no'),
  585
+            # not03 to not05 removed, now TemplateSyntaxErrors
565 586
 
566 587
             'if-tag-not06': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {}, 'no'),
567 588
             'if-tag-not07': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'),
@@ -599,12 +620,21 @@ def get_template_tests(self):
599 620
             'if-tag-not34': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'),
600 621
             'if-tag-not35': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'),
601 622
 
602  
-            # AND and OR raises a TemplateSyntaxError
603  
-            'if-tag-error01': ("{% if foo or bar and baz %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, template.TemplateSyntaxError),
  623
+            # Various syntax errors
  624
+            'if-tag-error01': ("{% if %}yes{% endif %}", {}, template.TemplateSyntaxError),
604 625
             'if-tag-error02': ("{% if foo and %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
605 626
             'if-tag-error03': ("{% if foo or %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
606 627
             'if-tag-error04': ("{% if not foo and %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
607 628
             'if-tag-error05': ("{% if not foo or %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
  629
+            'if-tag-error06': ("{% if abc def %}yes{% endif %}", {}, template.TemplateSyntaxError),
  630
+            'if-tag-error07': ("{% if not %}yes{% endif %}", {}, template.TemplateSyntaxError),
  631
+            'if-tag-error08': ("{% if and %}yes{% endif %}", {}, template.TemplateSyntaxError),
  632
+            'if-tag-error09': ("{% if or %}yes{% endif %}", {}, template.TemplateSyntaxError),
  633
+            'if-tag-error10': ("{% if == %}yes{% endif %}", {}, template.TemplateSyntaxError),
  634
+            'if-tag-error11': ("{% if 1 == %}yes{% endif %}", {}, template.TemplateSyntaxError),
  635
+            'if-tag-error12': ("{% if a not b %}yes{% endif %}", {}, template.TemplateSyntaxError),
  636
+
  637
+            # Additional, more precise parsing tests are in SmartIfTests
608 638
 
609 639
             ### IFCHANGED TAG #########################################################
610 640
             'ifchanged01': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', {'num': (1,2,3)}, '123'),

0 notes on commit 2c2f5ae

Please sign in to comment.
Something went wrong with that request. Please try again.