Author: russellm
Date: 2010-04-19 09:18:14 -0500 (Mon, 19 Apr 2010)
New Revision: 13001

Modified:
   django/trunk/django/template/smartif.py
   django/trunk/tests/regressiontests/templates/tests.py
Log:
Fixed #13373 -- Ensured that {% if %} statements will short circuit template 
logic and not evaluate clauses that don't require evaluation. Thanks to Jerry 
Stratton for the report.

Modified: django/trunk/django/template/smartif.py
===================================================================
--- django/trunk/django/template/smartif.py     2010-04-19 12:40:46 UTC (rev 
13000)
+++ django/trunk/django/template/smartif.py     2010-04-19 14:18:14 UTC (rev 
13001)
@@ -56,7 +56,7 @@
 
         def eval(self, context):
             try:
-                return func(self.first.eval(context), 
self.second.eval(context))
+                return func(context, self.first, self.second)
             except Exception:
                 # Templates shouldn't throw exceptions when rendering.  We are
                 # most likely to get exceptions for things like {% if foo in 
bar
@@ -81,7 +81,7 @@
 
         def eval(self, context):
             try:
-                return func(self.first.eval(context))
+                return func(context, self.first)
             except Exception:
                 return False
 
@@ -91,20 +91,21 @@
 # Operator precedence follows Python.
 # NB - we can get slightly more accurate syntax error messages by not using the
 # same object for '==' and '='.
-
+# We defer variable evaluation to the lambda to ensure that terms are
+# lazily evaluated using Python's boolean parsing logic.
 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),
-    'not in': infix(9, lambda x, y: x not 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),
+    'or': infix(6, lambda context, x, y: x.eval(context) or y.eval(context)),
+    'and': infix(7, lambda context, x, y: x.eval(context) and y.eval(context)),
+    'not': prefix(8, lambda context, x: not x.eval(context)),
+    'in': infix(9, lambda context, x, y: x.eval(context) in y.eval(context)),
+    'not in': infix(9, lambda context, x, y: x.eval(context) not in 
y.eval(context)),
+    '=': infix(10, lambda context, x, y: x.eval(context) == y.eval(context)),
+    '==': infix(10, lambda context, x, y: x.eval(context) == y.eval(context)),
+    '!=': infix(10, lambda context, x, y: x.eval(context) != y.eval(context)),
+    '>': infix(10, lambda context, x, y: x.eval(context) > y.eval(context)),
+    '>=': infix(10, lambda context, x, y: x.eval(context) >= y.eval(context)),
+    '<': infix(10, lambda context, x, y: x.eval(context) < y.eval(context)),
+    '<=': infix(10, lambda context, x, y: x.eval(context) <= y.eval(context)),
 }
 
 # Assign 'id' to each:
@@ -151,7 +152,7 @@
     error_class = ValueError
 
     def __init__(self, tokens):
-        # pre-pass necessary to turn  'not','in' into single token 
+        # pre-pass necessary to turn  'not','in' into single token
         l = len(tokens)
         mapped_tokens = []
         i = 0

Modified: django/trunk/tests/regressiontests/templates/tests.py
===================================================================
--- django/trunk/tests/regressiontests/templates/tests.py       2010-04-19 
12:40:46 UTC (rev 13000)
+++ django/trunk/tests/regressiontests/templates/tests.py       2010-04-19 
14:18:14 UTC (rev 13001)
@@ -7,6 +7,7 @@
     settings.configure()
 
 from datetime import datetime, timedelta
+import time
 import os
 import sys
 import traceback
@@ -97,6 +98,17 @@
     def method(self):
         return "OtherClass.method"
 
+class TestObj(object):
+    def is_true(self):
+        return True
+
+    def is_false(self):
+        return False
+
+    def is_bad(self):
+        time.sleep(0.3)
+        return True
+
 class SilentGetItemClass(object):
     def __getitem__(self, key):
         raise SomeException
@@ -342,6 +354,11 @@
         old_invalid = settings.TEMPLATE_STRING_IF_INVALID
         expected_invalid_str = 'INVALID'
 
+        # Warm the URL reversing cache. This ensures we don't pay the cost
+        # warming the cache during one of the tests.
+        urlresolvers.reverse('regressiontests.templates.views.client_action',
+                             kwargs={'id':0,'action':"update"})
+
         for name, vals in tests:
             if isinstance(vals[2], tuple):
                 normal_string_result = vals[2][0]
@@ -367,9 +384,14 @@
                         start = datetime.now()
                         test_template = loader.get_template(name)
                         end = datetime.now()
+                        if end-start > timedelta(seconds=0.2):
+                            failures.append("Template test (Cached='%s', 
TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Took too long to parse test" % 
(is_cached, invalid_str, name))
+
+                        start = datetime.now()
                         output = self.render(test_template, vals)
+                        end = datetime.now()
                         if end-start > timedelta(seconds=0.2):
-                            failures.append("Template test (Cached='%s', 
TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Took too long to parse test" % 
(is_cached, invalid_str, name))
+                            failures.append("Template test (Cached='%s', 
TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Took too long to render test" % 
(is_cached, invalid_str, name))
                     except ContextStackException:
                         failures.append("Template test (Cached='%s', 
TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Context stack was left 
imbalanced" % (is_cached, invalid_str, name))
                         continue
@@ -782,6 +804,13 @@
             'if-tag-error11': ("{% if 1 == %}yes{% endif %}", {}, 
template.TemplateSyntaxError),
             'if-tag-error12': ("{% if a not b %}yes{% endif %}", {}, 
template.TemplateSyntaxError),
 
+            # If evaluations are shortcircuited where possible
+            # These tests will fail by taking too long to run. When the if 
clause
+            # is shortcircuiting correctly, the is_bad() function shouldn't be
+            # evaluated, and the deliberate sleep won't happen.
+            'if-tag-shortcircuit01': ('{% if x.is_true or x.is_bad %}yes{% 
else %}no{% endif %}', {'x': TestObj()}, "yes"),
+            'if-tag-shortcircuit02': ('{% if x.is_false and x.is_bad %}yes{% 
else %}no{% endif %}', {'x': TestObj()}, "no"),
+
             # Non-existent args
             'if-tag-badarg01':("{% if x|default_if_none:y %}yes{% endif %}", 
{}, ''),
             'if-tag-badarg02':("{% if x|default_if_none:y %}yes{% endif %}", 
{'y': 0}, ''),

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

Reply via email to