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
This commit is contained in:
parent
e973ee6a98
commit
e2f06226ea
|
@ -121,55 +121,34 @@ class ExtendsNode(Node):
|
||||||
# the same.
|
# the same.
|
||||||
return compiled_parent._render(context)
|
return compiled_parent._render(context)
|
||||||
|
|
||||||
class BaseIncludeNode(Node):
|
class IncludeNode(Node):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, template, *args, **kwargs):
|
||||||
|
self.template = template
|
||||||
self.extra_context = kwargs.pop('extra_context', {})
|
self.extra_context = kwargs.pop('extra_context', {})
|
||||||
self.isolated_context = kwargs.pop('isolated_context', False)
|
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)
|
super(IncludeNode, self).__init__(*args, **kwargs)
|
||||||
self.template_name = template_name
|
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
try:
|
try:
|
||||||
template = self.template_name.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 get_template
|
||||||
template = get_template(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:
|
except:
|
||||||
if settings.TEMPLATE_DEBUG:
|
if settings.TEMPLATE_DEBUG:
|
||||||
raise
|
raise
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
||||||
@register.tag('block')
|
@register.tag('block')
|
||||||
def do_block(parser, token):
|
def do_block(parser, token):
|
||||||
"""
|
"""
|
||||||
|
@ -258,9 +237,5 @@ def do_include(parser, token):
|
||||||
options[option] = value
|
options[option] = value
|
||||||
isolated_context = options.get('only', False)
|
isolated_context = options.get('only', False)
|
||||||
namemap = options.get('with', {})
|
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,
|
return IncludeNode(parser.compile_filter(bits[1]), extra_context=namemap,
|
||||||
isolated_context=isolated_context)
|
isolated_context=isolated_context)
|
||||||
|
|
|
@ -263,6 +263,8 @@ Templates
|
||||||
arguments will be looked up using
|
arguments will be looked up using
|
||||||
:func:`~django.template.loader.get_template` as always.
|
:func:`~django.template.loader.get_template` as always.
|
||||||
|
|
||||||
|
* It is now possible to :ttag:`include` templates recursively.
|
||||||
|
|
||||||
Backwards incompatible changes in 1.7
|
Backwards incompatible changes in 1.7
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
Recursion!
|
||||||
|
{% for comment in comments %}
|
||||||
|
{{ comment.comment }}
|
||||||
|
{% if comment.children %}
|
||||||
|
{% include "recursive_include.html" with comments=comment.children %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
|
@ -349,6 +349,40 @@ class TemplateLoaderTests(TestCase):
|
||||||
output = outer_tmpl.render(ctx)
|
output = outer_tmpl.render(ctx)
|
||||||
self.assertEqual(output, 'This worked!')
|
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):
|
class TemplateRegressionTests(TestCase):
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue