summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatti Pohjanvirta <matti.pohjanvirta@iki.fi>2025-04-20 18:22:51 +0300
committerNatalia <124304+nessita@users.noreply.github.com>2025-04-23 16:18:55 -0300
commit305aa4d0c5507072fa3a1c2f03fba9559329b499 (patch)
tree123f9a18f13881f4fc5360b11b3ac6279dcfb9c6
parent97c93549fc864af61cb1ae1fd171a3c7ac6ee89a (diff)
[5.2.x] Fixed #36341 -- Preserved whitespaces in wordwrap template filter.
Regression in 55d89e25f4115c5674cdd9b9bcba2bb2bb6d820b. This work improves the django.utils.text.wrap() function to ensure that empty lines and lines with whitespace only are kept instead of being dropped. Thanks Matti Pohjanvirta for the report and fix. Co-authored-by: Natalia <124304+nessita@users.noreply.github.com> Backport of 1e9db35836d42a3c72f3d1015c2f302eb6fee046 from main.
-rw-r--r--django/utils/text.py13
-rw-r--r--docs/releases/5.2.1.txt4
-rw-r--r--tests/template_tests/filter_tests/test_wordwrap.py41
3 files changed, 56 insertions, 2 deletions
diff --git a/django/utils/text.py b/django/utils/text.py
index 05b781b011..26edde99e3 100644
--- a/django/utils/text.py
+++ b/django/utils/text.py
@@ -54,10 +54,19 @@ def wrap(text, width):
width=width,
break_long_words=False,
break_on_hyphens=False,
+ replace_whitespace=False,
)
result = []
- for line in text.splitlines(True):
- result.extend(wrapper.wrap(line))
+ for line in text.splitlines():
+ wrapped = wrapper.wrap(line)
+ if not wrapped:
+ # If `line` contains only whitespaces that are dropped, restore it.
+ result.append(line)
+ else:
+ result.extend(wrapped)
+ if text.endswith("\n"):
+ # If `text` ends with a newline, preserve it.
+ result.append("")
return "\n".join(result)
diff --git a/docs/releases/5.2.1.txt b/docs/releases/5.2.1.txt
index caebde7f02..82df478d96 100644
--- a/docs/releases/5.2.1.txt
+++ b/docs/releases/5.2.1.txt
@@ -44,3 +44,7 @@ Bugfixes
* Fixed a regression in Django 5.2 that caused the ``object-tools`` block to be
rendered twice when using custom admin templates with overridden blocks due
to changes in the base admin page block structure (:ticket:`36331`).
+
+* Fixed a regression in Django 5.2, introduced when fixing :cve:`2025-26699`,
+ where the :tfilter:`wordwrap` template filter did not preserve empty lines
+ between paragraphs after wrapping text (:ticket:`36341`).
diff --git a/tests/template_tests/filter_tests/test_wordwrap.py b/tests/template_tests/filter_tests/test_wordwrap.py
index 4afa1dd234..1692332e1e 100644
--- a/tests/template_tests/filter_tests/test_wordwrap.py
+++ b/tests/template_tests/filter_tests/test_wordwrap.py
@@ -89,3 +89,44 @@ class FunctionTests(SimpleTestCase):
"I'm afraid",
wordwrap(long_text, 10),
)
+
+ def test_wrap_preserve_newlines(self):
+ cases = [
+ (
+ "this is a long paragraph of text that really needs to be wrapped\n\n"
+ "that is followed by another paragraph separated by an empty line\n",
+ "this is a long paragraph of\ntext that really needs to be\nwrapped\n\n"
+ "that is followed by another\nparagraph separated by an\nempty line\n",
+ 30,
+ ),
+ ("\n\n\n", "\n\n\n", 5),
+ ("\n\n\n\n\n\n", "\n\n\n\n\n\n", 5),
+ ]
+ for text, expected, width in cases:
+ with self.subTest(text=text):
+ self.assertEqual(wordwrap(text, width), expected)
+
+ def test_wrap_preserve_whitespace(self):
+ width = 5
+ width_spaces = " " * width
+ cases = [
+ (
+ f"first line\n{width_spaces}\nsecond line",
+ f"first\nline\n{width_spaces}\nsecond\nline",
+ ),
+ (
+ "first line\n \t\t\t \nsecond line",
+ "first\nline\n \t\t\t \nsecond\nline",
+ ),
+ (
+ f"first line\n{width_spaces}\nsecond line\n\nthird{width_spaces}\n",
+ f"first\nline\n{width_spaces}\nsecond\nline\n\nthird\n",
+ ),
+ (
+ f"first line\n{width_spaces}{width_spaces}\nsecond line",
+ f"first\nline\n{width_spaces}{width_spaces}\nsecond\nline",
+ ),
+ ]
+ for text, expected in cases:
+ with self.subTest(text=text):
+ self.assertEqual(wordwrap(text, width), expected)