Ticket #6262: loader_tags.2.diff

File loader_tags.2.diff, 10.2 KB (added by mzzzzc, 6 years ago)

revised loader_tags.diff, fixes IncludeNode BlockContext handling

  • django/template/loader_tags.py

    diff --git a/django/template/loader_tags.py b/django/template/loader_tags.py
    index 38b2fff..39050f5 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 = []
    3254
    33     def add_parent(self, nodelist):
    34         if self.parent:
    35             self.parent.add_parent(nodelist)
     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
     92
     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)
     151
     152
     153def _render_include(template, context):
     154    """
     155    Don't pass block context into included templates.
     156    """
     157    block_context = context.get(BlockContext.KEY, None)
     158    if block_context is None:
     159        return template.render(context)
     160    else:
     161        context[BlockContext.KEY] = None
     162        result = template.render(context)
     163        # this may result in multiple references to block_context in the context stack
     164        # but that is OK since they all point to the same object
     165        context[BlockContext.KEY] = block_context
     166        return result
    98167
    99168class ConstantIncludeNode(Node):
    100169    def __init__(self, template_path):
    class ConstantIncludeNode(Node): 
    108177
    109178    def render(self, context):
    110179        if self.template:
    111             return self.template.render(context)
     180            return _render_include(self.template, context)
    112181        else:
    113182            return ''
    114183
    class IncludeNode(Node): 
    120189        try:
    121190            template_name = self.template_name.resolve(context)
    122191            t = get_template(template_name)
    123             return t.render(context)
     192            return _render_include(t, context)
    124193        except TemplateSyntaxError, e:
    125194            if settings.TEMPLATE_DEBUG:
    126195                raise
Back to Top