kopia lustrzana https://github.com/wagtail/wagtail
Fix IntegrityError for Orderable translation key on copy (#8321)
tl;dr - EditView builds the page instance from its latest revision and that is what is passed to the form and ultimately submitted. In order to test the `translation_key`s for orderables, we need to be as close to it as possible, a simple `page.copy()` then `page.save_revision().publish()` doesn't capture the subtleties Co-authored-by: Kalob Taulien <4743971+KalobTaulien@users.noreply.github.com>stable/2.15.x
rodzic
1fdf8d299f
commit
2559d57c13
|
@ -6,6 +6,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)
|
||||
|
||||
|
||||
2.15.4 (11.02.2022)
|
||||
|
|
|
@ -15,6 +15,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)
|
||||
|
||||
|
||||
Upgrade considerations
|
||||
|
|
|
@ -4,7 +4,7 @@ from django.test import TestCase
|
|||
from django.urls import reverse
|
||||
|
||||
from wagtail.core.models import GroupPagePermission, Page
|
||||
from wagtail.tests.testapp.models import SimplePage
|
||||
from wagtail.tests.testapp.models import EventPage, EventPageSpeaker, SimplePage
|
||||
from wagtail.tests.utils import WagtailTestUtils
|
||||
|
||||
|
||||
|
@ -587,3 +587,57 @@ class TestPageCopy(TestCase, WagtailTestUtils):
|
|||
|
||||
# We only need to check that it didn't crash
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def test_copy_page_with_unique_uuids_in_orderables(self):
|
||||
"""
|
||||
Test that a page with orderables can be copied and the translation
|
||||
keys are updated.
|
||||
"""
|
||||
event_page = EventPage(
|
||||
title="Moon Landing",
|
||||
location="the moon",
|
||||
audience="public",
|
||||
cost="free on TV",
|
||||
date_from="1969-07-20",
|
||||
)
|
||||
self.root_page.add_child(instance=event_page)
|
||||
|
||||
event_page.speakers.add(
|
||||
EventPageSpeaker(
|
||||
first_name="Neil",
|
||||
last_name="Armstrong",
|
||||
)
|
||||
)
|
||||
# ensure there's a revision (which should capture the new speaker orderables)
|
||||
# before copying the page
|
||||
event_page.save_revision().publish()
|
||||
|
||||
post_data = {
|
||||
"new_title": "New Moon landing",
|
||||
"new_slug": "moon-landing-redux",
|
||||
"new_parent_page": str(self.root_page.id),
|
||||
"copy_subpages": False,
|
||||
"publish_copies": False,
|
||||
"alias": False,
|
||||
}
|
||||
self.client.post(
|
||||
reverse("wagtailadmin_pages:copy", args=[event_page.id]), post_data
|
||||
)
|
||||
new_page = EventPage.objects.last()
|
||||
|
||||
# Hack: get the page instance from the edit form which assembles it from the
|
||||
# latest revision. While we could do new_page.get_latest_revision().as_page_object()
|
||||
# this follow the edit view closer and should it change the test is less
|
||||
# prone to continue working because we're skipping some step
|
||||
response = self.client.get(
|
||||
reverse("wagtailadmin_pages:edit", args=[new_page.id])
|
||||
)
|
||||
new_page_on_edit_form = response.context["form"].instance
|
||||
|
||||
# publish the page, similar to EditView.publish_action()
|
||||
new_page_on_edit_form.save_revision().publish()
|
||||
|
||||
self.assertNotEqual(
|
||||
event_page.speakers.first().translation_key,
|
||||
new_page.speakers.first().translation_key,
|
||||
)
|
||||
|
|
|
@ -26,6 +26,7 @@ from django.core.cache import cache
|
|||
from django.core.exceptions import PermissionDenied, ValidationError
|
||||
from django.core.handlers.base import BaseHandler
|
||||
from django.core.handlers.wsgi import WSGIRequest
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.db import models, transaction
|
||||
from django.db.models import DEFERRED, Q, Value
|
||||
from django.db.models.expressions import OuterRef, Subquery
|
||||
|
@ -1584,6 +1585,17 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
|
|||
|
||||
page_copy, child_object_map = _copy(specific_self, exclude_fields=exclude_fields, update_attrs=base_update_attrs)
|
||||
|
||||
uuid_mapping = {}
|
||||
|
||||
def generate_translation_key(uuid_mapping, old_uuid):
|
||||
"""
|
||||
Generates a new UUID based on an old one, then re-uses it
|
||||
"""
|
||||
if old_uuid not in uuid_mapping:
|
||||
uuid_mapping[old_uuid] = uuid.uuid4()
|
||||
|
||||
return uuid_mapping[old_uuid]
|
||||
|
||||
# Save copied child objects and run process_child_object on them if we need to
|
||||
for (child_relation, old_pk), child_object in child_object_map.items():
|
||||
if process_child_object:
|
||||
|
@ -1591,7 +1603,7 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
|
|||
|
||||
# When we're not copying for translation, we should give the translation_key a new value for each child object as well
|
||||
if reset_translation_key and isinstance(child_object, TranslatableMixin):
|
||||
child_object.translation_key = uuid.uuid4()
|
||||
child_object.translation_key = generate_translation_key(uuid_mapping, child_object.translation_key)
|
||||
|
||||
# Save the new page
|
||||
if _mpnode_attrs:
|
||||
|
@ -1642,7 +1654,12 @@ class Page(AbstractPage, index.Indexed, ClusterableModel, metaclass=PageBase):
|
|||
copied_child_object = child_object_map.get((child_relation, child_object['pk']))
|
||||
child_object['pk'] = copied_child_object.pk if copied_child_object else None
|
||||
|
||||
revision.content_json = json.dumps(revision_content)
|
||||
if reset_translation_key and "translation_key" in child_object:
|
||||
child_object["translation_key"] = generate_translation_key(
|
||||
uuid_mapping, child_object["translation_key"]
|
||||
)
|
||||
|
||||
revision.content_json = json.dumps(revision_content, cls=DjangoJSONEncoder)
|
||||
|
||||
# Save
|
||||
revision.save()
|
||||
|
|
Ładowanie…
Reference in New Issue