~canonical-django/canonical-django/project-template

« back to all changes in this revision

Viewing changes to trunk/python-packages/django/template/loader_tags.py

  • Committer: Matthew Nuzum
  • Date: 2008-11-13 05:46:03 UTC
  • Revision ID: matthew.nuzum@canonical.com-20081113054603-v0kvr6z6xyexvqt3
adding to version control

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
        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)
 
98
 
 
99
class ConstantIncludeNode(Node):
 
100
    def __init__(self, template_path):
 
101
        try:
 
102
            t = get_template(template_path)
 
103
            self.template = t
 
104
        except:
 
105
            if settings.TEMPLATE_DEBUG:
 
106
                raise
 
107
            self.template = None
 
108
 
 
109
    def render(self, context):
 
110
        if self.template:
 
111
            return self.template.render(context)
 
112
        else:
 
113
            return ''
 
114
 
 
115
class IncludeNode(Node):
 
116
    def __init__(self, template_name):
 
117
        self.template_name = Variable(template_name)
 
118
 
 
119
    def render(self, context):
 
120
        try:
 
121
            template_name = self.template_name.resolve(context)
 
122
            t = get_template(template_name)
 
123
            return t.render(context)
 
124
        except TemplateSyntaxError, e:
 
125
            if settings.TEMPLATE_DEBUG:
 
126
                raise
 
127
            return ''
 
128
        except:
 
129
            return '' # Fail silently for invalid included templates.
 
130
 
 
131
def do_block(parser, token):
 
132
    """
 
133
    Define a block that can be overridden by child templates.
 
134
    """
 
135
    bits = token.contents.split()
 
136
    if len(bits) != 2:
 
137
        raise TemplateSyntaxError, "'%s' tag takes only one argument" % bits[0]
 
138
    block_name = bits[1]
 
139
    # Keep track of the names of BlockNodes found in this template, so we can
 
140
    # check for duplication.
 
141
    try:
 
142
        if block_name in parser.__loaded_blocks:
 
143
            raise TemplateSyntaxError, "'%s' tag with name '%s' appears more than once" % (bits[0], block_name)
 
144
        parser.__loaded_blocks.append(block_name)
 
145
    except AttributeError: # parser.__loaded_blocks isn't a list yet
 
146
        parser.__loaded_blocks = [block_name]
 
147
    nodelist = parser.parse(('endblock', 'endblock %s' % block_name))
 
148
    parser.delete_first_token()
 
149
    return BlockNode(block_name, nodelist)
 
150
 
 
151
def do_extends(parser, token):
 
152
    """
 
153
    Signal that this template extends a parent template.
 
154
 
 
155
    This tag may be used in two ways: ``{% extends "base" %}`` (with quotes)
 
156
    uses the literal value "base" as the name of the parent template to extend,
 
157
    or ``{% extends variable %}`` uses the value of ``variable`` as either the
 
158
    name of the parent template to extend (if it evaluates to a string) or as
 
159
    the parent tempate itelf (if it evaluates to a Template object).
 
160
    """
 
161
    bits = token.contents.split()
 
162
    if len(bits) != 2:
 
163
        raise TemplateSyntaxError, "'%s' takes one argument" % bits[0]
 
164
    parent_name, parent_name_expr = None, None
 
165
    if bits[1][0] in ('"', "'") and bits[1][-1] == bits[1][0]:
 
166
        parent_name = bits[1][1:-1]
 
167
    else:
 
168
        parent_name_expr = parser.compile_filter(bits[1])
 
169
    nodelist = parser.parse()
 
170
    if nodelist.get_nodes_by_type(ExtendsNode):
 
171
        raise TemplateSyntaxError, "'%s' cannot appear more than once in the same template" % bits[0]
 
172
    return ExtendsNode(nodelist, parent_name, parent_name_expr)
 
173
 
 
174
def do_include(parser, token):
 
175
    """
 
176
    Loads a template and renders it with the current context.
 
177
 
 
178
    Example::
 
179
 
 
180
        {% include "foo/some_include" %}
 
181
    """
 
182
    bits = token.contents.split()
 
183
    if len(bits) != 2:
 
184
        raise TemplateSyntaxError, "%r tag takes one argument: the name of the template to be included" % bits[0]
 
185
    path = bits[1]
 
186
    if path[0] in ('"', "'") and path[-1] == path[0]:
 
187
        return ConstantIncludeNode(path[1:-1])
 
188
    return IncludeNode(bits[1])
 
189
 
 
190
register.tag('block', do_block)
 
191
register.tag('extends', do_extends)
 
192
register.tag('include', do_include)