From df6ad35c56c270ea04865e43a83b1ff0f35a71f4 Mon Sep 17 00:00:00 2001 From: Chris Beaven Date: Sun, 19 Dec 2010 23:47:24 +0000 Subject: [PATCH] Fixed #7153 -- _resolve_lookup now does a better job of resolving callables and correctly catches all silent_variable_exceptions git-svn-id: http://code.djangoproject.com/svn/django/trunk@14992 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/template/base.py | 67 ++++++++++-------------- tests/regressiontests/templates/tests.py | 32 +++++++++++ 2 files changed, 61 insertions(+), 38 deletions(-) diff --git a/django/template/base.py b/django/template/base.py index 2a1d8be1ce..1440869098 100644 --- a/django/template/base.py +++ b/django/template/base.py @@ -674,46 +674,37 @@ class Variable(object): instead. """ current = context - for bit in self.lookups: - try: # dictionary lookup - current = current[bit] - except (TypeError, AttributeError, KeyError): - try: # attribute lookup - current = getattr(current, bit) - if callable(current): - if getattr(current, 'alters_data', False): - current = settings.TEMPLATE_STRING_IF_INVALID - else: - try: # method call (assuming no args required) - current = current() - except TypeError: # arguments *were* required - # GOTCHA: This will also catch any TypeError - # raised in the function itself. - current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call - except Exception, e: - if getattr(e, 'silent_variable_failure', False): - current = settings.TEMPLATE_STRING_IF_INVALID - else: - raise - except (TypeError, AttributeError): - try: # list-index lookup - current = current[int(bit)] - except (IndexError, # list index out of range - ValueError, # invalid literal for int() - KeyError, # current is a dict without `int(bit)` key - TypeError, # unsubscriptable object - ): - raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bit, current)) # missing attribute - except Exception, e: - if getattr(e, 'silent_variable_failure', False): + try: # catch-all for silent variable failures + for bit in self.lookups: + try: # dictionary lookup + current = current[bit] + except (TypeError, AttributeError, KeyError): + try: # attribute lookup + current = getattr(current, bit) + except (TypeError, AttributeError): + try: # list-index lookup + current = current[int(bit)] + except (IndexError, # list index out of range + ValueError, # invalid literal for int() + KeyError, # current is a dict without `int(bit)` key + TypeError, # unsubscriptable object + ): + raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bit, current)) # missing attribute + if callable(current): + if getattr(current, 'alters_data', False): current = settings.TEMPLATE_STRING_IF_INVALID else: - raise - except Exception, e: - if getattr(e, 'silent_variable_failure', False): - current = settings.TEMPLATE_STRING_IF_INVALID - else: - raise + try: # method call (assuming no args required) + current = current() + except TypeError: # arguments *were* required + # GOTCHA: This will also catch any TypeError + # raised in the function itself. + current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call + except Exception, e: + if getattr(e, 'silent_variable_failure', False): + current = settings.TEMPLATE_STRING_IF_INVALID + else: + raise return current diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 8f31bd4ad6..2bd6a878ca 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -91,6 +91,21 @@ class SomeClass: def method4(self): raise SomeOtherException + def __getitem__(self, key): + if key == 'silent_fail_key': + raise SomeException + elif key == 'noisy_fail_key': + raise SomeOtherException + raise KeyError + + def silent_fail_attribute(self): + raise SomeException + silent_fail_attribute = property(silent_fail_attribute) + + def noisy_fail_attribute(self): + raise SomeOtherException + noisy_fail_attribute = property(noisy_fail_attribute) + class OtherClass: def method(self): return "OtherClass.method" @@ -529,6 +544,12 @@ class Templates(unittest.TestCase): 'basic-syntax35': ("{{ 1 }}", {"1": "abc"}, "1"), 'basic-syntax36': ("{{ 1.2 }}", {"1": "abc"}, "1.2"), + # Call methods in the top level of the context + 'basic-syntax37': ('{{ callable }}', {"callable": lambda: "foo bar"}, "foo bar"), + + # Call methods returned from dictionary lookups + 'basic-syntax38': ('{{ var.callable }}', {"var": {"callable": lambda: "foo bar"}}, "foo bar"), + # List-index syntax allows a template to access a certain item of a subscriptable object. 'list-index01': ("{{ var.1 }}", {"var": ["first item", "second item"]}, "second item"), @@ -616,6 +637,17 @@ class Templates(unittest.TestCase): #filters should accept empty string constants 'filter-syntax20': ('{{ ""|default_if_none:"was none" }}', {}, ""), + # Fail silently for non-callable attribute and dict lookups which + # raise an exception with a "silent_variable_failure" attribute + 'filter-syntax21': (r'1{{ var.silent_fail_key }}2', {"var": SomeClass()}, ("12", "1INVALID2")), + 'filter-syntax22': (r'1{{ var.silent_fail_attribute }}2', {"var": SomeClass()}, ("12", "1INVALID2")), + + # In attribute and dict lookups that raise an unexpected exception + # without a "silent_variable_attribute" set to True, the exception + # propagates + 'filter-syntax23': (r'1{{ var.noisy_fail_key }}2', {"var": SomeClass()}, SomeOtherException), + 'filter-syntax24': (r'1{{ var.noisy_fail_attribute }}2', {"var": SomeClass()}, SomeOtherException), + ### COMMENT SYNTAX ######################################################## 'comment-syntax01': ("{# this is hidden #}hello", {}, "hello"), 'comment-syntax02': ("{# this is hidden #}hello{# foo #}", {}, "hello"),