### Eclipse Workspace Patch 1.0
#P Django trunk
Index: django/template/loader_tags.py
===================================================================
--- django/template/loader_tags.py	(revision 11453)
+++ django/template/loader_tags.py	(working copy)
@@ -6,7 +6,13 @@
 
 register = Library()
 
-class ExtendsError(Exception):
+class ExtendsError(TemplateSyntaxError):
+    pass
+
+class ExtendsRecursionError(ExtendsError):
+    pass
+
+class ExtendsNotFoundError(ExtendsError):
     pass
 
 class BlockNode(Node):
@@ -43,6 +49,7 @@
         self.nodelist = nodelist
         self.parent_name, self.parent_name_expr = parent_name, parent_name_expr
         self.template_dirs = template_dirs
+        self.seen_parent_names = []
 
     def __repr__(self):
         if self.parent_name_expr:
@@ -57,15 +64,26 @@
             error_msg = "Invalid template name in 'extends' tag: %r." % parent
             if self.parent_name_expr:
                 error_msg += " Got this from the '%s' variable." % self.parent_name_expr.token
-            raise TemplateSyntaxError, error_msg
+            raise ExtendsError(error_msg)
         if hasattr(parent, 'render'):
             return parent # parent is a Template object
+        if parent in self.seen_parent_names:
+            raise ExtendsRecursionError('Template %r cannot be extended, '
+                    'because it would cause a circular recursion' % parent)
         try:
             source, origin = find_template_source(parent, self.template_dirs)
         except TemplateDoesNotExist:
-            raise TemplateSyntaxError, "Template %r cannot be extended, because it doesn't exist" % parent
-        else:
-            return get_template_from_string(source, origin, parent)
+            raise ExtendsNotFoundError("Template %r cannot be extended, "
+                    "because it doesn't exist" % parent)
+        compiled_parent = get_template_from_string(source, origin, parent)
+        # Let the parent extends node know about seen names (to avoid circular
+        # recursion).
+        parent_extends_node = self.get_extends_node(compiled_parent)
+        if parent_extends_node:
+            seen_parent_names = self.seen_parent_names[:]
+            seen_parent_names.append(parent)
+            parent_extends_node.seen_parent_names.extend(seen_parent_names)
+        return compiled_parent
 
     def render(self, context):
         compiled_parent = self.get_parent(context)
@@ -81,14 +99,9 @@
                 # it'll be checked when the parent node's render() is called.
 
                 # Find out if the parent template has a parent itself
-                for node in compiled_parent.nodelist:
-                    if not isinstance(node, TextNode):
-                        # If the first non-text node is an extends, handle it.
-                        if isinstance(node, ExtendsNode):
-                            node.nodelist.append(block_node)
-                        # Extends must be the first non-text node, so once you find
-                        # the first non-text node you can stop looking. 
-                        break
+                node = self.get_extends_node(compiled_parent)
+                if node:
+                    node.nodelist.append(block_node)
             else:
                 # Keep any existing parents and add a new one. Used by BlockNode.
                 parent_block.parent = block_node.parent
@@ -96,6 +109,15 @@
                 parent_block.nodelist = block_node.nodelist
         return compiled_parent.render(context)
 
+    def get_extends_node(self, template):
+        for node in template.nodelist:
+            if not isinstance(node, TextNode):
+                if isinstance(node, ExtendsNode):
+                    return node
+                # Since extends must be the first non-text node, stop looking
+                # after the first one.
+                break
+
 class ConstantIncludeNode(Node):
     def __init__(self, template_path):
         try:
Index: tests/regressiontests/templates/tests.py
===================================================================
--- tests/regressiontests/templates/tests.py	(revision 11453)
+++ tests/regressiontests/templates/tests.py	(working copy)
@@ -15,6 +15,7 @@
 from django import template
 from django.core import urlresolvers
 from django.template import loader
+from django.template import loader_tags
 from django.template.loaders import app_directories, filesystem
 from django.utils.translation import activate, deactivate, ugettext as _
 from django.utils.safestring import mark_safe
@@ -215,7 +216,8 @@
             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
@@ -474,10 +476,10 @@
             ### EXCEPTIONS ############################################################
 
             # Raise exception for invalid template name
-            'exception01': ("{% extends 'nonexistent' %}", {}, template.TemplateSyntaxError),
+            'exception01': ("{% extends 'nonexistent' %}", {}, loader_tags.ExtendsNotFoundError),
 
             # Raise exception for invalid template name (in variable)
-            'exception02': ("{% extends nonexistent %}", {}, template.TemplateSyntaxError),
+            'exception02': ("{% extends nonexistent %}", {}, (loader_tags.ExtendsError, loader_tags.ExtendsNotFoundError)),
 
             # Raise exception for extra {% extends %} tags
             'exception03': ("{% extends 'inheritance01' %}{% block first %}2{% endblock %}{% extends 'inheritance16' %}", {}, template.TemplateSyntaxError),
@@ -802,6 +804,24 @@
             # Inheritance from a template with a space in its name should work.
             'inheritance29': ("{% extends 'inheritance 28' %}", {}, '!'),
 
+            ### CIRCULAR INHERITANCE EXCEPTIONS #######################################
+
+            # Self-extending template
+            'circularinheritance01': ("{% extends 'circularinheritance01' %}", {}, loader_tags.ExtendsRecursionError),
+
+            # Circular extending template
+            'circularinheritance02': ("{% extends 'circularinheritance04' %}", {}, loader_tags.ExtendsRecursionError),
+            'circularinheritance03': ("{% extends 'circularinheritance02' %}", {}, loader_tags.ExtendsRecursionError),
+            'circularinheritance04': ("{% extends 'circularinheritance03' %}", {}, loader_tags.ExtendsRecursionError),
+
+            # Self-extending template (by variable)
+            'circularinheritance05': ("{% extends var %}", {'var': 'circularinheritance05'}, loader_tags.ExtendsRecursionError),
+
+            # Circular template (by variable)
+            'circularinheritance06': ("{% extends var %}", {'var': 'circularinheritance08'}, loader_tags.ExtendsRecursionError),
+            'circularinheritance07': ("{% extends var %}", {'var': 'circularinheritance06'}, loader_tags.ExtendsRecursionError),
+            'circularinheritance08': ("{% extends var %}", {'var': 'circularinheritance07'}, loader_tags.ExtendsRecursionError),
+
             ### I18N ##################################################################
 
             # {% spaceless %} tag
