Commit e2f06226 authored by Curtis Maloney's avatar Curtis Maloney Committed by Anssi Kääriäinen
Browse files

Improved {% include %} implementation

Merged BaseIncludeNode, ConstantIncludeNode and Include node.

This avoids raising TemplateDoesNotExist at parsing time, allows recursion
when passing a literal template name, and should make TEMPLATE_DEBUG behavior
consistant.

Thanks loic84 for help with the tests.

Fixed #3544, fixed #12064, fixed #16147
parent e973ee6a
Loading
Loading
Loading
Loading
+13 −38
Original line number Diff line number Diff line
@@ -121,55 +121,34 @@ class ExtendsNode(Node):
        # the same.
        return compiled_parent._render(context)

class BaseIncludeNode(Node):
    def __init__(self, *args, **kwargs):
class IncludeNode(Node):
    def __init__(self, template, *args, **kwargs):
        self.template = template
        self.extra_context = kwargs.pop('extra_context', {})
        self.isolated_context = kwargs.pop('isolated_context', False)
        super(BaseIncludeNode, self).__init__(*args, **kwargs)

    def render_template(self, template, context):
        values = dict([(name, var.resolve(context)) for name, var
                       in six.iteritems(self.extra_context)])
        if self.isolated_context:
            return template.render(context.new(values))
        with context.push(**values):
            return template.render(context)


class ConstantIncludeNode(BaseIncludeNode):
    def __init__(self, template_path, *args, **kwargs):
        super(ConstantIncludeNode, self).__init__(*args, **kwargs)
        try:
            t = get_template(template_path)
            self.template = t
        except:
            if settings.TEMPLATE_DEBUG:
                raise
            self.template = None

    def render(self, context):
        if not self.template:
            return ''
        return self.render_template(self.template, context)

class IncludeNode(BaseIncludeNode):
    def __init__(self, template_name, *args, **kwargs):
        super(IncludeNode, self).__init__(*args, **kwargs)
        self.template_name = template_name

    def render(self, context):
        try:
            template = self.template_name.resolve(context)
            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 = get_template(template)
            return self.render_template(template, context)
            values = {
                name: var.resolve(context)
                for name, var in six.iteritems(self.extra_context)
            }
            if self.isolated_context:
                return template.render(context.new(values))
            with context.push(**values):
                return template.render(context)
        except:
            if settings.TEMPLATE_DEBUG:
                raise
            return ''


@register.tag('block')
def do_block(parser, token):
    """
@@ -258,9 +237,5 @@ def do_include(parser, token):
        options[option] = value
    isolated_context = options.get('only', False)
    namemap = options.get('with', {})
    path = bits[1]
    if path[0] in ('"', "'") and path[-1] == path[0]:
        return ConstantIncludeNode(path[1:-1], extra_context=namemap,
                                   isolated_context=isolated_context)
    return IncludeNode(parser.compile_filter(bits[1]), extra_context=namemap,
                       isolated_context=isolated_context)
+2 −0
Original line number Diff line number Diff line
@@ -263,6 +263,8 @@ Templates
  arguments will be looked up using
  :func:`~django.template.loader.get_template` as always.

* It is now possible to :ttag:`include` templates recursively.

Backwards incompatible changes in 1.7
=====================================

+7 −0
Original line number Diff line number Diff line
Recursion!
{% for comment in comments %}
    {{ comment.comment }}
    {% if comment.children %}
        {% include "recursive_include.html" with comments=comment.children %}
    {% endif %}
{% endfor %}
+34 −0
Original line number Diff line number Diff line
@@ -349,6 +349,40 @@ class TemplateLoaderTests(TestCase):
        output = outer_tmpl.render(ctx)
        self.assertEqual(output, 'This worked!')

    @override_settings(TEMPLATE_DEBUG=True)
    def test_include_immediate_missing(self):
        """
        Regression test for #16417 -- {% include %} tag raises TemplateDoesNotExist at compile time if TEMPLATE_DEBUG is True

        Test that an {% include %} tag with a literal string referencing a
        template that does not exist does not raise an exception at parse
        time.
        """
        ctx = Context()
        tmpl = Template('{% include "this_does_not_exist.html" %}')
        self.assertIsInstance(tmpl, Template)

    @override_settings(TEMPLATE_DEBUG=True)
    def test_include_recursive(self):
        comments = [
            {
                'comment': 'A1',
                'children': [
                    {'comment': 'B1', 'children': []},
                    {'comment': 'B2', 'children': []},
                    {'comment': 'B3', 'children': [
                        {'comment': 'C1', 'children': []}
                    ]},
                ]
            }
        ]

        t = loader.get_template('recursive_include.html')
        self.assertEqual(
            "Recursion!  A1  Recursion!  B1   B2   B3  Recursion!  C1",
            t.render(Context({'comments': comments})).replace(' ', '').replace('\n', ' ').strip(),
        )


class TemplateRegressionTests(TestCase):