test_compare.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736
  1. import unittest
  2. from django.test import TestCase
  3. from django.utils.functional import curry
  4. from django.utils.safestring import SafeText
  5. from wagtail.admin import compare
  6. from wagtail.core.blocks import StreamValue
  7. from wagtail.images import get_image_model
  8. from wagtail.images.tests.utils import get_test_image_file
  9. from wagtail.tests.testapp.models import (
  10. EventCategory, EventPage, EventPageSpeaker, HeadCountRelatedModelUsingPK, SimplePage,
  11. StreamPage, TaggedPage)
  12. class TestFieldComparison(TestCase):
  13. comparison_class = compare.FieldComparison
  14. def test_hasnt_changed(self):
  15. comparison = self.comparison_class(
  16. SimplePage._meta.get_field('content'),
  17. SimplePage(content="Content"),
  18. SimplePage(content="Content"),
  19. )
  20. self.assertTrue(comparison.is_field)
  21. self.assertFalse(comparison.is_child_relation)
  22. self.assertEqual(comparison.field_label(), "Content")
  23. self.assertEqual(comparison.htmldiff(), 'Content')
  24. self.assertIsInstance(comparison.htmldiff(), SafeText)
  25. self.assertFalse(comparison.has_changed())
  26. def test_has_changed(self):
  27. comparison = self.comparison_class(
  28. SimplePage._meta.get_field('content'),
  29. SimplePage(content="Original content"),
  30. SimplePage(content="Modified content"),
  31. )
  32. self.assertEqual(comparison.htmldiff(), '<span class="deletion">Original content</span><span class="addition">Modified content</span>')
  33. self.assertIsInstance(comparison.htmldiff(), SafeText)
  34. self.assertTrue(comparison.has_changed())
  35. def test_htmldiff_escapes_value(self):
  36. comparison = self.comparison_class(
  37. SimplePage._meta.get_field('content'),
  38. SimplePage(content='Original content'),
  39. SimplePage(content='<script type="text/javascript">doSomethingBad();</script>'),
  40. )
  41. self.assertEqual(comparison.htmldiff(), '<span class="deletion">Original content</span><span class="addition">&lt;script type=&quot;text/javascript&quot;&gt;doSomethingBad();&lt;/script&gt;</span>')
  42. self.assertIsInstance(comparison.htmldiff(), SafeText)
  43. class TestTextFieldComparison(TestFieldComparison):
  44. comparison_class = compare.TextFieldComparison
  45. # Only change from FieldComparison is the HTML diff is performed on words
  46. # instead of the whole field value.
  47. def test_has_changed(self):
  48. comparison = self.comparison_class(
  49. SimplePage._meta.get_field('content'),
  50. SimplePage(content="Original content"),
  51. SimplePage(content="Modified content"),
  52. )
  53. self.assertEqual(comparison.htmldiff(), '<span class="deletion">Original</span><span class="addition">Modified</span> content')
  54. self.assertIsInstance(comparison.htmldiff(), SafeText)
  55. self.assertTrue(comparison.has_changed())
  56. class TestRichTextFieldComparison(TestTextFieldComparison):
  57. comparison_class = compare.RichTextFieldComparison
  58. # Only change from FieldComparison is that this comparison disregards HTML tags
  59. def test_has_changed_html(self):
  60. comparison = self.comparison_class(
  61. SimplePage._meta.get_field('content'),
  62. SimplePage(content="<b>Original</b> content"),
  63. SimplePage(content="Modified <i>content</i>"),
  64. )
  65. self.assertEqual(comparison.htmldiff(), '<span class="deletion">Original</span><span class="addition">Modified</span> content')
  66. self.assertIsInstance(comparison.htmldiff(), SafeText)
  67. self.assertTrue(comparison.has_changed())
  68. def test_htmldiff_escapes_value(self):
  69. # Need to override this one as the HTML tags are stripped by RichTextFieldComparison
  70. comparison = self.comparison_class(
  71. SimplePage._meta.get_field('content'),
  72. SimplePage(content='Original content'),
  73. SimplePage(content='<script type="text/javascript">doSomethingBad();</script>'),
  74. )
  75. self.assertEqual(comparison.htmldiff(), '<span class="deletion">Original content</span><span class="addition">doSomethingBad();</span>')
  76. self.assertIsInstance(comparison.htmldiff(), SafeText)
  77. class TestStreamFieldComparison(TestCase):
  78. comparison_class = compare.StreamFieldComparison
  79. def test_hasnt_changed(self):
  80. field = StreamPage._meta.get_field('body')
  81. comparison = self.comparison_class(
  82. field,
  83. StreamPage(body=StreamValue(field.stream_block, [
  84. ('text', "Content"),
  85. ])),
  86. StreamPage(body=StreamValue(field.stream_block, [
  87. ('text', "Content"),
  88. ])),
  89. )
  90. self.assertTrue(comparison.is_field)
  91. self.assertFalse(comparison.is_child_relation)
  92. self.assertEqual(comparison.field_label(), "Body")
  93. self.assertEqual(comparison.htmldiff(), 'Content')
  94. self.assertIsInstance(comparison.htmldiff(), SafeText)
  95. self.assertFalse(comparison.has_changed())
  96. def test_has_changed(self):
  97. field = StreamPage._meta.get_field('body')
  98. comparison = self.comparison_class(
  99. field,
  100. StreamPage(body=StreamValue(field.stream_block, [
  101. ('text', "Original content"),
  102. ])),
  103. StreamPage(body=StreamValue(field.stream_block, [
  104. ('text', "Modified content"),
  105. ])),
  106. )
  107. self.assertEqual(comparison.htmldiff(), '<span class="deletion">Original</span><span class="addition">Modified</span> content')
  108. self.assertIsInstance(comparison.htmldiff(), SafeText)
  109. self.assertTrue(comparison.has_changed())
  110. @unittest.expectedFailure
  111. def test_has_changed_richtext(self):
  112. field = StreamPage._meta.get_field('body')
  113. comparison = self.comparison_class(
  114. field,
  115. StreamPage(body=StreamValue(field.stream_block, [
  116. ('rich_text', "<b>Original</b> content"),
  117. ])),
  118. StreamPage(body=StreamValue(field.stream_block, [
  119. ('rich_text', "Modified <i>content</i>"),
  120. ])),
  121. )
  122. self.assertEqual(comparison.htmldiff(), '<span class="deletion">Original</span><span class="addition">Modified</span> content')
  123. self.assertIsInstance(comparison.htmldiff(), SafeText)
  124. self.assertTrue(comparison.has_changed())
  125. def test_htmldiff_escapes_value(self):
  126. field = StreamPage._meta.get_field('body')
  127. comparison = self.comparison_class(
  128. field,
  129. StreamPage(body=StreamValue(field.stream_block, [
  130. ('text', "Original content"),
  131. ])),
  132. StreamPage(body=StreamValue(field.stream_block, [
  133. ('text', '<script type="text/javascript">doSomethingBad();</script>'),
  134. ])),
  135. )
  136. self.assertEqual(comparison.htmldiff(), '<span class="deletion">Original content</span><span class="addition">&lt;script type=&quot;text/javascript&quot;&gt;doSomethingBad();&lt;/script&gt;</span>')
  137. self.assertIsInstance(comparison.htmldiff(), SafeText)
  138. @unittest.expectedFailure
  139. def test_htmldiff_escapes_value_richtext(self):
  140. field = StreamPage._meta.get_field('body')
  141. comparison = self.comparison_class(
  142. field,
  143. StreamPage(body=StreamValue(field.stream_block, [
  144. ('rich_text', "Original content"),
  145. ])),
  146. StreamPage(body=StreamValue(field.stream_block, [
  147. ('rich_text', '<script type="text/javascript">doSomethingBad();</script>'),
  148. ])),
  149. )
  150. self.assertEqual(comparison.htmldiff(), '<span class="deletion">Original content</span><span class="addition">doSomethingBad();</span>')
  151. self.assertIsInstance(comparison.htmldiff(), SafeText)
  152. class TestChoiceFieldComparison(TestCase):
  153. comparison_class = compare.ChoiceFieldComparison
  154. def test_hasnt_changed(self):
  155. comparison = self.comparison_class(
  156. EventPage._meta.get_field('audience'),
  157. EventPage(audience="public"),
  158. EventPage(audience="public"),
  159. )
  160. self.assertTrue(comparison.is_field)
  161. self.assertFalse(comparison.is_child_relation)
  162. self.assertEqual(comparison.field_label(), "Audience")
  163. self.assertEqual(comparison.htmldiff(), 'Public')
  164. self.assertIsInstance(comparison.htmldiff(), SafeText)
  165. self.assertFalse(comparison.has_changed())
  166. def test_has_changed(self):
  167. comparison = self.comparison_class(
  168. EventPage._meta.get_field('audience'),
  169. EventPage(audience="public"),
  170. EventPage(audience="private"),
  171. )
  172. self.assertEqual(comparison.htmldiff(), '<span class="deletion">Public</span><span class="addition">Private</span>')
  173. self.assertIsInstance(comparison.htmldiff(), SafeText)
  174. self.assertTrue(comparison.has_changed())
  175. class TestTagsFieldComparison(TestCase):
  176. comparison_class = compare.TagsFieldComparison
  177. def test_hasnt_changed(self):
  178. a = TaggedPage()
  179. a.tags.add('wagtail')
  180. a.tags.add('bird')
  181. b = TaggedPage()
  182. b.tags.add('wagtail')
  183. b.tags.add('bird')
  184. comparison = self.comparison_class(TaggedPage._meta.get_field('tags'), a, b)
  185. self.assertTrue(comparison.is_field)
  186. self.assertFalse(comparison.is_child_relation)
  187. self.assertEqual(comparison.field_label(), "Tags")
  188. self.assertEqual(comparison.htmldiff(), 'wagtail, bird')
  189. self.assertIsInstance(comparison.htmldiff(), SafeText)
  190. self.assertFalse(comparison.has_changed())
  191. def test_has_changed(self):
  192. a = TaggedPage()
  193. a.tags.add('wagtail')
  194. a.tags.add('bird')
  195. b = TaggedPage()
  196. b.tags.add('wagtail')
  197. b.tags.add('motacilla')
  198. comparison = self.comparison_class(TaggedPage._meta.get_field('tags'), a, b)
  199. self.assertEqual(comparison.htmldiff(), 'wagtail, <span class="deletion">bird</span>, <span class="addition">motacilla</span>')
  200. self.assertIsInstance(comparison.htmldiff(), SafeText)
  201. self.assertTrue(comparison.has_changed())
  202. class TestM2MFieldComparison(TestCase):
  203. fixtures = ['test.json']
  204. comparison_class = compare.M2MFieldComparison
  205. def setUp(self):
  206. self.meetings_category = EventCategory.objects.create(name='Meetings')
  207. self.parties_category = EventCategory.objects.create(name='Parties')
  208. self.holidays_category = EventCategory.objects.create(name='Holidays')
  209. def test_hasnt_changed(self):
  210. christmas_event = EventPage.objects.get(url_path='/home/events/christmas/')
  211. saint_patrick_event = EventPage.objects.get(url_path='/home/events/saint-patrick/')
  212. christmas_event.categories = [self.meetings_category, self.parties_category]
  213. saint_patrick_event.categories = [self.meetings_category, self.parties_category]
  214. comparison = self.comparison_class(
  215. EventPage._meta.get_field('categories'), christmas_event, saint_patrick_event
  216. )
  217. self.assertTrue(comparison.is_field)
  218. self.assertFalse(comparison.is_child_relation)
  219. self.assertEqual(comparison.field_label(), "Categories")
  220. self.assertFalse(comparison.has_changed())
  221. self.assertEqual(comparison.htmldiff(), 'Meetings, Parties')
  222. self.assertIsInstance(comparison.htmldiff(), SafeText)
  223. def test_has_changed(self):
  224. christmas_event = EventPage.objects.get(url_path='/home/events/christmas/')
  225. saint_patrick_event = EventPage.objects.get(url_path='/home/events/saint-patrick/')
  226. christmas_event.categories = [self.meetings_category, self.parties_category]
  227. saint_patrick_event.categories = [self.meetings_category, self.holidays_category]
  228. comparison = self.comparison_class(
  229. EventPage._meta.get_field('categories'), christmas_event, saint_patrick_event
  230. )
  231. self.assertTrue(comparison.has_changed())
  232. self.assertEqual(comparison.htmldiff(), 'Meetings, <span class="deletion">Parties</span>, <span class="addition">Holidays</span>')
  233. self.assertIsInstance(comparison.htmldiff(), SafeText)
  234. class TestForeignObjectComparison(TestCase):
  235. comparison_class = compare.ForeignObjectComparison
  236. @classmethod
  237. def setUpTestData(cls):
  238. image_model = get_image_model()
  239. cls.test_image_1 = image_model.objects.create(
  240. title="Test image 1",
  241. file=get_test_image_file(),
  242. )
  243. cls.test_image_2 = image_model.objects.create(
  244. title="Test image 2",
  245. file=get_test_image_file(),
  246. )
  247. def test_hasnt_changed(self):
  248. comparison = self.comparison_class(
  249. EventPage._meta.get_field('feed_image'),
  250. EventPage(feed_image=self.test_image_1),
  251. EventPage(feed_image=self.test_image_1),
  252. )
  253. self.assertTrue(comparison.is_field)
  254. self.assertFalse(comparison.is_child_relation)
  255. self.assertEqual(comparison.field_label(), "Feed image")
  256. self.assertEqual(comparison.htmldiff(), 'Test image 1')
  257. self.assertIsInstance(comparison.htmldiff(), SafeText)
  258. self.assertFalse(comparison.has_changed())
  259. def test_has_changed(self):
  260. comparison = self.comparison_class(
  261. EventPage._meta.get_field('feed_image'),
  262. EventPage(feed_image=self.test_image_1),
  263. EventPage(feed_image=self.test_image_2),
  264. )
  265. self.assertEqual(comparison.htmldiff(), '<span class="deletion">Test image 1</span><span class="addition">Test image 2</span>')
  266. self.assertIsInstance(comparison.htmldiff(), SafeText)
  267. self.assertTrue(comparison.has_changed())
  268. class TestChildRelationComparison(TestCase):
  269. field_comparison_class = compare.FieldComparison
  270. comparison_class = compare.ChildRelationComparison
  271. def test_hasnt_changed(self):
  272. # Two event pages with speaker called "Father Christmas". Neither of
  273. # the speaker objects have an ID so this tests that the code can match
  274. # the two together by field content.
  275. event_page = EventPage(title="Event page", slug="event")
  276. event_page.speakers.add(EventPageSpeaker(
  277. first_name="Father",
  278. last_name="Christmas",
  279. ))
  280. modified_event_page = EventPage(title="Event page", slug="event")
  281. modified_event_page.speakers.add(EventPageSpeaker(
  282. first_name="Father",
  283. last_name="Christmas",
  284. ))
  285. comparison = self.comparison_class(
  286. EventPage._meta.get_field('speakers'),
  287. [
  288. curry(self.field_comparison_class, EventPageSpeaker._meta.get_field('first_name')),
  289. curry(self.field_comparison_class, EventPageSpeaker._meta.get_field('last_name')),
  290. ],
  291. event_page,
  292. modified_event_page,
  293. )
  294. self.assertFalse(comparison.is_field)
  295. self.assertTrue(comparison.is_child_relation)
  296. self.assertEqual(comparison.field_label(), "Speakers")
  297. self.assertFalse(comparison.has_changed())
  298. # Check mapping
  299. objs_a = list(comparison.val_a.all())
  300. objs_b = list(comparison.val_b.all())
  301. map_forwards, map_backwards, added, deleted = comparison.get_mapping(objs_a, objs_b)
  302. self.assertEqual(map_forwards, {0: 0})
  303. self.assertEqual(map_backwards, {0: 0})
  304. self.assertEqual(added, [])
  305. self.assertEqual(deleted, [])
  306. def test_has_changed(self):
  307. # Father Christmas renamed to Santa Claus. And Father Ted added.
  308. # Father Christmas should be mapped to Father Ted because they
  309. # are most alike. Santa claus should be displayed as "new"
  310. event_page = EventPage(title="Event page", slug="event")
  311. event_page.speakers.add(EventPageSpeaker(
  312. first_name="Father",
  313. last_name="Christmas",
  314. sort_order=0,
  315. ))
  316. modified_event_page = EventPage(title="Event page", slug="event")
  317. modified_event_page.speakers.add(EventPageSpeaker(
  318. first_name="Santa",
  319. last_name="Claus",
  320. sort_order=0,
  321. ))
  322. modified_event_page.speakers.add(EventPageSpeaker(
  323. first_name="Father",
  324. last_name="Ted",
  325. sort_order=1,
  326. ))
  327. comparison = self.comparison_class(
  328. EventPage._meta.get_field('speakers'),
  329. [
  330. curry(self.field_comparison_class, EventPageSpeaker._meta.get_field('first_name')),
  331. curry(self.field_comparison_class, EventPageSpeaker._meta.get_field('last_name')),
  332. ],
  333. event_page,
  334. modified_event_page,
  335. )
  336. self.assertFalse(comparison.is_field)
  337. self.assertTrue(comparison.is_child_relation)
  338. self.assertEqual(comparison.field_label(), "Speakers")
  339. self.assertTrue(comparison.has_changed())
  340. # Check mapping
  341. objs_a = list(comparison.val_a.all())
  342. objs_b = list(comparison.val_b.all())
  343. map_forwards, map_backwards, added, deleted = comparison.get_mapping(objs_a, objs_b)
  344. self.assertEqual(map_forwards, {0: 1}) # Map Father Christmas to Father Ted
  345. self.assertEqual(map_backwards, {1: 0}) # Map Father Ted ot Father Christmas
  346. self.assertEqual(added, [0]) # Add Santa Claus
  347. self.assertEqual(deleted, [])
  348. def test_has_changed_with_same_id(self):
  349. # Father Christmas renamed to Santa Claus, but this time the ID of the
  350. # child object remained the same. It should now be detected as the same
  351. # object
  352. event_page = EventPage(title="Event page", slug="event")
  353. event_page.speakers.add(EventPageSpeaker(
  354. id=1,
  355. first_name="Father",
  356. last_name="Christmas",
  357. sort_order=0,
  358. ))
  359. modified_event_page = EventPage(title="Event page", slug="event")
  360. modified_event_page.speakers.add(EventPageSpeaker(
  361. id=1,
  362. first_name="Santa",
  363. last_name="Claus",
  364. sort_order=0,
  365. ))
  366. modified_event_page.speakers.add(EventPageSpeaker(
  367. first_name="Father",
  368. last_name="Ted",
  369. sort_order=1,
  370. ))
  371. comparison = self.comparison_class(
  372. EventPage._meta.get_field('speakers'),
  373. [
  374. curry(self.field_comparison_class, EventPageSpeaker._meta.get_field('first_name')),
  375. curry(self.field_comparison_class, EventPageSpeaker._meta.get_field('last_name')),
  376. ],
  377. event_page,
  378. modified_event_page,
  379. )
  380. self.assertFalse(comparison.is_field)
  381. self.assertTrue(comparison.is_child_relation)
  382. self.assertEqual(comparison.field_label(), "Speakers")
  383. self.assertTrue(comparison.has_changed())
  384. # Check mapping
  385. objs_a = list(comparison.val_a.all())
  386. objs_b = list(comparison.val_b.all())
  387. map_forwards, map_backwards, added, deleted = comparison.get_mapping(objs_a, objs_b)
  388. self.assertEqual(map_forwards, {0: 0}) # Map Father Christmas to Santa Claus
  389. self.assertEqual(map_backwards, {0: 0}) # Map Santa Claus to Father Christmas
  390. self.assertEqual(added, [1]) # Add Father Ted
  391. self.assertEqual(deleted, [])
  392. def test_hasnt_changed_with_different_id(self):
  393. # Both of the child objects have the same field content but have a
  394. # different ID so they should be detected as separate objects
  395. event_page = EventPage(title="Event page", slug="event")
  396. event_page.speakers.add(EventPageSpeaker(
  397. id=1,
  398. first_name="Father",
  399. last_name="Christmas",
  400. ))
  401. modified_event_page = EventPage(title="Event page", slug="event")
  402. modified_event_page.speakers.add(EventPageSpeaker(
  403. id=2,
  404. first_name="Father",
  405. last_name="Christmas",
  406. ))
  407. comparison = self.comparison_class(
  408. EventPage._meta.get_field('speakers'),
  409. [
  410. curry(self.field_comparison_class, EventPageSpeaker._meta.get_field('first_name')),
  411. curry(self.field_comparison_class, EventPageSpeaker._meta.get_field('last_name')),
  412. ],
  413. event_page,
  414. modified_event_page,
  415. )
  416. self.assertFalse(comparison.is_field)
  417. self.assertTrue(comparison.is_child_relation)
  418. self.assertEqual(comparison.field_label(), "Speakers")
  419. self.assertTrue(comparison.has_changed())
  420. # Check mapping
  421. objs_a = list(comparison.val_a.all())
  422. objs_b = list(comparison.val_b.all())
  423. map_forwards, map_backwards, added, deleted = comparison.get_mapping(objs_a, objs_b)
  424. self.assertEqual(map_forwards, {})
  425. self.assertEqual(map_backwards, {})
  426. self.assertEqual(added, [0]) # Add new Father Christmas
  427. self.assertEqual(deleted, [0]) # Delete old Father Christmas
  428. class TestChildObjectComparison(TestCase):
  429. field_comparison_class = compare.FieldComparison
  430. comparison_class = compare.ChildObjectComparison
  431. def test_same_object(self):
  432. obj_a = EventPageSpeaker(
  433. first_name="Father",
  434. last_name="Christmas",
  435. )
  436. obj_b = EventPageSpeaker(
  437. first_name="Father",
  438. last_name="Christmas",
  439. )
  440. comparison = self.comparison_class(
  441. EventPageSpeaker,
  442. [
  443. curry(self.field_comparison_class, EventPageSpeaker._meta.get_field('first_name')),
  444. curry(self.field_comparison_class, EventPageSpeaker._meta.get_field('last_name')),
  445. ],
  446. obj_a,
  447. obj_b,
  448. )
  449. self.assertFalse(comparison.is_addition())
  450. self.assertFalse(comparison.is_deletion())
  451. self.assertFalse(comparison.has_changed())
  452. self.assertEqual(comparison.get_position_change(), 0)
  453. self.assertEqual(comparison.get_num_differences(), 0)
  454. def test_different_object(self):
  455. obj_a = EventPageSpeaker(
  456. first_name="Father",
  457. last_name="Christmas",
  458. )
  459. obj_b = EventPageSpeaker(
  460. first_name="Santa",
  461. last_name="Claus",
  462. )
  463. comparison = self.comparison_class(
  464. EventPageSpeaker,
  465. [
  466. curry(self.field_comparison_class, EventPageSpeaker._meta.get_field('first_name')),
  467. curry(self.field_comparison_class, EventPageSpeaker._meta.get_field('last_name')),
  468. ],
  469. obj_a,
  470. obj_b,
  471. )
  472. self.assertFalse(comparison.is_addition())
  473. self.assertFalse(comparison.is_deletion())
  474. self.assertTrue(comparison.has_changed())
  475. self.assertEqual(comparison.get_position_change(), 0)
  476. self.assertEqual(comparison.get_num_differences(), 2)
  477. def test_moved_object(self):
  478. obj_a = EventPageSpeaker(
  479. first_name="Father",
  480. last_name="Christmas",
  481. sort_order=1,
  482. )
  483. obj_b = EventPageSpeaker(
  484. first_name="Father",
  485. last_name="Christmas",
  486. sort_order=5,
  487. )
  488. comparison = self.comparison_class(
  489. EventPageSpeaker,
  490. [
  491. curry(self.field_comparison_class, EventPageSpeaker._meta.get_field('first_name')),
  492. curry(self.field_comparison_class, EventPageSpeaker._meta.get_field('last_name')),
  493. ],
  494. obj_a,
  495. obj_b,
  496. )
  497. self.assertFalse(comparison.is_addition())
  498. self.assertFalse(comparison.is_deletion())
  499. self.assertFalse(comparison.has_changed())
  500. self.assertEqual(comparison.get_position_change(), 4)
  501. self.assertEqual(comparison.get_num_differences(), 0)
  502. def test_addition(self):
  503. obj = EventPageSpeaker(
  504. first_name="Father",
  505. last_name="Christmas",
  506. )
  507. comparison = self.comparison_class(
  508. EventPageSpeaker,
  509. [
  510. curry(self.field_comparison_class, EventPageSpeaker._meta.get_field('first_name')),
  511. curry(self.field_comparison_class, EventPageSpeaker._meta.get_field('last_name')),
  512. ],
  513. None,
  514. obj,
  515. )
  516. self.assertTrue(comparison.is_addition())
  517. self.assertFalse(comparison.is_deletion())
  518. self.assertFalse(comparison.has_changed())
  519. self.assertIsNone(comparison.get_position_change(), 0)
  520. self.assertEqual(comparison.get_num_differences(), 0)
  521. def test_deletion(self):
  522. obj = EventPageSpeaker(
  523. first_name="Father",
  524. last_name="Christmas",
  525. )
  526. comparison = self.comparison_class(
  527. EventPageSpeaker,
  528. [
  529. curry(self.field_comparison_class, EventPageSpeaker._meta.get_field('first_name')),
  530. curry(self.field_comparison_class, EventPageSpeaker._meta.get_field('last_name')),
  531. ],
  532. obj,
  533. None,
  534. )
  535. self.assertFalse(comparison.is_addition())
  536. self.assertTrue(comparison.is_deletion())
  537. self.assertFalse(comparison.has_changed())
  538. self.assertIsNone(comparison.get_position_change())
  539. self.assertEqual(comparison.get_num_differences(), 0)
  540. class TestChildRelationComparisonUsingPK(TestCase):
  541. """Test related objects can be compred if they do not use id for primary key"""
  542. field_comparison_class = compare.FieldComparison
  543. comparison_class = compare.ChildRelationComparison
  544. def test_has_changed_with_same_id(self):
  545. # Head Count was changed but the PK of the child object remained the same.
  546. # It should be detected as the same object
  547. event_page = EventPage(title="Semi Finals", slug="semi-finals-2018")
  548. event_page.head_counts.add(HeadCountRelatedModelUsingPK(
  549. custom_id=1,
  550. head_count=22,
  551. ))
  552. modified_event_page = EventPage(title="Semi Finals", slug="semi-finals-2018")
  553. modified_event_page.head_counts.add(HeadCountRelatedModelUsingPK(
  554. custom_id=1,
  555. head_count=23,
  556. ))
  557. modified_event_page.head_counts.add(HeadCountRelatedModelUsingPK(
  558. head_count=25,
  559. ))
  560. comparison = self.comparison_class(
  561. EventPage._meta.get_field('head_counts'),
  562. [curry(self.field_comparison_class, HeadCountRelatedModelUsingPK._meta.get_field('head_count'))],
  563. event_page,
  564. modified_event_page,
  565. )
  566. self.assertFalse(comparison.is_field)
  567. self.assertTrue(comparison.is_child_relation)
  568. self.assertEqual(comparison.field_label(), 'Head counts')
  569. self.assertTrue(comparison.has_changed())
  570. # Check mapping
  571. objs_a = list(comparison.val_a.all())
  572. objs_b = list(comparison.val_b.all())
  573. map_forwards, map_backwards, added, deleted = comparison.get_mapping(objs_a, objs_b)
  574. self.assertEqual(map_forwards, {0: 0}) # map head count 22 to 23
  575. self.assertEqual(map_backwards, {0: 0}) # map head count 23 to 22
  576. self.assertEqual(added, [1]) # add second head count
  577. self.assertEqual(deleted, [])
  578. def test_hasnt_changed_with_different_id(self):
  579. # Both of the child objects have the same field content but have a
  580. # different PK (ID) so they should be detected as separate objects
  581. event_page = EventPage(title="Finals", slug="finals-event-abc")
  582. event_page.head_counts.add(HeadCountRelatedModelUsingPK(
  583. custom_id=1,
  584. head_count=220
  585. ))
  586. modified_event_page = EventPage(title="Finals", slug="finals-event-abc")
  587. modified_event_page.head_counts.add(HeadCountRelatedModelUsingPK(
  588. custom_id=2,
  589. head_count=220
  590. ))
  591. comparison = self.comparison_class(
  592. EventPage._meta.get_field('head_counts'),
  593. [curry(self.field_comparison_class, HeadCountRelatedModelUsingPK._meta.get_field('head_count'))],
  594. event_page,
  595. modified_event_page,
  596. )
  597. self.assertFalse(comparison.is_field)
  598. self.assertTrue(comparison.is_child_relation)
  599. self.assertEqual(comparison.field_label(), "Head counts")
  600. self.assertTrue(comparison.has_changed())
  601. # Check mapping
  602. objs_a = list(comparison.val_a.all())
  603. objs_b = list(comparison.val_b.all())
  604. map_forwards, map_backwards, added, deleted = comparison.get_mapping(objs_a, objs_b)
  605. self.assertEqual(map_forwards, {})
  606. self.assertEqual(map_backwards, {})
  607. self.assertEqual(added, [0]) # Add new head count
  608. self.assertEqual(deleted, [0]) # Delete old head count