from django.template import engines from django.template.loader import render_to_string from django.test import TestCase from django.utils.safestring import mark_safe from wagtail import __version__, blocks from wagtail.coreutils import get_dummy_request from wagtail.models import Page, Site from wagtail.test.testapp.blocks import SectionBlock class TestCoreGlobalsAndFilters(TestCase): def setUp(self): self.engine = engines["jinja2"] def render(self, string, context=None, request_context=True): if context is None: context = {} # Add a request to the template, to simulate a RequestContext if request_context: site = Site.objects.get(is_default_site=True) context["request"] = get_dummy_request(site=site) template = self.engine.from_string(string) return template.render(context) def test_richtext(self): richtext = '

Merry Christmas!

' self.assertEqual( self.render("{{ text|richtext }}", {"text": richtext}), '

Merry Christmas!

', ) def test_pageurl(self): page = Page.objects.get(pk=2) self.assertEqual(self.render("{{ pageurl(page) }}", {"page": page}), page.url) def test_fullpageurl(self): page = Page.objects.get(pk=2) self.assertEqual( self.render("{{ fullpageurl(page) }}", {"page": page}), page.full_url ) def test_slugurl(self): page = Page.objects.get(pk=2) self.assertEqual( self.render("{{ slugurl(page.slug) }}", {"page": page}), page.url ) def test_bad_slugurl(self): self.assertEqual( self.render('{{ slugurl("bad-slug-doesnt-exist") }}', {}), "None" ) def test_wagtail_site(self): self.assertEqual(self.render("{{ wagtail_site().hostname }}"), "localhost") def test_wagtail_version(self): self.assertEqual(self.render("{{ wagtail_version() }}"), __version__) class TestJinjaEscaping(TestCase): fixtures = ["test.json"] def test_block_render_result_is_safe(self): """ Ensure that any results of template rendering in block.render are marked safe so that they don't get double-escaped when inserted into a parent template (#2541) """ stream_block = blocks.StreamBlock( [("paragraph", blocks.CharBlock(template="tests/jinja2/paragraph.html"))] ) stream_value = stream_block.to_python( [ {"type": "paragraph", "value": "hello world"}, ] ) result = render_to_string( "tests/jinja2/stream.html", { "value": stream_value, }, ) self.assertIn("

hello world

", result) def test_rich_text_is_safe(self): """ Ensure that RichText values are marked safe so that they don't get double-escaped when inserted into a parent template (#2542) """ stream_block = blocks.StreamBlock( [ ( "paragraph", blocks.RichTextBlock(template="tests/jinja2/rich_text.html"), ) ] ) stream_value = stream_block.to_python( [ { "type": "paragraph", "value": '

Merry Christmas!

', }, ] ) result = render_to_string( "tests/jinja2/stream.html", { "value": stream_value, }, ) self.assertIn( '

Merry Christmas!

', result ) class TestIncludeBlockTag(TestCase): def test_include_block_tag_with_boundblock(self): """ The include_block tag should be able to render a BoundBlock's template while keeping the parent template's context """ block = blocks.CharBlock(template="tests/jinja2/heading_block.html") bound_block = block.bind("bonjour") result = render_to_string( "tests/jinja2/include_block_test.html", { "test_block": bound_block, "language": "fr", }, ) self.assertIn('

bonjour

', result) def test_include_block_tag_with_structvalue(self): """ The include_block tag should be able to render a StructValue's template while keeping the parent template's context """ block = SectionBlock() struct_value = block.to_python( {"title": "Bonjour", "body": "monde italique"} ) result = render_to_string( "tests/jinja2/include_block_test.html", { "test_block": struct_value, "language": "fr", }, ) self.assertIn( """

Bonjour

monde italique""", result ) def test_include_block_tag_with_streamvalue(self): """ The include_block tag should be able to render a StreamValue's template while keeping the parent template's context """ block = blocks.StreamBlock( [ ( "heading", blocks.CharBlock(template="tests/jinja2/heading_block.html"), ), ("paragraph", blocks.CharBlock()), ], template="tests/jinja2/stream_with_language.html", ) stream_value = block.to_python([{"type": "heading", "value": "Bonjour"}]) result = render_to_string( "tests/jinja2/include_block_test.html", { "test_block": stream_value, "language": "fr", }, ) self.assertIn( '

Bonjour

', result ) def test_include_block_tag_with_plain_value(self): """ The include_block tag should be able to render a value without a render_as_block method by just rendering it as a string """ result = render_to_string( "tests/jinja2/include_block_test.html", { "test_block": 42, }, ) self.assertIn("42", result) def test_include_block_tag_with_filtered_value(self): """ The block parameter on include_block tag should support complex values including filters, e.g. {% include_block foo|default:123 %} """ block = blocks.CharBlock(template="tests/jinja2/heading_block.html") bound_block = block.bind("bonjour") result = render_to_string( "tests/jinja2/include_block_test_with_filter.html", { "test_block": bound_block, "language": "fr", }, ) self.assertIn('

bonjour

', result) result = render_to_string( "tests/jinja2/include_block_test_with_filter.html", { "test_block": None, "language": "fr", }, ) self.assertIn("999", result) def test_include_block_tag_with_additional_variable(self): """ The include_block tag should be able to pass local variables from parent context to the child context """ block = blocks.CharBlock(template="tests/blocks/heading_block.html") bound_block = block.bind("bonjour") result = render_to_string( "tests/jinja2/include_block_tag_with_additional_variable.html", {"test_block": bound_block}, ) self.assertIn('

bonjour

', result) def test_include_block_html_escaping(self): """ Output of include_block should be escaped as per Django autoescaping rules """ block = blocks.CharBlock() bound_block = block.bind(block.to_python("some evil HTML")) result = render_to_string( "tests/jinja2/include_block_test.html", { "test_block": bound_block, }, ) self.assertIn("some <em>evil</em> HTML", result) # {% autoescape off %} should be respected result = render_to_string( "tests/blocks/include_block_autoescape_off_test.html", { "test_block": bound_block, }, ) self.assertIn("some evil HTML", result) # The same escaping should be applied when passed a plain value rather than a BoundBlock - # a typical situation where this would occur would be rendering an item of a StructBlock, # e.g. {% include_block person_block.first_name %} as opposed to # {% include_block person_block.bound_blocks.first_name %} result = render_to_string( "tests/jinja2/include_block_test.html", { "test_block": "some evil HTML", }, ) self.assertIn("some <em>evil</em> HTML", result) result = render_to_string( "tests/jinja2/include_block_autoescape_off_test.html", { "test_block": "some evil HTML", }, ) self.assertIn("some evil HTML", result) # Blocks that explicitly return 'safe HTML'-marked values (such as RawHTMLBlock) should # continue to produce unescaped output block = blocks.RawHTMLBlock() bound_block = block.bind(block.to_python("some evil HTML")) result = render_to_string( "tests/jinja2/include_block_test.html", { "test_block": bound_block, }, ) self.assertIn("some evil HTML", result) # likewise when applied to a plain 'safe HTML' value rather than a BoundBlock result = render_to_string( "tests/jinja2/include_block_test.html", { "test_block": mark_safe("some evil HTML"), }, ) self.assertIn("some evil HTML", result)