207 lines
8.4 KiB
Python
207 lines
8.4 KiB
Python
# Wrapper for loading templates from storage of some sort (e.g. filesystem, database).
|
|
#
|
|
# This uses the TEMPLATE_LOADERS setting, which is a list of loaders to use.
|
|
# Each loader is expected to have this interface:
|
|
#
|
|
# callable(name, dirs=[])
|
|
#
|
|
# name is the template name.
|
|
# dirs is an optional list of directories to search instead of TEMPLATE_DIRS.
|
|
#
|
|
# Each loader should have an "is_usable" attribute set. This is a boolean that
|
|
# specifies whether the loader can be used in this Python installation. Each
|
|
# loader is responsible for setting this when it's initialized.
|
|
#
|
|
# For example, the eggs loader (which is capable of loading templates from
|
|
# Python eggs) sets is_usable to False if the "pkg_resources" module isn't
|
|
# installed, because pkg_resources is necessary to read eggs.
|
|
|
|
from django.core.exceptions import ImproperlyConfigured
|
|
from django.core.template import Template, Context, Node, TemplateDoesNotExist, TemplateSyntaxError, resolve_variable_with_filters, register_tag
|
|
from django.conf.settings import TEMPLATE_LOADERS
|
|
|
|
template_source_loaders = []
|
|
for path in TEMPLATE_LOADERS:
|
|
i = path.rfind('.')
|
|
module, attr = path[:i], path[i+1:]
|
|
try:
|
|
mod = __import__(module, globals(), locals(), [attr])
|
|
except ImportError, e:
|
|
raise ImproperlyConfigured, 'Error importing template source loader %s: "%s"' % (module, e)
|
|
try:
|
|
func = getattr(mod, attr)
|
|
except AttributeError:
|
|
raise ImproperlyConfigured, 'Module "%s" does not define a "%s" callable template source loader' % (module, attr)
|
|
if not func.is_usable:
|
|
import warnings
|
|
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)
|
|
else:
|
|
template_source_loaders.append(func)
|
|
|
|
def load_template_source(name, dirs=None):
|
|
for loader in template_source_loaders:
|
|
try:
|
|
return loader(name, dirs)
|
|
except TemplateDoesNotExist:
|
|
pass
|
|
raise TemplateDoesNotExist, name
|
|
|
|
class ExtendsError(Exception):
|
|
pass
|
|
|
|
def get_template(template_name):
|
|
"""
|
|
Returns a compiled Template object for the given template name,
|
|
handling template inheritance recursively.
|
|
"""
|
|
return get_template_from_string(load_template_source(template_name))
|
|
|
|
def get_template_from_string(source):
|
|
"""
|
|
Returns a compiled Template object for the given template code,
|
|
handling template inheritance recursively.
|
|
"""
|
|
return Template(source)
|
|
|
|
def render_to_string(template_name, dictionary=None, context_instance=None):
|
|
"""
|
|
Loads the given template_name and renders it with the given dictionary as
|
|
context. The template_name may be a string to load a single template using
|
|
get_template, or it may be a tuple to use select_template to find one of
|
|
the templates in the list. Returns a string.
|
|
"""
|
|
dictionary = dictionary or {}
|
|
if isinstance(template_name, (list, tuple)):
|
|
t = select_template(template_name)
|
|
else:
|
|
t = get_template(template_name)
|
|
if context_instance:
|
|
context_instance.update(dictionary)
|
|
else:
|
|
context_instance = Context(dictionary)
|
|
return t.render(context_instance)
|
|
|
|
def select_template(template_name_list):
|
|
"Given a list of template names, returns the first that can be loaded."
|
|
for template_name in template_name_list:
|
|
try:
|
|
return get_template(template_name)
|
|
except TemplateDoesNotExist:
|
|
continue
|
|
# If we get here, none of the templates could be loaded
|
|
raise TemplateDoesNotExist, ', '.join(template_name_list)
|
|
|
|
class BlockNode(Node):
|
|
def __init__(self, name, nodelist, parent=None):
|
|
self.name, self.nodelist, self.parent = name, nodelist, parent
|
|
|
|
def __repr__(self):
|
|
return "<Block Node: %s. Contents: %r>" % (self.name, self.nodelist)
|
|
|
|
def render(self, context):
|
|
context.push()
|
|
# Save context in case of block.super().
|
|
self.context = context
|
|
context['block'] = self
|
|
result = self.nodelist.render(context)
|
|
context.pop()
|
|
return result
|
|
|
|
def super(self):
|
|
if self.parent:
|
|
return self.parent.render(self.context)
|
|
return ''
|
|
|
|
def add_parent(self, nodelist):
|
|
if self.parent:
|
|
self.parent.add_parent(nodelist)
|
|
else:
|
|
self.parent = BlockNode(self.name, nodelist)
|
|
|
|
class ExtendsNode(Node):
|
|
def __init__(self, nodelist, parent_name, parent_name_var, template_dirs=None):
|
|
self.nodelist = nodelist
|
|
self.parent_name, self.parent_name_var = parent_name, parent_name_var
|
|
self.template_dirs = template_dirs
|
|
|
|
def get_parent(self, context):
|
|
if self.parent_name_var:
|
|
self.parent_name = resolve_variable_with_filters(self.parent_name_var, context)
|
|
parent = self.parent_name
|
|
if not parent:
|
|
error_msg = "Invalid template name in 'extends' tag: %r." % parent
|
|
if self.parent_name_var:
|
|
error_msg += " Got this from the %r variable." % self.parent_name_var
|
|
raise TemplateSyntaxError, error_msg
|
|
try:
|
|
return get_template_from_string(load_template_source(parent, self.template_dirs))
|
|
except TemplateDoesNotExist:
|
|
raise TemplateSyntaxError, "Template %r cannot be extended, because it doesn't exist" % parent
|
|
|
|
def render(self, context):
|
|
compiled_parent = self.get_parent(context)
|
|
parent_is_child = isinstance(compiled_parent.nodelist[0], ExtendsNode)
|
|
parent_blocks = dict([(n.name, n) for n in compiled_parent.nodelist.get_nodes_by_type(BlockNode)])
|
|
for block_node in self.nodelist.get_nodes_by_type(BlockNode):
|
|
# Check for a BlockNode with this node's name, and replace it if found.
|
|
try:
|
|
parent_block = parent_blocks[block_node.name]
|
|
except KeyError:
|
|
# This BlockNode wasn't found in the parent template, but the
|
|
# parent block might be defined in the parent's *parent*, so we
|
|
# add this BlockNode to the parent's ExtendsNode nodelist, so
|
|
# it'll be checked when the parent node's render() is called.
|
|
if parent_is_child:
|
|
compiled_parent.nodelist[0].nodelist.append(block_node)
|
|
else:
|
|
# Keep any existing parents and add a new one. Used by BlockNode.
|
|
parent_block.parent = block_node.parent
|
|
parent_block.add_parent(parent_block.nodelist)
|
|
parent_block.nodelist = block_node.nodelist
|
|
return compiled_parent.render(context)
|
|
|
|
def do_block(parser, token):
|
|
"""
|
|
Define a block that can be overridden by child templates.
|
|
"""
|
|
bits = token.contents.split()
|
|
if len(bits) != 2:
|
|
raise TemplateSyntaxError, "'%s' tag takes only one argument" % bits[0]
|
|
block_name = bits[1]
|
|
# Keep track of the names of BlockNodes found in this template, so we can
|
|
# check for duplication.
|
|
try:
|
|
if block_name in parser.__loaded_blocks:
|
|
raise TemplateSyntaxError, "'%s' tag with name '%s' appears more than once" % (bits[0], block_name)
|
|
parser.__loaded_blocks.append(block_name)
|
|
except AttributeError: # parser._loaded_blocks isn't a list yet
|
|
parser.__loaded_blocks = [block_name]
|
|
nodelist = parser.parse(('endblock',))
|
|
parser.delete_first_token()
|
|
return BlockNode(block_name, nodelist)
|
|
|
|
def do_extends(parser, token):
|
|
"""
|
|
Signal that this template extends a parent template.
|
|
|
|
This tag may be used in two ways: ``{% extends "base" %}`` (with quotes)
|
|
uses the literal value "base" as the name of the parent template to extend,
|
|
or ``{% entends variable %}`` uses the value of ``variable`` as the name
|
|
of the parent template to extend.
|
|
"""
|
|
bits = token.contents.split()
|
|
if len(bits) != 2:
|
|
raise TemplateSyntaxError, "'%s' takes one argument" % bits[0]
|
|
parent_name, parent_name_var = None, None
|
|
if (bits[1].startswith('"') and bits[1].endswith('"')) or (bits[1].startswith("'") and bits[1].endswith("'")):
|
|
parent_name = bits[1][1:-1]
|
|
else:
|
|
parent_name_var = bits[1]
|
|
nodelist = parser.parse()
|
|
if nodelist.get_nodes_by_type(ExtendsNode):
|
|
raise TemplateSyntaxError, "'%s' cannot appear more than once in the same template" % bits[0]
|
|
return ExtendsNode(nodelist, parent_name, parent_name_var)
|
|
|
|
register_tag('block', do_block)
|
|
register_tag('extends', do_extends)
|