Ticket #6262: loader_tags.3.diff

File loader_tags.3.diff, 10.8 KB (added by mzzzzc, 16 years ago)

revised loader_tags.diff, reset BlockContext in !Template.render()

  • django/template/__init__.py

    diff --git a/django/template/__init__.py b/django/template/__init__.py
    index f84a1ce..4a08f7b 100644
    a b builtins = []  
    100100# uninitialised.
    101101invalid_var_format_string = None
    102102
     103# location in template render context to store BlockContext object
     104# 'block' is used to enable {{ block.super }}
     105# see django.template.loader_tags for more information
     106BLOCK_CONTEXT_KEY = 'block'
     107
    103108class TemplateSyntaxError(Exception):
    104109    def __str__(self):
    105110        try:
    class Template(object):  
    173178
    174179    def render(self, context):
    175180        "Display stage -- can be called many times"
    176         return self.nodelist.render(context)
     181        # Clear BlockContext when entering a template,
     182        # reset it when we are done.
     183        # This needs to apply to templates included within a template,
     184        # or rendering multiple templates with the same context.
     185        block_context = context.get(BLOCK_CONTEXT_KEY, None)
     186        if block_context is None:
     187            return self.nodelist.render(context)
     188        else:
     189            context[BLOCK_CONTEXT_KEY] = None
     190            result = self.nodelist.render(context)
     191            # this may result in multiple references to block_context in the context stack
     192            # but that is OK since they all point to the same object
     193            context[BLOCK_CONTEXT_KEY] = block_context
     194            return result
    177195
    178196def compile_string(template_string, origin):
    179197    "Compiles template_string into NodeList ready for rendering"
  • django/template/loader_tags.py

    diff --git a/django/template/loader_tags.py b/django/template/loader_tags.py
    index 38b2fff..6a8f0d3 100644
    a b  
    11from django.template import TemplateSyntaxError, TemplateDoesNotExist, Variable
    2 from django.template import Library, Node, TextNode
    3 from django.template.loader import get_template, get_template_from_string, find_template_source
     2from django.template import Library, Node, TextNode, BLOCK_CONTEXT_KEY
     3from django.template.loader import get_template
    44from django.conf import settings
    55from django.utils.safestring import mark_safe
    66
    register = Library()  
    99class ExtendsError(Exception):
    1010    pass
    1111
    12 class BlockNode(Node):
    13     def __init__(self, name, nodelist, parent=None):
    14         self.name, self.nodelist, self.parent = name, nodelist, parent
     12class 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 = []
    1524
    1625    def __repr__(self):
    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
    2762
    2863    def super(self):
    29         if self.parent:
    30             return mark_safe(self.parent.render(self.context))
    31         return ''
     64        "Implement block.super"
     65        parent = self.get_parent(self.block_stack[-1])
     66        if parent:
     67            return mark_safe(parent.render(self.context, force_self=True))
     68        else:
     69            return u''
    3270
    33     def add_parent(self, nodelist):
    34         if self.parent:
    35             self.parent.add_parent(nodelist)
     71class 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
    3691        else:
    37             self.parent = BlockNode(self.name, nodelist)
     92            block = block_context[self.name]
     93
     94        block_context.enter(block)
     95        result = block.nodelist.render(context)
     96        block_context.exit(block)
     97        return result
    3898
    3999class ExtendsNode(Node):
    40100    must_be_first = True
    41101
    42102    def __init__(self, nodelist, parent_name, parent_name_expr, template_dirs=None):
    43103        self.nodelist = nodelist
    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
    46112
    47113    def __repr__(self):
    48114        if self.parent_name_expr:
    49115            return "<ExtendsNode: extends %s>" % self.parent_name_expr.token
    50         return '<ExtendsNode: extends "%s">' % self.parent_name
     116        return '<ExtendsNode: extends "%s">' % self.parent.name
    51117
    52118    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
     119        if hasattr(self, 'parent'):
     120            return self.parent
     121        parent = self.parent_name_expr.resolve(context)
    61122        if hasattr(parent, 'render'):
    62123            return parent # parent is a Template object
    63124        try:
    64             source, origin = find_template_source(parent, self.template_dirs)
     125            return get_template(parent)
    65126        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)
     127            error_msg = "Invalid template name in 'extends' tag: %r." % parent
     128            error_msg += " Got this from the '%s' variable." % self.parent_name_expr.token
     129            raise TemplateSyntaxError, error_msg
    69130
    70131    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)
     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)
    98149
    99150class ConstantIncludeNode(Node):
    100151    def __init__(self, template_path):
Back to Top