test_loaders.py 9.6 KB

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