app/django/template/loader_tags.py
changeset 54 03e267d67478
child 323 ff1a9aa48cfd
equal deleted inserted replaced
53:57b4279d8c4e 54:03e267d67478
       
     1 from 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
       
     4 from django.conf import settings
       
     5 from django.utils.safestring import mark_safe
       
     6 
       
     7 register = Library()
       
     8 
       
     9 class ExtendsError(Exception):
       
    10     pass
       
    11 
       
    12 class BlockNode(Node):
       
    13     def __init__(self, name, nodelist, parent=None):
       
    14         self.name, self.nodelist, self.parent = name, nodelist, parent
       
    15 
       
    16     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
       
    27 
       
    28     def super(self):
       
    29         if self.parent:
       
    30             return mark_safe(self.parent.render(self.context))
       
    31         return ''
       
    32 
       
    33     def add_parent(self, nodelist):
       
    34         if self.parent:
       
    35             self.parent.add_parent(nodelist)
       
    36         else:
       
    37             self.parent = BlockNode(self.name, nodelist)
       
    38 
       
    39 class ExtendsNode(Node):
       
    40     must_be_first = True
       
    41 
       
    42     def __init__(self, nodelist, parent_name, parent_name_expr, template_dirs=None):
       
    43         self.nodelist = nodelist
       
    44         self.parent_name, self.parent_name_expr = parent_name, parent_name_expr
       
    45         self.template_dirs = template_dirs
       
    46 
       
    47     def __repr__(self):
       
    48         if self.parent_name_expr:
       
    49             return "<ExtendsNode: extends %s>" % self.parent_name_expr.token
       
    50         return '<ExtendsNode: extends "%s">' % self.parent_name
       
    51 
       
    52     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
       
    61         if hasattr(parent, 'render'):
       
    62             return parent # parent is a Template object
       
    63         try:
       
    64             source, origin = find_template_source(parent, self.template_dirs)
       
    65         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)
       
    69 
       
    70     def render(self, context):
       
    71         compiled_parent = self.get_parent(context)
       
    72         pos = 0
       
    73         while isinstance(compiled_parent.nodelist[pos], TextNode):
       
    74             pos += 1
       
    75         parent_is_child = isinstance(compiled_parent.nodelist[pos], ExtendsNode)
       
    76         parent_blocks = dict([(n.name, n) for n in compiled_parent.nodelist.get_nodes_by_type(BlockNode)])
       
    77         for block_node in self.nodelist.get_nodes_by_type(BlockNode):
       
    78             # Check for a BlockNode with this node's name, and replace it if found.
       
    79             try:
       
    80                 parent_block = parent_blocks[block_node.name]
       
    81             except KeyError:
       
    82                 # This BlockNode wasn't found in the parent template, but the
       
    83                 # parent block might be defined in the parent's *parent*, so we
       
    84                 # add this BlockNode to the parent's ExtendsNode nodelist, so
       
    85                 # it'll be checked when the parent node's render() is called.
       
    86                 if parent_is_child:
       
    87                     compiled_parent.nodelist[pos].nodelist.append(block_node)
       
    88             else:
       
    89                 # Keep any existing parents and add a new one. Used by BlockNode.
       
    90                 parent_block.parent = block_node.parent
       
    91                 parent_block.add_parent(parent_block.nodelist)
       
    92                 parent_block.nodelist = block_node.nodelist
       
    93         return compiled_parent.render(context)
       
    94 
       
    95 class ConstantIncludeNode(Node):
       
    96     def __init__(self, template_path):
       
    97         try:
       
    98             t = get_template(template_path)
       
    99             self.template = t
       
   100         except:
       
   101             if settings.TEMPLATE_DEBUG:
       
   102                 raise
       
   103             self.template = None
       
   104 
       
   105     def render(self, context):
       
   106         if self.template:
       
   107             return self.template.render(context)
       
   108         else:
       
   109             return ''
       
   110 
       
   111 class IncludeNode(Node):
       
   112     def __init__(self, template_name):
       
   113         self.template_name = Variable(template_name)
       
   114 
       
   115     def render(self, context):
       
   116         try:
       
   117             template_name = self.template_name.resolve(context)
       
   118             t = get_template(template_name)
       
   119             return t.render(context)
       
   120         except TemplateSyntaxError, e:
       
   121             if settings.TEMPLATE_DEBUG:
       
   122                 raise
       
   123             return ''
       
   124         except:
       
   125             return '' # Fail silently for invalid included templates.
       
   126 
       
   127 def do_block(parser, token):
       
   128     """
       
   129     Define a block that can be overridden by child templates.
       
   130     """
       
   131     bits = token.contents.split()
       
   132     if len(bits) != 2:
       
   133         raise TemplateSyntaxError, "'%s' tag takes only one argument" % bits[0]
       
   134     block_name = bits[1]
       
   135     # Keep track of the names of BlockNodes found in this template, so we can
       
   136     # check for duplication.
       
   137     try:
       
   138         if block_name in parser.__loaded_blocks:
       
   139             raise TemplateSyntaxError, "'%s' tag with name '%s' appears more than once" % (bits[0], block_name)
       
   140         parser.__loaded_blocks.append(block_name)
       
   141     except AttributeError: # parser.__loaded_blocks isn't a list yet
       
   142         parser.__loaded_blocks = [block_name]
       
   143     nodelist = parser.parse(('endblock', 'endblock %s' % block_name))
       
   144     parser.delete_first_token()
       
   145     return BlockNode(block_name, nodelist)
       
   146 
       
   147 def do_extends(parser, token):
       
   148     """
       
   149     Signal that this template extends a parent template.
       
   150 
       
   151     This tag may be used in two ways: ``{% extends "base" %}`` (with quotes)
       
   152     uses the literal value "base" as the name of the parent template to extend,
       
   153     or ``{% extends variable %}`` uses the value of ``variable`` as either the
       
   154     name of the parent template to extend (if it evaluates to a string) or as
       
   155     the parent tempate itelf (if it evaluates to a Template object).
       
   156     """
       
   157     bits = token.contents.split()
       
   158     if len(bits) != 2:
       
   159         raise TemplateSyntaxError, "'%s' takes one argument" % bits[0]
       
   160     parent_name, parent_name_expr = None, None
       
   161     if bits[1][0] in ('"', "'") and bits[1][-1] == bits[1][0]:
       
   162         parent_name = bits[1][1:-1]
       
   163     else:
       
   164         parent_name_expr = parser.compile_filter(bits[1])
       
   165     nodelist = parser.parse()
       
   166     if nodelist.get_nodes_by_type(ExtendsNode):
       
   167         raise TemplateSyntaxError, "'%s' cannot appear more than once in the same template" % bits[0]
       
   168     return ExtendsNode(nodelist, parent_name, parent_name_expr)
       
   169 
       
   170 def do_include(parser, token):
       
   171     """
       
   172     Loads a template and renders it with the current context.
       
   173 
       
   174     Example::
       
   175 
       
   176         {% include "foo/some_include" %}
       
   177     """
       
   178     bits = token.contents.split()
       
   179     if len(bits) != 2:
       
   180         raise TemplateSyntaxError, "%r tag takes one argument: the name of the template to be included" % bits[0]
       
   181     path = bits[1]
       
   182     if path[0] in ('"', "'") and path[-1] == path[0]:
       
   183         return ConstantIncludeNode(path[1:-1])
       
   184     return IncludeNode(bits[1])
       
   185 
       
   186 register.tag('block', do_block)
       
   187 register.tag('extends', do_extends)
       
   188 register.tag('include', do_include)