diff --git a/wagtail/wagtailadmin/views/pages.py b/wagtail/wagtailadmin/views/pages.py index a30548b15a..6390eeeb24 100644 --- a/wagtail/wagtailadmin/views/pages.py +++ b/wagtail/wagtailadmin/views/pages.py @@ -665,11 +665,9 @@ def copy(request, page_id): 'slug': form.cleaned_data['new_slug'], }, keep_live=(can_publish and form.cleaned_data.get('publish_copies')), + user=request.user, ) - # Assign user of this request as the owner of all the new pages - new_page.get_descendants(inclusive=True).update(owner=request.user) - # Give a success message back to the user if form.cleaned_data.get('copy_subpages'): messages.success(request, _("Page '{0}' and {1} subpages copied.").format(page.title, new_page.get_descendants().count())) diff --git a/wagtail/wagtailcore/models.py b/wagtail/wagtailcore/models.py index 47b9305c46..50a3269242 100644 --- a/wagtail/wagtailcore/models.py +++ b/wagtail/wagtailcore/models.py @@ -1,5 +1,6 @@ import logging import warnings +import json import six from six import StringIO @@ -729,7 +730,7 @@ class Page(six.with_metaclass(PageBase, MP_Node, ClusterableModel, index.Indexed # Log logger.info("Page moved: \"%s\" id=%d path=%s", self.title, self.id, new_url_path) - def copy(self, recursive=False, to=None, update_attrs=None, copy_revisions=True, keep_live=True): + def copy(self, recursive=False, to=None, update_attrs=None, copy_revisions=True, keep_live=True, user=None): # Make a copy page_copy = Page.objects.get(id=self.id).specific page_copy.pk = None @@ -742,6 +743,9 @@ class Page(six.with_metaclass(PageBase, MP_Node, ClusterableModel, index.Indexed page_copy.live = False page_copy.has_unpublished_changes = True + if user: + page_copy.owner = user + if update_attrs: for field, value in update_attrs.items(): setattr(page_copy, field, value) @@ -770,15 +774,47 @@ class Page(six.with_metaclass(PageBase, MP_Node, ClusterableModel, index.Indexed revision.submitted_for_moderation = False revision.approved_go_live_at = None revision.page = page_copy + + # Update ID fields in content + revision_content = json.loads(revision.content_json) + revision_content['pk'] = page_copy.pk + + for child_relation in get_all_child_relations(specific_self): + try: + child_objects = revision_content[child_relation.get_accessor_name()] + except KeyError: + # KeyErrors are possible if the revision was created + # before this child relation was added to the database + continue + + for child_object in child_objects: + child_object[child_relation.field.name] = page_copy.pk + + revision.content_json = json.dumps(revision_content) + + # Save revision.save() + # Create a new revision + # This code serves a few purposes: + # * It makes sure update_attrs gets applied to the latest revision so the changes are reflected in the editor + # * It bumps the last_revision_created_at value so the new page gets ordered as if it was just created + # * It sets the user of the new revision so it's possible to see who copied the page by looking at its history + latest_revision = page_copy.get_latest_revision_as_page() + + if update_attrs: + for field, value in update_attrs.items(): + setattr(latest_revision, field, value) + + latest_revision.save_revision(user=user) + # Log logger.info("Page copied: \"%s\" id=%d from=%d", page_copy.title, page_copy.id, self.id) # Copy child pages if recursive: for child_page in self.get_children(): - child_page.specific.copy(recursive=True, to=page_copy, copy_revisions=copy_revisions, keep_live=keep_live) + child_page.specific.copy(recursive=True, to=page_copy, copy_revisions=copy_revisions, keep_live=keep_live, user=user) return page_copy diff --git a/wagtail/wagtailcore/tests/test_page_model.py b/wagtail/wagtailcore/tests/test_page_model.py index 8e049129b4..be549f502e 100644 --- a/wagtail/wagtailcore/tests/test_page_model.py +++ b/wagtail/wagtailcore/tests/test_page_model.py @@ -1,5 +1,6 @@ import warnings import datetime +import json import pytz @@ -7,6 +8,7 @@ from django.test import TestCase, Client from django.test.utils import override_settings from django.http import HttpRequest, Http404 from django.contrib.contenttypes.models import ContentType +from django.contrib.auth import get_user_model from wagtail.wagtailcore.models import Page, Site from wagtail.tests.models import EventPage, EventIndex, SimplePage, PageWithOldStyleRouteMethod, BusinessIndex, BusinessSubIndex, BusinessChild, StandardIndex @@ -425,11 +427,23 @@ class TestCopyPage(TestCase): new_christmas_event = christmas_event.copy(update_attrs={'title': "New christmas event", 'slug': 'new-christmas-event'}) # Check that the revisions were copied - self.assertEqual(new_christmas_event.revisions.count(), 1, "Revisions weren't copied") + # Copying creates a new revision so we're expecting the new page to have two revisions + self.assertEqual(new_christmas_event.revisions.count(), 2) # Check that the revisions weren't removed from old page self.assertEqual(christmas_event.revisions.count(), 1, "Revisions were removed from the original page") + # Check that the attributes were updated in the latest revision + latest_revision = new_christmas_event.get_latest_revision_as_page() + self.assertEqual(latest_revision.title, "New christmas event") + self.assertEqual(latest_revision.slug, 'new-christmas-event') + + # Check that the ids within the revision were updated correctly + new_revision = new_christmas_event.revisions.first() + new_revision_content = json.loads(new_revision.content_json) + self.assertEqual(new_revision_content['pk'], new_christmas_event.id) + self.assertEqual(new_revision_content['speakers'][0]['page'], new_christmas_event.id) + def test_copy_page_copies_revisions_and_doesnt_submit_for_moderation(self): christmas_event = EventPage.objects.get(url_path='/home/events/christmas/') christmas_event.save_revision(submitted_for_moderation=True) @@ -438,10 +452,10 @@ class TestCopyPage(TestCase): new_christmas_event = christmas_event.copy(update_attrs={'title': "New christmas event", 'slug': 'new-christmas-event'}) # Check that the old revision is still submitted for moderation - self.assertTrue(christmas_event.revisions.first().submitted_for_moderation) + self.assertTrue(christmas_event.revisions.order_by('created_at').first().submitted_for_moderation) # Check that the new revision is not submitted for moderation - self.assertFalse(new_christmas_event.revisions.first().submitted_for_moderation) + self.assertFalse(new_christmas_event.revisions.order_by('created_at').first().submitted_for_moderation) def test_copy_page_copies_revisions_and_doesnt_change_created_at(self): christmas_event = EventPage.objects.get(url_path='/home/events/christmas/') @@ -456,8 +470,8 @@ class TestCopyPage(TestCase): new_christmas_event = christmas_event.copy(update_attrs={'title': "New christmas event", 'slug': 'new-christmas-event'}) # Check that the created_at time is the same - christmas_event_created_at = christmas_event.get_latest_revision().created_at - new_christmas_event_created_at = new_christmas_event.get_latest_revision().created_at + christmas_event_created_at = christmas_event.revisions.order_by('created_at').first().created_at + new_christmas_event_created_at = new_christmas_event.revisions.order_by('created_at').first().created_at self.assertEqual(christmas_event_created_at, new_christmas_event_created_at) def test_copy_page_copies_revisions_and_doesnt_schedule(self): @@ -468,10 +482,10 @@ class TestCopyPage(TestCase): new_christmas_event = christmas_event.copy(update_attrs={'title': "New christmas event", 'slug': 'new-christmas-event'}) # Check that the old revision is still scheduled - self.assertEqual(christmas_event.revisions.first().approved_go_live_at, datetime.datetime(2014, 9, 16, 9, 12, 00, tzinfo=pytz.utc)) + self.assertEqual(christmas_event.revisions.order_by('created_at').first().approved_go_live_at, datetime.datetime(2014, 9, 16, 9, 12, 00, tzinfo=pytz.utc)) # Check that the new revision is not scheduled - self.assertEqual(new_christmas_event.revisions.first().approved_go_live_at, None) + self.assertEqual(new_christmas_event.revisions.order_by('created_at').first().approved_go_live_at, None) def test_copy_page_doesnt_copy_revisions_if_told_not_to_do_so(self): christmas_event = EventPage.objects.get(url_path='/home/events/christmas/') @@ -481,7 +495,8 @@ class TestCopyPage(TestCase): new_christmas_event = christmas_event.copy(update_attrs={'title': "New christmas event", 'slug': 'new-christmas-event'}, copy_revisions=False) # Check that the revisions weren't copied - self.assertEqual(new_christmas_event.revisions.count(), 0, "Revisions were copied") + # Copying creates a new revision so we're expecting the new page to have one revision + self.assertEqual(new_christmas_event.revisions.count(), 1) # Check that the revisions weren't removed from old page self.assertEqual(christmas_event.revisions.count(), 1, "Revisions were removed from the original page") @@ -544,7 +559,8 @@ class TestCopyPage(TestCase): new_christmas_event = new_events_index.get_children().filter(slug='christmas').first() # Check that the revisions were copied - self.assertEqual(new_christmas_event.specific.revisions.count(), 1, "Revisions weren't copied") + # Copying creates a new revision so we're expecting the new page to have two revisions + self.assertEqual(new_christmas_event.specific.revisions.count(), 2) # Check that the revisions weren't removed from old page self.assertEqual(old_christmas_event.specific.revisions.count(), 1, "Revisions were removed from the original page") @@ -561,11 +577,29 @@ class TestCopyPage(TestCase): new_christmas_event = new_events_index.get_children().filter(slug='christmas').first() # Check that the revisions weren't copied - self.assertEqual(new_christmas_event.specific.revisions.count(), 0, "Revisions were copied") + # Copying creates a new revision so we're expecting the new page to have one revision + self.assertEqual(new_christmas_event.specific.revisions.count(), 1) # Check that the revisions weren't removed from old page self.assertEqual(old_christmas_event.specific.revisions.count(), 1, "Revisions were removed from the original page") + def test_copy_page_updates_user(self): + event_moderator = get_user_model().objects.get(username='eventmoderator') + christmas_event = EventPage.objects.get(url_path='/home/events/christmas/') + christmas_event.save_revision() + + # Copy it + new_christmas_event = christmas_event.copy( + update_attrs={'title': "New christmas event", 'slug': 'new-christmas-event'}, + user=event_moderator, + ) + + # Check that the owner has been updated + self.assertEqual(new_christmas_event.owner, event_moderator) + + # Check that the user on the last revision is correct + self.assertEqual(new_christmas_event.get_latest_revision().user, event_moderator) + class TestSubpageTypeBusinessRules(TestCase): def test_allowed_subpage_types(self):