diff --git a/AUTHORS b/AUTHORS index e3db830b74..4d57503bf8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -104,7 +104,6 @@ answer newbie questions, and generally made Django that much better: mattycakes@gmail.com Jason McBrayer michael.mcewan@gmail.com - mir@noris.de mmarshall Eric Moritz Robin Munn @@ -121,12 +120,14 @@ answer newbie questions, and generally made Django that much better: plisk Daniel Poelzleithner J. Rademaker + Michael Radziej Brian Ray rhettg@gmail.com Oliver Rutherfurd Ivan Sagalaev (Maniac) David Schein Pete Shinners + SmileyChris sopel Thomas Steinacher Radek Švarz diff --git a/django/template/__init__.py b/django/template/__init__.py index faf31c1e46..c1d6ca1497 100644 --- a/django/template/__init__.py +++ b/django/template/__init__.py @@ -549,9 +549,12 @@ class FilterExpression(object): obj = resolve_variable(self.var, context) except VariableDoesNotExist: if ignore_failures: - return None + obj = None else: - return settings.TEMPLATE_STRING_IF_INVALID + if settings.TEMPLATE_STRING_IF_INVALID: + return settings.TEMPLATE_STRING_IF_INVALID + else: + obj = settings.TEMPLATE_STRING_IF_INVALID for func, args in self.filters: arg_vals = [] for lookup, arg in args: diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index d111ddca89..691b40f332 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -86,7 +86,7 @@ class ForNode(Node): parentloop = {} context.push() try: - values = self.sequence.resolve(context) + values = self.sequence.resolve(context, True) except VariableDoesNotExist: values = [] if values is None: @@ -212,13 +212,13 @@ class RegroupNode(Node): self.var_name = var_name def render(self, context): - obj_list = self.target.resolve(context) - if obj_list == '': # target_var wasn't found in context; fail silently + obj_list = self.target.resolve(context, True) + if obj_list == None: # target_var wasn't found in context; fail silently context[self.var_name] = [] return '' output = [] # list of dictionaries in the format {'grouper': 'key', 'list': [list of contents]} for obj in obj_list: - grouper = self.expression.resolve(Context({'var': obj})) + grouper = self.expression.resolve(Context({'var': obj}), True) # TODO: Is this a sensible way to determine equality? if output and repr(output[-1]['grouper']) == repr(grouper): output[-1]['list'].append(obj) diff --git a/docs/templates_python.txt b/docs/templates_python.txt index aa7fa901b0..8ca68d87d3 100644 --- a/docs/templates_python.txt +++ b/docs/templates_python.txt @@ -198,9 +198,19 @@ some things to keep in mind: How invalid variables are handled ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -If a variable doesn't exist, the template system inserts the value of the -``TEMPLATE_STRING_IF_INVALID`` setting, which is set to ``''`` (the empty -string) by default. +Generally, if a variable doesn't exist, the template system inserts the +value of the ``TEMPLATE_STRING_IF_INVALID`` setting, which is set to ``''`` +(the empty string) by default. + +Filters that are applied to an invalid variable will only be applied if +``TEMPLATE_STRING_IF_INVALID`` is set to ``''`` (the empty string). If +``TEMPLATE_STRING_IF_INVALID`` is set to any other value, variable +filters will be ignored. + +This behaviour is slightly different for the ``if``, ``for`` and ``regroup`` +template tags. If an invalid variable is provided to one of these template +tags, the variable will be interpreted as ``None``. Filters are always +applied to invalid variables within these template tags. Playing with Context objects ---------------------------- diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 2d1ce192ef..5a8dd2d6a2 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -84,7 +84,7 @@ class Templates(unittest.TestCase): 'basic-syntax03': ("{{ first }} --- {{ second }}", {"first" : 1, "second" : 2}, "1 --- 2"), # Fail silently when a variable is not found in the current context - 'basic-syntax04': ("as{{ missing }}df", {}, "asINVALIDdf"), + 'basic-syntax04': ("as{{ missing }}df", {}, ("asdf","asINVALIDdf")), # A variable may not contain more than one word 'basic-syntax06': ("{{ multi word variable }}", {}, template.TemplateSyntaxError), @@ -100,7 +100,7 @@ class Templates(unittest.TestCase): 'basic-syntax10': ("{{ var.otherclass.method }}", {"var": SomeClass()}, "OtherClass.method"), # Fail silently when a variable's attribute isn't found - 'basic-syntax11': ("{{ var.blech }}", {"var": SomeClass()}, "INVALID"), + 'basic-syntax11': ("{{ var.blech }}", {"var": SomeClass()}, ("","INVALID")), # Raise TemplateSyntaxError when trying to access a variable beginning with an underscore 'basic-syntax12': ("{{ var.__dict__ }}", {"var": SomeClass()}, template.TemplateSyntaxError), @@ -116,10 +116,10 @@ class Templates(unittest.TestCase): 'basic-syntax18': ("{{ foo.bar }}", {"foo" : {"bar" : "baz"}}, "baz"), # Fail silently when a variable's dictionary key isn't found - 'basic-syntax19': ("{{ foo.spam }}", {"foo" : {"bar" : "baz"}}, "INVALID"), + 'basic-syntax19': ("{{ foo.spam }}", {"foo" : {"bar" : "baz"}}, ("","INVALID")), # Fail silently when accessing a non-simple method - 'basic-syntax20': ("{{ var.method2 }}", {"var": SomeClass()}, "INVALID"), + 'basic-syntax20': ("{{ var.method2 }}", {"var": SomeClass()}, ("","INVALID")), # Basic filter usage 'basic-syntax21': ("{{ var|upper }}", {"var": "Django is the greatest!"}, "DJANGO IS THE GREATEST!"), @@ -158,7 +158,7 @@ class Templates(unittest.TestCase): 'basic-syntax32': (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'), # Fail silently for methods that raise an exception with a "silent_variable_failure" attribute - 'basic-syntax33': (r'1{{ var.method3 }}2', {"var": SomeClass()}, "1INVALID2"), + 'basic-syntax33': (r'1{{ var.method3 }}2', {"var": SomeClass()}, ("12", "1INVALID2")), # In methods that raise an exception without a "silent_variable_attribute" set to True, # the exception propogates @@ -464,6 +464,14 @@ class Templates(unittest.TestCase): # translation of a constant string 'i18n13': ('{{ _("Page not found") }}', {'LANGUAGE_CODE': 'de'}, 'Seite nicht gefunden'), + ### HANDLING OF TEMPLATE_TAG_IF_INVALID ################################### + + 'invalidstr01': ('{{ var|default:"Foo" }}', {}, ('Foo','INVALID')), + 'invalidstr02': ('{{ var|default_if_none:"Foo" }}', {}, ('','INVALID')), + 'invalidstr03': ('{% for v in var %}({{ v }}){% endfor %}', {}, ''), + 'invalidstr04': ('{% if var %}Yes{% else %}No{% endif %}', {}, 'No'), + 'invalidstr04': ('{% if var|default:"Foo" %}Yes{% else %}No{% endif %}', {}, 'Yes'), + ### MULTILINE ############################################################# 'multiline01': (""" @@ -507,7 +515,7 @@ class Templates(unittest.TestCase): '{{ item.foo }}' + \ '{% endfor %},' + \ '{% endfor %}', - {}, 'INVALID:INVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALID,'), + {}, ''), ### TEMPLATETAG TAG ####################################################### 'templatetag01': ('{% templatetag openblock %}', {}, '{%'), @@ -592,30 +600,44 @@ class Templates(unittest.TestCase): old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False # Set TEMPLATE_STRING_IF_INVALID to a known string - old_invalid, settings.TEMPLATE_STRING_IF_INVALID = settings.TEMPLATE_STRING_IF_INVALID, 'INVALID' + old_invalid = settings.TEMPLATE_STRING_IF_INVALID for name, vals in tests: install() + + if isinstance(vals[2], tuple): + normal_string_result = vals[2][0] + invalid_string_result = vals[2][1] + else: + normal_string_result = vals[2] + invalid_string_result = vals[2] + if 'LANGUAGE_CODE' in vals[1]: activate(vals[1]['LANGUAGE_CODE']) else: activate('en-us') - try: - output = loader.get_template(name).render(template.Context(vals[1])) - except Exception, e: - if e.__class__ != vals[2]: - failures.append("Template test: %s -- FAILED. Got %s, exception: %s" % (name, e.__class__, e)) - continue + + for invalid_str, result in [('', normal_string_result), + ('INVALID', invalid_string_result)]: + settings.TEMPLATE_STRING_IF_INVALID = invalid_str + try: + output = loader.get_template(name).render(template.Context(vals[1])) + except Exception, e: + if e.__class__ != result: + failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Got %s, exception: %s" % (invalid_str, name, e.__class__, e)) + continue + if output != result: + failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Expected %r, got %r" % (invalid_str, name, result, output)) + if 'LANGUAGE_CODE' in vals[1]: deactivate() - if output != vals[2]: - failures.append("Template test: %s -- FAILED. Expected %r, got %r" % (name, vals[2], output)) + loader.template_source_loaders = old_template_loaders deactivate() settings.TEMPLATE_DEBUG = old_td settings.TEMPLATE_STRING_IF_INVALID = old_invalid - self.assertEqual(failures, []) + self.assertEqual(failures, [], '\n'.join(failures)) if __name__ == "__main__": unittest.main()