Prevent cascade deletion of pages when a ContentType is deleted

Fixes #2024
This commit is contained in:
Karl Hobley 2015-12-22 09:36:21 +00:00 committed by Matt Westcott
parent d8bceff38b
commit 078491b6e0
5 changed files with 57 additions and 3 deletions

View file

@ -7,6 +7,7 @@ Changelog
* The `Document` model can now be overridden using the new `WAGTAILDOCS_DOCUMENT_MODEL` setting (Alex Gleason)
* Fix: Custom page managers no longer raise an error when used on an abstract model
* Fix: Wagtail's migrations are now all reversible (benjaoming)
* Fix: Deleting a page content type now preserves existing pages as basic Page instances, to prevent tree corruption
1.3.1 (05.01.2016)
~~~~~~~~~~~~~~~~~~

View file

@ -24,6 +24,7 @@ Bug fixes
* Custom page managers no longer raise an error when used on an abstract model
* Wagtail's migrations are now all reversible (benjaoming)
* Deleting a page content type now preserves existing pages as basic Page instances, to prevent tree corruption
Upgrade considerations

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2015-12-22 09:34
from __future__ import unicode_literals
from django.db import migrations, models
import wagtail.wagtailcore.models
class Migration(migrations.Migration):
dependencies = [
('wagtailcore', '0023_alter_page_revision_on_delete_behaviour'),
]
operations = [
migrations.AlterField(
model_name='page',
name='content_type',
field=models.ForeignKey(on_delete=models.SET(wagtail.wagtailcore.models.get_default_page_content_type), related_name='pages', to='contenttypes.ContentType', verbose_name='content type'),
),
]

View file

@ -205,6 +205,14 @@ def get_page_models():
return PAGE_MODEL_CLASSES
def get_default_page_content_type():
"""
Returns the content type to use as a default for pages whose content type
has been deleted.
"""
return ContentType.objects.get_for_model(Page)
def get_page_types():
"""
DEPRECATED.
@ -277,7 +285,12 @@ class Page(six.with_metaclass(PageBase, MP_Node, ClusterableModel, index.Indexed
)
# TODO: enforce uniqueness on slug field per parent (will have to be done at the Django
# level rather than db, since there is no explicit parent relation in the db)
content_type = models.ForeignKey('contenttypes.ContentType', verbose_name=_('content type'), related_name='pages')
content_type = models.ForeignKey(
'contenttypes.ContentType',
verbose_name=_('content type'),
related_name='pages',
on_delete=models.SET(get_default_page_content_type)
)
live = models.BooleanField(verbose_name=_('live'), default=True, editable=False)
has_unpublished_changes = models.BooleanField(
verbose_name=_('has unpublished changes'),
@ -452,8 +465,6 @@ class Page(six.with_metaclass(PageBase, MP_Node, ClusterableModel, index.Indexed
for model in [cls] + list(cls._meta.get_parent_list())
for field in model._meta.parents.values() if field]
field_exceptions += ['content_type']
for field in cls._meta.fields:
if isinstance(field, models.ForeignKey) and field.name not in field_exceptions:
if field.rel.on_delete == models.CASCADE:

View file

@ -1036,3 +1036,23 @@ class TestPageManager(TestCase):
their custom Manager inherits from PageManager.
"""
self.assertIs(type(MyCustomPage.objects), CustomManager)
class TestIssue2024(TestCase):
"""
This tests that deleting a content type can't delete any Page objects.
"""
fixtures = ['test.json']
def test_delete_content_type(self):
event_index = Page.objects.get(url_path='/home/events/')
# Delete the content type
event_index_content_type = event_index.content_type
event_index_content_type.delete()
# Fetch the page again, it should still exist
event_index = Page.objects.get(url_path='/home/events/')
# Check that the content_type changed to Page
self.assertEqual(event_index.content_type, ContentType.objects.get_for_model(Page))