Browse Source

Fixed #12787: Correctly identify the template that does not exist when a template being extended includes another template that does not exist. Thanks to trigeek38 for the report.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@12792 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Karen Tracey 15 years ago
parent
commit
80e744945c

+ 9 - 2
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):
         """

+ 1 - 4
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)

+ 8 - 1
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
 

+ 1 - 0
tests/regressiontests/templates/templates/broken_base.html

@@ -0,0 +1 @@
+{% include "missing.html" %}

+ 1 - 0
tests/regressiontests/templates/templates/test_extends_error.html

@@ -0,0 +1 @@
+{% extends "broken_base.html" %}

+ 101 - 33
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
 
+        try:
+            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)
+
+            # 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),
+                '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),
+                '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
-        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)
-
-        # 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),
-            '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),
-            'Cached 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
+
+        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),