summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVitaly Bogomolov <mail@vitaly-bogomolov.ru>2016-03-24 11:39:37 +0300
committerTim Graham <timograham@gmail.com>2016-05-07 16:21:57 -0400
commitaec4f97555cbfc9d14d698f61d43a478f5911661 (patch)
tree9c866ff43cae89b657699968dc47d1552f1a9d61
parentad403ffa45db3becc2fec727212538446b40d380 (diff)
Fixed #26402 -- Added relative path support in include/extends template tags.
-rw-r--r--django/template/loader_tags.py33
-rw-r--r--docs/ref/templates/builtins.txt27
-rw-r--r--docs/releases/1.10.txt3
-rw-r--r--tests/template_tests/relative_templates/dir1/dir2/inc1.html1
-rw-r--r--tests/template_tests/relative_templates/dir1/dir2/inc2.html1
-rw-r--r--tests/template_tests/relative_templates/dir1/dir2/include_content.html1
-rw-r--r--tests/template_tests/relative_templates/dir1/dir2/one.html3
-rw-r--r--tests/template_tests/relative_templates/dir1/looped.html3
-rw-r--r--tests/template_tests/relative_templates/dir1/one.html3
-rw-r--r--tests/template_tests/relative_templates/dir1/one1.html3
-rw-r--r--tests/template_tests/relative_templates/dir1/one2.html3
-rw-r--r--tests/template_tests/relative_templates/dir1/one3.html3
-rw-r--r--tests/template_tests/relative_templates/dir1/three.html3
-rw-r--r--tests/template_tests/relative_templates/dir1/two.html3
-rw-r--r--tests/template_tests/relative_templates/error_extends.html3
-rw-r--r--tests/template_tests/relative_templates/error_include.html1
-rw-r--r--tests/template_tests/relative_templates/one.html3
-rw-r--r--tests/template_tests/relative_templates/three.html1
-rw-r--r--tests/template_tests/relative_templates/two.html3
-rw-r--r--tests/template_tests/test_extends_relative.py105
20 files changed, 206 insertions, 0 deletions
diff --git a/django/template/loader_tags.py b/django/template/loader_tags.py
index ebd8a1a9f7..839eacfd95 100644
--- a/django/template/loader_tags.py
+++ b/django/template/loader_tags.py
@@ -1,4 +1,5 @@
import logging
+import posixpath
from collections import defaultdict
from django.utils import six
@@ -249,6 +250,36 @@ def do_block(parser, token):
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')
def do_extends(parser, token):
"""
@@ -263,6 +294,7 @@ def do_extends(parser, token):
bits = token.split_contents()
if len(bits) != 2:
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])
nodelist = parser.parse()
if nodelist.get_nodes_by_type(ExtendsNode):
@@ -313,5 +345,6 @@ def do_include(parser, token):
options[option] = value
isolated_context = options.get('only', False)
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,
isolated_context=isolated_context)
diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt
index 6c766c8a14..ed904b1872 100644
--- a/docs/ref/templates/builtins.txt
+++ b/docs/ref/templates/builtins.txt
@@ -212,6 +212,26 @@ This tag can be used in two ways:
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
``filter``
@@ -663,6 +683,13 @@ This example includes the contents of the template ``"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
the variable ``template_name``::
diff --git a/docs/releases/1.10.txt b/docs/releases/1.10.txt
index dd874e2ae8..0d808a9454 100644
--- a/docs/releases/1.10.txt
+++ b/docs/releases/1.10.txt
@@ -451,6 +451,9 @@ Templates
* The :func:`~django.template.context_processors.debug` context processor
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
~~~~~
diff --git a/tests/template_tests/relative_templates/dir1/dir2/inc1.html b/tests/template_tests/relative_templates/dir1/dir2/inc1.html
new file mode 100644
index 0000000000..a854bef662
--- /dev/null
+++ b/tests/template_tests/relative_templates/dir1/dir2/inc1.html
@@ -0,0 +1 @@
+{% include "./../../three.html" %}
diff --git a/tests/template_tests/relative_templates/dir1/dir2/inc2.html b/tests/template_tests/relative_templates/dir1/dir2/inc2.html
new file mode 100644
index 0000000000..376f47975e
--- /dev/null
+++ b/tests/template_tests/relative_templates/dir1/dir2/inc2.html
@@ -0,0 +1 @@
+{% include "./include_content.html" %}
diff --git a/tests/template_tests/relative_templates/dir1/dir2/include_content.html b/tests/template_tests/relative_templates/dir1/dir2/include_content.html
new file mode 100644
index 0000000000..132d8b8145
--- /dev/null
+++ b/tests/template_tests/relative_templates/dir1/dir2/include_content.html
@@ -0,0 +1 @@
+dir2 include
diff --git a/tests/template_tests/relative_templates/dir1/dir2/one.html b/tests/template_tests/relative_templates/dir1/dir2/one.html
new file mode 100644
index 0000000000..11e6424213
--- /dev/null
+++ b/tests/template_tests/relative_templates/dir1/dir2/one.html
@@ -0,0 +1,3 @@
+{% extends "./../../one.html" %}
+
+{% block content %}{{ block.super }} dir2 one{% endblock %}
diff --git a/tests/template_tests/relative_templates/dir1/looped.html b/tests/template_tests/relative_templates/dir1/looped.html
new file mode 100644
index 0000000000..8e9d8ac4e5
--- /dev/null
+++ b/tests/template_tests/relative_templates/dir1/looped.html
@@ -0,0 +1,3 @@
+{% extends "./dir2/../looped.html" %}
+
+{% block content %}{{ block.super }} dir1 three{% endblock %}
diff --git a/tests/template_tests/relative_templates/dir1/one.html b/tests/template_tests/relative_templates/dir1/one.html
new file mode 100644
index 0000000000..3b89c23330
--- /dev/null
+++ b/tests/template_tests/relative_templates/dir1/one.html
@@ -0,0 +1,3 @@
+{% extends "./../one.html" %}
+
+{% block content %}{{ block.super }} dir1 one{% endblock %}
diff --git a/tests/template_tests/relative_templates/dir1/one1.html b/tests/template_tests/relative_templates/dir1/one1.html
new file mode 100644
index 0000000000..9f60109975
--- /dev/null
+++ b/tests/template_tests/relative_templates/dir1/one1.html
@@ -0,0 +1,3 @@
+{% extends './../one.html' %}
+
+{% block content %}{{ block.super }} dir1 one{% endblock %}
diff --git a/tests/template_tests/relative_templates/dir1/one2.html b/tests/template_tests/relative_templates/dir1/one2.html
new file mode 100644
index 0000000000..1ca9f17b21
--- /dev/null
+++ b/tests/template_tests/relative_templates/dir1/one2.html
@@ -0,0 +1,3 @@
+{% extends '../one.html' %}
+
+{% block content %}{{ block.super }} dir1 one{% endblock %}
diff --git a/tests/template_tests/relative_templates/dir1/one3.html b/tests/template_tests/relative_templates/dir1/one3.html
new file mode 100644
index 0000000000..3df6195fbb
--- /dev/null
+++ b/tests/template_tests/relative_templates/dir1/one3.html
@@ -0,0 +1,3 @@
+{% extends "../one.html" %}
+
+{% block content %}{{ block.super }} dir1 one{% endblock %}
diff --git a/tests/template_tests/relative_templates/dir1/three.html b/tests/template_tests/relative_templates/dir1/three.html
new file mode 100644
index 0000000000..d8e3c3cb74
--- /dev/null
+++ b/tests/template_tests/relative_templates/dir1/three.html
@@ -0,0 +1,3 @@
+{% extends "./dir2/../../three.html" %}
+
+{% block content %}{{ block.super }} dir1 three{% endblock %}
diff --git a/tests/template_tests/relative_templates/dir1/two.html b/tests/template_tests/relative_templates/dir1/two.html
new file mode 100644
index 0000000000..b6542b8a3e
--- /dev/null
+++ b/tests/template_tests/relative_templates/dir1/two.html
@@ -0,0 +1,3 @@
+{% extends "./dir2/one.html" %}
+
+{% block content %}{{ block.super }} dir1 two{% endblock %}
diff --git a/tests/template_tests/relative_templates/error_extends.html b/tests/template_tests/relative_templates/error_extends.html
new file mode 100644
index 0000000000..83e41b2999
--- /dev/null
+++ b/tests/template_tests/relative_templates/error_extends.html
@@ -0,0 +1,3 @@
+{% extends "./../two.html" %}
+
+{% block content %}{{ block.super }} one{% endblock %}
diff --git a/tests/template_tests/relative_templates/error_include.html b/tests/template_tests/relative_templates/error_include.html
new file mode 100644
index 0000000000..a5efe30fbc
--- /dev/null
+++ b/tests/template_tests/relative_templates/error_include.html
@@ -0,0 +1 @@
+{% include "./../three.html" %}
diff --git a/tests/template_tests/relative_templates/one.html b/tests/template_tests/relative_templates/one.html
new file mode 100644
index 0000000000..9ced0ff8e4
--- /dev/null
+++ b/tests/template_tests/relative_templates/one.html
@@ -0,0 +1,3 @@
+{% extends "./two.html" %}
+
+{% block content %}{{ block.super }} one{% endblock %}
diff --git a/tests/template_tests/relative_templates/three.html b/tests/template_tests/relative_templates/three.html
new file mode 100644
index 0000000000..360aeeea5e
--- /dev/null
+++ b/tests/template_tests/relative_templates/three.html
@@ -0,0 +1 @@
+{% block content %}three{% endblock %}
diff --git a/tests/template_tests/relative_templates/two.html b/tests/template_tests/relative_templates/two.html
new file mode 100644
index 0000000000..5fb317db93
--- /dev/null
+++ b/tests/template_tests/relative_templates/two.html
@@ -0,0 +1,3 @@
+{% extends "./three.html" %}
+
+{% block content %}{{ block.super }} two{% endblock %}
diff --git a/tests/template_tests/test_extends_relative.py b/tests/template_tests/test_extends_relative.py
new file mode 100644
index 0000000000..12324f0df6
--- /dev/null
+++ b/tests/template_tests/test_extends_relative.py
@@ -0,0 +1,105 @@
+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')