12 | | class BlockNode(Node): |
13 | | def __init__(self, name, nodelist, parent=None): |
14 | | self.name, self.nodelist, self.parent = name, nodelist, parent |
| 12 | class BlockContext(object): |
| 13 | """ |
| 14 | BlockContext contains the block definitions for a chain of ExtendsNodes. |
| 15 | The BlockContext is dynamically generated during rendering because the |
| 16 | extends tag supports setting the parent template dynamically. |
| 17 | Unlike template.Context, BlockContext.get(name) returns the "first" |
| 18 | version added since that is defined in the youngest ancestor. |
| 19 | """ |
| 20 | def __init__(self, context): |
| 21 | self.context = context |
| 22 | self.dicts = [] |
| 23 | self.block_stack = [] |
17 | | return "<Block Node: %s. Contents: %r>" % (self.name, self.nodelist) |
18 | | |
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() |
26 | | return result |
| 26 | return 'BlockContext(%r, %r)' % (self.block_stack, self.dicts) |
| 27 | |
| 28 | def __getitem__(self, key): |
| 29 | "Get a block by name" |
| 30 | for d in self.dicts: |
| 31 | if key in d: |
| 32 | return d[key] |
| 33 | raise KeyError(key) |
| 34 | |
| 35 | def add_blocks(self, blocks_dict): |
| 36 | "Like Context.update() but puts blocks_dict last." |
| 37 | if not hasattr(blocks_dict, '__getitem__'): |
| 38 | raise TypeError('blocks_dict must be a mapping (dictionary-like) object.') |
| 39 | self.dicts.append(blocks_dict) |
| 40 | |
| 41 | def enter(self, block): |
| 42 | # TODO: check for loops |
| 43 | self.block_stack.append(block) |
| 44 | |
| 45 | def exit(self, block): |
| 46 | last = self.block_stack.pop() |
| 47 | if last is not block: |
| 48 | raise ValueError('Invalid block stack: %r' % (self.block_stack + [last])) |
| 49 | |
| 50 | def get_parent(self, block): |
| 51 | "Find the parent version of a given block, if any" |
| 52 | name = block.name |
| 53 | found = False |
| 54 | for d in self.dicts: |
| 55 | b = d.get(name, None) |
| 56 | if b: |
| 57 | if found: |
| 58 | return b |
| 59 | elif b is block: |
| 60 | found = True |
| 61 | return None |
33 | | def add_parent(self, nodelist): |
34 | | if self.parent: |
35 | | self.parent.add_parent(nodelist) |
| 71 | class BlockNode(Node): |
| 72 | def __init__(self, name, nodelist): |
| 73 | self.name, self.nodelist = name, nodelist |
| 74 | |
| 75 | def __repr__(self): |
| 76 | return "<Block Node: %s. Contents: %r>" % (self.name, self.nodelist) |
| 77 | |
| 78 | def render(self, context, force_self=False): |
| 79 | """ |
| 80 | Find the correct BlockNode and render its nodelist. |
| 81 | case 1: no extends tags encountered => render self |
| 82 | case 2: in block.super => render self |
| 83 | case 3: extends tags but not in block.super => render "youngest" version of this block |
| 84 | """ |
| 85 | block_context = context.get(BLOCK_CONTEXT_KEY, None) |
| 86 | if block_context is None: |
| 87 | # no extends tag encountered; block tags have no effect |
| 88 | return self.nodelist.render(context) |
| 89 | elif force_self: |
| 90 | block = self |
44 | | self.parent_name, self.parent_name_expr = parent_name, parent_name_expr |
45 | | self.template_dirs = template_dirs |
| 104 | self.block_nodes = dict([(n.name, n) for n in nodelist.get_nodes_by_type(BlockNode)]) |
| 105 | if parent_name: |
| 106 | try: |
| 107 | self.parent = get_template(parent_name) |
| 108 | except TemplateDoesNotExist: |
| 109 | error_msg = "Invalid template name in 'extends' tag: %r." % parent_name |
| 110 | raise TemplateSyntaxError, error_msg |
| 111 | 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 |
| 119 | if hasattr(self, 'parent'): |
| 120 | return self.parent |
| 121 | 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) |
| 132 | block_context = context.get(BLOCK_CONTEXT_KEY, None) |
| 133 | if block_context is None: |
| 134 | block_context = BlockContext(context) |
| 135 | context[BLOCK_CONTEXT_KEY] = block_context |
| 136 | block_context.add_blocks(self.block_nodes) |
| 137 | |
| 138 | parent = self.get_parent(context) |
| 139 | |
| 140 | # if parent is the base template, add all its blocks here |
| 141 | # we only need to check the first two nodes of the parent to find ExtendsNode |
| 142 | #if not parent.nodelist.get_nodes_by_type(ExtendsNode): |
| 143 | if not filter(lambda n : isinstance(n, ExtendsNode), parent.nodelist[:2]): |
| 144 | if not hasattr(parent, '_block_nodes'): |
| 145 | parent._block_nodes = dict([(n.name, n) for n in parent.nodelist.get_nodes_by_type(BlockNode)]) |
| 146 | block_context.add_blocks(parent._block_nodes) |
| 147 | |
| 148 | return parent.nodelist.render(context) |