Fixed #23516 -- Added caching of include tag Template objects

This also speeds up for loops that render the same template
multiple times.
This commit is contained in:
Matthew Somerville 2015-06-04 12:50:07 +01:00 committed by Tim Graham
parent 2a7c59cd88
commit a391b17ad2
3 changed files with 31 additions and 2 deletions

View File

@ -171,6 +171,8 @@ class ExtendsNode(Node):
class IncludeNode(Node): class IncludeNode(Node):
context_key = '__include_context'
def __init__(self, template, *args, **kwargs): def __init__(self, template, *args, **kwargs):
self.template = template self.template = template
self.extra_context = kwargs.pop('extra_context', {}) self.extra_context = kwargs.pop('extra_context', {})
@ -178,12 +180,22 @@ class IncludeNode(Node):
super(IncludeNode, self).__init__(*args, **kwargs) super(IncludeNode, self).__init__(*args, **kwargs)
def render(self, context): def render(self, context):
"""
Render the specified template and context. Cache the template object
in render_context to avoid reparsing and loading when used in a for
loop.
"""
try: try:
template = self.template.resolve(context) template = self.template.resolve(context)
# Does this quack like a Template? # Does this quack like a Template?
if not callable(getattr(template, 'render', None)): if not callable(getattr(template, 'render', None)):
# If not, we'll try get_template # If not, we'll try our cache, and get_template()
template = context.template.engine.get_template(template) template_name = template
cache = context.render_context.setdefault(self.context_key, {})
template = cache.get(template_name)
if template is None:
template = context.template.engine.get_template(template_name)
cache[template_name] = template
values = { values = {
name: var.resolve(context) name: var.resolve(context)
for name, var in six.iteritems(self.extra_context) for name, var in six.iteritems(self.extra_context)

View File

@ -314,6 +314,9 @@ Templates
* The ``timesince`` and ``timeuntil`` filters were improved to deal with leap * The ``timesince`` and ``timeuntil`` filters were improved to deal with leap
years when given large time spans. years when given large time spans.
* The ``include`` tag now caches parsed templates objects during template
rendering, speeding up reuse in places such as for loops.
Requests and Responses Requests and Responses
^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^

View File

@ -196,3 +196,17 @@ class IfChangedTests(SimpleTestCase):
template = self.engine.from_string('{% ifchanged %}{% cycle "1st time" "2nd time" %}{% endifchanged %}') template = self.engine.from_string('{% ifchanged %}{% cycle "1st time" "2nd time" %}{% endifchanged %}')
output = template.render(Context({})) output = template.render(Context({}))
self.assertEqual(output, '1st time') self.assertEqual(output, '1st time')
def test_include(self):
"""
#23516 -- This works as a regression test only if the cached loader
isn't used. Hence we don't use the @setup decorator.
"""
engine = Engine(loaders=[
('django.template.loaders.locmem.Loader', {
'template': '{% for x in vars %}{% include "include" %}{% endfor %}',
'include': '{% ifchanged %}{{ x }}{% endifchanged %}',
}),
])
output = engine.render_to_string('template', dict(vars=[1, 1, 2, 2, 3, 3]))
self.assertEqual(output, "123")