diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 5de37966aa..b77adedb41 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -6,6 +6,7 @@ Changelog * Removed leftover Python 2.x compatibility code (Sergey Fedoseev) * Combine flake8 configurations (Sergey Fedoseev) + * Improved diffing behavior for text fields (Aliosha Padovani) * Fix: Rename documents listing column 'uploaded' to 'created' (LB (Ben Johnston)) * Fix: Submenu items longer then the page height are no longer broken by the submenu footer (Igor van Spengen) diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst index ed50a25df8..f655a78800 100644 --- a/CONTRIBUTORS.rst +++ b/CONTRIBUTORS.rst @@ -419,6 +419,7 @@ Contributors * Thijs Baaijen * Igor van Spengen * Stefani Castellanos +* Aliosha Padovani Translators =========== diff --git a/docs/releases/2.8.rst b/docs/releases/2.8.rst index 7eff759d8a..c73d02706f 100644 --- a/docs/releases/2.8.rst +++ b/docs/releases/2.8.rst @@ -14,16 +14,17 @@ What's new Other features ~~~~~~~~~~~~~~ - * ... + * Removed leftover Python 2.x compatibility code (Sergey Fedoseev) + * Combine flake8 configurations (Sergey Fedoseev) + * Improved diffing behavior for text fields (Aliosha Padovani) Bug fixes ~~~~~~~~~ - * Removed leftover Python 2.x compatibility code (Sergey Fedoseev) - * Combine flake8 configurations (Sergey Fedoseev) * Rename documents listing column 'uploaded' to 'created' (LB (Ben Johnston)) * Submenu items longer then the page height are no longer broken by the submenu footer (Igor van Spengen) + Upgrade considerations ====================== diff --git a/wagtail/admin/compare.py b/wagtail/admin/compare.py index fe0f9ef1ed..dda29d36a1 100644 --- a/wagtail/admin/compare.py +++ b/wagtail/admin/compare.py @@ -230,7 +230,14 @@ class ChoiceFieldComparison(FieldComparison): val_b = force_str(dict(self.field.flatchoices).get(self.val_b, self.val_b), strings_only=True) if self.val_a != self.val_b: - return TextDiff([('deletion', val_a), ('addition', val_b)]).to_html() + diffs = [] + + if val_a: + diffs += [('deletion', val_a)] + if val_b: + diffs += [('addition', val_b)] + + return TextDiff(diffs).to_html() else: return escape(val_a) @@ -593,7 +600,7 @@ def diff_text(a, b): tokens = [] current_token = "" - for c in text: + for c in text or "": if c.isalnum(): current_token += c else: diff --git a/wagtail/admin/edit_handlers.py b/wagtail/admin/edit_handlers.py index c354b5c511..233538f8b3 100644 --- a/wagtail/admin/edit_handlers.py +++ b/wagtail/admin/edit_handlers.py @@ -3,6 +3,7 @@ import re from django import forms from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured +from django.db.models.fields import CharField, TextField from django.forms.formsets import DELETION_FIELD_NAME, ORDERING_FIELD_NAME from django.forms.models import fields_for_model from django.template.loader import render_to_string @@ -500,6 +501,10 @@ class FieldPanel(EditHandler): if isinstance(field, RichTextField): return compare.RichTextFieldComparison + + if isinstance(field, (CharField, TextField)): + return compare.TextFieldComparison + except FieldDoesNotExist: pass diff --git a/wagtail/admin/tests/test_compare.py b/wagtail/admin/tests/test_compare.py index d45972d44e..f3cf3c710e 100644 --- a/wagtail/admin/tests/test_compare.py +++ b/wagtail/admin/tests/test_compare.py @@ -67,10 +67,45 @@ class TestTextFieldComparison(TestFieldComparison): self.assertIsInstance(comparison.htmldiff(), SafeString) self.assertTrue(comparison.has_changed()) + def test_from_none_to_value_only_shows_addition(self): + comparison = self.comparison_class( + SimplePage._meta.get_field('content'), + SimplePage(content=None), + SimplePage(content="Added content") + ) -class TestRichTextFieldComparison(TestTextFieldComparison): + self.assertEqual(comparison.htmldiff(), 'Added content') + self.assertIsInstance(comparison.htmldiff(), SafeString) + self.assertTrue(comparison.has_changed()) + + def test_from_value_to_none_only_shows_deletion(self): + comparison = self.comparison_class( + SimplePage._meta.get_field('content'), + SimplePage(content="Removed content"), + SimplePage(content=None) + ) + + self.assertEqual(comparison.htmldiff(), 'Removed content') + self.assertIsInstance(comparison.htmldiff(), SafeString) + self.assertTrue(comparison.has_changed()) + + +class TestRichTextFieldComparison(TestFieldComparison): comparison_class = compare.RichTextFieldComparison + # Only change from FieldComparison is the HTML diff is performed on words + # instead of the whole field value. + def test_has_changed(self): + comparison = self.comparison_class( + SimplePage._meta.get_field('content'), + SimplePage(content="Original content"), + SimplePage(content="Modified content"), + ) + + self.assertEqual(comparison.htmldiff(), 'OriginalModified content') + self.assertIsInstance(comparison.htmldiff(), SafeString) + self.assertTrue(comparison.has_changed()) + # Only change from FieldComparison is that this comparison disregards HTML tags def test_has_changed_html(self): comparison = self.comparison_class( @@ -329,6 +364,28 @@ class TestChoiceFieldComparison(TestCase): self.assertIsInstance(comparison.htmldiff(), SafeString) self.assertTrue(comparison.has_changed()) + def test_from_none_to_value_only_shows_addition(self): + comparison = self.comparison_class( + EventPage._meta.get_field('audience'), + EventPage(audience=None), + EventPage(audience="private"), + ) + + self.assertEqual(comparison.htmldiff(), 'Private') + self.assertIsInstance(comparison.htmldiff(), SafeString) + self.assertTrue(comparison.has_changed()) + + def test_from_value_to_none_only_shows_deletion(self): + comparison = self.comparison_class( + EventPage._meta.get_field('audience'), + EventPage(audience="public"), + EventPage(audience=None), + ) + + self.assertEqual(comparison.htmldiff(), 'Public') + self.assertIsInstance(comparison.htmldiff(), SafeString) + self.assertTrue(comparison.has_changed()) + class TestTagsFieldComparison(TestCase): comparison_class = compare.TagsFieldComparison