from django.template import TemplateSyntaxError, TemplateDoesNotExist from django.template import Library, Node, TextNode from django.template.loader_tags import ExtendsNode from django.template.loader import get_template_from_string, make_origin from django.template.loaders.filesystem import get_template_sources from django.conf import settings register = Library() def find_default_template_source(name, dirs=None): try: source, display_name = load_default_template_source(name, dirs) return (source, make_origin(display_name, load_default_template_source, name, dirs)) except TemplateDoesNotExist: raise TemplateDoesNotExist, name class SawExtendsSelf(IOError): pass def load_default_template_source(template_name, template_dirs=None): tried = [] for filepath in get_template_sources(template_name, template_dirs): try: template = open(filepath) file_source_line_1 = template.readline() if 'load extends_default' in file_source_line_1: file_source_line_2 = template.readline() if 'extends_default' in file_source_line_2: raise SawExtendsSelf template.close() return (open(filepath).read().decode(settings.FILE_CHARSET), filepath) except SawExtendsSelf: tried.append("%s (Saw extends_default and continuing...)" % filepath) except IOError: tried.append(filepath) if tried: error_msg = "Tried %s" % tried else: error_msg = "Your TEMPLATE_DIRS setting is empty. Change it to point to at least one template directory." raise TemplateDoesNotExist, error_msg class ExtendsDefaultNode(ExtendsNode): must_be_first = False def __repr__(self): if self.parent_name_expr: return "" % self.parent_name_expr.token return '' % self.parent_name def get_parent(self, context): if self.parent_name_expr: self.parent_name = self.parent_name_expr.resolve(context) parent = self.parent_name if not parent: error_msg = "Invalid template name in 'extends_default' tag: %r." % parent if self.parent_name_expr: error_msg += " Got this from the '%s' variable." % self.parent_name_expr.token raise TemplateSyntaxError, error_msg if hasattr(parent, 'render'): return parent # parent is a Template object try: source, origin = find_default_template_source(parent, self.template_dirs) except TemplateDoesNotExist: raise TemplateSyntaxError, "Template %r cannot be extended, because it doesn't exist" % parent else: return get_template_from_string(source, origin, parent) def do_extends_default(parser, token): """ Enables extension of a default template with the same name. Usage: {% load extends_default %} {% extends_default "same_directory/hierarchy/template_name.html" %} Works exactly like the standard "extends" tag but enables one to fallback on a default template. This tag is LIMITED, as it falls back to the next template with the same name that DOES NOT contain "extends_default" (does NOT simulate full inheritance, just a single level). """ bits = token.contents.split() if len(bits) != 2: raise TemplateSyntaxError, "'%s' takes one argument" % bits[0] parent_name, parent_name_expr = None, None if bits[1][0] in ('"', "'") and bits[1][-1] == bits[1][0]: parent_name = bits[1][1:-1] else: parent_name_expr = parser.compile_filter(bits[1]) nodelist = parser.parse() if nodelist.get_nodes_by_type(ExtendsDefaultNode) or nodelist.get_nodes_by_type(ExtendsNode): raise TemplateSyntaxError, "'%s' cannot appear more than once in the same template" % bits[0] return ExtendsDefaultNode(nodelist, parent_name, parent_name_expr) register.tag('extends_default', do_extends_default)