diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 308307f71f..7a6f1d5797 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -7,6 +7,7 @@ Changelog * Fix: Allow bulk publishing of pages without revisions (Andy Chosak) * Fix: Ensure that all descendant pages are logged when deleting a page, not just immediate children (Jake Howard) * Fix: Translation key `IntegrityError` when publishing pages with translatable `Orderable`s that were copied without being published (Kalob Taulien, Dan Braghis) + * Fix: Ignore `GenericRelation` when copying pages (John-Scott Atlakson) 2.15.4 (11.02.2022) diff --git a/docs/releases/2.15.5.rst b/docs/releases/2.15.5.rst index 0fa7dc2f13..1cdae2bc01 100644 --- a/docs/releases/2.15.5.rst +++ b/docs/releases/2.15.5.rst @@ -16,6 +16,7 @@ Bug fixes * Allow bulk publishing of pages without revisions (Andy Chosak) * Ensure that all descendant pages are logged when deleting a page, not just immediate children (Jake Howard) * Generate new translation keys for translatable ``Orderable`` when page is copied without being published (Kalob Taulien, Dan Braghis) + * Ignore `GenericRelation` when copying pages (John-Scott Atlakson) Upgrade considerations diff --git a/wagtail/core/models/copying.py b/wagtail/core/models/copying.py index 2832ebef39..48a5589bc7 100644 --- a/wagtail/core/models/copying.py +++ b/wagtail/core/models/copying.py @@ -1,3 +1,4 @@ +from django.contrib.contenttypes.fields import GenericRelation from django.db import models from modelcluster.fields import ParentalKey, ParentalManyToManyField from modelcluster.models import ClusterableModel @@ -21,6 +22,10 @@ def _extract_field_data(source, exclude_fields=None): if field.auto_created: continue + # Ignore reverse generic relations + if isinstance(field, GenericRelation): + continue + # Copy parental m2m relations if field.many_to_many: if isinstance(field, ParentalManyToManyField): diff --git a/wagtail/core/tests/test_page_model.py b/wagtail/core/tests/test_page_model.py index b3e3e9a4d9..6b77379814 100644 --- a/wagtail/core/tests/test_page_model.py +++ b/wagtail/core/tests/test_page_model.py @@ -27,8 +27,9 @@ from wagtail.tests.testapp.models import ( BusinessIndex, BusinessNowherePage, BusinessSubIndex, CustomManager, CustomManagerPage, CustomPageQuerySet, EventCategory, EventIndex, EventPage, EventPageSpeaker, GenericSnippetPage, ManyToManyBlogPage, MTIBasePage, MTIChildPage, MyCustomPage, OneToOnePage, - PageWithExcludedCopyField, SimpleChildPage, SimplePage, SimpleParentPage, SingleEventPage, - SingletonPage, StandardIndex, StreamPage, TaggedGrandchildPage, TaggedPage) + PageWithExcludedCopyField, PageWithGenericRelation, RelatedGenericRelation, SimpleChildPage, + SimplePage, SimpleParentPage, SingleEventPage, SingletonPage, StandardIndex, StreamPage, + TaggedGrandchildPage, TaggedPage) from wagtail.tests.utils import WagtailTestUtils @@ -1630,6 +1631,26 @@ class TestCopyPage(TestCase): # special_field is in the list to be excluded self.assertNotEqual(page.special_field, new_page.special_field) + def test_page_with_generic_relation(self): + """Test that a page with a GenericRelation will have that relation ignored when + copying. + """ + homepage = Page.objects.get(url_path="/home/") + original_page = homepage.add_child( + instance=PageWithGenericRelation( + title="PageWithGenericRelation", + slug="page-with-generic-relation", + live=True, + has_unpublished_changes=False, + ) + ) + RelatedGenericRelation.objects.create(content_object=original_page) + self.assertIsNotNone(original_page.generic_relation.first()) + page_copy = original_page.copy( + to=homepage, update_attrs={"slug": f"{original_page.slug}-2"} + ) + self.assertIsNone(page_copy.generic_relation.first()) + def test_copy_page_with_excluded_parental_and_child_relations(self): """Test that a page will be copied with parental and child relations removed if excluded.""" diff --git a/wagtail/tests/testapp/migrations/0061_pagewithgenericrelation_relatedgenericrelation.py b/wagtail/tests/testapp/migrations/0061_pagewithgenericrelation_relatedgenericrelation.py new file mode 100644 index 0000000000..012008959f --- /dev/null +++ b/wagtail/tests/testapp/migrations/0061_pagewithgenericrelation_relatedgenericrelation.py @@ -0,0 +1,34 @@ +# Generated by Django 3.2.12 on 2022-04-10 08:36 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailcore', '0066_collection_management_permissions'), + ('contenttypes', '0002_remove_content_type_name'), + ('tests', '0060_taggedchildpage_taggedgrandchildpage'), + ] + + operations = [ + migrations.CreateModel( + name='PageWithGenericRelation', + fields=[ + ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')), + ], + options={ + 'abstract': False, + }, + bases=('wagtailcore.page',), + ), + migrations.CreateModel( + name='RelatedGenericRelation', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('object_id', models.PositiveIntegerField()), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.contenttype')), + ], + ), + ] diff --git a/wagtail/tests/testapp/models.py b/wagtail/tests/testapp/models.py index 06a82f401a..4741cebc4b 100644 --- a/wagtail/tests/testapp/models.py +++ b/wagtail/tests/testapp/models.py @@ -5,7 +5,7 @@ import uuid from django import forms from django.conf import settings -from django.contrib.contenttypes.fields import GenericForeignKey +from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator @@ -169,6 +169,16 @@ class PageWithExcludedCopyField(Page): ] +class RelatedGenericRelation(models.Model): + content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) + object_id = models.PositiveIntegerField() + content_object = GenericForeignKey("content_type", "object_id") + + +class PageWithGenericRelation(Page): + generic_relation = GenericRelation("tests.RelatedGenericRelation") + + class PageWithOldStyleRouteMethod(Page): """ Prior to Wagtail 0.4, the route() method on Page returned an HttpResponse