19 | | def render(self, context): |
20 | | context.push() |
21 | | # Save context in case of block.super(). |
22 | | self.context = context |
23 | | context['block'] = self |
24 | | result = self.nodelist.render(context) |
25 | | context.pop() |
| 19 | def render(self, context, force_self=False): |
| 20 | """ |
| 21 | Find the correct BlockNode and render its nodelist. |
| 22 | case 1: no extends tags encountered => render self |
| 23 | case 2: in block.super => render self |
| 24 | case 3: extends tags but not in block.super => render "youngest" version of this block |
| 25 | """ |
| 26 | block_context = context.get(BlockContext.KEY, None) |
| 27 | if block_context is None: |
| 28 | # no extends tag encountered; block tags have no effect |
| 29 | return self.nodelist.render(context) |
| 30 | elif force_self: |
| 31 | block = self |
| 32 | else: |
| 33 | block = block_context[self.name] |
| 34 | |
| 35 | block_context.enter(block) |
| 36 | result = block.nodelist.render(context) |
| 37 | block_context.exit(block) |
28 | | def super(self): |
29 | | if self.parent: |
30 | | return mark_safe(self.parent.render(self.context)) |
31 | | return '' |
| 40 | class BlockContext(object): |
| 41 | """ |
| 42 | BlockContext contains the block definitions for a chain of ExtendsNodes. |
| 43 | The BlockContext is dynamically generated during rendering because the |
| 44 | extends tag supports setting the parent template dynamically. |
| 45 | Unlike template.Context, BlockContext.get(name) returns the "first" |
| 46 | version added since that is defined in the youngest ancestor. |
| 47 | """ |
| 48 | KEY = 'block' # so that {{ block.super }} works |
| 49 | |
| 50 | def __init__(self, context): |
| 51 | self.context = context |
| 52 | self.dicts = [] |
| 53 | self.block_stack = [] |
| 54 | |
| 55 | def __repr__(self): |
| 56 | return 'BlockContext(%r, %r)' % (self.block_stack, self.dicts) |
| 57 | |
| 58 | def __getitem__(self, key): |
| 59 | "Get a block by name" |
| 60 | for d in self.dicts: |
| 61 | if key in d: |
| 62 | return d[key] |
| 63 | raise KeyError(key) |
| 64 | |
| 65 | def add_blocks(self, blocks_dict): |
| 66 | "Like Context.update() but puts blocks_dict last." |
| 67 | if not hasattr(blocks_dict, '__getitem__'): |
| 68 | raise TypeError('blocks_dict must be a mapping (dictionary-like) object.') |
| 69 | self.dicts.append(blocks_dict) |
| 70 | |
| 71 | def enter(self, block): |
| 72 | # TODO: check for loops |
| 73 | self.block_stack.append(block) |
| 74 | |
| 75 | def exit(self, block): |
| 76 | last = self.block_stack.pop() |
| 77 | if last is not block: |
| 78 | raise ValueError('Invalid block stack: %r' % (self.block_stack + [last])) |
| 79 | |
| 80 | def get_parent(self, block): |
| 81 | "Find the parent version of a given block, if any" |
| 82 | name = block.name |
| 83 | found = False |
| 84 | for d in self.dicts: |
| 85 | b = d.get(name, None) |
| 86 | if b: |
| 87 | if found: |
| 88 | return b |
| 89 | elif b is block: |
| 90 | found = True |
| 91 | return None |
44 | | self.parent_name, self.parent_name_expr = parent_name, parent_name_expr |
45 | | self.template_dirs = template_dirs |
| 106 | self.block_nodes = dict([(n.name, n) for n in nodelist.get_nodes_by_type(BlockNode)]) |
| 107 | if parent_name: |
| 108 | try: |
| 109 | self.parent = get_template(parent_name) |
| 110 | except TemplateDoesNotExist: |
| 111 | error_msg = "Invalid template name in 'extends' tag: %r." % parent_name |
| 112 | raise TemplateSyntaxError, error_msg |
| 113 | self.parent_name_expr = parent_name_expr |
53 | | if self.parent_name_expr: |
54 | | self.parent_name = self.parent_name_expr.resolve(context) |
55 | | parent = self.parent_name |
56 | | if not parent: |
57 | | error_msg = "Invalid template name in 'extends' tag: %r." % parent |
58 | | if self.parent_name_expr: |
59 | | error_msg += " Got this from the '%s' variable." % self.parent_name_expr.token |
60 | | raise TemplateSyntaxError, error_msg |
| 121 | if hasattr(self, 'parent'): |
| 122 | return self.parent |
| 123 | parent = self.parent_name_expr.resolve(context) |
71 | | compiled_parent = self.get_parent(context) |
72 | | parent_blocks = dict([(n.name, n) for n in compiled_parent.nodelist.get_nodes_by_type(BlockNode)]) |
73 | | for block_node in self.nodelist.get_nodes_by_type(BlockNode): |
74 | | # Check for a BlockNode with this node's name, and replace it if found. |
75 | | try: |
76 | | parent_block = parent_blocks[block_node.name] |
77 | | except KeyError: |
78 | | # This BlockNode wasn't found in the parent template, but the |
79 | | # parent block might be defined in the parent's *parent*, so we |
80 | | # add this BlockNode to the parent's ExtendsNode nodelist, so |
81 | | # it'll be checked when the parent node's render() is called. |
82 | | |
83 | | # Find out if the parent template has a parent itself |
84 | | for node in compiled_parent.nodelist: |
85 | | if not isinstance(node, TextNode): |
86 | | # If the first non-text node is an extends, handle it. |
87 | | if isinstance(node, ExtendsNode): |
88 | | node.nodelist.append(block_node) |
89 | | # Extends must be the first non-text node, so once you find |
90 | | # the first non-text node you can stop looking. |
91 | | break |
92 | | else: |
93 | | # Keep any existing parents and add a new one. Used by BlockNode. |
94 | | parent_block.parent = block_node.parent |
95 | | parent_block.add_parent(parent_block.nodelist) |
96 | | parent_block.nodelist = block_node.nodelist |
97 | | return compiled_parent.render(context) |
| 134 | block_context = context.get(BlockContext.KEY, None) |
| 135 | if block_context is None: |
| 136 | block_context = BlockContext(context) |
| 137 | context[BlockContext.KEY] = block_context |
| 138 | block_context.add_blocks(self.block_nodes) |
| 139 | |
| 140 | parent = self.get_parent(context) |
| 141 | |
| 142 | # if parent is the base template, add all its blocks here |
| 143 | # we only need to check the first two nodes of the parent to find ExtendsNode |
| 144 | #if not parent.nodelist.get_nodes_by_type(ExtendsNode): |
| 145 | if not filter(lambda n : isinstance(n, ExtendsNode), parent.nodelist[:2]): |
| 146 | if not hasattr(parent, '_block_nodes'): |
| 147 | parent._block_nodes = dict([(n.name, n) for n in parent.nodelist.get_nodes_by_type(BlockNode)]) |
| 148 | block_context.add_blocks(parent._block_nodes) |
| 149 | |
| 150 | return parent.render(context) |