Kaydet (Commit) aec4f975 authored tarafından Vitaly Bogomolov's avatar Vitaly Bogomolov Kaydeden (comit) Tim Graham

Fixed #26402 -- Added relative path support in include/extends template tags.

üst ad403ffa
import logging import logging
import posixpath
from collections import defaultdict from collections import defaultdict
from django.utils import six from django.utils import six
...@@ -249,6 +250,36 @@ def do_block(parser, token): ...@@ -249,6 +250,36 @@ def do_block(parser, token):
return BlockNode(block_name, nodelist) return BlockNode(block_name, nodelist)
def construct_relative_path(current_template_name, relative_name):
"""
Convert a relative path (starting with './' or '../') to the full template
name based on the current_template_name.
"""
if not any(relative_name.startswith(x) for x in ["'./", "'../", '"./', '"../']):
# relative_name is a variable or a literal that doesn't contain a
# relative path.
return relative_name
new_name = posixpath.normpath(
posixpath.join(
posixpath.dirname(current_template_name.lstrip('/')),
relative_name.strip('\'"')
)
)
if new_name.startswith('../'):
raise TemplateSyntaxError(
"The relative path '%s' points outside the file hierarchy that "
"template '%s' is in." % (relative_name, current_template_name)
)
if current_template_name.lstrip('/') == new_name:
raise TemplateSyntaxError(
"The relative path '%s' was translated to template name '%s', the "
"same template in which the tag appears."
% (relative_name, current_template_name)
)
return '"%s"' % new_name
@register.tag('extends') @register.tag('extends')
def do_extends(parser, token): def do_extends(parser, token):
""" """
...@@ -263,6 +294,7 @@ def do_extends(parser, token): ...@@ -263,6 +294,7 @@ def do_extends(parser, token):
bits = token.split_contents() bits = token.split_contents()
if len(bits) != 2: if len(bits) != 2:
raise TemplateSyntaxError("'%s' takes one argument" % bits[0]) raise TemplateSyntaxError("'%s' takes one argument" % bits[0])
bits[1] = construct_relative_path(parser.origin.template_name, bits[1])
parent_name = parser.compile_filter(bits[1]) parent_name = parser.compile_filter(bits[1])
nodelist = parser.parse() nodelist = parser.parse()
if nodelist.get_nodes_by_type(ExtendsNode): if nodelist.get_nodes_by_type(ExtendsNode):
...@@ -313,5 +345,6 @@ def do_include(parser, token): ...@@ -313,5 +345,6 @@ 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', {})
bits[1] = construct_relative_path(parser.origin.template_name, bits[1])
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)
...@@ -212,6 +212,26 @@ This tag can be used in two ways: ...@@ -212,6 +212,26 @@ This tag can be used in two ways:
See :ref:`template-inheritance` for more information. See :ref:`template-inheritance` for more information.
A string argument may be a relative path starting with ``./`` or ``../``. For
example, assume the following directory structure::
dir1/
template.html
base2.html
my/
base3.html
base1.html
In ``template.html``, the following paths would be valid::
{% extends "./base2.html" %}
{% extends "../base1.html" %}
{% extends "./my/base3.html" %}
.. versionadded:: 1.10
The ability to use relative paths was added.
.. templatetag:: filter .. templatetag:: filter
``filter`` ``filter``
...@@ -663,6 +683,13 @@ This example includes the contents of the template ``"foo/bar.html"``:: ...@@ -663,6 +683,13 @@ This example includes the contents of the template ``"foo/bar.html"``::
{% include "foo/bar.html" %} {% include "foo/bar.html" %}
A string argument may be a relative path starting with ``./`` or ``../`` as
described in the :ttag:`extends` tag.
.. versionadded:: 1.10
The ability to use a relative path was added.
This example includes the contents of the template whose name is contained in This example includes the contents of the template whose name is contained in
the variable ``template_name``:: the variable ``template_name``::
......
...@@ -451,6 +451,9 @@ Templates ...@@ -451,6 +451,9 @@ Templates
* The :func:`~django.template.context_processors.debug` context processor * The :func:`~django.template.context_processors.debug` context processor
contains queries for all database aliases instead of only the default alias. contains queries for all database aliases instead of only the default alias.
* Added relative path support for string arguments of the :ttag:`extends` and
:ttag:`include` template tags.
Tests Tests
~~~~~ ~~~~~
......
{% extends "./../../one.html" %}
{% block content %}{{ block.super }} dir2 one{% endblock %}
{% extends "./dir2/../looped.html" %}
{% block content %}{{ block.super }} dir1 three{% endblock %}
{% extends "./../one.html" %}
{% block content %}{{ block.super }} dir1 one{% endblock %}
{% extends './../one.html' %}
{% block content %}{{ block.super }} dir1 one{% endblock %}
{% extends '../one.html' %}
{% block content %}{{ block.super }} dir1 one{% endblock %}
{% extends "../one.html" %}
{% block content %}{{ block.super }} dir1 one{% endblock %}
{% extends "./dir2/../../three.html" %}
{% block content %}{{ block.super }} dir1 three{% endblock %}
{% extends "./dir2/one.html" %}
{% block content %}{{ block.super }} dir1 two{% endblock %}
{% extends "./../two.html" %}
{% block content %}{{ block.super }} one{% endblock %}
{% extends "./two.html" %}
{% block content %}{{ block.super }} one{% endblock %}
{% extends "./three.html" %}
{% block content %}{{ block.super }} two{% endblock %}
import os
from django.template import Context, Engine, TemplateSyntaxError
from django.test import SimpleTestCase
from .utils import ROOT
RELATIVE = os.path.join(ROOT, 'relative_templates')
class ExtendsRelativeBehaviorTests(SimpleTestCase):
def test_normal_extend(self):
engine = Engine(dirs=[RELATIVE])
template = engine.get_template('one.html')
output = template.render(Context({}))
self.assertEqual(output.strip(), 'three two one')
def test_dir1_extend(self):
engine = Engine(dirs=[RELATIVE])
template = engine.get_template('dir1/one.html')
output = template.render(Context({}))
self.assertEqual(output.strip(), 'three two one dir1 one')
def test_dir1_extend1(self):
engine = Engine(dirs=[RELATIVE])
template = engine.get_template('dir1/one1.html')
output = template.render(Context({}))
self.assertEqual(output.strip(), 'three two one dir1 one')
def test_dir1_extend2(self):
engine = Engine(dirs=[RELATIVE])
template = engine.get_template('dir1/one2.html')
output = template.render(Context({}))
self.assertEqual(output.strip(), 'three two one dir1 one')
def test_dir1_extend3(self):
engine = Engine(dirs=[RELATIVE])
template = engine.get_template('dir1/one3.html')
output = template.render(Context({}))
self.assertEqual(output.strip(), 'three two one dir1 one')
def test_dir2_extend(self):
engine = Engine(dirs=[RELATIVE])
template = engine.get_template('dir1/dir2/one.html')
output = template.render(Context({}))
self.assertEqual(output.strip(), 'three two one dir2 one')
def test_extend_error(self):
engine = Engine(dirs=[RELATIVE])
msg = (
"The relative path '\"./../two.html\"' points outside the file "
"hierarchy that template 'error_extends.html' is in."
)
with self.assertRaisesMessage(TemplateSyntaxError, msg):
engine.render_to_string('error_extends.html')
class IncludeRelativeBehaviorTests(SimpleTestCase):
def test_normal_include(self):
engine = Engine(dirs=[RELATIVE])
template = engine.get_template('dir1/dir2/inc2.html')
output = template.render(Context({}))
self.assertEqual(output.strip(), 'dir2 include')
def test_dir2_include(self):
engine = Engine(dirs=[RELATIVE])
template = engine.get_template('dir1/dir2/inc1.html')
output = template.render(Context({}))
self.assertEqual(output.strip(), 'three')
def test_include_error(self):
engine = Engine(dirs=[RELATIVE])
msg = (
"The relative path '\"./../three.html\"' points outside the file "
"hierarchy that template 'error_include.html' is in."
)
with self.assertRaisesMessage(TemplateSyntaxError, msg):
engine.render_to_string('error_include.html')
class ExtendsMixedBehaviorTests(SimpleTestCase):
def test_mixing1(self):
engine = Engine(dirs=[RELATIVE])
template = engine.get_template('dir1/two.html')
output = template.render(Context({}))
self.assertEqual(output.strip(), 'three two one dir2 one dir1 two')
def test_mixing2(self):
engine = Engine(dirs=[RELATIVE])
template = engine.get_template('dir1/three.html')
output = template.render(Context({}))
self.assertEqual(output.strip(), 'three dir1 three')
def test_mixing_loop(self):
engine = Engine(dirs=[RELATIVE])
msg = (
"The relative path '\"./dir2/../looped.html\"' was translated to "
"template name \'dir1/looped.html\', the same template in which "
"the tag appears."
)
with self.assertRaisesMessage(TemplateSyntaxError, msg):
engine.render_to_string('dir1/looped.html')
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment