From 061a8a1bd818ca2c8a6493f33cae2379e34e181f Mon Sep 17 00:00:00 2001 From: Arthur Moreira Date: Fri, 19 May 2023 19:33:51 -0300 Subject: [PATCH] Fixed #34577 -- Added escapeseq template filter. --- AUTHORS | 1 + django/template/defaultfilters.py | 10 ++++ docs/ref/templates/builtins.txt | 19 ++++++ docs/releases/5.0.txt | 3 +- .../filter_tests/test_escapeseq.py | 59 +++++++++++++++++++ 5 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 tests/template_tests/filter_tests/test_escapeseq.py diff --git a/AUTHORS b/AUTHORS index 2e3a91c756..95f24762ee 100644 --- a/AUTHORS +++ b/AUTHORS @@ -108,6 +108,7 @@ answer newbie questions, and generally made Django that much better: Arthur Arthur Jovart Arthur Koziel + Arthur Moreira Arthur Rio Arvis Bickovskis Arya Khaligh diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index 5289ef34a0..589ca38414 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -444,6 +444,16 @@ def escape_filter(value): return conditional_escape(value) +@register.filter(is_safe=True) +def escapeseq(value): + """ + An "escape" filter for sequences. Mark each element in the sequence, + individually, as a string that should be auto-escaped. Return a list with + the results. + """ + return [conditional_escape(obj) for obj in value] + + @register.filter(is_safe=True) @stringfilter def force_escape(value): diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 3aa20dfc71..695357c5a0 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -1831,6 +1831,8 @@ For example, you can apply ``escape`` to fields when :ttag:`autoescape` is off: {{ title|escape }} {% endautoescape %} +To escape each element of a sequence, use the :tfilter:`escapeseq` filter. + .. templatefilter:: escapejs ``escapejs`` @@ -1849,6 +1851,23 @@ For example: If ``value`` is ``"testing\r\njavascript 'string\" escaping"``, the output will be ``"testing\\u000D\\u000Ajavascript \\u0027string\\u0022 \\u003Cb\\u003Eescaping\\u003C/b\\u003E"``. +.. templatefilter:: escapeseq + +``escapeseq`` +------------- + +.. versionadded:: 5.0 + +Applies the :tfilter:`escape` filter to each element of a sequence. Useful in +conjunction with other filters that operate on sequences, such as +:tfilter:`join`. For example: + +.. code-block:: html+django + + {% autoescape off %} + {{ my_list|escapeseq|join:", " }} + {% endautoescape %} + .. templatefilter:: filesizeformat ``filesizeformat`` diff --git a/docs/releases/5.0.txt b/docs/releases/5.0.txt index 611a7bd68b..e6526dd798 100644 --- a/docs/releases/5.0.txt +++ b/docs/releases/5.0.txt @@ -345,7 +345,8 @@ Signals Templates ~~~~~~~~~ -* ... +* The new :tfilter:`escapeseq` template filter applies :tfilter:`escape` to + each element of a sequence. Tests ~~~~~ diff --git a/tests/template_tests/filter_tests/test_escapeseq.py b/tests/template_tests/filter_tests/test_escapeseq.py new file mode 100644 index 0000000000..27092f5828 --- /dev/null +++ b/tests/template_tests/filter_tests/test_escapeseq.py @@ -0,0 +1,59 @@ +from django.test import SimpleTestCase +from django.utils.safestring import mark_safe + +from ..utils import setup + + +class EscapeseqTests(SimpleTestCase): + """ + The "escapeseq" filter works the same whether autoescape is on or off, + and has no effect on strings already marked as safe. + """ + + @setup( + { + "escapeseq_basic": ( + '{{ a|escapeseq|join:", " }} -- {{ b|escapeseq|join:", " }}' + ), + } + ) + def test_basic(self): + output = self.engine.render_to_string( + "escapeseq_basic", + {"a": ["x&y", "

"], "b": [mark_safe("x&y"), mark_safe("

")]}, + ) + self.assertEqual(output, "x&y, <p> -- x&y,

") + + @setup( + { + "escapeseq_autoescape_off": ( + '{% autoescape off %}{{ a|escapeseq|join:", " }}' + " -- " + '{{ b|escapeseq|join:", "}}{% endautoescape %}' + ) + } + ) + def test_autoescape_off(self): + output = self.engine.render_to_string( + "escapeseq_autoescape_off", + {"a": ["x&y", "

"], "b": [mark_safe("x&y"), mark_safe("

")]}, + ) + self.assertEqual(output, "x&y, <p> -- x&y,

") + + @setup({"escapeseq_join": '{{ a|escapeseq|join:"
" }}'}) + def test_chain_join(self): + output = self.engine.render_to_string("escapeseq_join", {"a": ["x&y", "

"]}) + self.assertEqual(output, "x&y
<p>") + + @setup( + { + "escapeseq_join_autoescape_off": ( + '{% autoescape off %}{{ a|escapeseq|join:"
" }}{% endautoescape %}' + ), + } + ) + def test_chain_join_autoescape_off(self): + output = self.engine.render_to_string( + "escapeseq_join_autoescape_off", {"a": ["x&y", "

"]} + ) + self.assertEqual(output, "x&y
<p>")