Commit a391b17a authored by Matthew Somerville's avatar Matthew Somerville Committed by Tim Graham
Browse files

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

This also speeds up for loops that render the same template
multiple times.
parent 2a7c59cd
Loading
Loading
Loading
Loading
+14 −2
Original line number Diff line number Diff line
@@ -171,6 +171,8 @@ class ExtendsNode(Node):


class IncludeNode(Node):
    context_key = '__include_context'

    def __init__(self, template, *args, **kwargs):
        self.template = template
        self.extra_context = kwargs.pop('extra_context', {})
@@ -178,12 +180,22 @@ class IncludeNode(Node):
        super(IncludeNode, self).__init__(*args, **kwargs)

    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:
            template = self.template.resolve(context)
            # Does this quack like a Template?
            if not callable(getattr(template, 'render', None)):
                # If not, we'll try get_template
                template = context.template.engine.get_template(template)
                # If not, we'll try our cache, and get_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 = {
                name: var.resolve(context)
                for name, var in six.iteritems(self.extra_context)
+3 −0
Original line number Diff line number Diff line
@@ -314,6 +314,9 @@ Templates
* The ``timesince`` and ``timeuntil`` filters were improved to deal with leap
  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
^^^^^^^^^^^^^^^^^^^^^^

+14 −0
Original line number Diff line number Diff line
@@ -196,3 +196,17 @@ class IfChangedTests(SimpleTestCase):
        template = self.engine.from_string('{% ifchanged %}{% cycle "1st time" "2nd time" %}{% endifchanged %}')
        output = template.render(Context({}))
        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")