Repository: wagtail/wagtail

Issue:
Information disappearing on copied page
We have a page type with orderable items, which when copied starts to display incorrectly.

So if the original page is edited, with no changes made, and you click publish, the copied page doesn't display the orderable items for that page. It also works in the other direction, so if you edit the copied page, the orderable items in the original stop displaying. 

They display fine in the preview for both. The templates that displays the items for the page uses a regroup template tag. 

Only pages copied from each other at the same level in the tree seemed to be affected.

The items are always still there when you go back to edit the page, so they're not being lost, just not displayed if the copied or original page is edited and re-published.



Discussion:
We're using 0.8.7 btw

Thanks @jossingram - have confirmed that this bug still exists on current master using wagtaildemo:
- Create a new 'person' page within People, with a single related link, and publish it
- Copy that page within the same parent (entering a new title and slug, and leaving 'Publish copied page' ticked)
- Edit the new page, and publish
- View the live version of the original page
- The related link is no longer present on the original page


Resolving patch:
diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py
--- a/wagtail/wagtailcore/models.py
+++ b/wagtail/wagtailcore/models.py
@@ -2,6 +2,7 @@
 
 import logging
 import json
+from collections import defaultdict
 
 from modelcluster.models import ClusterableModel, get_all_child_relations
 
@@ -810,18 +811,27 @@ def copy(self, recursive=False, to=None, update_attrs=None, copy_revisions=True,
         else:
             page_copy = self.add_sibling(instance=page_copy)
 
+        # A dict that maps child objects to their new ids
+        # Used to remap child object ids in revisions
+        child_object_id_map = defaultdict(dict)
+
         # Copy child objects
         specific_self = self.specific
         for child_relation in get_all_child_relations(specific_self):
+            accessor_name = child_relation.get_accessor_name()
             parental_key_name = child_relation.field.attname
-            child_objects = getattr(specific_self, child_relation.get_accessor_name(), None)
+            child_objects = getattr(specific_self, accessor_name, None)
 
             if child_objects:
                 for child_object in child_objects.all():
+                    old_pk = child_object.pk
                     child_object.pk = None
                     setattr(child_object, parental_key_name, page_copy.id)
                     child_object.save()
 
+                    # Add mapping to new primary key (so we can apply this change to revisions)
+                    child_object_id_map[accessor_name][old_pk] = child_object.pk
+
         # Copy revisions
         if copy_revisions:
             for revision in self.revisions.all():
@@ -835,8 +845,9 @@ def copy(self, recursive=False, to=None, update_attrs=None, copy_revisions=True,
                 revision_content['pk'] = page_copy.pk
 
                 for child_relation in get_all_child_relations(specific_self):
+                    accessor_name = child_relation.get_accessor_name()
                     try:
-                        child_objects = revision_content[child_relation.get_accessor_name()]
+                        child_objects = revision_content[accessor_name]
                     except KeyError:
                         # KeyErrors are possible if the revision was created
                         # before this child relation was added to the database
@@ -845,6 +856,11 @@ def copy(self, recursive=False, to=None, update_attrs=None, copy_revisions=True,
                     for child_object in child_objects:
                         child_object[child_relation.field.name] = page_copy.pk
 
+                        # Remap primary key to copied versions
+                        # If the primary key is not recognised (eg, the child object has been deleted from the database)
+                        # set the primary key to None
+                        child_object['pk'] = child_object_id_map[accessor_name].get(child_object['pk'], None)
+
                 revision.content_json = json.dumps(revision_content)
 
                 # Save


Test patch:
diff --git a/wagtail/wagtailcore/tests/test_page_model.py b/wagtail/wagtailcore/tests/test_page_model.py
--- a/wagtail/wagtailcore/tests/test_page_model.py
+++ b/wagtail/wagtailcore/tests/test_page_model.py
@@ -448,6 +448,11 @@ def test_copy_page_copies_revisions(self):
         self.assertEqual(new_revision_content['pk'], new_christmas_event.id)
         self.assertEqual(new_revision_content['speakers'][0]['page'], new_christmas_event.id)
 
+        # Also, check that the child objects in the new revision are given new IDs
+        old_speakers_ids = set(christmas_event.speakers.values_list('id', flat=True))
+        new_speakers_ids = set(speaker['pk'] for speaker in new_revision_content['speakers'])
+        self.assertFalse(old_speakers_ids.intersection(new_speakers_ids), "Child objects in revisions were not given a new primary key")
+
     def test_copy_page_copies_revisions_and_doesnt_submit_for_moderation(self):
         christmas_event = EventPage.objects.get(url_path='/home/events/christmas/')
         christmas_event.save_revision(submitted_for_moderation=True)