Repository: django-cms/django-cms

Issue:
undo and revert create doublicate placeholders sometimes



Discussion:

Resolving patch:
diff --git a/cms/admin/pageadmin.py b/cms/admin/pageadmin.py
--- a/cms/admin/pageadmin.py
+++ b/cms/admin/pageadmin.py
@@ -775,14 +775,28 @@ def undo(self, request, object_id):
         placeholder_ids = []
         for placeholder in placeholders:
             placeholder_ids.append(placeholder.pk)
-        plugins = CMSPlugin.objects.filter(placeholder__in=placeholder_ids)
-        plugins.delete()
+        plugins = CMSPlugin.objects.filter(placeholder__in=placeholder_ids).order_by('-level')
+        for plugin in plugins:
+            plugin._no_reorder = True
+            plugin.delete()
+        # TODO: delete placeholders instead of finding duplicates for 3.1
+        #page.placeholders.all().delete()
 
         previous_revision.revert(True)
         rev_page = get_object_or_404(Page, pk=page.pk)
         rev_page.revision_id = previous_revision.pk
         rev_page.publisher_public_id = page.publisher_public_id
         rev_page.save()
+        new_placeholders = rev_page.placeholders.all()
+        slots = {}
+        for new_ph in new_placeholders:
+            if not new_ph.slot in slots:
+                slots[new_ph.slot] = new_ph
+            else:
+                if new_ph in placeholder_ids:
+                    new_ph.delete()
+                elif slots[new_ph.slot] in placeholder_ids:
+                    slots[new_ph.slot].delete()
         new_titles = rev_page.title_set.all()
         for title in new_titles:
             try:
@@ -827,14 +841,27 @@ def redo(self, request, object_id):
         placeholder_ids = []
         for placeholder in placeholders:
             placeholder_ids.append(placeholder.pk)
-        plugins = CMSPlugin.objects.filter(placeholder__in=placeholder_ids)
-        plugins.delete()
-
+        plugins = CMSPlugin.objects.filter(placeholder__in=placeholder_ids).order_by('-level')
+        for plugin in plugins:
+            plugin._no_reorder = True
+            plugin.delete()
+        # TODO: 3.1 remove the placeholder matching from below and just delete them
+        #page.placeholders.all().delete()
         next_revision.revert(True)
         rev_page = get_object_or_404(Page, pk=page.pk)
         rev_page.revision_id = next_revision.pk
         rev_page.publisher_public_id = page.publisher_public_id
         rev_page.save()
+        new_placeholders = rev_page.placeholders.all()
+        slots = {}
+        for new_ph in new_placeholders:
+            if not new_ph.slot in slots:
+                slots[new_ph.slot] = new_ph
+            else:
+                if new_ph in placeholder_ids:
+                    new_ph.delete()
+                elif slots[new_ph.slot] in placeholder_ids:
+                    slots[new_ph.slot].delete()
         new_titles = rev_page.title_set.all()
         for title in new_titles:
             try:
diff --git a/cms/models/pagemodel.py b/cms/models/pagemodel.py
--- a/cms/models/pagemodel.py
+++ b/cms/models/pagemodel.py
@@ -299,9 +299,6 @@ def copy_page(self, target, site, position='first-child',
         Note for issue #1166: when copying pages there is no need to check for
         conflicting URLs as pages are copied unpublished.
         """
-
-        page_copy = None
-
         pages = [self] + list(self.get_descendants().order_by('-rght'))
 
         site_reverse_ids = Page.objects.filter(site=site, reverse_id__isnull=False).values_list('reverse_id', flat=True)
@@ -406,14 +403,10 @@ def copy_page(self, target, site, position='first-child',
                     ph.pk = None  # make a new instance
                     ph.save()
                     page.placeholders.add(ph)
-                    # update the page copy
-                    page_copy = page
                 if plugins:
                     copy_plugins_to(plugins, ph)
-
         # invalidate the menu for this site
         menu_pool.clear(site_id=site.pk)
-        return page_copy  # return the page_copy or None
 
     def save(self, no_signals=False, commit=True, **kwargs):
         """
diff --git a/cms/utils/helpers.py b/cms/utils/helpers.py
--- a/cms/utils/helpers.py
+++ b/cms/utils/helpers.py
@@ -62,6 +62,13 @@ def make_revision_with_plugins(obj, user=None, message=None):
                 revision_context.add_to_context(revision_manager, obj, adapter.get_version_data(obj, VERSION_CHANGE))
             else:
                 revision_context.add_to_context(revision_manager, obj, adapter.get_version_data(obj))
+            # add placeholders to the revision
+            for ph in obj.placeholders.all():
+                phadapter = revision_manager.get_adapter(ph.__class__)
+                if hasattr(reversion.models, 'VERSION_CHANGE'):
+                    revision_context.add_to_context(revision_manager, ph, phadapter.get_version_data(ph, VERSION_CHANGE))
+                else:
+                    revision_context.add_to_context(revision_manager, ph, phadapter.get_version_data(ph))
             # add plugins and subclasses to the revision
             filters = {'placeholder__%s' % placeholder_relation: obj}
             for plugin in CMSPlugin.objects.filter(**filters):


Test patch:
diff --git a/cms/tests/reversion_tests.py b/cms/tests/reversion_tests.py
--- a/cms/tests/reversion_tests.py
+++ b/cms/tests/reversion_tests.py
@@ -11,7 +11,7 @@
 import reversion
 from reversion.models import Revision, Version
 
-from cms.models import Page, Title
+from cms.models import Page, Title, Placeholder
 from cms.models.pluginmodel import CMSPlugin
 from cms.test_utils.project.fileapp.models import FileModel
 from cms.test_utils.testcases import CMSTestCase, TransactionCMSTestCase, URL_CMS_PAGE, URL_CMS_PAGE_CHANGE, URL_CMS_PAGE_ADD, \
@@ -123,6 +123,7 @@ def test_undo_redo(self):
             self.assertEqual(Title.objects.all().count(), 2)
             self.assertEqual(CMSPlugin.objects.all().count(), 2)
             self.assertEqual(Revision.objects.all().count(), 5)
+            self.assertEqual(Placeholder.objects.count(), 5)
 
             ctype = ContentType.objects.get_for_model(Page)
             revision = Revision.objects.all()[2]
@@ -167,6 +168,7 @@ def test_undo_redo(self):
             self.client.post(undo_url)
             self.client.post(undo_url)
             self.assertEqual(2, CMSPlugin.objects.all().count())
+            self.assertEqual(Placeholder.objects.count(), 5)
 
     def test_undo_slug_collision(self):
         data1 = self.get_new_page_data()