1 # Wrapper for loading templates from storage of some sort (e.g. filesystem, database).
3 # This uses the TEMPLATE_LOADERS setting, which is a list of loaders to use.
4 # Each loader is expected to have this interface:
6 # callable(name, dirs=[])
8 # name is the template name.
9 # dirs is an optional list of directories to search instead of TEMPLATE_DIRS.
11 # The loader should return a tuple of (template_source, path). The path returned
12 # might be shown to the user for debugging purposes, so it should identify where
13 # the template was loaded from.
15 # Each loader should have an "is_usable" attribute set. This is a boolean that
16 # specifies whether the loader can be used in this Python installation. Each
17 # loader is responsible for setting this when it's initialized.
19 # For example, the eggs loader (which is capable of loading templates from
20 # Python eggs) sets is_usable to False if the "pkg_resources" module isn't
21 # installed, because pkg_resources is necessary to read eggs.
23 from django
.core
.exceptions
import ImproperlyConfigured
24 from django
.core
.template
import Origin
, StringOrigin
, Template
, Context
, Node
, TemplateDoesNotExist
, TemplateSyntaxError
, resolve_variable_with_filters
, resolve_variable
, register_tag
25 from django
.conf
.settings
import TEMPLATE_LOADERS
, TEMPLATE_DEBUG
27 template_source_loaders
= []
28 for path
in TEMPLATE_LOADERS
:
30 module
, attr
= path
[:i
], path
[i
+1:]
32 mod
= __import__(module
, globals(), locals(), [attr
])
33 except ImportError, e
:
34 raise ImproperlyConfigured
, 'Error importing template source loader %s: "%s"' % (module
, e
)
36 func
= getattr(mod
, attr
)
37 except AttributeError:
38 raise ImproperlyConfigured
, 'Module "%s" does not define a "%s" callable template source loader' % (module
, attr
)
39 if not func
.is_usable
:
41 warnings
.warn("Your TEMPLATE_LOADERS setting includes %r, but your Python installation doesn't support that type of template loading. Consider removing that line from TEMPLATE_LOADERS." % path
)
43 template_source_loaders
.append(func
)
45 class LoaderOrigin(Origin
):
46 def __init__(self
, display_name
, loader
, name
, dirs
):
47 super(LoaderOrigin
, self
).__init
__(display_name
)
48 self
.loader
, self
.loadname
, self
.dirs
= loader
, name
, dirs
51 return self
.loader(self
.loadname
, self
.dirs
)[0]
53 def make_origin(display_name
, loader
, name
, dirs
):
55 return LoaderOrigin(display_name
, loader
, name
, dirs
)
59 def find_template_source(name
, dirs
=None):
60 for loader
in template_source_loaders
:
62 source
, display_name
= loader(name
, dirs
)
63 return (source
, make_origin(display_name
, loader
, name
, dirs
))
64 except TemplateDoesNotExist
:
66 raise TemplateDoesNotExist
, name
68 def load_template_source(name
, dirs
=None):
69 find_template_source(name
, dirs
)[0]
71 class ExtendsError(Exception):
74 def get_template(template_name
):
76 Returns a compiled Template object for the given template name,
77 handling template inheritance recursively.
79 return get_template_from_string(*find_template_source(template_name
))
81 def get_template_from_string(source
, origin
=None ):
83 Returns a compiled Template object for the given template code,
84 handling template inheritance recursively.
86 return Template(source
, origin
)
88 def render_to_string(template_name
, dictionary
=None, context_instance
=None):
90 Loads the given template_name and renders it with the given dictionary as
91 context. The template_name may be a string to load a single template using
92 get_template, or it may be a tuple to use select_template to find one of
93 the templates in the list. Returns a string.
95 dictionary
= dictionary
or {}
96 if isinstance(template_name
, (list, tuple)):
97 t
= select_template(template_name
)
99 t
= get_template(template_name
)
101 context_instance
.update(dictionary
)
103 context_instance
= Context(dictionary
)
104 return t
.render(context_instance
)
106 def select_template(template_name_list
):
107 "Given a list of template names, returns the first that can be loaded."
108 for template_name
in template_name_list
:
110 return get_template(template_name
)
111 except TemplateDoesNotExist
:
113 # If we get here, none of the templates could be loaded
114 raise TemplateDoesNotExist
, ', '.join(template_name_list
)
116 class BlockNode(Node
):
117 def __init__(self
, name
, nodelist
, parent
=None):
118 self
.name
, self
.nodelist
, self
.parent
= name
, nodelist
, parent
121 return "<Block Node: %s. Contents: %r>" % (self
.name
, self
.nodelist
)
123 def render(self
, context
):
125 # Save context in case of block.super().
126 self
.context
= context
127 context
['block'] = self
128 result
= self
.nodelist
.render(context
)
134 return self
.parent
.render(self
.context
)
137 def add_parent(self
, nodelist
):
139 self
.parent
.add_parent(nodelist
)
141 self
.parent
= BlockNode(self
.name
, nodelist
)
143 class ExtendsNode(Node
):
144 def __init__(self
, nodelist
, parent_name
, parent_name_var
, template_dirs
=None):
145 self
.nodelist
= nodelist
146 self
.parent_name
, self
.parent_name_var
= parent_name
, parent_name_var
147 self
.template_dirs
= template_dirs
149 def get_parent(self
, context
):
150 if self
.parent_name_var
:
151 self
.parent_name
= resolve_variable_with_filters(self
.parent_name_var
, context
)
152 parent
= self
.parent_name
154 error_msg
= "Invalid template name in 'extends' tag: %r." % parent
155 if self
.parent_name_var
:
156 error_msg
+= " Got this from the %r variable." % self
.parent_name_var
157 raise TemplateSyntaxError
, error_msg
159 return get_template_from_string(*find_template_source(parent
, self
.template_dirs
))
160 except TemplateDoesNotExist
:
161 raise TemplateSyntaxError
, "Template %r cannot be extended, because it doesn't exist" % parent
163 def render(self
, context
):
164 compiled_parent
= self
.get_parent(context
)
165 parent_is_child
= isinstance(compiled_parent
.nodelist
[0], ExtendsNode
)
166 parent_blocks
= dict([(n
.name
, n
) for n
in compiled_parent
.nodelist
.get_nodes_by_type(BlockNode
)])
167 for block_node
in self
.nodelist
.get_nodes_by_type(BlockNode
):
168 # Check for a BlockNode with this node's name, and replace it if found.
170 parent_block
= parent_blocks
[block_node
.name
]
172 # This BlockNode wasn't found in the parent template, but the
173 # parent block might be defined in the parent's *parent*, so we
174 # add this BlockNode to the parent's ExtendsNode nodelist, so
175 # it'll be checked when the parent node's render() is called.
177 compiled_parent
.nodelist
[0].nodelist
.append(block_node
)
179 # Keep any existing parents and add a new one. Used by BlockNode.
180 parent_block
.parent
= block_node
.parent
181 parent_block
.add_parent(parent_block
.nodelist
)
182 parent_block
.nodelist
= block_node
.nodelist
183 return compiled_parent
.render(context
)
185 class ConstantIncludeNode(Node
):
186 def __init__(self
, template_path
):
188 t
= get_template(template_path
)
195 def render(self
, context
):
197 return self
.template
.render(context
)
201 class IncludeNode(Node
):
202 def __init__(self
, template_name
):
203 self
.template_name
= template_name
205 def render(self
, context
):
207 template_name
= resolve_variable(self
.template_name
, context
)
208 t
= get_template(template_name
)
209 return t
.render(context
)
210 except TemplateSyntaxError
, e
:
215 return '' # Fail silently for invalid included templates.
217 def do_block(parser
, token
):
219 Define a block that can be overridden by child templates.
221 bits
= token
.contents
.split()
223 raise TemplateSyntaxError
, "'%s' tag takes only one argument" % bits
[0]
225 # Keep track of the names of BlockNodes found in this template, so we can
226 # check for duplication.
228 if block_name
in parser
.__loaded
_blocks
:
229 raise TemplateSyntaxError
, "'%s' tag with name '%s' appears more than once" % (bits
[0], block_name
)
230 parser
.__loaded
_blocks
.append(block_name
)
231 except AttributeError: # parser._loaded_blocks isn't a list yet
232 parser
.__loaded
_blocks
= [block_name
]
233 nodelist
= parser
.parse(('endblock',))
234 parser
.delete_first_token()
235 return BlockNode(block_name
, nodelist
)
237 def do_extends(parser
, token
):
239 Signal that this template extends a parent template.
241 This tag may be used in two ways: ``{% extends "base" %}`` (with quotes)
242 uses the literal value "base" as the name of the parent template to extend,
243 or ``{% extends variable %}`` uses the value of ``variable`` as the name
244 of the parent template to extend.
246 bits
= token
.contents
.split()
248 raise TemplateSyntaxError
, "'%s' takes one argument" % bits
[0]
249 parent_name
, parent_name_var
= None, None
250 if bits
[1][0] in ('"', "'") and bits
[1][-1] == bits
[1][0]:
251 parent_name
= bits
[1][1:-1]
253 parent_name_var
= bits
[1]
254 nodelist
= parser
.parse()
255 if nodelist
.get_nodes_by_type(ExtendsNode
):
256 raise TemplateSyntaxError
, "'%s' cannot appear more than once in the same template" % bits
[0]
257 return ExtendsNode(nodelist
, parent_name
, parent_name_var
)
259 def do_include(parser
, token
):
261 Loads a template and renders it with the current context.
265 {% include "foo/some_include" %}
268 bits
= token
.contents
.split()
270 raise TemplateSyntaxError
, "%r tag takes one argument: the name of the template to be included" % bits
[0]
272 if path
[0] in ('"', "'") and path
[-1] == path
[0]:
273 return ConstantIncludeNode(path
[1:-1])
274 return IncludeNode(bits
[1])
276 register_tag('block', do_block
)
277 register_tag('extends', do_extends
)
278 register_tag('include', do_include
)