Skip to content


Subversion checkout URL

You can clone with
Download ZIP


Make templates more reusable by Improving template loading algorithm to avoid extending infinite recursion #217

wants to merge 8 commits into from

5 participants


Please remove...

This was removed in this commit (2 minutes after) bf23921


What is this if-else for, when it contains the same code?

Probably LoaderOriginLite should be in the second line

This a intermediate step. Please, see the final diff:


didn't I convince you? :-( Tell me something


@goinnn as @akaariai noted on the ticket this patch needs tests and docs! (And yes I'd really like to see this feature in core :))


Hi @apollo13. I'm sorry but I was trying for two years to add this feature. I'm bored to try it. If you can get it. Congratulantions!


Ok, closing for now then. If someone else wants to work on it please open a new pull request for review.

@apollo13 apollo13 closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
6 django/template/
@@ -227,9 +227,14 @@ def create_token(self, token_string, in_tag):
token = Token(TOKEN_TEXT, token_string)
token.lineno = self.lineno
+ # token.source is a tuple, this contains origin object and
+ # the range of the columns where is this token.
+ # If TEMPLATE_DEBUG = False we don't need it, therefore we set with (-1, -1)
+ token.source = self.origin, (-1, -1)
self.lineno += token_string.count('\n')
return token
class Parser(object):
def __init__(self, tokens):
self.tokens = tokens
@@ -304,6 +309,7 @@ def extend_nodelist(self, nodelist, node, token):
"in the template." % node)
if isinstance(nodelist, NodeList) and not isinstance(node, TextNode):
nodelist.contains_nontext = True
+ node.source = token.source
def enter_command(self, command, token):
4 django/template/
@@ -55,10 +55,6 @@ def create_nodelist(self):
def create_variable_node(self, contents):
return DebugVariableNode(contents)
- def extend_nodelist(self, nodelist, node, token):
- node.source = token.source
- super(DebugParser, self).extend_nodelist(nodelist, node, token)
def unclosed_block_tag(self, parse_until):
command, source = self.command_stack.pop()
msg = "Unclosed tag '%s'. Looking for one of: %s " % (command, ', '.join(parse_until))
32 django/template/
@@ -79,10 +79,9 @@ def reload(self):
return self.loader(self.loadname, self.dirs)[0]
def make_origin(display_name, loader, name, dirs):
- if settings.TEMPLATE_DEBUG and display_name:
+ if display_name:
return LoaderOrigin(display_name, loader, name, dirs)
- else:
- return None
+ return None
def find_template_loader(loader):
if isinstance(loader, (tuple, list)):
@@ -117,7 +116,12 @@ def find_template_loader(loader):
raise ImproperlyConfigured('Loader does not define a "load_template" callable template source loader')
-def find_template(name, dirs=None):
+def find_template(name, dirs=None, skip_template=None):
+ """
+ Returns a tuple with a compiled Template object for the given template name,
+ and a origin object. Skipping the current template (skip_template),
+ this param contain the absolute path of the template.
+ """
# Calculate template_source_loaders the first time the function is executed
# because putting this logic in the module-level namespace may cause
# circular import errors. See Django ticket #1292.
@@ -129,20 +133,32 @@ def find_template(name, dirs=None):
if loader is not None:
template_source_loaders = tuple(loaders)
+ template_candidate = None
for loader in template_source_loaders:
source, display_name = loader(name, dirs)
- return (source, make_origin(display_name, loader, name, dirs))
+ if skip_template and skip_template.endswith(name):
+ extends_tags = source.nodelist[0]
+ extends_tags_origin, extends_tags_source = extends_tags.source
+ if == skip_template:
+ template_candidate = None
+ continue
+ if not template_candidate:
+ template_candidate = (source, make_origin(display_name, loader, name, dirs))
+ else:
+ return (source, make_origin(display_name, loader, name, dirs))
except TemplateDoesNotExist:
- raise TemplateDoesNotExist(name)
+ if not template_candidate:
+ raise TemplateDoesNotExist(name)
+ return template_candidate
-def get_template(template_name):
+def get_template(template_name, skip_template=None):
Returns a compiled Template object for the given template name,
handling template inheritance recursively.
- template, origin = find_template(template_name)
+ template, origin = find_template(template_name, skip_template=skip_template)
if not hasattr(template, 'render'):
# template needs to be compiled
template = get_template_from_string(template, origin, template_name)
5 django/template/
@@ -94,8 +94,9 @@ def get_parent(self, context):
raise TemplateSyntaxError(error_msg)
if hasattr(parent, 'render'):
- return parent # parent is a Template object
- return get_template(parent)
+ return parent # parent is a Template object
+ origin, source = self.source
+ return get_template(parent,
def render(self, context):
compiled_parent = self.get_parent(context)
Something went wrong with that request. Please try again.