import os from django.template import Context, Engine, TemplateDoesNotExist, TemplateSyntaxError from django.test import SimpleTestCase from .utils import ROOT, setup RECURSIVE = os.path.join(ROOT, "recursive_templates") class ExtendsBehaviorTests(SimpleTestCase): def test_normal_extend(self): engine = Engine(dirs=[os.path.join(RECURSIVE, "fs")]) template = engine.get_template("one.html") output = template.render(Context({})) self.assertEqual(output.strip(), "three two one") def test_extend_recursive(self): engine = Engine( dirs=[ os.path.join(RECURSIVE, "fs"), os.path.join(RECURSIVE, "fs2"), os.path.join(RECURSIVE, "fs3"), ] ) template = engine.get_template("recursive.html") output = template.render(Context({})) self.assertEqual(output.strip(), "fs3/recursive fs2/recursive fs/recursive") def test_extend_missing(self): engine = Engine(dirs=[os.path.join(RECURSIVE, "fs")]) template = engine.get_template("extend-missing.html") with self.assertRaises(TemplateDoesNotExist) as e: template.render(Context({})) tried = e.exception.tried self.assertEqual(len(tried), 1) self.assertEqual(tried[0][0].template_name, "missing.html") def test_recursive_multiple_loaders(self): engine = Engine( dirs=[os.path.join(RECURSIVE, "fs")], loaders=[ ( "django.template.loaders.locmem.Loader", { "one.html": ( '{% extends "one.html" %}{% block content %}' "{{ block.super }} locmem-one{% endblock %}" ), "two.html": ( '{% extends "two.html" %}{% block content %}' "{{ block.super }} locmem-two{% endblock %}" ), "three.html": ( '{% extends "three.html" %}{% block content %}' "{{ block.super }} locmem-three{% endblock %}" ), }, ), "django.template.loaders.filesystem.Loader", ], ) template = engine.get_template("one.html") output = template.render(Context({})) self.assertEqual( output.strip(), "three locmem-three two locmem-two one locmem-one" ) def test_extend_self_error(self): """ Catch if a template extends itself and no other matching templates are found. """ engine = Engine(dirs=[os.path.join(RECURSIVE, "fs")]) template = engine.get_template("self.html") with self.assertRaises(TemplateDoesNotExist) as e: template.render(Context({})) tried = e.exception.tried self.assertEqual(len(tried), 1) origin, message = tried[0] self.assertEqual(origin.template_name, "self.html") self.assertEqual(message, "Skipped to avoid recursion") def test_extend_cached(self): engine = Engine( dirs=[ os.path.join(RECURSIVE, "fs"), os.path.join(RECURSIVE, "fs2"), os.path.join(RECURSIVE, "fs3"), ], loaders=[ ( "django.template.loaders.cached.Loader", [ "django.template.loaders.filesystem.Loader", ], ), ], ) template = engine.get_template("recursive.html") output = template.render(Context({})) self.assertEqual(output.strip(), "fs3/recursive fs2/recursive fs/recursive") cache = engine.template_loaders[0].get_template_cache self.assertEqual(len(cache), 3) expected_path = os.path.join("fs", "recursive.html") self.assertTrue(cache["recursive.html"].origin.name.endswith(expected_path)) # Render another path that uses the same templates from the cache template = engine.get_template("other-recursive.html") output = template.render(Context({})) self.assertEqual(output.strip(), "fs3/recursive fs2/recursive fs/recursive") # Template objects should not be duplicated. self.assertEqual(len(cache), 4) expected_path = os.path.join("fs", "other-recursive.html") self.assertTrue( cache["other-recursive.html"].origin.name.endswith(expected_path) ) def test_unique_history_per_loader(self): """ Extending should continue even if two loaders return the same name for a template. """ engine = Engine( loaders=[ [ "django.template.loaders.locmem.Loader", { "base.html": ( '{% extends "base.html" %}{% block content %}' "{{ block.super }} loader1{% endblock %}" ), }, ], [ "django.template.loaders.locmem.Loader", { "base.html": "{% block content %}loader2{% endblock %}", }, ], ] ) template = engine.get_template("base.html") output = template.render(Context({})) self.assertEqual(output.strip(), "loader2 loader1") def test_block_override_in_extended_included_template(self): """ ExtendsNode.find_template() initializes history with self.origin (#28071). """ engine = Engine( loaders=[ [ "django.template.loaders.locmem.Loader", { "base.html": ( "{% extends 'base.html' %}{% block base %}{{ block.super }}" "2{% endblock %}" ), "included.html": ( "{% extends 'included.html' %}{% block included %}" "{{ block.super }}B{% endblock %}" ), }, ], [ "django.template.loaders.locmem.Loader", { "base.html": ( "{% block base %}1{% endblock %}" "{% include 'included.html' %}" ), "included.html": "{% block included %}A{% endblock %}", }, ], ], ) template = engine.get_template("base.html") self.assertEqual(template.render(Context({})), "12AB") @setup( {"index.html": "{% block content %}B{% endblock %}{% extends 'base.html' %}"} ) def test_extends_not_first_tag_in_extended_template(self): msg = "{% extends 'base.html' %} must be the first tag in 'index.html'." with self.assertRaisesMessage(TemplateSyntaxError, msg): self.engine.get_template("index.html") def test_extends_not_first_tag_in_extended_template_from_string(self): template_string = "{% block content %}B{% endblock %}{% extends 'base.html' %}" msg = "{% extends 'base.html' %} must be the first tag in the template." with self.assertRaisesMessage(TemplateSyntaxError, msg): Engine().from_string(template_string)