diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 6f036000f4..1c27122bf6 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -63,8 +63,9 @@ Changelog * Fix: Add missing `lang` attributes to `` elements (James Ray) * Fix: Avoid 503 server error when entering tags over 100chars and instead show a user facing validation error (Vu Pham, Khanh Hoang) * Fix: Ensure `thumb_col_header_text` is correctly used by `ThumbnailMixin` within `ModelAdmin` as the column header label (Kyle J. Roux) - * Fix: page copy in Wagtail admin ignores `exclude_fields_in_copy` (John-Scott Atlakson) + * Fix: Page copy in Wagtail admin ignores `exclude_fields_in_copy` (John-Scott Atlakson) * 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.16.2 (xx.xx.xxxx) - IN DEVELOPMENT diff --git a/docs/releases/3.0.md b/docs/releases/3.0.md index 982ea3d982..0020662a17 100644 --- a/docs/releases/3.0.md +++ b/docs/releases/3.0.md @@ -99,6 +99,7 @@ class LandingPage(Page): * Ensure `thumb_col_header_text` is correctly used by `ThumbnailMixin` within `ModelAdmin` as the column header label (Kyle J. Roux) * Ensure page copy in Wagtail admin doesn't ignore `exclude_fields_in_copy` (John-Scott Atlakson) * Generate new translation keys for translatable `Orderable`s 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/models/copying.py b/wagtail/models/copying.py index ecff7eb431..178c260ba8 100644 --- a/wagtail/models/copying.py +++ b/wagtail/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/test/testapp/migrations/0066_pagewithgenericrelation_relatedgenericrelation.py b/wagtail/test/testapp/migrations/0066_pagewithgenericrelation_relatedgenericrelation.py new file mode 100644 index 0000000000..e63e0c8bc8 --- /dev/null +++ b/wagtail/test/testapp/migrations/0066_pagewithgenericrelation_relatedgenericrelation.py @@ -0,0 +1,58 @@ +# Generated by Django 4.0.2 on 2022-02-28 01:28 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("wagtailcore", "0067_alter_pagerevision_content_json"), + ("contenttypes", "0002_remove_content_type_name"), + ("tests", "0065_alter_extendedformfield_choices_and_more"), + ] + + 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.PositiveBigIntegerField()), + ( + "content_type", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="contenttypes.contenttype", + ), + ), + ], + ), + ] diff --git a/wagtail/test/testapp/models.py b/wagtail/test/testapp/models.py index 05c73a71a7..f7a8d38315 100644 --- a/wagtail/test/testapp/models.py +++ b/wagtail/test/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 @@ -188,6 +188,16 @@ class PageWithExcludedCopyField(Page): ] +class RelatedGenericRelation(models.Model): + content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) + object_id = models.PositiveBigIntegerField() + 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 diff --git a/wagtail/tests/test_page_model.py b/wagtail/tests/test_page_model.py index 576163aee1..69ea79b22a 100644 --- a/wagtail/tests/test_page_model.py +++ b/wagtail/tests/test_page_model.py @@ -51,6 +51,8 @@ from wagtail.test.testapp.models import ( MyCustomPage, OneToOnePage, PageWithExcludedCopyField, + PageWithGenericRelation, + RelatedGenericRelation, SimpleChildPage, SimplePage, SimpleParentPage, @@ -1970,6 +1972,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."""