| 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) |