Ticket #6262: loader_tags.diff

File loader_tags.diff, 9.1 KB (added by cainmatt@…, 6 years ago)

Re-written BlockNode and ExtendsNode to not update nodelist in render()

  • django/template/loader_tags.py

    diff --git a/django/template/loader_tags.py b/django/template/loader_tags.py
    index 38b2fff..d688983 100644
    a b  
    11from django.template import TemplateSyntaxError, TemplateDoesNotExist, Variable
    22from django.template import Library, Node, TextNode
    3 from django.template.loader import get_template, get_template_from_string, find_template_source
     3from django.template.loader import get_template
    44from django.conf import settings
    55from django.utils.safestring import mark_safe
    66
    class ExtendsError(Exception): 
    1010    pass
    1111
    1212class BlockNode(Node):
    13     def __init__(self, name, nodelist, parent=None):
    14         self.name, self.nodelist, self.parent = name, nodelist, parent
     13    def __init__(self, name, nodelist):
     14        self.name, self.nodelist = name, nodelist
    1515
    1616    def __repr__(self):
    1717        return "<Block Node: %s. Contents: %r>" % (self.name, self.nodelist)
    1818
    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)
    2638        return result
    2739
    28     def super(self):
    29         if self.parent:
    30             return mark_safe(self.parent.render(self.context))
    31         return ''
     40class 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
    3292
    33     def add_parent(self, nodelist):
    34         if self.parent:
    35             self.parent.add_parent(nodelist)
     93    def super(self):
     94        "Implement block.super"
     95        parent = self.get_parent(self.block_stack[-1])
     96        if parent:
     97            return mark_safe(parent.render(self.context, force_self=True))
    3698        else:
    37             self.parent = BlockNode(self.name, nodelist)
     99            return u''
    38100
    39101class ExtendsNode(Node):
    40102    must_be_first = True
    41103
    42104    def __init__(self, nodelist, parent_name, parent_name_expr, template_dirs=None):
    43105        self.nodelist = nodelist
    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
    46114
    47115    def __repr__(self):
    48116        if self.parent_name_expr:
    49117            return "<ExtendsNode: extends %s>" % self.parent_name_expr.token
    50         return '<ExtendsNode: extends "%s">' % self.parent_name
     118        return '<ExtendsNode: extends "%s">' % self.parent.name
    51119
    52120    def get_parent(self, context):
    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)
    61124        if hasattr(parent, 'render'):
    62125            return parent # parent is a Template object
    63126        try:
    64             source, origin = find_template_source(parent, self.template_dirs)
     127            return get_template(parent)
    65128        except TemplateDoesNotExist:
    66             raise TemplateSyntaxError, "Template %r cannot be extended, because it doesn't exist" % parent
    67         else:
    68             return get_template_from_string(source, origin, parent)
     129            error_msg = "Invalid template name in 'extends' tag: %r." % parent
     130            error_msg += " Got this from the '%s' variable." % self.parent_name_expr.token
     131            raise TemplateSyntaxError, error_msg
    69132
    70133    def render(self, 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)
    98151
    99152class ConstantIncludeNode(Node):
    100153    def __init__(self, template_path):
Back to Top