Repository: django-cms/django-cms

Issue:
Deleting a page with a lot of plugins: webserver timeout
There seams to be a performance problem when deleting a page with a lot of plugins



Discussion:

Resolving patch:
diff --git a/cms/signals/page.py b/cms/signals/page.py
--- a/cms/signals/page.py
+++ b/cms/signals/page.py
@@ -48,7 +48,11 @@ def post_save_page(instance, **kwargs):
 
 def pre_delete_page(instance, **kwargs):
     menu_pool.clear(instance.site_id)
-    instance.placeholders.all().delete()
+    for placeholder in instance.placeholders.all():
+        for plugin in placeholder.cmsplugin_set.all():
+            plugin._no_reorder = True
+            plugin.delete()
+        placeholder.delete()
     clear_permission_cache()
 
 
diff --git a/cms/signals/plugins.py b/cms/signals/plugins.py
--- a/cms/signals/plugins.py
+++ b/cms/signals/plugins.py
@@ -5,12 +5,14 @@
 
 def pre_save_plugins(**kwargs):
     plugin = kwargs['instance']
+    placeholder = None
     if plugin.placeholder:
-        placeholder = plugin.placeholder
+        try:
+            placeholder = plugin.placeholder
+        except Placeholder.DoesNotExist:
+            pass
     elif plugin.placeholder_id:
         placeholder = Placeholder.objects.get(pk=plugin.placeholder_id)
-    else:
-        placeholder = None
     if placeholder:
         attached_model = placeholder._get_attached_model()
         if attached_model == Page:
@@ -34,10 +36,14 @@ def pre_save_plugins(**kwargs):
 
 def pre_delete_plugins(**kwargs):
     plugin = kwargs['instance']
+    if hasattr(plugin, '_no_reorder'):
+        return
+    placeholder = None
     if plugin.placeholder_id:
-        placeholder = plugin.placeholder
-    else:
-        placeholder = None
+        try:
+            placeholder = plugin.placeholder
+        except Placeholder.DoesNotExist:
+            raise
     if placeholder:
         attached_model = placeholder._get_attached_model()
         if attached_model == Page:
@@ -49,7 +55,10 @@ def pre_delete_plugins(**kwargs):
 
 def post_delete_plugins(**kwargs):
     plugin = kwargs['instance']
-    plugins = CMSPlugin.objects.filter(language=plugin.language, placeholder=plugin.placeholder_id).order_by("position")
+    if hasattr(plugin, '_no_reorder'):
+        return
+    plugins = CMSPlugin.objects.filter(language=plugin.language, placeholder=plugin.placeholder_id,
+                                       parent=plugin.parent_id).order_by("position")
     last = 0
     for p in plugins:
         if p.position != last:
diff --git a/cms/signals/reversion_signals.py b/cms/signals/reversion_signals.py
--- a/cms/signals/reversion_signals.py
+++ b/cms/signals/reversion_signals.py
@@ -8,5 +8,5 @@ def post_revision(instances, **kwargs):
             page = Page.objects.get(pk=inst.pk)
             page.revision_id = 0
             page._publisher_keep_state = True
-            page.save()
+            page.save(no_signals=True)
             return


Test patch:
diff --git a/cms/test_utils/cli.py b/cms/test_utils/cli.py
--- a/cms/test_utils/cli.py
+++ b/cms/test_utils/cli.py
@@ -57,7 +57,7 @@ def configure(db_url, **extra):
             os.path.abspath(os.path.join(os.path.dirname(__file__), 'project', 'templates'))
         ],
         MIDDLEWARE_CLASSES=[
-           # 'debug_toolbar.middleware.DebugToolbarMiddleware',
+        #    'debug_toolbar.middleware.DebugToolbarMiddleware',
             'django.contrib.sessions.middleware.SessionMiddleware',
             'django.contrib.auth.middleware.AuthenticationMiddleware',
             'django.contrib.messages.middleware.MessageMiddleware',
diff --git a/cms/tests/admin.py b/cms/tests/admin.py
--- a/cms/tests/admin.py
+++ b/cms/tests/admin.py
@@ -299,14 +299,14 @@ def test_delete(self):
                            created_by=admin, published=True)
         child = create_page('child-page', "nav_playground.html", "en",
                             created_by=admin, published=True, parent=page)
+        body = page.placeholders.get(slot='body')
+        add_plugin(body, 'TextPlugin', 'en', body='text')
+        page.publish('en')
         with self.login_user_context(admin):
             data = {'post': 'yes'}
-            response = self.client.post(URL_CMS_PAGE_DELETE % page.pk, data)
+            with self.assertNumQueries(FuzzyInt(300, 380)):
+                response = self.client.post(URL_CMS_PAGE_DELETE % page.pk, data)
             self.assertRedirects(response, URL_CMS_PAGE)
-            # TODO - The page should be marked for deletion, but nothing more
-            # until publishing
-            #self.assertRaises(Page.DoesNotExist, self.reload, page)
-            #self.assertRaises(Page.DoesNotExist, self.reload, child)
 
     def test_search_fields(self):
         superuser = self.get_superuser()