Code

Ticket #6262: loader_tags.3.diff

File loader_tags.3.diff, 10.8 KB (added by mzzzzc, 5 years ago)

revised loader_tags.diff, reset BlockContext in !Template.render()

Line 
1diff --git a/django/template/__init__.py b/django/template/__init__.py
2index f84a1ce..4a08f7b 100644
3--- a/django/template/__init__.py
4+++ b/django/template/__init__.py
5@@ -100,6 +100,11 @@ builtins = []
6 # uninitialised.
7 invalid_var_format_string = None
8 
9+# location in template render context to store BlockContext object
10+# 'block' is used to enable {{ block.super }}
11+# see django.template.loader_tags for more information
12+BLOCK_CONTEXT_KEY = 'block'
13+
14 class TemplateSyntaxError(Exception):
15     def __str__(self):
16         try:
17@@ -173,7 +178,20 @@ class Template(object):
18 
19     def render(self, context):
20         "Display stage -- can be called many times"
21-        return self.nodelist.render(context)
22+        # Clear BlockContext when entering a template,
23+        # reset it when we are done.
24+        # This needs to apply to templates included within a template,
25+        # or rendering multiple templates with the same context.
26+        block_context = context.get(BLOCK_CONTEXT_KEY, None)
27+        if block_context is None:
28+            return self.nodelist.render(context)
29+        else:
30+            context[BLOCK_CONTEXT_KEY] = None
31+            result = self.nodelist.render(context)
32+            # this may result in multiple references to block_context in the context stack
33+            # but that is OK since they all point to the same object
34+            context[BLOCK_CONTEXT_KEY] = block_context
35+            return result
36 
37 def compile_string(template_string, origin):
38     "Compiles template_string into NodeList ready for rendering"
39diff --git a/django/template/loader_tags.py b/django/template/loader_tags.py
40index 38b2fff..6a8f0d3 100644
41--- a/django/template/loader_tags.py
42+++ b/django/template/loader_tags.py
43@@ -1,6 +1,6 @@
44 from django.template import TemplateSyntaxError, TemplateDoesNotExist, Variable
45-from django.template import Library, Node, TextNode
46-from django.template.loader import get_template, get_template_from_string, find_template_source
47+from django.template import Library, Node, TextNode, BLOCK_CONTEXT_KEY
48+from django.template.loader import get_template
49 from django.conf import settings
50 from django.utils.safestring import mark_safe
51 
52@@ -9,92 +9,143 @@ register = Library()
53 class ExtendsError(Exception):
54     pass
55 
56-class BlockNode(Node):
57-    def __init__(self, name, nodelist, parent=None):
58-        self.name, self.nodelist, self.parent = name, nodelist, parent
59+class BlockContext(object):
60+    """
61+    BlockContext contains the block definitions for a chain of ExtendsNodes.
62+    The BlockContext is dynamically generated during rendering because the
63+    extends tag supports setting the parent template dynamically.
64+    Unlike template.Context, BlockContext.get(name) returns the "first"
65+    version added since that is defined in the youngest ancestor.
66+    """
67+    def __init__(self, context):
68+        self.context = context
69+        self.dicts = []
70+        self.block_stack = []
71 
72     def __repr__(self):
73-        return "<Block Node: %s. Contents: %r>" % (self.name, self.nodelist)
74-
75-    def render(self, context):
76-        context.push()
77-        # Save context in case of block.super().
78-        self.context = context
79-        context['block'] = self
80-        result = self.nodelist.render(context)
81-        context.pop()
82-        return result
83+        return 'BlockContext(%r, %r)' % (self.block_stack, self.dicts)
84+
85+    def __getitem__(self, key):
86+        "Get a block by name"
87+        for d in self.dicts:
88+            if key in d:
89+                return d[key]
90+        raise KeyError(key)
91+
92+    def add_blocks(self, blocks_dict):
93+        "Like Context.update() but puts blocks_dict last."
94+        if not hasattr(blocks_dict, '__getitem__'):
95+            raise TypeError('blocks_dict must be a mapping (dictionary-like) object.')
96+        self.dicts.append(blocks_dict)
97+
98+    def enter(self, block):
99+        # TODO: check for loops
100+        self.block_stack.append(block)
101+
102+    def exit(self, block):
103+        last = self.block_stack.pop()
104+        if last is not block:
105+            raise ValueError('Invalid block stack: %r' % (self.block_stack + [last]))
106+
107+    def get_parent(self, block):
108+        "Find the parent version of a given block, if any"
109+        name = block.name
110+        found = False
111+        for d in self.dicts:
112+            b = d.get(name, None)
113+            if b:
114+                if found:
115+                    return b
116+                elif b is block:
117+                    found = True
118+        return None
119 
120     def super(self):
121-        if self.parent:
122-            return mark_safe(self.parent.render(self.context))
123-        return ''
124+        "Implement block.super"
125+        parent = self.get_parent(self.block_stack[-1])
126+        if parent:
127+            return mark_safe(parent.render(self.context, force_self=True))
128+        else:
129+            return u''
130 
131-    def add_parent(self, nodelist):
132-        if self.parent:
133-            self.parent.add_parent(nodelist)
134+class BlockNode(Node):
135+    def __init__(self, name, nodelist):
136+        self.name, self.nodelist = name, nodelist
137+
138+    def __repr__(self):
139+        return "<Block Node: %s. Contents: %r>" % (self.name, self.nodelist)
140+
141+    def render(self, context, force_self=False):
142+        """
143+        Find the correct BlockNode and render its nodelist.
144+        case 1: no extends tags encountered => render self
145+        case 2: in block.super => render self
146+        case 3: extends tags but not in block.super => render "youngest" version of this block
147+        """
148+        block_context = context.get(BLOCK_CONTEXT_KEY, None)
149+        if block_context is None:
150+            # no extends tag encountered; block tags have no effect
151+            return self.nodelist.render(context)
152+        elif force_self:
153+            block = self
154         else:
155-            self.parent = BlockNode(self.name, nodelist)
156+            block = block_context[self.name]
157+
158+        block_context.enter(block)
159+        result = block.nodelist.render(context)
160+        block_context.exit(block)
161+        return result
162 
163 class ExtendsNode(Node):
164     must_be_first = True
165 
166     def __init__(self, nodelist, parent_name, parent_name_expr, template_dirs=None):
167         self.nodelist = nodelist
168-        self.parent_name, self.parent_name_expr = parent_name, parent_name_expr
169-        self.template_dirs = template_dirs
170+        self.block_nodes = dict([(n.name, n) for n in nodelist.get_nodes_by_type(BlockNode)])
171+        if parent_name:
172+            try:
173+                self.parent = get_template(parent_name)
174+            except TemplateDoesNotExist:
175+                error_msg = "Invalid template name in 'extends' tag: %r." % parent_name
176+                raise TemplateSyntaxError, error_msg
177+        self.parent_name_expr = parent_name_expr
178 
179     def __repr__(self):
180         if self.parent_name_expr:
181             return "<ExtendsNode: extends %s>" % self.parent_name_expr.token
182-        return '<ExtendsNode: extends "%s">' % self.parent_name
183+        return '<ExtendsNode: extends "%s">' % self.parent.name
184 
185     def get_parent(self, context):
186-        if self.parent_name_expr:
187-            self.parent_name = self.parent_name_expr.resolve(context)
188-        parent = self.parent_name
189-        if not parent:
190-            error_msg = "Invalid template name in 'extends' tag: %r." % parent
191-            if self.parent_name_expr:
192-                error_msg += " Got this from the '%s' variable." % self.parent_name_expr.token
193-            raise TemplateSyntaxError, error_msg
194+        if hasattr(self, 'parent'):
195+            return self.parent
196+        parent = self.parent_name_expr.resolve(context)
197         if hasattr(parent, 'render'):
198             return parent # parent is a Template object
199         try:
200-            source, origin = find_template_source(parent, self.template_dirs)
201+            return get_template(parent)
202         except TemplateDoesNotExist:
203-            raise TemplateSyntaxError, "Template %r cannot be extended, because it doesn't exist" % parent
204-        else:
205-            return get_template_from_string(source, origin, parent)
206+            error_msg = "Invalid template name in 'extends' tag: %r." % parent
207+            error_msg += " Got this from the '%s' variable." % self.parent_name_expr.token
208+            raise TemplateSyntaxError, error_msg
209 
210     def render(self, context):
211-        compiled_parent = self.get_parent(context)
212-        parent_blocks = dict([(n.name, n) for n in compiled_parent.nodelist.get_nodes_by_type(BlockNode)])
213-        for block_node in self.nodelist.get_nodes_by_type(BlockNode):
214-            # Check for a BlockNode with this node's name, and replace it if found.
215-            try:
216-                parent_block = parent_blocks[block_node.name]
217-            except KeyError:
218-                # This BlockNode wasn't found in the parent template, but the
219-                # parent block might be defined in the parent's *parent*, so we
220-                # add this BlockNode to the parent's ExtendsNode nodelist, so
221-                # it'll be checked when the parent node's render() is called.
222-
223-                # Find out if the parent template has a parent itself
224-                for node in compiled_parent.nodelist:
225-                    if not isinstance(node, TextNode):
226-                        # If the first non-text node is an extends, handle it.
227-                        if isinstance(node, ExtendsNode):
228-                            node.nodelist.append(block_node)
229-                        # Extends must be the first non-text node, so once you find
230-                        # the first non-text node you can stop looking.
231-                        break
232-            else:
233-                # Keep any existing parents and add a new one. Used by BlockNode.
234-                parent_block.parent = block_node.parent
235-                parent_block.add_parent(parent_block.nodelist)
236-                parent_block.nodelist = block_node.nodelist
237-        return compiled_parent.render(context)
238+        block_context = context.get(BLOCK_CONTEXT_KEY, None)
239+        if block_context is None:
240+            block_context = BlockContext(context)
241+            context[BLOCK_CONTEXT_KEY] = block_context
242+        block_context.add_blocks(self.block_nodes)
243+
244+        parent = self.get_parent(context)
245+
246+        # if parent is the base template, add all its blocks here
247+        # we only need to check the first two nodes of the parent to find ExtendsNode
248+        #if not parent.nodelist.get_nodes_by_type(ExtendsNode):
249+        if not filter(lambda n : isinstance(n, ExtendsNode), parent.nodelist[:2]):
250+            if not hasattr(parent, '_block_nodes'):
251+                parent._block_nodes = dict([(n.name, n) for n in parent.nodelist.get_nodes_by_type(BlockNode)])
252+            block_context.add_blocks(parent._block_nodes)
253+
254+        return parent.nodelist.render(context)
255 
256 class ConstantIncludeNode(Node):
257     def __init__(self, template_path):