1. import os.path
    
  2. import sys
    
  3. import tempfile
    
  4. import unittest
    
  5. from contextlib import contextmanager
    
  6. 
    
  7. from django.template import TemplateDoesNotExist
    
  8. from django.template.engine import Engine
    
  9. from django.test import SimpleTestCase, override_settings
    
  10. from django.utils.functional import lazystr
    
  11. 
    
  12. from .utils import TEMPLATE_DIR
    
  13. 
    
  14. 
    
  15. class CachedLoaderTests(SimpleTestCase):
    
  16.     def setUp(self):
    
  17.         self.engine = Engine(
    
  18.             dirs=[TEMPLATE_DIR],
    
  19.             loaders=[
    
  20.                 (
    
  21.                     "django.template.loaders.cached.Loader",
    
  22.                     [
    
  23.                         "django.template.loaders.filesystem.Loader",
    
  24.                     ],
    
  25.                 ),
    
  26.             ],
    
  27.         )
    
  28. 
    
  29.     def test_get_template(self):
    
  30.         template = self.engine.get_template("index.html")
    
  31.         self.assertEqual(template.origin.name, os.path.join(TEMPLATE_DIR, "index.html"))
    
  32.         self.assertEqual(template.origin.template_name, "index.html")
    
  33.         self.assertEqual(
    
  34.             template.origin.loader, self.engine.template_loaders[0].loaders[0]
    
  35.         )
    
  36. 
    
  37.         cache = self.engine.template_loaders[0].get_template_cache
    
  38.         self.assertEqual(cache["index.html"], template)
    
  39. 
    
  40.         # Run a second time from cache
    
  41.         template = self.engine.get_template("index.html")
    
  42.         self.assertEqual(template.origin.name, os.path.join(TEMPLATE_DIR, "index.html"))
    
  43.         self.assertEqual(template.origin.template_name, "index.html")
    
  44.         self.assertEqual(
    
  45.             template.origin.loader, self.engine.template_loaders[0].loaders[0]
    
  46.         )
    
  47. 
    
  48.     def test_get_template_missing_debug_off(self):
    
  49.         """
    
  50.         With template debugging disabled, the raw TemplateDoesNotExist class
    
  51.         should be cached when a template is missing. See ticket #26306 and
    
  52.         docstrings in the cached loader for details.
    
  53.         """
    
  54.         self.engine.debug = False
    
  55.         with self.assertRaises(TemplateDoesNotExist):
    
  56.             self.engine.get_template("prod-template-missing.html")
    
  57.         e = self.engine.template_loaders[0].get_template_cache[
    
  58.             "prod-template-missing.html"
    
  59.         ]
    
  60.         self.assertEqual(e, TemplateDoesNotExist)
    
  61. 
    
  62.     def test_get_template_missing_debug_on(self):
    
  63.         """
    
  64.         With template debugging enabled, a TemplateDoesNotExist instance
    
  65.         should be cached when a template is missing.
    
  66.         """
    
  67.         self.engine.debug = True
    
  68.         with self.assertRaises(TemplateDoesNotExist):
    
  69.             self.engine.get_template("debug-template-missing.html")
    
  70.         e = self.engine.template_loaders[0].get_template_cache[
    
  71.             "debug-template-missing.html"
    
  72.         ]
    
  73.         self.assertIsInstance(e, TemplateDoesNotExist)
    
  74.         self.assertEqual(e.args[0], "debug-template-missing.html")
    
  75. 
    
  76.     def test_cached_exception_no_traceback(self):
    
  77.         """
    
  78.         When a TemplateDoesNotExist instance is cached, the cached instance
    
  79.         should not contain the __traceback__, __context__, or __cause__
    
  80.         attributes that Python sets when raising exceptions.
    
  81.         """
    
  82.         self.engine.debug = True
    
  83.         with self.assertRaises(TemplateDoesNotExist):
    
  84.             self.engine.get_template("no-traceback-in-cache.html")
    
  85.         e = self.engine.template_loaders[0].get_template_cache[
    
  86.             "no-traceback-in-cache.html"
    
  87.         ]
    
  88. 
    
  89.         error_msg = "Cached TemplateDoesNotExist must not have been thrown."
    
  90.         self.assertIsNone(e.__traceback__, error_msg)
    
  91.         self.assertIsNone(e.__context__, error_msg)
    
  92.         self.assertIsNone(e.__cause__, error_msg)
    
  93. 
    
  94.     def test_template_name_leading_dash_caching(self):
    
  95.         """
    
  96.         #26536 -- A leading dash in a template name shouldn't be stripped
    
  97.         from its cache key.
    
  98.         """
    
  99.         self.assertEqual(
    
  100.             self.engine.template_loaders[0].cache_key("-template.html", []),
    
  101.             "-template.html",
    
  102.         )
    
  103. 
    
  104.     def test_template_name_lazy_string(self):
    
  105.         """
    
  106.         #26603 -- A template name specified as a lazy string should be forced
    
  107.         to text before computing its cache key.
    
  108.         """
    
  109.         self.assertEqual(
    
  110.             self.engine.template_loaders[0].cache_key(lazystr("template.html"), []),
    
  111.             "template.html",
    
  112.         )
    
  113. 
    
  114.     def test_get_dirs(self):
    
  115.         inner_dirs = self.engine.template_loaders[0].loaders[0].get_dirs()
    
  116.         self.assertSequenceEqual(
    
  117.             list(self.engine.template_loaders[0].get_dirs()), list(inner_dirs)
    
  118.         )
    
  119. 
    
  120. 
    
  121. class FileSystemLoaderTests(SimpleTestCase):
    
  122.     @classmethod
    
  123.     def setUpClass(cls):
    
  124.         cls.engine = Engine(
    
  125.             dirs=[TEMPLATE_DIR], loaders=["django.template.loaders.filesystem.Loader"]
    
  126.         )
    
  127.         super().setUpClass()
    
  128. 
    
  129.     @contextmanager
    
  130.     def set_dirs(self, dirs):
    
  131.         original_dirs = self.engine.dirs
    
  132.         self.engine.dirs = dirs
    
  133.         try:
    
  134.             yield
    
  135.         finally:
    
  136.             self.engine.dirs = original_dirs
    
  137. 
    
  138.     @contextmanager
    
  139.     def source_checker(self, dirs):
    
  140.         loader = self.engine.template_loaders[0]
    
  141. 
    
  142.         def check_sources(path, expected_sources):
    
  143.             expected_sources = [os.path.abspath(s) for s in expected_sources]
    
  144.             self.assertEqual(
    
  145.                 [origin.name for origin in loader.get_template_sources(path)],
    
  146.                 expected_sources,
    
  147.             )
    
  148. 
    
  149.         with self.set_dirs(dirs):
    
  150.             yield check_sources
    
  151. 
    
  152.     def test_get_template(self):
    
  153.         template = self.engine.get_template("index.html")
    
  154.         self.assertEqual(template.origin.name, os.path.join(TEMPLATE_DIR, "index.html"))
    
  155.         self.assertEqual(template.origin.template_name, "index.html")
    
  156.         self.assertEqual(template.origin.loader, self.engine.template_loaders[0])
    
  157.         self.assertEqual(
    
  158.             template.origin.loader_name, "django.template.loaders.filesystem.Loader"
    
  159.         )
    
  160. 
    
  161.     def test_loaders_dirs(self):
    
  162.         engine = Engine(
    
  163.             loaders=[("django.template.loaders.filesystem.Loader", [TEMPLATE_DIR])]
    
  164.         )
    
  165.         template = engine.get_template("index.html")
    
  166.         self.assertEqual(template.origin.name, os.path.join(TEMPLATE_DIR, "index.html"))
    
  167. 
    
  168.     def test_loaders_dirs_empty(self):
    
  169.         """An empty dirs list in loaders overrides top level dirs."""
    
  170.         engine = Engine(
    
  171.             dirs=[TEMPLATE_DIR],
    
  172.             loaders=[("django.template.loaders.filesystem.Loader", [])],
    
  173.         )
    
  174.         with self.assertRaises(TemplateDoesNotExist):
    
  175.             engine.get_template("index.html")
    
  176. 
    
  177.     def test_directory_security(self):
    
  178.         with self.source_checker(["/dir1", "/dir2"]) as check_sources:
    
  179.             check_sources("index.html", ["/dir1/index.html", "/dir2/index.html"])
    
  180.             check_sources("/etc/passwd", [])
    
  181.             check_sources("etc/passwd", ["/dir1/etc/passwd", "/dir2/etc/passwd"])
    
  182.             check_sources("../etc/passwd", [])
    
  183.             check_sources("../../../etc/passwd", [])
    
  184.             check_sources("/dir1/index.html", ["/dir1/index.html"])
    
  185.             check_sources("../dir2/index.html", ["/dir2/index.html"])
    
  186.             check_sources("/dir1blah", [])
    
  187.             check_sources("../dir1blah", [])
    
  188. 
    
  189.     def test_unicode_template_name(self):
    
  190.         with self.source_checker(["/dir1", "/dir2"]) as check_sources:
    
  191.             check_sources("Ångström", ["/dir1/Ångström", "/dir2/Ångström"])
    
  192. 
    
  193.     def test_bytestring(self):
    
  194.         loader = self.engine.template_loaders[0]
    
  195.         msg = "Can't mix strings and bytes in path components"
    
  196.         with self.assertRaisesMessage(TypeError, msg):
    
  197.             list(loader.get_template_sources(b"\xc3\x85ngstr\xc3\xb6m"))
    
  198. 
    
  199.     def test_unicode_dir_name(self):
    
  200.         with self.source_checker(["/Straße"]) as check_sources:
    
  201.             check_sources("Ångström", ["/Straße/Ångström"])
    
  202. 
    
  203.     @unittest.skipUnless(
    
  204.         os.path.normcase("/TEST") == os.path.normpath("/test"),
    
  205.         "This test only runs on case-sensitive file systems.",
    
  206.     )
    
  207.     def test_case_sensitivity(self):
    
  208.         with self.source_checker(["/dir1", "/DIR2"]) as check_sources:
    
  209.             check_sources("index.html", ["/dir1/index.html", "/DIR2/index.html"])
    
  210.             check_sources("/DIR1/index.HTML", ["/DIR1/index.HTML"])
    
  211. 
    
  212.     def test_file_does_not_exist(self):
    
  213.         with self.assertRaises(TemplateDoesNotExist):
    
  214.             self.engine.get_template("doesnotexist.html")
    
  215. 
    
  216.     @unittest.skipIf(
    
  217.         sys.platform == "win32",
    
  218.         "Python on Windows doesn't have working os.chmod().",
    
  219.     )
    
  220.     def test_permissions_error(self):
    
  221.         with tempfile.NamedTemporaryFile() as tmpfile:
    
  222.             tmpdir = os.path.dirname(tmpfile.name)
    
  223.             tmppath = os.path.join(tmpdir, tmpfile.name)
    
  224.             os.chmod(tmppath, 0o0222)
    
  225.             with self.set_dirs([tmpdir]):
    
  226.                 with self.assertRaisesMessage(PermissionError, "Permission denied"):
    
  227.                     self.engine.get_template(tmpfile.name)
    
  228. 
    
  229.     def test_notafile_error(self):
    
  230.         # Windows raises PermissionError when trying to open a directory.
    
  231.         with self.assertRaises(
    
  232.             PermissionError if sys.platform == "win32" else IsADirectoryError
    
  233.         ):
    
  234.             self.engine.get_template("first")
    
  235. 
    
  236. 
    
  237. class AppDirectoriesLoaderTests(SimpleTestCase):
    
  238.     @classmethod
    
  239.     def setUpClass(cls):
    
  240.         cls.engine = Engine(
    
  241.             loaders=["django.template.loaders.app_directories.Loader"],
    
  242.         )
    
  243.         super().setUpClass()
    
  244. 
    
  245.     @override_settings(INSTALLED_APPS=["template_tests"])
    
  246.     def test_get_template(self):
    
  247.         template = self.engine.get_template("index.html")
    
  248.         self.assertEqual(template.origin.name, os.path.join(TEMPLATE_DIR, "index.html"))
    
  249.         self.assertEqual(template.origin.template_name, "index.html")
    
  250.         self.assertEqual(template.origin.loader, self.engine.template_loaders[0])
    
  251. 
    
  252.     @override_settings(INSTALLED_APPS=[])
    
  253.     def test_not_installed(self):
    
  254.         with self.assertRaises(TemplateDoesNotExist):
    
  255.             self.engine.get_template("index.html")
    
  256. 
    
  257. 
    
  258. class LocmemLoaderTests(SimpleTestCase):
    
  259.     @classmethod
    
  260.     def setUpClass(cls):
    
  261.         cls.engine = Engine(
    
  262.             loaders=[
    
  263.                 (
    
  264.                     "django.template.loaders.locmem.Loader",
    
  265.                     {
    
  266.                         "index.html": "index",
    
  267.                     },
    
  268.                 )
    
  269.             ],
    
  270.         )
    
  271.         super().setUpClass()
    
  272. 
    
  273.     def test_get_template(self):
    
  274.         template = self.engine.get_template("index.html")
    
  275.         self.assertEqual(template.origin.name, "index.html")
    
  276.         self.assertEqual(template.origin.template_name, "index.html")
    
  277.         self.assertEqual(template.origin.loader, self.engine.template_loaders[0])