Commit fef0d25b authored by Russell Keith-Magee's avatar Russell Keith-Magee
Browse files

Fixed #13373 -- Ensured that {% if %} statements will short circuit template...

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.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@13001 bcc190cf-cafb-0310-a4f2-bffc1f526a37
parent ebfe9383
Loading
Loading
Loading
Loading
+17 −16
Original line number Diff line number Diff line
@@ -56,7 +56,7 @@ def infix(bp, func):

        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 prefix(bp, func):

        def eval(self, context):
            try:
                return func(self.first.eval(context))
                return func(context, self.first)
            except Exception:
                return False

@@ -91,20 +91,21 @@ def prefix(bp, func):
# 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:
+30 −1
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@ if __name__ == '__main__':
    settings.configure()

from datetime import datetime, timedelta
import time
import os
import sys
import traceback
@@ -97,6 +98,17 @@ class OtherClass:
    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 @@ class Templates(unittest.TestCase):
        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 @@ class Templates(unittest.TestCase):
                        start = datetime.now()
                        test_template = loader.get_template(name)
                        end = datetime.now()
                        output = self.render(test_template, vals)
                        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 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 @@ class Templates(unittest.TestCase):
            '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}, ''),