Added a get_template_sources generator function to filesystem and app_directories...
[fdr-django.git] / django / core / template / loader.py
blob6d747d560d4ba389369cc1ad1c4d13104ffa3620
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:
29 i = path.rfind('.')
30 module, attr = path[:i], path[i+1:]
31 try:
32 mod = __import__(module, globals(), locals(), [attr])
33 except ImportError, e:
34 raise ImproperlyConfigured, 'Error importing template source loader %s: "%s"' % (module, e)
35 try:
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:
40 import warnings
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)
42 else:
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
50 def reload(self):
51 return self.loader(self.loadname, self.dirs)[0]
53 def make_origin(display_name, loader, name, dirs):
54 if TEMPLATE_DEBUG:
55 return LoaderOrigin(display_name, loader, name, dirs)
56 else:
57 return None
59 def find_template_source(name, dirs=None):
60 for loader in template_source_loaders:
61 try:
62 source, display_name = loader(name, dirs)
63 return (source, make_origin(display_name, loader, name, dirs))
64 except TemplateDoesNotExist:
65 pass
66 raise TemplateDoesNotExist, name
68 def load_template_source(name, dirs=None):
69 find_template_source(name, dirs)[0]
71 class ExtendsError(Exception):
72 pass
74 def get_template(template_name):
75 """
76 Returns a compiled Template object for the given template name,
77 handling template inheritance recursively.
78 """
79 return get_template_from_string(*find_template_source(template_name))
81 def get_template_from_string(source, origin=None ):
82 """
83 Returns a compiled Template object for the given template code,
84 handling template inheritance recursively.
85 """
86 return Template(source, origin)
88 def render_to_string(template_name, dictionary=None, context_instance=None):
89 """
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.
94 """
95 dictionary = dictionary or {}
96 if isinstance(template_name, (list, tuple)):
97 t = select_template(template_name)
98 else:
99 t = get_template(template_name)
100 if context_instance:
101 context_instance.update(dictionary)
102 else:
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:
109 try:
110 return get_template(template_name)
111 except TemplateDoesNotExist:
112 continue
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
120 def __repr__(self):
121 return "<Block Node: %s. Contents: %r>" % (self.name, self.nodelist)
123 def render(self, context):
124 context.push()
125 # Save context in case of block.super().
126 self.context = context
127 context['block'] = self
128 result = self.nodelist.render(context)
129 context.pop()
130 return result
132 def super(self):
133 if self.parent:
134 return self.parent.render(self.context)
135 return ''
137 def add_parent(self, nodelist):
138 if self.parent:
139 self.parent.add_parent(nodelist)
140 else:
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
153 if not parent:
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
158 try:
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.
169 try:
170 parent_block = parent_blocks[block_node.name]
171 except KeyError:
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.
176 if parent_is_child:
177 compiled_parent.nodelist[0].nodelist.append(block_node)
178 else:
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):
187 try:
188 t = get_template(template_path)
189 self.template = t
190 except Exception, e:
191 if TEMPLATE_DEBUG:
192 raise
193 self.template = None
195 def render(self, context):
196 if self.template:
197 return self.template.render(context)
198 else:
199 return ''
201 class IncludeNode(Node):
202 def __init__(self, template_name):
203 self.template_name = template_name
205 def render(self, context):
206 try:
207 template_name = resolve_variable(self.template_name, context)
208 t = get_template(template_name)
209 return t.render(context)
210 except TemplateSyntaxError, e:
211 if TEMPLATE_DEBUG:
212 raise
213 return ''
214 except:
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()
222 if len(bits) != 2:
223 raise TemplateSyntaxError, "'%s' tag takes only one argument" % bits[0]
224 block_name = bits[1]
225 # Keep track of the names of BlockNodes found in this template, so we can
226 # check for duplication.
227 try:
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()
247 if len(bits) != 2:
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]
252 else:
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.
263 Example::
265 {% include "foo/some_include" %}
268 bits = token.contents.split()
269 if len(bits) != 2:
270 raise TemplateSyntaxError, "%r tag takes one argument: the name of the template to be included" % bits[0]
271 path = bits[1]
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)