Initial import.

This commit is contained in:
Stephen McDonald 2012-06-09 13:12:50 +10:00
commit 81a7da04d4
4 changed files with 126 additions and 0 deletions

2
overextends/__init__.py Normal file
View File

@ -0,0 +1,2 @@
__version__ = "0.1.0"

7
overextends/models.py Normal file
View File

@ -0,0 +1,7 @@
# This app doesn't contain any models, but as its template tags need to
# be added to built-ins at start-up time, this is a good palce to do it.
from django.template.loader import add_to_builtins
add_to_builtins("overextends.templatetags.overextends_tags")

View File

View File

@ -0,0 +1,117 @@
from django import template
from django.template import Template, TemplateSyntaxError, TemplateDoesNotExist
from django.template.loader_tags import ExtendsNode
from django.template.loader import find_template_loader
register = template.Library()
class OverExtendsNode(ExtendsNode):
"""
Allows the template ``foo/bar.html`` to extend ``foo/bar.html``,
given that there is another version of it that can be loaded. This
allows templates to be created in a project that extend their app
template counterparts, or even app templates that extend other app
templates with the same relative name/path.
We use our own version of ``find_template``, that uses an explict
list of template directories to search for the template, based on
the directories that the known template loaders
(``app_directories`` and ``filesystem``) use. This list gets stored
in the template context, and each time a template is found, its
absolute path gets removed from the list, so that subsequent
searches for the same relative name/path can find parent templates
in other directories, which allows circular inheritance to occur.
Django's ``app_directories``, ``filesystem``, and ``cached``
loaders are supported. The ``eggs`` loader, and any loader that
implements ``load_template_source`` with a source string returned,
should also theoretically work.
"""
def find_template(self, name, context):
"""
Replacement for Django's ``find_template`` that uses the current
template context to keep track of which template directories it
has used when finding a template. This allows multiple templates
with the same relative name/path to be discovered, so that
circular template inheritance can occur.
"""
# These imports want settings, which aren't available when this
# module is imported to ``add_to_builtins``, so do them here.
from django.template.loaders.app_directories import app_template_dirs
from django.conf import settings
# Store a dictionary in the template context mapping template
# names to the lists of template directories available to
# search for that template. Each time a template is loaded, its
# origin directory is removed from its directories list.
context_name = "OVEREXTENDS_DIRS"
if context_name not in context:
context[context_name] = {}
if name not in context[context_name]:
all_dirs = list(settings.TEMPLATE_DIRS + app_template_dirs)
context[context_name][name] = all_dirs
# Build a list of template loaders to use. For loaders that wrap
# other loaders like the ``cached`` template loader, unwind its
# internal loaders and add those instead.
loaders = []
for loader_name in settings.TEMPLATE_LOADERS:
loader = find_template_loader(loader_name)
loaders.extend(getattr(loader, "loaders", [loader]))
# Go through the loaders and try to find the template. When
# found, removed its absolute path from the context dict so
# that it won't be used again when the same relative name/path
# is requested.
for loader in loaders:
dirs = context[context_name][name]
try:
source, path = loader.load_template_source(name, dirs)
except TemplateDoesNotExist:
pass
else:
context[context_name][name].remove(path[:-len(name) - 1])
return Template(source)
raise TemplateDoesNotExist(name)
def get_parent(self, context):
"""
Load the parent template using our own ``find_template``, which
will cause its absolute path to not be used again. Then peek at
the first node, and if its parent arg is the same as the
current parent arg, we know circular inheritance is going to
occur, in which case we try and find the template again, with
the absolute directory removed from the search list.
"""
parent = self.parent_name.resolve(context)
# If parent is a template object, just return it.
if hasattr(parent, "render"):
return parent
template = self.find_template(parent, context)
if (isinstance(template.nodelist[0], ExtendsNode) and
template.nodelist[0].parent_name.resolve(context) == parent):
return self.find_template(parent, context)
return template
@register.tag
def overextends(parser, token):
"""
Extended version of Django's ``extends`` tag that allows circular
inheritance to occur, eg a template can both be overridden and
extended at once.
"""
bits = token.split_contents()
if len(bits) != 2:
raise TemplateSyntaxError("'%s' takes one argument" % bits[0])
parent_name = parser.compile_filter(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 OverExtendsNode(nodelist, parent_name)