test_include.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. from django.template import (
  2. Context, Engine, TemplateDoesNotExist, TemplateSyntaxError, loader,
  3. )
  4. from django.test import SimpleTestCase
  5. from ..utils import setup
  6. from .test_basic import basic_templates
  7. include_fail_templates = {
  8. 'include-fail1': '{% load bad_tag %}{% badtag %}',
  9. 'include-fail2': '{% load broken_tag %}',
  10. }
  11. class IncludeTagTests(SimpleTestCase):
  12. libraries = {'bad_tag': 'template_tests.templatetags.bad_tag'}
  13. @setup({'include01': '{% include "basic-syntax01" %}'}, basic_templates)
  14. def test_include01(self):
  15. output = self.engine.render_to_string('include01')
  16. self.assertEqual(output, 'something cool')
  17. @setup({'include02': '{% include "basic-syntax02" %}'}, basic_templates)
  18. def test_include02(self):
  19. output = self.engine.render_to_string('include02', {'headline': 'Included'})
  20. self.assertEqual(output, 'Included')
  21. @setup({'include03': '{% include template_name %}'}, basic_templates)
  22. def test_include03(self):
  23. output = self.engine.render_to_string(
  24. 'include03',
  25. {'template_name': 'basic-syntax02', 'headline': 'Included'},
  26. )
  27. self.assertEqual(output, 'Included')
  28. @setup({'include04': 'a{% include "nonexistent" %}b'})
  29. def test_include04(self):
  30. template = self.engine.get_template('include04')
  31. with self.assertRaises(TemplateDoesNotExist):
  32. template.render(Context({}))
  33. @setup({
  34. 'include 05': 'template with a space',
  35. 'include06': '{% include "include 05"%}',
  36. })
  37. def test_include06(self):
  38. output = self.engine.render_to_string('include06')
  39. self.assertEqual(output, "template with a space")
  40. @setup({'include07': '{% include "basic-syntax02" with headline="Inline" %}'}, basic_templates)
  41. def test_include07(self):
  42. output = self.engine.render_to_string('include07', {'headline': 'Included'})
  43. self.assertEqual(output, 'Inline')
  44. @setup({'include08': '{% include headline with headline="Dynamic" %}'}, basic_templates)
  45. def test_include08(self):
  46. output = self.engine.render_to_string('include08', {'headline': 'basic-syntax02'})
  47. self.assertEqual(output, 'Dynamic')
  48. @setup(
  49. {'include09': '{{ first }}--'
  50. '{% include "basic-syntax03" with first=second|lower|upper second=first|upper %}'
  51. '--{{ second }}'},
  52. basic_templates,
  53. )
  54. def test_include09(self):
  55. output = self.engine.render_to_string('include09', {'first': 'Ul', 'second': 'lU'})
  56. self.assertEqual(output, 'Ul--LU --- UL--lU')
  57. @setup({'include10': '{% include "basic-syntax03" only %}'}, basic_templates)
  58. def test_include10(self):
  59. output = self.engine.render_to_string('include10', {'first': '1'})
  60. if self.engine.string_if_invalid:
  61. self.assertEqual(output, 'INVALID --- INVALID')
  62. else:
  63. self.assertEqual(output, ' --- ')
  64. @setup({'include11': '{% include "basic-syntax03" only with second=2 %}'}, basic_templates)
  65. def test_include11(self):
  66. output = self.engine.render_to_string('include11', {'first': '1'})
  67. if self.engine.string_if_invalid:
  68. self.assertEqual(output, 'INVALID --- 2')
  69. else:
  70. self.assertEqual(output, ' --- 2')
  71. @setup({'include12': '{% include "basic-syntax03" with first=1 only %}'}, basic_templates)
  72. def test_include12(self):
  73. output = self.engine.render_to_string('include12', {'second': '2'})
  74. if self.engine.string_if_invalid:
  75. self.assertEqual(output, '1 --- INVALID')
  76. else:
  77. self.assertEqual(output, '1 --- ')
  78. @setup(
  79. {'include13': '{% autoescape off %}{% include "basic-syntax03" %}{% endautoescape %}'},
  80. basic_templates,
  81. )
  82. def test_include13(self):
  83. output = self.engine.render_to_string('include13', {'first': '&'})
  84. if self.engine.string_if_invalid:
  85. self.assertEqual(output, '& --- INVALID')
  86. else:
  87. self.assertEqual(output, '& --- ')
  88. @setup(
  89. {'include14': '{% autoescape off %}'
  90. '{% include "basic-syntax03" with first=var1 only %}'
  91. '{% endautoescape %}'},
  92. basic_templates,
  93. )
  94. def test_include14(self):
  95. output = self.engine.render_to_string('include14', {'var1': '&'})
  96. if self.engine.string_if_invalid:
  97. self.assertEqual(output, '& --- INVALID')
  98. else:
  99. self.assertEqual(output, '& --- ')
  100. # Include syntax errors
  101. @setup({'include-error01': '{% include "basic-syntax01" with %}'})
  102. def test_include_error01(self):
  103. with self.assertRaises(TemplateSyntaxError):
  104. self.engine.get_template('include-error01')
  105. @setup({'include-error02': '{% include "basic-syntax01" with "no key" %}'})
  106. def test_include_error02(self):
  107. with self.assertRaises(TemplateSyntaxError):
  108. self.engine.get_template('include-error02')
  109. @setup({'include-error03': '{% include "basic-syntax01" with dotted.arg="error" %}'})
  110. def test_include_error03(self):
  111. with self.assertRaises(TemplateSyntaxError):
  112. self.engine.get_template('include-error03')
  113. @setup({'include-error04': '{% include "basic-syntax01" something_random %}'})
  114. def test_include_error04(self):
  115. with self.assertRaises(TemplateSyntaxError):
  116. self.engine.get_template('include-error04')
  117. @setup({'include-error05': '{% include "basic-syntax01" foo="duplicate" foo="key" %}'})
  118. def test_include_error05(self):
  119. with self.assertRaises(TemplateSyntaxError):
  120. self.engine.get_template('include-error05')
  121. @setup({'include-error06': '{% include "basic-syntax01" only only %}'})
  122. def test_include_error06(self):
  123. with self.assertRaises(TemplateSyntaxError):
  124. self.engine.get_template('include-error06')
  125. @setup(include_fail_templates)
  126. def test_include_fail1(self):
  127. with self.assertRaises(RuntimeError):
  128. self.engine.get_template('include-fail1')
  129. @setup(include_fail_templates)
  130. def test_include_fail2(self):
  131. with self.assertRaises(TemplateSyntaxError):
  132. self.engine.get_template('include-fail2')
  133. @setup({'include-error07': '{% include "include-fail1" %}'}, include_fail_templates)
  134. def test_include_error07(self):
  135. template = self.engine.get_template('include-error07')
  136. with self.assertRaises(RuntimeError):
  137. template.render(Context())
  138. @setup({'include-error08': '{% include "include-fail2" %}'}, include_fail_templates)
  139. def test_include_error08(self):
  140. template = self.engine.get_template('include-error08')
  141. with self.assertRaises(TemplateSyntaxError):
  142. template.render(Context())
  143. @setup({'include-error09': '{% include failed_include %}'}, include_fail_templates)
  144. def test_include_error09(self):
  145. context = Context({'failed_include': 'include-fail1'})
  146. template = self.engine.get_template('include-error09')
  147. with self.assertRaises(RuntimeError):
  148. template.render(context)
  149. @setup({'include-error10': '{% include failed_include %}'}, include_fail_templates)
  150. def test_include_error10(self):
  151. context = Context({'failed_include': 'include-fail2'})
  152. template = self.engine.get_template('include-error10')
  153. with self.assertRaises(TemplateSyntaxError):
  154. template.render(context)
  155. @setup({'include_empty': '{% include %}'})
  156. def test_include_empty(self):
  157. msg = (
  158. "'include' tag takes at least one argument: the name of the "
  159. "template to be included."
  160. )
  161. with self.assertRaisesMessage(TemplateSyntaxError, msg):
  162. self.engine.get_template('include_empty')
  163. class IncludeTests(SimpleTestCase):
  164. def test_include_missing_template(self):
  165. """
  166. The correct template is identified as not existing
  167. when {% include %} specifies a template that does not exist.
  168. """
  169. engine = Engine(app_dirs=True, debug=True)
  170. template = engine.get_template('test_include_error.html')
  171. with self.assertRaisesMessage(TemplateDoesNotExist, 'missing.html'):
  172. template.render(Context())
  173. def test_extends_include_missing_baseloader(self):
  174. """
  175. #12787 -- The correct template is identified as not existing
  176. when {% extends %} specifies a template that does exist, but that
  177. template has an {% include %} of something that does not exist.
  178. """
  179. engine = Engine(app_dirs=True, debug=True)
  180. template = engine.get_template('test_extends_error.html')
  181. with self.assertRaisesMessage(TemplateDoesNotExist, 'missing.html'):
  182. template.render(Context())
  183. def test_extends_include_missing_cachedloader(self):
  184. engine = Engine(debug=True, loaders=[
  185. ('django.template.loaders.cached.Loader', [
  186. 'django.template.loaders.app_directories.Loader',
  187. ]),
  188. ])
  189. template = engine.get_template('test_extends_error.html')
  190. with self.assertRaisesMessage(TemplateDoesNotExist, 'missing.html'):
  191. template.render(Context())
  192. # Repeat to ensure it still works when loading from the cache
  193. template = engine.get_template('test_extends_error.html')
  194. with self.assertRaisesMessage(TemplateDoesNotExist, 'missing.html'):
  195. template.render(Context())
  196. def test_include_template_argument(self):
  197. """
  198. Support any render() supporting object
  199. """
  200. engine = Engine()
  201. ctx = Context({
  202. 'tmpl': engine.from_string('This worked!'),
  203. })
  204. outer_tmpl = engine.from_string('{% include tmpl %}')
  205. output = outer_tmpl.render(ctx)
  206. self.assertEqual(output, 'This worked!')
  207. def test_include_template_iterable(self):
  208. engine = Engine.get_default()
  209. outer_temp = engine.from_string('{% include var %}')
  210. tests = [
  211. ('admin/fail.html', 'index.html'),
  212. ['admin/fail.html', 'index.html'],
  213. ]
  214. for template_names in tests:
  215. with self.subTest(template_names):
  216. output = outer_temp.render(Context({'var': template_names}))
  217. self.assertEqual(output, 'index\n')
  218. def test_include_template_none(self):
  219. engine = Engine.get_default()
  220. outer_temp = engine.from_string('{% include var %}')
  221. ctx = Context({'var': None})
  222. msg = 'No template names provided'
  223. with self.assertRaisesMessage(TemplateDoesNotExist, msg):
  224. outer_temp.render(ctx)
  225. def test_include_from_loader_get_template(self):
  226. tmpl = loader.get_template('include_tpl.html') # {% include tmpl %}
  227. output = tmpl.render({'tmpl': loader.get_template('index.html')})
  228. self.assertEqual(output, 'index\n\n')
  229. def test_include_immediate_missing(self):
  230. """
  231. #16417 -- Include tags pointing to missing templates should not raise
  232. an error at parsing time.
  233. """
  234. Engine(debug=True).from_string('{% include "this_does_not_exist.html" %}')
  235. def test_include_recursive(self):
  236. comments = [
  237. {
  238. 'comment': 'A1',
  239. 'children': [
  240. {'comment': 'B1', 'children': []},
  241. {'comment': 'B2', 'children': []},
  242. {'comment': 'B3', 'children': [
  243. {'comment': 'C1', 'children': []}
  244. ]},
  245. ]
  246. }
  247. ]
  248. engine = Engine(app_dirs=True)
  249. t = engine.get_template('recursive_include.html')
  250. self.assertEqual(
  251. "Recursion! A1 Recursion! B1 B2 B3 Recursion! C1",
  252. t.render(Context({'comments': comments})).replace(' ', '').replace('\n', ' ').strip(),
  253. )
  254. def test_include_cache(self):
  255. """
  256. {% include %} keeps resolved templates constant (#27974). The
  257. CounterNode object in the {% counter %} template tag is created once
  258. if caching works properly. Each iteration increases the counter instead
  259. of restarting it.
  260. This works as a regression test only if the cached loader
  261. isn't used, so the @setup decorator isn't used.
  262. """
  263. engine = Engine(loaders=[
  264. ('django.template.loaders.locmem.Loader', {
  265. 'template': '{% for x in vars %}{% include "include" %}{% endfor %}',
  266. 'include': '{% include "next" %}',
  267. 'next': '{% load custom %}{% counter %}'
  268. }),
  269. ], libraries={'custom': 'template_tests.templatetags.custom'})
  270. output = engine.render_to_string('template', {'vars': range(9)})
  271. self.assertEqual(output, '012345678')