diff --git a/django/template/loader.py b/django/template/loader.py index d81c896ad4..e171360e6a 100644 --- a/django/template/loader.py +++ b/django/template/loader.py @@ -44,8 +44,15 @@ class BaseLoader(object): def load_template(self, template_name, template_dirs=None): source, display_name = self.load_template_source(template_name, template_dirs) origin = make_origin(display_name, self.load_template_source, template_name, template_dirs) - template = get_template_from_string(source, origin, template_name) - return template, None + try: + template = get_template_from_string(source, origin, template_name) + return template, None + except TemplateDoesNotExist: + # If compiling the template we found raises TemplateDoesNotExist, back off to + # returning the source and display name for the template we were asked to load. + # This allows for correct identification (later) of the actual template that does + # not exist. + return source, display_name def load_template_source(self, template_name, template_dirs=None): """ diff --git a/django/template/loader_tags.py b/django/template/loader_tags.py index 0e9b39a3e9..b8bc741d41 100644 --- a/django/template/loader_tags.py +++ b/django/template/loader_tags.py @@ -97,10 +97,7 @@ class ExtendsNode(Node): raise TemplateSyntaxError(error_msg) if hasattr(parent, 'render'): return parent # parent is a Template object - try: - return get_template(parent) - except TemplateDoesNotExist: - raise TemplateSyntaxError("Template %r cannot be extended, because it doesn't exist" % parent) + return get_template(parent) def render(self, context): compiled_parent = self.get_parent(context) diff --git a/django/template/loaders/cached.py b/django/template/loaders/cached.py index 6209d766c0..788dad90bc 100644 --- a/django/template/loaders/cached.py +++ b/django/template/loaders/cached.py @@ -37,7 +37,14 @@ class Loader(BaseLoader): if template_name not in self.template_cache: template, origin = self.find_template(template_name, template_dirs) if not hasattr(template, 'render'): - template = get_template_from_string(template, origin, template_name) + try: + template = get_template_from_string(template, origin, template_name) + except TemplateDoesNotExist: + # If compiling the template we found raises TemplateDoesNotExist, + # back off to returning the source and display name for the template + # we were asked to load. This allows for correct identification (later) + # of the actual template that does not exist. + return template, origin self.template_cache[template_name] = template return self.template_cache[template_name], None diff --git a/tests/regressiontests/templates/templates/broken_base.html b/tests/regressiontests/templates/templates/broken_base.html new file mode 100644 index 0000000000..aa41f44de2 --- /dev/null +++ b/tests/regressiontests/templates/templates/broken_base.html @@ -0,0 +1 @@ +{% include "missing.html" %} diff --git a/tests/regressiontests/templates/templates/test_extends_error.html b/tests/regressiontests/templates/templates/test_extends_error.html new file mode 100755 index 0000000000..fc74690de6 --- /dev/null +++ b/tests/regressiontests/templates/templates/test_extends_error.html @@ -0,0 +1 @@ +{% extends "broken_base.html" %} diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index e603ec33c2..c3ffb6f81d 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -160,38 +160,106 @@ class Templates(unittest.TestCase): # Turn TEMPLATE_DEBUG on, so that the origin file name will be kept with # the compiled templates. old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, True - old_loaders = loader.template_source_loaders - loader.template_source_loaders = (filesystem.Loader(),) - # We rely on the fact that runtests.py sets up TEMPLATE_DIRS to - # point to a directory containing a 404.html file. Also that - # the file system and app directories loaders both inherit the - # load_template method from the BaseLoader class, so we only need - # to test one of them. - load_name = '404.html' - template = loader.get_template(load_name) - template_name = template.nodelist[0].source[0].name - self.assertTrue(template_name.endswith(load_name), - 'Template loaded by filesystem loader has incorrect name for debug page: %s' % template_name) + try: + loader.template_source_loaders = (filesystem.Loader(),) - # Aso test the cached loader, since it overrides load_template - cache_loader = cached.Loader(('',)) - cache_loader._cached_loaders = loader.template_source_loaders - loader.template_source_loaders = (cache_loader,) + # We rely on the fact that runtests.py sets up TEMPLATE_DIRS to + # point to a directory containing a 404.html file. Also that + # the file system and app directories loaders both inherit the + # load_template method from the BaseLoader class, so we only need + # to test one of them. + load_name = '404.html' + template = loader.get_template(load_name) + template_name = template.nodelist[0].source[0].name + self.assertTrue(template_name.endswith(load_name), + 'Template loaded by filesystem loader has incorrect name for debug page: %s' % template_name) - template = loader.get_template(load_name) - template_name = template.nodelist[0].source[0].name - self.assertTrue(template_name.endswith(load_name), - 'Template loaded through cached loader has incorrect name for debug page: %s' % template_name) + # Aso test the cached loader, since it overrides load_template + cache_loader = cached.Loader(('',)) + cache_loader._cached_loaders = loader.template_source_loaders + loader.template_source_loaders = (cache_loader,) - template = loader.get_template(load_name) - template_name = template.nodelist[0].source[0].name - self.assertTrue(template_name.endswith(load_name), - 'Cached template loaded through cached loader has incorrect name for debug page: %s' % template_name) + template = loader.get_template(load_name) + template_name = template.nodelist[0].source[0].name + self.assertTrue(template_name.endswith(load_name), + 'Template loaded through cached loader has incorrect name for debug page: %s' % template_name) - loader.template_source_loaders = old_loaders - settings.TEMPLATE_DEBUG = old_td + template = loader.get_template(load_name) + template_name = template.nodelist[0].source[0].name + self.assertTrue(template_name.endswith(load_name), + 'Cached template loaded through cached loader has incorrect name for debug page: %s' % template_name) + finally: + loader.template_source_loaders = old_loaders + settings.TEMPLATE_DEBUG = old_td + + def test_extends_include_missing_baseloader(self): + """ + Tests that the correct template is identified as not existing + when {% extends %} specifies a template that does exist, but + that template has an {% include %} of something that does not + exist. See #12787. + """ + + # TEMPLATE_DEBUG must be true, otherwise the exception raised + # during {% include %} processing will be suppressed. + old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, True + old_loaders = loader.template_source_loaders + + try: + # Test the base loader class via the app loader. load_template + # from base is used by all shipped loaders excepting cached, + # which has its own test. + loader.template_source_loaders = (app_directories.Loader(),) + + load_name = 'test_extends_error.html' + tmpl = loader.get_template(load_name) + r = None + try: + r = tmpl.render(template.Context({})) + except template.TemplateSyntaxError, e: + settings.TEMPLATE_DEBUG = old_td + self.assertEqual(e.args[0], 'Caught TemplateDoesNotExist while rendering: missing.html') + self.assertEqual(r, None, 'Template rendering unexpectedly succeeded, produced: ->%r<-' % r) + finally: + loader.template_source_loaders = old_loaders + settings.TEMPLATE_DEBUG = old_td + + def test_extends_include_missing_cachedloader(self): + """ + Same as test_extends_include_missing_baseloader, only tests + behavior of the cached loader instead of BaseLoader. + """ + + old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, True + old_loaders = loader.template_source_loaders + + try: + cache_loader = cached.Loader(('',)) + cache_loader._cached_loaders = (app_directories.Loader(),) + loader.template_source_loaders = (cache_loader,) + + load_name = 'test_extends_error.html' + tmpl = loader.get_template(load_name) + r = None + try: + r = tmpl.render(template.Context({})) + except template.TemplateSyntaxError, e: + self.assertEqual(e.args[0], 'Caught TemplateDoesNotExist while rendering: missing.html') + self.assertEqual(r, None, 'Template rendering unexpectedly succeeded, produced: ->%r<-' % r) + + # For the cached loader, repeat the test, to ensure the first attempt did not cache a + # result that behaves incorrectly on subsequent attempts. + tmpl = loader.get_template(load_name) + try: + tmpl.render(template.Context({})) + except template.TemplateSyntaxError, e: + self.assertEqual(e.args[0], 'Caught TemplateDoesNotExist while rendering: missing.html') + self.assertEqual(r, None, 'Template rendering unexpectedly succeeded, produced: ->%r<-' % r) + finally: + loader.template_source_loaders = old_loaders + settings.TEMPLATE_DEBUG = old_td def test_token_smart_split(self): # Regression test for #7027 @@ -269,7 +337,7 @@ class Templates(unittest.TestCase): if isinstance(vals[2], tuple): normal_string_result = vals[2][0] invalid_string_result = vals[2][1] - if '%s' in invalid_string_result: + if isinstance(invalid_string_result, basestring) and '%s' in invalid_string_result: expected_invalid_str = 'INVALID %s' invalid_string_result = invalid_string_result % vals[2][2] template.invalid_var_format_string = True @@ -530,10 +598,10 @@ class Templates(unittest.TestCase): ### EXCEPTIONS ############################################################ # Raise exception for invalid template name - 'exception01': ("{% extends 'nonexistent' %}", {}, template.TemplateSyntaxError), + 'exception01': ("{% extends 'nonexistent' %}", {}, template.TemplateDoesNotExist), # Raise exception for invalid template name (in variable) - 'exception02': ("{% extends nonexistent %}", {}, template.TemplateSyntaxError), + 'exception02': ("{% extends nonexistent %}", {}, (template.TemplateSyntaxError, template.TemplateDoesNotExist)), # Raise exception for extra {% extends %} tags 'exception03': ("{% extends 'inheritance01' %}{% block first %}2{% endblock %}{% extends 'inheritance16' %}", {}, template.TemplateSyntaxError),