Django

Code

Ticket #6262: loader_tags.2.diff

File loader_tags.2.diff, 10.2 kB (added by mzzzzc, 11 months ago)

revised loader_tags.diff, fixes IncludeNode BlockContext handling

  • a/django/template/loader_tags.py

    old new  
    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 
     
    1010    pass 
    1111 
    1212class BlockNode(Node): 
    13     def __init__(self, name, nodelist, parent=None): 
    14         self.name, self.nodelist, self.parent = name, nodelist, paren
     13    def __init__(self, name, nodelist): 
     14        self.name, self.nodelist = name, nodelis
    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): 
     
    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 
     
    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