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
9
class ExtendsError(Exception):
12
class BlockNode(Node):
13
def __init__(self, name, nodelist, parent=None):
14
self.name, self.nodelist, self.parent = name, nodelist, parent
17
return "<Block Node: %s. Contents: %r>" % (self.name, self.nodelist)
19
def render(self, context):
21
# Save context in case of block.super().
22
self.context = context
23
context['block'] = self
24
result = self.nodelist.render(context)
30
return mark_safe(self.parent.render(self.context))
33
def add_parent(self, nodelist):
35
self.parent.add_parent(nodelist)
37
self.parent = BlockNode(self.name, nodelist)
39
class ExtendsNode(Node):
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
48
if self.parent_name_expr:
49
return "<ExtendsNode: extends %s>" % self.parent_name_expr.token
50
return '<ExtendsNode: extends "%s">' % self.parent_name
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
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
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
68
return get_template_from_string(source, origin, parent)
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.
76
parent_block = parent_blocks[block_node.name]
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.
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.
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)
99
class ConstantIncludeNode(Node):
100
def __init__(self, template_path):
102
t = get_template(template_path)
105
if settings.TEMPLATE_DEBUG:
109
def render(self, context):
111
return self.template.render(context)
115
class IncludeNode(Node):
116
def __init__(self, template_name):
117
self.template_name = Variable(template_name)
119
def render(self, context):
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:
129
return '' # Fail silently for invalid included templates.
131
def do_block(parser, token):
133
Define a block that can be overridden by child templates.
135
bits = token.contents.split()
137
raise TemplateSyntaxError, "'%s' tag takes only one argument" % bits[0]
139
# Keep track of the names of BlockNodes found in this template, so we can
140
# check for duplication.
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)
151
def do_extends(parser, token):
153
Signal that this template extends a parent template.
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).
161
bits = token.contents.split()
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]
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)
174
def do_include(parser, token):
176
Loads a template and renders it with the current context.
180
{% include "foo/some_include" %}
182
bits = token.contents.split()
184
raise TemplateSyntaxError, "%r tag takes one argument: the name of the template to be included" % bits[0]
186
if path[0] in ('"', "'") and path[-1] == path[0]:
187
return ConstantIncludeNode(path[1:-1])
188
return IncludeNode(bits[1])
190
register.tag('block', do_block)
191
register.tag('extends', do_extends)
192
register.tag('include', do_include)