Django

Code

Ticket #6262: loader_tags.3.diff

File loader_tags.3.diff, 10.8 kB (added by mzzzzc, 10 months ago)

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

  • a/django/template/__init__.py

    old new  
    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: 
     
    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" 
  • a/django/template/loader_tags.py

    old new  
    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 
     
    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):