278 lines
10 KiB
Python
278 lines
10 KiB
Python
import os.path
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from contextlib import contextmanager
|
|
|
|
from django.template import TemplateDoesNotExist
|
|
from django.template.engine import Engine
|
|
from django.test import SimpleTestCase, override_settings
|
|
from django.utils.functional import lazystr
|
|
|
|
from .utils import TEMPLATE_DIR
|
|
|
|
|
|
class CachedLoaderTests(SimpleTestCase):
|
|
def setUp(self):
|
|
self.engine = Engine(
|
|
dirs=[TEMPLATE_DIR],
|
|
loaders=[
|
|
(
|
|
"django.template.loaders.cached.Loader",
|
|
[
|
|
"django.template.loaders.filesystem.Loader",
|
|
],
|
|
),
|
|
],
|
|
)
|
|
|
|
def test_get_template(self):
|
|
template = self.engine.get_template("index.html")
|
|
self.assertEqual(template.origin.name, os.path.join(TEMPLATE_DIR, "index.html"))
|
|
self.assertEqual(template.origin.template_name, "index.html")
|
|
self.assertEqual(
|
|
template.origin.loader, self.engine.template_loaders[0].loaders[0]
|
|
)
|
|
|
|
cache = self.engine.template_loaders[0].get_template_cache
|
|
self.assertEqual(cache["index.html"], template)
|
|
|
|
# Run a second time from cache
|
|
template = self.engine.get_template("index.html")
|
|
self.assertEqual(template.origin.name, os.path.join(TEMPLATE_DIR, "index.html"))
|
|
self.assertEqual(template.origin.template_name, "index.html")
|
|
self.assertEqual(
|
|
template.origin.loader, self.engine.template_loaders[0].loaders[0]
|
|
)
|
|
|
|
def test_get_template_missing_debug_off(self):
|
|
"""
|
|
With template debugging disabled, the raw TemplateDoesNotExist class
|
|
should be cached when a template is missing. See ticket #26306 and
|
|
docstrings in the cached loader for details.
|
|
"""
|
|
self.engine.debug = False
|
|
with self.assertRaises(TemplateDoesNotExist):
|
|
self.engine.get_template("prod-template-missing.html")
|
|
e = self.engine.template_loaders[0].get_template_cache[
|
|
"prod-template-missing.html"
|
|
]
|
|
self.assertEqual(e, TemplateDoesNotExist)
|
|
|
|
def test_get_template_missing_debug_on(self):
|
|
"""
|
|
With template debugging enabled, a TemplateDoesNotExist instance
|
|
should be cached when a template is missing.
|
|
"""
|
|
self.engine.debug = True
|
|
with self.assertRaises(TemplateDoesNotExist):
|
|
self.engine.get_template("debug-template-missing.html")
|
|
e = self.engine.template_loaders[0].get_template_cache[
|
|
"debug-template-missing.html"
|
|
]
|
|
self.assertIsInstance(e, TemplateDoesNotExist)
|
|
self.assertEqual(e.args[0], "debug-template-missing.html")
|
|
|
|
def test_cached_exception_no_traceback(self):
|
|
"""
|
|
When a TemplateDoesNotExist instance is cached, the cached instance
|
|
should not contain the __traceback__, __context__, or __cause__
|
|
attributes that Python sets when raising exceptions.
|
|
"""
|
|
self.engine.debug = True
|
|
with self.assertRaises(TemplateDoesNotExist):
|
|
self.engine.get_template("no-traceback-in-cache.html")
|
|
e = self.engine.template_loaders[0].get_template_cache[
|
|
"no-traceback-in-cache.html"
|
|
]
|
|
|
|
error_msg = "Cached TemplateDoesNotExist must not have been thrown."
|
|
self.assertIsNone(e.__traceback__, error_msg)
|
|
self.assertIsNone(e.__context__, error_msg)
|
|
self.assertIsNone(e.__cause__, error_msg)
|
|
|
|
def test_template_name_leading_dash_caching(self):
|
|
"""
|
|
#26536 -- A leading dash in a template name shouldn't be stripped
|
|
from its cache key.
|
|
"""
|
|
self.assertEqual(
|
|
self.engine.template_loaders[0].cache_key("-template.html", []),
|
|
"-template.html",
|
|
)
|
|
|
|
def test_template_name_lazy_string(self):
|
|
"""
|
|
#26603 -- A template name specified as a lazy string should be forced
|
|
to text before computing its cache key.
|
|
"""
|
|
self.assertEqual(
|
|
self.engine.template_loaders[0].cache_key(lazystr("template.html"), []),
|
|
"template.html",
|
|
)
|
|
|
|
def test_get_dirs(self):
|
|
inner_dirs = self.engine.template_loaders[0].loaders[0].get_dirs()
|
|
self.assertSequenceEqual(
|
|
list(self.engine.template_loaders[0].get_dirs()), list(inner_dirs)
|
|
)
|
|
|
|
|
|
class FileSystemLoaderTests(SimpleTestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.engine = Engine(
|
|
dirs=[TEMPLATE_DIR], loaders=["django.template.loaders.filesystem.Loader"]
|
|
)
|
|
super().setUpClass()
|
|
|
|
@contextmanager
|
|
def set_dirs(self, dirs):
|
|
original_dirs = self.engine.dirs
|
|
self.engine.dirs = dirs
|
|
try:
|
|
yield
|
|
finally:
|
|
self.engine.dirs = original_dirs
|
|
|
|
@contextmanager
|
|
def source_checker(self, dirs):
|
|
loader = self.engine.template_loaders[0]
|
|
|
|
def check_sources(path, expected_sources):
|
|
expected_sources = [os.path.abspath(s) for s in expected_sources]
|
|
self.assertEqual(
|
|
[origin.name for origin in loader.get_template_sources(path)],
|
|
expected_sources,
|
|
)
|
|
|
|
with self.set_dirs(dirs):
|
|
yield check_sources
|
|
|
|
def test_get_template(self):
|
|
template = self.engine.get_template("index.html")
|
|
self.assertEqual(template.origin.name, os.path.join(TEMPLATE_DIR, "index.html"))
|
|
self.assertEqual(template.origin.template_name, "index.html")
|
|
self.assertEqual(template.origin.loader, self.engine.template_loaders[0])
|
|
self.assertEqual(
|
|
template.origin.loader_name, "django.template.loaders.filesystem.Loader"
|
|
)
|
|
|
|
def test_loaders_dirs(self):
|
|
engine = Engine(
|
|
loaders=[("django.template.loaders.filesystem.Loader", [TEMPLATE_DIR])]
|
|
)
|
|
template = engine.get_template("index.html")
|
|
self.assertEqual(template.origin.name, os.path.join(TEMPLATE_DIR, "index.html"))
|
|
|
|
def test_loaders_dirs_empty(self):
|
|
"""An empty dirs list in loaders overrides top level dirs."""
|
|
engine = Engine(
|
|
dirs=[TEMPLATE_DIR],
|
|
loaders=[("django.template.loaders.filesystem.Loader", [])],
|
|
)
|
|
with self.assertRaises(TemplateDoesNotExist):
|
|
engine.get_template("index.html")
|
|
|
|
def test_directory_security(self):
|
|
with self.source_checker(["/dir1", "/dir2"]) as check_sources:
|
|
check_sources("index.html", ["/dir1/index.html", "/dir2/index.html"])
|
|
check_sources("/etc/passwd", [])
|
|
check_sources("etc/passwd", ["/dir1/etc/passwd", "/dir2/etc/passwd"])
|
|
check_sources("../etc/passwd", [])
|
|
check_sources("../../../etc/passwd", [])
|
|
check_sources("/dir1/index.html", ["/dir1/index.html"])
|
|
check_sources("../dir2/index.html", ["/dir2/index.html"])
|
|
check_sources("/dir1blah", [])
|
|
check_sources("../dir1blah", [])
|
|
|
|
def test_unicode_template_name(self):
|
|
with self.source_checker(["/dir1", "/dir2"]) as check_sources:
|
|
check_sources("Ångström", ["/dir1/Ångström", "/dir2/Ångström"])
|
|
|
|
def test_bytestring(self):
|
|
loader = self.engine.template_loaders[0]
|
|
msg = "Can't mix strings and bytes in path components"
|
|
with self.assertRaisesMessage(TypeError, msg):
|
|
list(loader.get_template_sources(b"\xc3\x85ngstr\xc3\xb6m"))
|
|
|
|
def test_unicode_dir_name(self):
|
|
with self.source_checker(["/Straße"]) as check_sources:
|
|
check_sources("Ångström", ["/Straße/Ångström"])
|
|
|
|
@unittest.skipUnless(
|
|
os.path.normcase("/TEST") == os.path.normpath("/test"),
|
|
"This test only runs on case-sensitive file systems.",
|
|
)
|
|
def test_case_sensitivity(self):
|
|
with self.source_checker(["/dir1", "/DIR2"]) as check_sources:
|
|
check_sources("index.html", ["/dir1/index.html", "/DIR2/index.html"])
|
|
check_sources("/DIR1/index.HTML", ["/DIR1/index.HTML"])
|
|
|
|
def test_file_does_not_exist(self):
|
|
with self.assertRaises(TemplateDoesNotExist):
|
|
self.engine.get_template("doesnotexist.html")
|
|
|
|
@unittest.skipIf(
|
|
sys.platform == "win32",
|
|
"Python on Windows doesn't have working os.chmod().",
|
|
)
|
|
def test_permissions_error(self):
|
|
with tempfile.NamedTemporaryFile() as tmpfile:
|
|
tmpdir = os.path.dirname(tmpfile.name)
|
|
tmppath = os.path.join(tmpdir, tmpfile.name)
|
|
os.chmod(tmppath, 0o0222)
|
|
with self.set_dirs([tmpdir]):
|
|
with self.assertRaisesMessage(PermissionError, "Permission denied"):
|
|
self.engine.get_template(tmpfile.name)
|
|
|
|
def test_notafile_error(self):
|
|
# Windows raises PermissionError when trying to open a directory.
|
|
with self.assertRaises(
|
|
PermissionError if sys.platform == "win32" else IsADirectoryError
|
|
):
|
|
self.engine.get_template("first")
|
|
|
|
|
|
class AppDirectoriesLoaderTests(SimpleTestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.engine = Engine(
|
|
loaders=["django.template.loaders.app_directories.Loader"],
|
|
)
|
|
super().setUpClass()
|
|
|
|
@override_settings(INSTALLED_APPS=["template_tests"])
|
|
def test_get_template(self):
|
|
template = self.engine.get_template("index.html")
|
|
self.assertEqual(template.origin.name, os.path.join(TEMPLATE_DIR, "index.html"))
|
|
self.assertEqual(template.origin.template_name, "index.html")
|
|
self.assertEqual(template.origin.loader, self.engine.template_loaders[0])
|
|
|
|
@override_settings(INSTALLED_APPS=[])
|
|
def test_not_installed(self):
|
|
with self.assertRaises(TemplateDoesNotExist):
|
|
self.engine.get_template("index.html")
|
|
|
|
|
|
class LocmemLoaderTests(SimpleTestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.engine = Engine(
|
|
loaders=[
|
|
(
|
|
"django.template.loaders.locmem.Loader",
|
|
{
|
|
"index.html": "index",
|
|
},
|
|
)
|
|
],
|
|
)
|
|
super().setUpClass()
|
|
|
|
def test_get_template(self):
|
|
template = self.engine.get_template("index.html")
|
|
self.assertEqual(template.origin.name, "index.html")
|
|
self.assertEqual(template.origin.template_name, "index.html")
|
|
self.assertEqual(template.origin.loader, self.engine.template_loaders[0])
|