+EMBED_CONTEXT_KEY = 'embed_context'
+
+
+class EmbedContext(object):
+ "Inspired by django.template.loader_tags.BlockContext."
+ def __init__(self):
+ self.embeds = {}
+ self.rendered = []
+
+ def add_embeds(self, embeds):
+ for content_type, embed_list in embeds.iteritems():
+ if content_type in self.embeds:
+ self.embeds[content_type] = embed_list + self.embeds[content_type]
+ else:
+ self.embeds[content_type] = embed_list
+
+ def get_embed_template(self, embed, context):
+ """To return a template for an embed node, find the node's position in the stack
+ and then progress up the stack until a template-defining node is found
+ """
+ ct = embed.get_content_type(context)
+ embeds = self.embeds[ct]
+ embeds = embeds[:embeds.index(embed)][::-1]
+ for e in embeds:
+ template = e.get_template(context)
+ if template:
+ return template
+
+ # No template was found in the current render_context - but perhaps one level up? Or more?
+ # We may be in an inclusion tag.
+ self_found = False
+ for context_dict in context.render_context.dicts[::-1]:
+ if not self_found:
+ if self in context_dict.values():
+ self_found = True
+ continue
+ elif EMBED_CONTEXT_KEY not in context_dict:
+ continue
+ else:
+ embed_context = context_dict[EMBED_CONTEXT_KEY]
+ # We can tell where we are in the list of embeds by which have already been rendered.
+ embeds = embed_context.embeds[ct][:len(embed_context.rendered)][::-1]
+ for e in embeds:
+ template = e.get_template(context)
+ if template:
+ return template
+
+ raise IndexError
+
+
+# Override ExtendsNode render method to have it handle EmbedNodes
+# similarly to BlockNodes.
+old_extends_node_init = ExtendsNode.__init__
+
+
+def get_embed_dict(embed_list, context):
+ embeds = {}
+ for e in embed_list:
+ ct = e.get_content_type(context)
+ if ct is None:
+ # Then the embed doesn't exist for this context.
+ continue
+ if ct not in embeds:
+ embeds[ct] = [e]
+ else:
+ embeds[ct].append(e)
+ return embeds
+
+
+def extends_node_init(self, nodelist, *args, **kwargs):
+ self.embed_list = nodelist.get_nodes_by_type(ConstantEmbedNode)
+ old_extends_node_init(self, nodelist, *args, **kwargs)
+
+
+def render_extends_node(self, context):
+ compiled_parent = self.get_parent(context)
+ embeds = get_embed_dict(self.embed_list, context)
+
+ if BLOCK_CONTEXT_KEY not in context.render_context:
+ context.render_context[BLOCK_CONTEXT_KEY] = BlockContext()
+ block_context = context.render_context[BLOCK_CONTEXT_KEY]
+
+ if EMBED_CONTEXT_KEY not in context.render_context:
+ context.render_context[EMBED_CONTEXT_KEY] = EmbedContext()
+ embed_context = context.render_context[EMBED_CONTEXT_KEY]
+
+ # Add the block nodes from this node to the block context
+ # Do the equivalent for embed nodes
+ block_context.add_blocks(self.blocks)
+ embed_context.add_embeds(embeds)
+
+ # If this block's parent doesn't have an extends node it is the root,
+ # and its block nodes also need to be added to the block context.
+ for node in compiled_parent.nodelist:
+ # The ExtendsNode has to be the first non-text node.
+ if not isinstance(node, TextNode):
+ if not isinstance(node, ExtendsNode):
+ blocks = dict([(n.name, n) for n in compiled_parent.nodelist.get_nodes_by_type(BlockNode)])
+ block_context.add_blocks(blocks)
+ embeds = get_embed_dict(compiled_parent.nodelist.get_nodes_by_type(ConstantEmbedNode), context)
+ embed_context.add_embeds(embeds)
+ break
+
+ # Explicitly render all direct embed children of this node.
+ if self.embed_list:
+ for node in self.nodelist:
+ if isinstance(node, ConstantEmbedNode):
+ node.render(context)
+
+ # Call Template._render explicitly so the parser context stays
+ # the same.
+ return compiled_parent._render(context)
+
+
+ExtendsNode.__init__ = extends_node_init
+ExtendsNode.render = render_extends_node