|
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) |