Index: loader_tags.py
===================================================================
--- loader_tags.py	(revision 7049)
+++ loader_tags.py	(working copy)
@@ -1,6 +1,6 @@
-from django.template import TemplateSyntaxError, TemplateDoesNotExist, Variable
+from django.template import Template, TemplateSyntaxError, TemplateDoesNotExist, Variable
 from django.template import Library, Node
-from django.template.loader import get_template, get_template_from_string, find_template_source
+from django.template.loader import get_template
 from django.conf import settings
 from django.utils.safestring import mark_safe
 
@@ -10,8 +10,10 @@
     pass
 
 class BlockNode(Node):
-    def __init__(self, name, nodelist, parent=None):
-        self.name, self.nodelist, self.parent = name, nodelist, parent
+    def __init__(self, name, nodelist):
+        self.name = name
+        self.nodelist = nodelist
+        self.parent = None
 
     def __repr__(self):
         return "<Block Node: %s. Contents: %r>" % (self.name, self.nodelist)
@@ -30,57 +32,71 @@
             return mark_safe(self.parent.render(self.context))
         return ''
 
-    def add_parent(self, nodelist):
-        if self.parent:
-            self.parent.add_parent(nodelist)
-        else:
-            self.parent = BlockNode(self.name, nodelist)
+    def insert_as_child(self, parent):
+        eldest = self
+        while eldest.parent:
+            eldest = eldest.parent
+        eldest.parent = BlockNode(parent.name, parent.nodelist)
+        parent.nodelist = self.nodelist
+        parent.parent = self.parent
 
 class ExtendsNode(Node):
-    def __init__(self, nodelist, parent_name, parent_name_expr, template_dirs=None):
+    def __init__(self, nodelist, extended_name=None, extended_name_expr=None):
         self.nodelist = nodelist
-        self.parent_name, self.parent_name_expr = parent_name, parent_name_expr
-        self.template_dirs = template_dirs
+        self.extended_name = extended_name
+        self.extended_name_expr = extended_name_expr
 
-    def get_parent(self, context):
-        if self.parent_name_expr:
-            self.parent_name = self.parent_name_expr.resolve(context)
-        parent = self.parent_name
-        if not parent:
-            error_msg = "Invalid template name in 'extends' tag: %r." % parent
-            if self.parent_name_expr:
-                error_msg += " Got this from the %r variable." % self.parent_name_expr #TODO nice repr.
-            raise TemplateSyntaxError, error_msg
-        if hasattr(parent, 'render'):
-            return parent # parent is a Template object
+    def _get_extended_template(self, context):
+        extended_name = self.extended_name
+        if self.extended_name_expr:
+            resolved_expr = self.extended_name_expr.resolve(context)
+            if not resolved_expr:
+                raise TemplateSyntaxError("Can't resolve 'extends' tag argument '%r'" % self.extended_name_expr)
+            # If the expr resolves to a template, just return it.
+            if isinstance(resolved_expr, Template):
+                return resolved_expr
+            # Assume that resolved_expr is the extended templates name string.
+            extended_name = resolved_expr
         try:
-            source, origin = find_template_source(parent, self.template_dirs)
+            return get_template(extended_name)
+        # Translate does not exist errors to syntax errors for extends tag.
         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 TemplateSyntaxError("Can't find %r in 'extends' tag" % extended_name)
 
     def render(self, context):
-        compiled_parent = self.get_parent(context)
-        parent_is_child = isinstance(compiled_parent.nodelist[0], ExtendsNode)
-        parent_blocks = dict([(n.name, n) for n in compiled_parent.nodelist.get_nodes_by_type(BlockNode)])
-        for block_node in self.nodelist.get_nodes_by_type(BlockNode):
-            # Check for a BlockNode with this node's name, and replace it if found.
+        extended = self._get_extended_template(context)
+        extended_extends = isinstance(extended.nodelist[0], ExtendsNode)
+        extended_blocks = dict([(n.name, n) for n in extended.nodelist.get_nodes_by_type(BlockNode)])
+
+        # For each block node in this template.
+        for block in self.nodelist.get_nodes_by_type(BlockNode):
+            # Check extended template for a BlockNode with this block's name
+            # and replace it with this block node if found.
             try:
-                parent_block = parent_blocks[block_node.name]
+                extended_block = extended_blocks[block.name]
+            # If this BlockNode wasn't found in the extended template.
             except KeyError:
-                # This BlockNode wasn't found in the parent template, but the
-                # parent block might be defined in the parent's *parent*, so we
-                # add this BlockNode to the parent's ExtendsNode nodelist, so
-                # it'll be checked when the parent node's render() is called.
-                if parent_is_child:
-                    compiled_parent.nodelist[0].nodelist.append(block_node)
+                # If the extended template doesn't extend another,
+                # this block will not be rendered, so we can just drop it.
+                #
+                # If the extended template extends another,
+                # it might be defined higher up the hierarchy.
+                #
+                # For this case, we add this block
+                # to the extended templates ExtendsNode nodelist so it will
+                # be checked when the extended template is rendered.
+                if extended_extends:
+                    extended.nodelist[0].nodelist.append(block)
+            # Block was found in extended template
             else:
