from functools import partial from django.test import TestCase from django.utils.safestring import SafeString from wagtail.admin import compare from wagtail.blocks import StreamValue from wagtail.images import get_image_model from wagtail.images.tests.utils import get_test_image_file from wagtail.test.testapp.models import ( AdvertWithCustomPrimaryKey, EventCategory, EventPage, EventPageSpeaker, HeadCountRelatedModelUsingPK, SimplePage, SnippetChooserModelWithCustomPrimaryKey, StreamPage, TaggedPage, ) class TestFieldComparison(TestCase): comparison_class = compare.FieldComparison def test_hasnt_changed(self): comparison = self.comparison_class( SimplePage._meta.get_field("content"), SimplePage(content="Content"), SimplePage(content="Content"), ) self.assertTrue(comparison.is_field) self.assertFalse(comparison.is_child_relation) self.assertEqual(comparison.field_label(), "Content") self.assertEqual(comparison.htmldiff(), "Content") self.assertIsInstance(comparison.htmldiff(), SafeString) self.assertFalse(comparison.has_changed()) 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(), 'Original contentModified content', ) self.assertIsInstance(comparison.htmldiff(), SafeString) self.assertTrue(comparison.has_changed()) def test_htmldiff_escapes_value(self): comparison = self.comparison_class( SimplePage._meta.get_field("content"), SimplePage(content="Original content"), SimplePage( content='' ), ) self.assertEqual( comparison.htmldiff(), 'Original content<script type="text/javascript">doSomethingBad();</script>', ) self.assertIsInstance(comparison.htmldiff(), SafeString) class TestTextFieldComparison(TestFieldComparison): comparison_class = compare.TextFieldComparison # 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()) 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"), ) 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( 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()) def test_htmldiff_escapes_value(self): # Need to override this one as the HTML tags are stripped by RichTextFieldComparison comparison = self.comparison_class( SimplePage._meta.get_field("content"), SimplePage(content="Original content"), SimplePage( content='Do something good. ' ), ) self.assertEqual( comparison.htmldiff(), 'Original contentDo something good.', ) self.assertIsInstance(comparison.htmldiff(), SafeString) class TestStreamFieldComparison(TestCase): comparison_class = compare.StreamFieldComparison def test_hasnt_changed(self): field = StreamPage._meta.get_field("body") comparison = self.comparison_class( field, StreamPage( body=StreamValue( field.stream_block, [ ("text", "Content", "1"), ], ) ), StreamPage( body=StreamValue( field.stream_block, [ ("text", "Content", "1"), ], ) ), ) self.assertTrue(comparison.is_field) self.assertFalse(comparison.is_child_relation) self.assertEqual(comparison.field_label(), "Body") self.assertEqual( comparison.htmldiff(), '
Content
' ) self.assertIsInstance(comparison.htmldiff(), SafeString) self.assertFalse(comparison.has_changed()) def test_has_changed(self): field = StreamPage._meta.get_field("body") comparison = self.comparison_class( field, StreamPage( body=StreamValue( field.stream_block, [ ("text", "Original content", "1"), ], ) ), StreamPage( body=StreamValue( field.stream_block, [ ("text", "Modified content", "1"), ], ) ), ) self.assertEqual( comparison.htmldiff(), '
OriginalModified content
', ) self.assertIsInstance(comparison.htmldiff(), SafeString) self.assertTrue(comparison.has_changed()) def test_add_block(self): field = StreamPage._meta.get_field("body") comparison = self.comparison_class( field, StreamPage( body=StreamValue( field.stream_block, [ ("text", "Content", "1"), ], ) ), StreamPage( body=StreamValue( field.stream_block, [ ("text", "Content", "1"), ("text", "New Content", "2"), ], ) ), ) self.assertEqual( comparison.htmldiff(), '
Content
\n
New Content
', ) self.assertIsInstance(comparison.htmldiff(), SafeString) self.assertTrue(comparison.has_changed()) def test_delete_block(self): field = StreamPage._meta.get_field("body") comparison = self.comparison_class( field, StreamPage( body=StreamValue( field.stream_block, [ ("text", "Content", "1"), ("text", "Content Foo", "2"), ("text", "Content Bar", "3"), ], ) ), StreamPage( body=StreamValue( field.stream_block, [ ("text", "Content", "1"), ("text", "Content Bar", "3"), ], ) ), ) self.assertEqual( comparison.htmldiff(), '
Content
\n
Content Foo
\n
Content Bar
', ) self.assertIsInstance(comparison.htmldiff(), SafeString) self.assertTrue(comparison.has_changed()) def test_edit_block(self): field = StreamPage._meta.get_field("body") comparison = self.comparison_class( field, StreamPage( body=StreamValue( field.stream_block, [ ("text", "Content", "1"), ("text", "Content Foo", "2"), ("text", "Content Bar", "3"), ], ) ), StreamPage( body=StreamValue( field.stream_block, [ ("text", "Content", "1"), ("text", "Content Baz", "2"), ("text", "Content Bar", "3"), ], ) ), ) self.assertEqual( comparison.htmldiff(), '
Content
\n
Content FooBaz
\n
Content Bar
', ) self.assertIsInstance(comparison.htmldiff(), SafeString) self.assertTrue(comparison.has_changed()) def test_has_changed_richtext(self): field = StreamPage._meta.get_field("body") comparison = self.comparison_class( field, StreamPage( body=StreamValue( field.stream_block, [ ("rich_text", "Original content", "1"), ], ) ), StreamPage( body=StreamValue( field.stream_block, [ ("rich_text", "Modified content", "1"), ], ) ), ) self.assertEqual( comparison.htmldiff(), '
OriginalModified content
', ) self.assertIsInstance(comparison.htmldiff(), SafeString) self.assertTrue(comparison.has_changed()) def test_htmldiff_escapes_value_on_change(self): field = StreamPage._meta.get_field("body") comparison = self.comparison_class( field, StreamPage( body=StreamValue( field.stream_block, [ ( "text", "I really like originalish content", "1", ), ], ) ), StreamPage( body=StreamValue( field.stream_block, [ ( "text", 'I really like evil code ', "1", ), ], ) ), ) self.assertEqual( comparison.htmldiff(), '
I <b>really</b> like original<i>ish</i> contentevil code <script type="text/javascript">doSomethingBad();</script>
', ) self.assertIsInstance(comparison.htmldiff(), SafeString) def test_htmldiff_escapes_value_on_addition(self): field = StreamPage._meta.get_field("body") comparison = self.comparison_class( field, StreamPage( body=StreamValue( field.stream_block, [ ("text", "Original and unchanged content", "1"), ], ) ), StreamPage( body=StreamValue( field.stream_block, [ ("text", "Original and unchanged content", "1"), ( "text", '', "2", ), ], ) ), ) self.assertEqual( comparison.htmldiff(), '
Original <em>and unchanged</em> content
\n
<script type="text/javascript">doSomethingBad();</script>
', ) self.assertIsInstance(comparison.htmldiff(), SafeString) def test_htmldiff_escapes_value_on_deletion(self): field = StreamPage._meta.get_field("body") comparison = self.comparison_class( field, StreamPage( body=StreamValue( field.stream_block, [ ("text", "Original and unchanged content", "1"), ( "text", '', "2", ), ], ) ), StreamPage( body=StreamValue( field.stream_block, [ ("text", "Original and unchanged content", "1"), ], ) ), ) self.assertEqual( comparison.htmldiff(), '
Original <em>and unchanged</em> content
\n
<script type="text/javascript">doSomethingBad();</script>
', ) self.assertIsInstance(comparison.htmldiff(), SafeString) def test_htmldiff_richtext_strips_tags_on_change(self): field = StreamPage._meta.get_field("body") comparison = self.comparison_class( field, StreamPage( body=StreamValue( field.stream_block, [ ("rich_text", "I really like Wagtail <3", "1"), ], ) ), StreamPage( body=StreamValue( field.stream_block, [ ( "rich_text", 'I really like evil code >_< ', "1", ), ], ) ), ) self.assertEqual( comparison.htmldiff(), '
I really like Wagtail <3evil code >_<
', ) self.assertIsInstance(comparison.htmldiff(), SafeString) def test_htmldiff_richtext_strips_tags_on_addition(self): field = StreamPage._meta.get_field("body") comparison = self.comparison_class( field, StreamPage( body=StreamValue( field.stream_block, [ ("rich_text", "Original and unchanged content", "1"), ], ) ), StreamPage( body=StreamValue( field.stream_block, [ ("rich_text", "Original and unchanged content", "1"), ( "rich_text", 'I really like evil code >_< ', "2", ), ], ) ), ) self.assertEqual( comparison.htmldiff(), '
Original and unchanged content
\n
I really like evil code >_<
', ) self.assertIsInstance(comparison.htmldiff(), SafeString) def test_htmldiff_richtext_strips_tags_on_deletion(self): field = StreamPage._meta.get_field("body") comparison = self.comparison_class( field, StreamPage( body=StreamValue( field.stream_block, [ ("rich_text", "Original and unchanged content", "1"), ( "rich_text", 'I really like evil code >_< ', "2", ), ], ) ), StreamPage( body=StreamValue( field.stream_block, [ ("rich_text", "Original and unchanged content", "1"), ], ) ), ) self.assertEqual( comparison.htmldiff(), '
Original and unchanged content
\n
I really like evil code >_<
', ) self.assertIsInstance(comparison.htmldiff(), SafeString) def test_htmldiff_raw_html_escapes_value_on_change(self): field = StreamPage._meta.get_field("body") comparison = self.comparison_class( field, StreamPage( body=StreamValue( field.stream_block, [ ("raw_html", "Originalish content", "1"), ], ) ), StreamPage( body=StreamValue( field.stream_block, [ ( "raw_html", '', "1", ), ], ) ), ) self.assertEqual( comparison.htmldiff(), '
Original<i>ish</i> content<script type="text/javascript">doSomethingBad();</script>
', ) self.assertIsInstance(comparison.htmldiff(), SafeString) def test_htmldiff_raw_html_escapes_value_on_addition(self): field = StreamPage._meta.get_field("body") comparison = self.comparison_class( field, StreamPage( body=StreamValue( field.stream_block, [ ("raw_html", "Original and unchanged content", "1"), ], ) ), StreamPage( body=StreamValue( field.stream_block, [ ("raw_html", "Original and unchanged content", "1"), ( "raw_html", '', "2", ), ], ) ), ) self.assertEqual( comparison.htmldiff(), '
Original <em>and unchanged</em> content
\n
<script type="text/javascript">doSomethingBad();</script>
', ) self.assertIsInstance(comparison.htmldiff(), SafeString) def test_htmldiff_raw_html_escapes_value_on_deletion(self): field = StreamPage._meta.get_field("body") comparison = self.comparison_class( field, StreamPage( body=StreamValue( field.stream_block, [ ("raw_html", "Original and unchanged content", "1"), ( "raw_html", '', "2", ), ], ) ), StreamPage( body=StreamValue( field.stream_block, [ ("raw_html", "Original and unchanged content", "1"), ], ) ), ) self.assertEqual( comparison.htmldiff(), '
Original <em>and unchanged</em> content
\n
<script type="text/javascript">doSomethingBad();</script>
', ) self.assertIsInstance(comparison.htmldiff(), SafeString) def test_compare_structblock(self): field = StreamPage._meta.get_field("body") comparison = self.comparison_class( field, StreamPage( body=StreamValue( field.stream_block, [ ("product", {"name": "a packet of rolos", "price": "75p"}, "1"), ], ) ), StreamPage( body=StreamValue( field.stream_block, [ ("product", {"name": "a packet of rolos", "price": "85p"}, "1"), ], ) ), ) expected = """
Name
a packet of rolos
Price
75p85p
""" self.assertHTMLEqual(comparison.htmldiff(), expected) self.assertIsInstance(comparison.htmldiff(), SafeString) self.assertTrue(comparison.has_changed()) def test_compare_listblock(self): field = StreamPage._meta.get_field("body") block = field.stream_block.child_blocks["title_list"] block_val = block.to_python( [ { "type": "item", "value": "foo", "id": "11111111-1111-1111-1111-111111111111", }, { "type": "item", "value": "bar", "id": "22222222-2222-2222-2222-222222222222", }, ] ) block_val_2 = block.to_python( [ { "type": "item", "value": "bard", "id": "22222222-2222-2222-2222-222222222222", }, { "type": "item", "value": "food", "id": "11111111-1111-1111-1111-111111111111", }, ] ) comparison = self.comparison_class( field, StreamPage( body=StreamValue( field.stream_block, [ ("title_list", block_val, "1"), ], ) ), StreamPage( body=StreamValue( field.stream_block, [ ("title_list", block_val_2, "1"), ], ) ), ) htmldiff = comparison.htmldiff() expected = """
bar bard
\n
foo food
""" self.assertHTMLEqual(htmldiff, expected) self.assertIsInstance(htmldiff, SafeString) self.assertTrue(comparison.has_changed()) def test_compare_listblock_old_format(self): field = StreamPage._meta.get_field("body") block = field.stream_block.child_blocks["title_list"] no_diff = """
foo
\n
bar
""" edit_and_add_diff = """
foo
\n
bar bap
\n
baz
""" edit_and_add_diff_reversed = """
foo fo
\n
bar
\n
baz
""" old_format_listblock_fixtures = [ (["foo", "bar"], ["foo", "bar"], no_diff), (["foo", "bar"], ["foo", "bap", "baz"], edit_and_add_diff), (["foo", "bar", "baz"], ["fo", "bar"], edit_and_add_diff_reversed), ] for list_1, list_2, expected_diff in old_format_listblock_fixtures: with self.subTest(list_1=list_1, list_2=list_2): block_val = block.to_python(list_1) block_val_2 = block.to_python(list_2) comparison = self.comparison_class( field, StreamPage( body=StreamValue( field.stream_block, [ ("title_list", block_val, "1"), ], ) ), StreamPage( body=StreamValue( field.stream_block, [ ("title_list", block_val_2, "1"), ], ) ), ) htmldiff = comparison.htmldiff() self.assertHTMLEqual(htmldiff, expected_diff) self.assertIsInstance(htmldiff, SafeString) self.assertTrue(comparison.has_changed()) def test_compare_nested_streamblock_uses_comparison_class(self): field = StreamPage._meta.get_field("body") stream_block = field.stream_block.child_blocks["books"] comparison = self.comparison_class( field, StreamPage( body=StreamValue( field.stream_block, [ ( "books", StreamValue( stream_block, [("title", "The Old Man and the Sea", "10")], ), "1", ), ], ) ), StreamPage( body=StreamValue( field.stream_block, [ ( "books", StreamValue( stream_block, [("author", "Oscar Wilde", "11")] ), "1", ), ], ) ), ) expected = """
Oscar Wilde
\n
The Old Man and the Sea
""" self.assertHTMLEqual(comparison.htmldiff(), expected) self.assertIsInstance(comparison.htmldiff(), SafeString) self.assertTrue(comparison.has_changed()) def test_compare_imagechooserblock(self): image_model = get_image_model() test_image_1 = image_model.objects.create( title="Test image 1", file=get_test_image_file(), ) test_image_2 = image_model.objects.create( title="Test image 2", file=get_test_image_file(), ) field = StreamPage._meta.get_field("body") comparison = self.comparison_class( field, StreamPage( body=StreamValue( field.stream_block, [ ("image", test_image_1, "1"), ], ) ), StreamPage( body=StreamValue( field.stream_block, [ ("image", test_image_2, "1"), ], ) ), ) result = comparison.htmldiff() self.assertIn('
', result) self.assertIn('alt="Test image 1"', result) self.assertIn('
', result) self.assertIn('alt="Test image 2"', result) self.assertIsInstance(result, SafeString) self.assertTrue(comparison.has_changed()) class TestChoiceFieldComparison(TestCase): comparison_class = compare.ChoiceFieldComparison def test_hasnt_changed(self): comparison = self.comparison_class( EventPage._meta.get_field("audience"), EventPage(audience="public"), EventPage(audience="public"), ) self.assertTrue(comparison.is_field) self.assertFalse(comparison.is_child_relation) self.assertEqual(comparison.field_label(), "Audience") self.assertEqual(comparison.htmldiff(), "Public") self.assertIsInstance(comparison.htmldiff(), SafeString) self.assertFalse(comparison.has_changed()) def test_has_changed(self): comparison = self.comparison_class( EventPage._meta.get_field("audience"), EventPage(audience="public"), EventPage(audience="private"), ) self.assertEqual( comparison.htmldiff(), 'PublicPrivate', ) 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 def test_hasnt_changed(self): a = TaggedPage() a.tags.add("wagtail") a.tags.add("bird") b = TaggedPage() b.tags.add("wagtail") b.tags.add("bird") comparison = self.comparison_class(TaggedPage._meta.get_field("tags"), a, b) self.assertTrue(comparison.is_field) self.assertFalse(comparison.is_child_relation) self.assertEqual(comparison.field_label(), "Tags") self.assertEqual(comparison.htmldiff(), "wagtail, bird") self.assertIsInstance(comparison.htmldiff(), SafeString) self.assertFalse(comparison.has_changed()) def test_has_changed(self): a = TaggedPage() a.tags.add("wagtail") a.tags.add("bird") b = TaggedPage() b.tags.add("wagtail") b.tags.add("motacilla") comparison = self.comparison_class(TaggedPage._meta.get_field("tags"), a, b) self.assertEqual( comparison.htmldiff(), 'wagtail, bird, motacilla', ) self.assertIsInstance(comparison.htmldiff(), SafeString) self.assertTrue(comparison.has_changed()) class TestM2MFieldComparison(TestCase): fixtures = ["test.json"] comparison_class = compare.M2MFieldComparison def setUp(self): self.meetings_category = EventCategory.objects.create(name="Meetings") self.parties_category = EventCategory.objects.create(name="Parties") self.holidays_category = EventCategory.objects.create(name="Holidays") def test_hasnt_changed(self): christmas_event = EventPage.objects.get(url_path="/home/events/christmas/") saint_patrick_event = EventPage.objects.get( url_path="/home/events/saint-patrick/" ) christmas_event.categories = [self.meetings_category, self.parties_category] saint_patrick_event.categories = [self.meetings_category, self.parties_category] comparison = self.comparison_class( EventPage._meta.get_field("categories"), christmas_event, saint_patrick_event, ) self.assertTrue(comparison.is_field) self.assertFalse(comparison.is_child_relation) self.assertEqual(comparison.field_label(), "Categories") self.assertFalse(comparison.has_changed()) self.assertEqual(comparison.htmldiff(), "Meetings, Parties") self.assertIsInstance(comparison.htmldiff(), SafeString) def test_has_changed(self): christmas_event = EventPage.objects.get(url_path="/home/events/christmas/") saint_patrick_event = EventPage.objects.get( url_path="/home/events/saint-patrick/" ) christmas_event.categories = [self.meetings_category, self.parties_category] saint_patrick_event.categories = [ self.meetings_category, self.holidays_category, ] comparison = self.comparison_class( EventPage._meta.get_field("categories"), christmas_event, saint_patrick_event, ) self.assertTrue(comparison.has_changed()) self.assertEqual( comparison.htmldiff(), 'Meetings, Parties, Holidays', ) self.assertIsInstance(comparison.htmldiff(), SafeString) class TestForeignObjectComparison(TestCase): comparison_class = compare.ForeignObjectComparison @classmethod def setUpTestData(cls): image_model = get_image_model() cls.test_image_1 = image_model.objects.create( title="Test image 1", file=get_test_image_file(), ) cls.test_image_2 = image_model.objects.create( title="Test image 2", file=get_test_image_file(), ) def test_hasnt_changed(self): comparison = self.comparison_class( EventPage._meta.get_field("feed_image"), EventPage(feed_image=self.test_image_1), EventPage(feed_image=self.test_image_1), ) self.assertTrue(comparison.is_field) self.assertFalse(comparison.is_child_relation) self.assertEqual(comparison.field_label(), "Feed image") self.assertEqual(comparison.htmldiff(), "Test image 1") self.assertIsInstance(comparison.htmldiff(), SafeString) self.assertFalse(comparison.has_changed()) def test_has_changed(self): comparison = self.comparison_class( EventPage._meta.get_field("feed_image"), EventPage(feed_image=self.test_image_1), EventPage(feed_image=self.test_image_2), ) self.assertEqual( comparison.htmldiff(), 'Test image 1Test image 2', ) self.assertIsInstance(comparison.htmldiff(), SafeString) self.assertTrue(comparison.has_changed()) class TestForeignObjectComparisonWithCustomPK(TestCase): """ForeignObjectComparison works with models declaring a custom primary key field""" comparison_class = compare.ForeignObjectComparison @classmethod def setUpTestData(cls): ad1 = AdvertWithCustomPrimaryKey.objects.create( advert_id="ad1", text="Advert 1" ) ad2 = AdvertWithCustomPrimaryKey.objects.create( advert_id="ad2", text="Advert 2" ) cls.test_obj_1 = SnippetChooserModelWithCustomPrimaryKey.objects.create( advertwithcustomprimarykey=ad1 ) cls.test_obj_2 = SnippetChooserModelWithCustomPrimaryKey.objects.create( advertwithcustomprimarykey=ad2 ) def test_hasnt_changed(self): comparison = self.comparison_class( SnippetChooserModelWithCustomPrimaryKey._meta.get_field( "advertwithcustomprimarykey" ), self.test_obj_1, self.test_obj_1, ) self.assertTrue(comparison.is_field) self.assertFalse(comparison.is_child_relation) self.assertEqual(comparison.field_label(), "Advertwithcustomprimarykey") self.assertEqual(comparison.htmldiff(), "Advert 1") self.assertIsInstance(comparison.htmldiff(), SafeString) self.assertFalse(comparison.has_changed()) def test_has_changed(self): comparison = self.comparison_class( SnippetChooserModelWithCustomPrimaryKey._meta.get_field( "advertwithcustomprimarykey" ), self.test_obj_1, self.test_obj_2, ) self.assertEqual( comparison.htmldiff(), 'Advert 1Advert 2', ) self.assertIsInstance(comparison.htmldiff(), SafeString) self.assertTrue(comparison.has_changed()) class TestChildRelationComparison(TestCase): field_comparison_class = compare.FieldComparison comparison_class = compare.ChildRelationComparison def test_hasnt_changed(self): # Two event pages with speaker called "Father Christmas". Neither of # the speaker objects have an ID so this tests that the code can match # the two together by field content. event_page = EventPage(title="Event page", slug="event") event_page.speakers.add( EventPageSpeaker( first_name="Father", last_name="Christmas", ) ) modified_event_page = EventPage(title="Event page", slug="event") modified_event_page.speakers.add( EventPageSpeaker( first_name="Father", last_name="Christmas", ) ) comparison = self.comparison_class( EventPage._meta.get_field("speaker"), [ partial( self.field_comparison_class, EventPageSpeaker._meta.get_field("first_name"), ), partial( self.field_comparison_class, EventPageSpeaker._meta.get_field("last_name"), ), ], event_page, modified_event_page, ) self.assertFalse(comparison.is_field) self.assertTrue(comparison.is_child_relation) self.assertEqual(comparison.field_label(), "Speaker") self.assertFalse(comparison.has_changed()) # Check mapping objs_a = list(comparison.val_a.all()) objs_b = list(comparison.val_b.all()) map_forwards, map_backwards, added, deleted = comparison.get_mapping( objs_a, objs_b ) self.assertEqual(map_forwards, {0: 0}) self.assertEqual(map_backwards, {0: 0}) self.assertEqual(added, []) self.assertEqual(deleted, []) def test_has_changed(self): # Father Christmas renamed to Santa Claus. And Father Ted added. # Father Christmas should be mapped to Father Ted because they # are most alike. Santa Claus should be displayed as "new" event_page = EventPage(title="Event page", slug="event") event_page.speakers.add( EventPageSpeaker( first_name="Father", last_name="Christmas", sort_order=0, ) ) modified_event_page = EventPage(title="Event page", slug="event") modified_event_page.speakers.add( EventPageSpeaker( first_name="Santa", last_name="Claus", sort_order=0, ) ) modified_event_page.speakers.add( EventPageSpeaker( first_name="Father", last_name="Ted", sort_order=1, ) ) comparison = self.comparison_class( EventPage._meta.get_field("speaker"), [ partial( self.field_comparison_class, EventPageSpeaker._meta.get_field("first_name"), ), partial( self.field_comparison_class, EventPageSpeaker._meta.get_field("last_name"), ), ], event_page, modified_event_page, ) self.assertFalse(comparison.is_field) self.assertTrue(comparison.is_child_relation) self.assertEqual(comparison.field_label(), "Speaker") self.assertTrue(comparison.has_changed()) # Check mapping objs_a = list(comparison.val_a.all()) objs_b = list(comparison.val_b.all()) map_forwards, map_backwards, added, deleted = comparison.get_mapping( objs_a, objs_b ) self.assertEqual(map_forwards, {0: 1}) # Map Father Christmas to Father Ted self.assertEqual(map_backwards, {1: 0}) # Map Father Ted to Father Christmas self.assertEqual(added, [0]) # Add Santa Claus self.assertEqual(deleted, []) def test_has_changed_with_same_id(self): # Father Christmas renamed to Santa Claus, but this time the ID of the # child object remained the same. It should now be detected as the same # object event_page = EventPage(title="Event page", slug="event") event_page.speakers.add( EventPageSpeaker( id=1, first_name="Father", last_name="Christmas", sort_order=0, ) ) modified_event_page = EventPage(title="Event page", slug="event") modified_event_page.speakers.add( EventPageSpeaker( id=1, first_name="Santa", last_name="Claus", sort_order=0, ) ) modified_event_page.speakers.add( EventPageSpeaker( first_name="Father", last_name="Ted", sort_order=1, ) ) comparison = self.comparison_class( EventPage._meta.get_field("speaker"), [ partial( self.field_comparison_class, EventPageSpeaker._meta.get_field("first_name"), ), partial( self.field_comparison_class, EventPageSpeaker._meta.get_field("last_name"), ), ], event_page, modified_event_page, ) self.assertFalse(comparison.is_field) self.assertTrue(comparison.is_child_relation) self.assertEqual(comparison.field_label(), "Speaker") self.assertTrue(comparison.has_changed()) # Check mapping objs_a = list(comparison.val_a.all()) objs_b = list(comparison.val_b.all()) map_forwards, map_backwards, added, deleted = comparison.get_mapping( objs_a, objs_b ) self.assertEqual(map_forwards, {0: 0}) # Map Father Christmas to Santa Claus self.assertEqual(map_backwards, {0: 0}) # Map Santa Claus to Father Christmas self.assertEqual(added, [1]) # Add Father Ted self.assertEqual(deleted, []) def test_hasnt_changed_with_different_id(self): # Both of the child objects have the same field content but have a # different ID so they should be detected as separate objects event_page = EventPage(title="Event page", slug="event") event_page.speakers.add( EventPageSpeaker( id=1, first_name="Father", last_name="Christmas", ) ) modified_event_page = EventPage(title="Event page", slug="event") modified_event_page.speakers.add( EventPageSpeaker( id=2, first_name="Father", last_name="Christmas", ) ) comparison = self.comparison_class( EventPage._meta.get_field("speaker"), [ partial( self.field_comparison_class, EventPageSpeaker._meta.get_field("first_name"), ), partial( self.field_comparison_class, EventPageSpeaker._meta.get_field("last_name"), ), ], event_page, modified_event_page, ) self.assertFalse(comparison.is_field) self.assertTrue(comparison.is_child_relation) self.assertEqual(comparison.field_label(), "Speaker") self.assertTrue(comparison.has_changed()) # Check mapping objs_a = list(comparison.val_a.all()) objs_b = list(comparison.val_b.all()) map_forwards, map_backwards, added, deleted = comparison.get_mapping( objs_a, objs_b ) self.assertEqual(map_forwards, {}) self.assertEqual(map_backwards, {}) self.assertEqual(added, [0]) # Add new Father Christmas self.assertEqual(deleted, [0]) # Delete old Father Christmas def test_panel_label_as_field_label(self): # Just to check whether passing `label` changes field_label event_page = EventPage(title="Event page", slug="event") event_page.speakers.add( EventPageSpeaker( first_name="Father", ) ) comparison = self.comparison_class( EventPage._meta.get_field("speaker"), [ partial( self.field_comparison_class, EventPageSpeaker._meta.get_field("first_name"), ) ], event_page, event_page, label="Speakers", ) self.assertEqual(comparison.field_label(), "Speakers") class TestChildObjectComparison(TestCase): field_comparison_class = compare.FieldComparison comparison_class = compare.ChildObjectComparison def test_same_object(self): obj_a = EventPageSpeaker( first_name="Father", last_name="Christmas", ) obj_b = EventPageSpeaker( first_name="Father", last_name="Christmas", ) comparison = self.comparison_class( EventPageSpeaker, [ partial( self.field_comparison_class, EventPageSpeaker._meta.get_field("first_name"), ), partial( self.field_comparison_class, EventPageSpeaker._meta.get_field("last_name"), ), ], obj_a, obj_b, ) self.assertFalse(comparison.is_addition()) self.assertFalse(comparison.is_deletion()) self.assertFalse(comparison.has_changed()) self.assertEqual(comparison.get_position_change(), 0) self.assertEqual(comparison.get_num_differences(), 0) def test_different_object(self): obj_a = EventPageSpeaker( first_name="Father", last_name="Christmas", ) obj_b = EventPageSpeaker( first_name="Santa", last_name="Claus", ) comparison = self.comparison_class( EventPageSpeaker, [ partial( self.field_comparison_class, EventPageSpeaker._meta.get_field("first_name"), ), partial( self.field_comparison_class, EventPageSpeaker._meta.get_field("last_name"), ), ], obj_a, obj_b, ) self.assertFalse(comparison.is_addition()) self.assertFalse(comparison.is_deletion()) self.assertTrue(comparison.has_changed()) self.assertEqual(comparison.get_position_change(), 0) self.assertEqual(comparison.get_num_differences(), 2) def test_moved_object(self): obj_a = EventPageSpeaker( first_name="Father", last_name="Christmas", sort_order=1, ) obj_b = EventPageSpeaker( first_name="Father", last_name="Christmas", sort_order=5, ) comparison = self.comparison_class( EventPageSpeaker, [ partial( self.field_comparison_class, EventPageSpeaker._meta.get_field("first_name"), ), partial( self.field_comparison_class, EventPageSpeaker._meta.get_field("last_name"), ), ], obj_a, obj_b, ) self.assertFalse(comparison.is_addition()) self.assertFalse(comparison.is_deletion()) self.assertFalse(comparison.has_changed()) self.assertEqual(comparison.get_position_change(), 4) self.assertEqual(comparison.get_num_differences(), 0) def test_addition(self): obj = EventPageSpeaker( first_name="Father", last_name="Christmas", ) comparison = self.comparison_class( EventPageSpeaker, [ partial( self.field_comparison_class, EventPageSpeaker._meta.get_field("first_name"), ), partial( self.field_comparison_class, EventPageSpeaker._meta.get_field("last_name"), ), ], None, obj, ) self.assertTrue(comparison.is_addition()) self.assertFalse(comparison.is_deletion()) self.assertFalse(comparison.has_changed()) self.assertIsNone(comparison.get_position_change(), 0) self.assertEqual(comparison.get_num_differences(), 0) def test_deletion(self): obj = EventPageSpeaker( first_name="Father", last_name="Christmas", ) comparison = self.comparison_class( EventPageSpeaker, [ partial( self.field_comparison_class, EventPageSpeaker._meta.get_field("first_name"), ), partial( self.field_comparison_class, EventPageSpeaker._meta.get_field("last_name"), ), ], obj, None, ) self.assertFalse(comparison.is_addition()) self.assertTrue(comparison.is_deletion()) self.assertFalse(comparison.has_changed()) self.assertIsNone(comparison.get_position_change()) self.assertEqual(comparison.get_num_differences(), 0) class TestChildRelationComparisonUsingPK(TestCase): """Test related objects can be compred if they do not use id for primary key""" field_comparison_class = compare.FieldComparison comparison_class = compare.ChildRelationComparison def test_has_changed_with_same_id(self): # Head Count was changed but the PK of the child object remained the same. # It should be detected as the same object event_page = EventPage(title="Semi Finals", slug="semi-finals-2018") event_page.head_counts.add( HeadCountRelatedModelUsingPK( custom_id=1, head_count=22, ) ) modified_event_page = EventPage(title="Semi Finals", slug="semi-finals-2018") modified_event_page.head_counts.add( HeadCountRelatedModelUsingPK( custom_id=1, head_count=23, ) ) modified_event_page.head_counts.add( HeadCountRelatedModelUsingPK( head_count=25, ) ) comparison = self.comparison_class( EventPage._meta.get_field("head_counts"), [ partial( self.field_comparison_class, HeadCountRelatedModelUsingPK._meta.get_field("head_count"), ) ], event_page, modified_event_page, ) self.assertFalse(comparison.is_field) self.assertTrue(comparison.is_child_relation) self.assertEqual(comparison.field_label(), "Head counts") self.assertTrue(comparison.has_changed()) # Check mapping objs_a = list(comparison.val_a.all()) objs_b = list(comparison.val_b.all()) map_forwards, map_backwards, added, deleted = comparison.get_mapping( objs_a, objs_b ) self.assertEqual(map_forwards, {0: 0}) # map head count 22 to 23 self.assertEqual(map_backwards, {0: 0}) # map head count 23 to 22 self.assertEqual(added, [1]) # add second head count self.assertEqual(deleted, []) def test_hasnt_changed_with_different_id(self): # Both of the child objects have the same field content but have a # different PK (ID) so they should be detected as separate objects event_page = EventPage(title="Finals", slug="finals-event-abc") event_page.head_counts.add( HeadCountRelatedModelUsingPK(custom_id=1, head_count=220) ) modified_event_page = EventPage(title="Finals", slug="finals-event-abc") modified_event_page.head_counts.add( HeadCountRelatedModelUsingPK(custom_id=2, head_count=220) ) comparison = self.comparison_class( EventPage._meta.get_field("head_counts"), [ partial( self.field_comparison_class, HeadCountRelatedModelUsingPK._meta.get_field("head_count"), ) ], event_page, modified_event_page, ) self.assertFalse(comparison.is_field) self.assertTrue(comparison.is_child_relation) self.assertEqual(comparison.field_label(), "Head counts") self.assertTrue(comparison.has_changed()) # Check mapping objs_a = list(comparison.val_a.all()) objs_b = list(comparison.val_b.all()) map_forwards, map_backwards, added, deleted = comparison.get_mapping( objs_a, objs_b ) self.assertEqual(map_forwards, {}) self.assertEqual(map_backwards, {}) self.assertEqual(added, [0]) # Add new head count self.assertEqual(deleted, [0]) # Delete old head count