-                # Keep any existing parents and add a new one. Used by BlockNode.
-                parent_block.parent = block_node.parent
-                parent_block.add_parent(parent_block.nodelist)
-                parent_block.nodelist = block_node.nodelist
-        return compiled_parent.render(context)
+                # Make the extended block a parent of block,
+                # and replace the extended block with block
+                # in the template
+                block.insert_as_child(extended_block)
+        # Return the result of rendering the extended template
+        # now that blocks in the extended template have been replaced with
+        # blocks from this template.
+        return extended.render(context)
 
 class ConstantIncludeNode(Node):
     def __init__(self, template_path):
@@ -95,8 +111,7 @@
     def render(self, context):
         if self.template:
             return self.template.render(context)
-        else:
-            return ''
+        return ''
 
 class IncludeNode(Node):
     def __init__(self, template_name):
@@ -107,12 +122,15 @@
             template_name = self.template_name.resolve(context)
             t = get_template(template_name)
             return t.render(context)
-        except TemplateSyntaxError, e:
+        # If debugging templates, propagate any syntax errors,
+        # otherwise, just eat any any syntax errors.
+        except TemplateSyntaxError:
             if settings.TEMPLATE_DEBUG:
                 raise
-            return ''
+        # Eat any other errors that may have occurred.
         except:
-            return '' # Fail silently for invalid included templates.
+            pass
+        return ''
 
 def do_block(parser, token):
     """
@@ -120,19 +138,21 @@
     """
     bits = token.contents.split()
     if len(bits) != 2:
-        raise TemplateSyntaxError, "'%s' tag takes only one argument" % bits[0]
+        raise TemplateSyntaxError("'%s' tag takes only one argument" % bits[0])
     block_name = bits[1]
-    # Keep track of the names of BlockNodes found in this template, so we can
-    # check for duplication.
+    # Keep track of the BlockNodes found in this template,
+    # so we can prevent duplication and reuse them.
     try:
         if block_name in parser.__loaded_blocks:
-            raise TemplateSyntaxError, "'%s' tag with name '%s' appears more than once" % (bits[0], block_name)
-        parser.__loaded_blocks.append(block_name)
-    except AttributeError: # parser.__loaded_blocks isn't a list yet
-        parser.__loaded_blocks = [block_name]
+            raise TemplateSyntaxError("'%s' tag with name '%s' appears more than once" % (bits[0], block_name))
+    except AttributeError:
+        # this is the first block, make __loaded_blocks dict
+        parser.__loaded_blocks = {}
     nodelist = parser.parse(('endblock', 'endblock %s' % block_name))
     parser.delete_first_token()
-    return BlockNode(block_name, nodelist)
+    block_node = BlockNode(block_name, nodelist)
+    parser.__loaded_blocks[block_name] = block_node
+    return block_node
 
 def do_extends(parser, token):
     """
@@ -142,24 +162,24 @@
     uses the literal value "base" as the name of the parent template to extend,
     or ``{% extends variable %}`` uses the value of ``variable`` as either the
     name of the parent template to extend (if it evaluates to a string) or as
-    the parent tempate itelf (if it evaluates to a Template object).
+    the parent tempate itself (if it evaluates to a Template object).
     """
+    nodelist = parser.parse()
+    # Make reasonably sure that there are no other extends tags.
+    if nodelist.get_nodes_by_type(ExtendsNode):
+        raise TemplateSyntaxError("'%s' tag cannot appear more than once in the same template" % bits[0])
+
     bits = token.contents.split()
     if len(bits) != 2:
-        raise TemplateSyntaxError, "'%s' takes one argument" % bits[0]
-    parent_name, parent_name_expr = None, None
+        raise TemplateSyntaxError("'%s' takes one argument" % bits[0])
     if bits[1][0] in ('"', "'") and bits[1][-1] == bits[1][0]:
-        parent_name = bits[1][1:-1]
+        return ExtendsNode(nodelist, extended_name=bits[1][1:-1])
     else:
-        parent_name_expr = parser.compile_filter(bits[1])
-    nodelist = parser.parse()
-    if nodelist.get_nodes_by_type(ExtendsNode):
-        raise TemplateSyntaxError, "'%s' cannot appear more than once in the same template" % bits[0]
-    return ExtendsNode(nodelist, parent_name, parent_name_expr)
+        return ExtendsNode(nodelist, extended_name_expr=parser.compile_filter(bits[1]))
 
 def do_include(parser, token):
     """
-    Loads a template and renders it with the current context.
+    Load a template and render it with the current context.
 
     Example::
 
@@ -167,7 +187,7 @@
     """
     bits = token.contents.split()
     if len(bits) != 2:
-        raise TemplateSyntaxError, "%r tag takes one argument: the name of the template to be included" % bits[0]
+        raise TemplateSyntaxError("%r tag takes one argument: the name of the template to be included" % bits[0])
     path = bits[1]
     if path[0] in ('"', "'") and path[-1] == path[0]:
         return ConstantIncludeNode(path[1:-1])
