diff --git a/wagtail/admin/forms/pages.py b/wagtail/admin/forms/pages.py index 69ca125854..6c1f747ffc 100644 --- a/wagtail/admin/forms/pages.py +++ b/wagtail/admin/forms/pages.py @@ -56,6 +56,13 @@ class CopyForm(forms.Form): required=False, initial=True, label=label, help_text=help_text ) + # Note that only users who can publish in the new parent page can create an alias. + # This is because alias pages must always match their original page's state. + self.fields['alias'] = forms.BooleanField( + required=False, initial=False, label=_("Alias"), + help_text=_("Keep the new pages updated with future changes") + ) + def clean(self): cleaned_data = super().clean() diff --git a/wagtail/admin/templates/wagtailadmin/pages/copy.html b/wagtail/admin/templates/wagtailadmin/pages/copy.html index c6467a24b4..05dbc8d975 100644 --- a/wagtail/admin/templates/wagtailadmin/pages/copy.html +++ b/wagtail/admin/templates/wagtailadmin/pages/copy.html @@ -22,6 +22,10 @@ {% if form.publish_copies %} {% include "wagtailadmin/shared/field_as_li.html" with field=form.publish_copies %} {% endif %} + + {% if form.alias %} + {% include "wagtailadmin/shared/field_as_li.html" with field=form.alias %} + {% endif %} diff --git a/wagtail/admin/tests/pages/test_copy_page.py b/wagtail/admin/tests/pages/test_copy_page.py index 5ace340b7f..2121236777 100644 --- a/wagtail/admin/tests/pages/test_copy_page.py +++ b/wagtail/admin/tests/pages/test_copy_page.py @@ -57,6 +57,7 @@ class TestPageCopy(TestCase, WagtailTestUtils): self.assertContains(response, "New parent page") self.assertContains(response, "Copy subpages") self.assertContains(response, "Publish copies") + self.assertContains(response, "Alias") def test_page_copy_bad_permissions(self): # Remove privileges from user @@ -72,6 +73,7 @@ class TestPageCopy(TestCase, WagtailTestUtils): 'new_slug': 'hello-world', 'new_parent_page': str(self.test_page.id), 'copy_subpages': False, + 'alias': False, } response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) @@ -93,6 +95,7 @@ class TestPageCopy(TestCase, WagtailTestUtils): 'new_slug': 'hello-world', 'new_parent_page': str(self.test_page.id), 'copy_subpages': False, + 'alias': False, } response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) form = response.context['form'] @@ -106,6 +109,7 @@ class TestPageCopy(TestCase, WagtailTestUtils): 'new_parent_page': str(self.root_page.id), 'copy_subpages': False, 'publish_copies': False, + 'alias': False, } response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) @@ -138,6 +142,7 @@ class TestPageCopy(TestCase, WagtailTestUtils): 'new_parent_page': str(self.root_page.id), 'copy_subpages': True, 'publish_copies': False, + 'alias': False, } response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) @@ -182,6 +187,7 @@ class TestPageCopy(TestCase, WagtailTestUtils): 'new_parent_page': str(self.root_page.id), 'copy_subpages': True, 'publish_copies': True, + 'alias': False, } response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) @@ -226,6 +232,7 @@ class TestPageCopy(TestCase, WagtailTestUtils): 'new_parent_page': str(self.test_child_page.id), 'copy_subpages': False, 'publish_copies': False, + 'alias': False, } response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) @@ -247,6 +254,7 @@ class TestPageCopy(TestCase, WagtailTestUtils): 'new_slug': 'hello-world', 'new_parent_page': str(self.root_page.id), 'copy_subpages': False, + 'alias': False, } response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) @@ -268,6 +276,7 @@ class TestPageCopy(TestCase, WagtailTestUtils): 'new_slug': 'hello-world', 'new_parent_page': str(self.test_child_page.id), 'copy_subpages': True, + 'alias': False, } response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id,)), post_data) @@ -288,6 +297,7 @@ class TestPageCopy(TestCase, WagtailTestUtils): 'new_slug': 'hello-world', 'new_parent_page': str(self.test_child_page.id), 'copy_subpages': False, + 'alias': False, } response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) @@ -301,6 +311,7 @@ class TestPageCopy(TestCase, WagtailTestUtils): 'new_slug': 'hello world!', 'new_parent_page': str(self.root_page.id), 'copy_subpages': False, + 'alias': False, } response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) @@ -323,6 +334,7 @@ class TestPageCopy(TestCase, WagtailTestUtils): 'new_slug': 'hello-wɜːld', 'new_parent_page': str(self.test_page.id), 'copy_subpages': False, + 'alias': False, } response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) @@ -371,6 +383,7 @@ class TestPageCopy(TestCase, WagtailTestUtils): 'new_parent_page': str(self.root_page.id), 'copy_subpages': True, 'publish_copies': True, + 'alias': False, } response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) @@ -432,6 +445,7 @@ class TestPageCopy(TestCase, WagtailTestUtils): 'new_parent_page': str(self.root_page.id), 'copy_subpages': False, 'publish_copies': False, + 'alias': False, } response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id,)), post_data) @@ -456,6 +470,7 @@ class TestPageCopy(TestCase, WagtailTestUtils): 'new_parent_page': str(self.root_page.id), 'copy_subpages': False, 'publish_copies': False, + 'alias': False, } response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id,)), post_data) @@ -464,3 +479,85 @@ class TestPageCopy(TestCase, WagtailTestUtils): # page should be copied self.assertTrue(Page.objects.filter(title="Hello world 2").exists()) + + def test_page_copy_alias_post(self): + post_data = { + 'new_title': "Hello world 2", + 'new_slug': 'hello-world-2', + 'new_parent_page': str(self.root_page.id), + 'copy_subpages': False, + 'publish_copies': False, + 'alias': True, + } + response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) + + # Check that the user was redirected to the parents explore page + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + + # Get copy + page_copy = self.root_page.get_children().get(slug='hello-world-2') + + # Check the copy is an alias of the original + self.assertEqual(page_copy.alias_of, self.test_page.page_ptr) + + # Check that the copy is live + # Note: publish_copies is ignored. Alias pages always keep the same state as their original + self.assertTrue(page_copy.live) + self.assertFalse(page_copy.has_unpublished_changes) + + # Check that the owner of the page is set correctly + self.assertEqual(page_copy.owner, self.user) + + # Check that the children were not copied + self.assertEqual(page_copy.get_children().count(), 0) + + # treebeard should report no consistency problems with the tree + self.assertFalse(any(Page.find_problems()), 'treebeard found consistency problems') + + def test_page_copy_alias_post_copy_subpages(self): + post_data = { + 'new_title': "Hello world 2", + 'new_slug': 'hello-world-2', + 'new_parent_page': str(self.root_page.id), + 'copy_subpages': True, + 'publish_copies': False, + 'alias': True, + } + response = self.client.post(reverse('wagtailadmin_pages:copy', args=(self.test_page.id, )), post_data) + + # Check that the user was redirected to the parents explore page + self.assertRedirects(response, reverse('wagtailadmin_explore', args=(self.root_page.id, ))) + + # Get copy + page_copy = self.root_page.get_children().get(slug='hello-world-2') + + # Check the copy is an alias of the original + self.assertEqual(page_copy.alias_of, self.test_page.page_ptr) + + # Check that the copy is live + # Note: publish_copies is ignored. Alias pages always keep the same state as their original + self.assertTrue(page_copy.live) + self.assertFalse(page_copy.has_unpublished_changes) + + # Check that the owner of the page is set correctly + self.assertEqual(page_copy.owner, self.user) + + # Check that the children were copied + self.assertEqual(page_copy.get_children().count(), 2) + + # Check the the child pages + # Neither of them should be live + child_copy = page_copy.get_children().filter(slug='child-page').first() + self.assertNotEqual(child_copy, None) + self.assertEqual(child_copy.alias_of, self.test_child_page.page_ptr) + self.assertTrue(child_copy.live) + self.assertFalse(child_copy.has_unpublished_changes) + + unpublished_child_copy = page_copy.get_children().filter(slug='unpublished-child-page').first() + self.assertNotEqual(unpublished_child_copy, None) + self.assertEqual(unpublished_child_copy.alias_of, self.test_unpublished_child_page.page_ptr) + self.assertFalse(unpublished_child_copy.live) + self.assertTrue(unpublished_child_copy.has_unpublished_changes) + + # treebeard should report no consistency problems with the tree + self.assertFalse(any(Page.find_problems()), 'treebeard found consistency problems') diff --git a/wagtail/admin/views/pages/copy.py b/wagtail/admin/views/pages/copy.py index 5be7950781..13e3a52234 100644 --- a/wagtail/admin/views/pages/copy.py +++ b/wagtail/admin/views/pages/copy.py @@ -51,16 +51,26 @@ def copy(request, page_id): keep_live = can_publish and form.cleaned_data.get('publish_copies') # Copy the page - new_page = page.specific.copy( - recursive=form.cleaned_data.get('copy_subpages'), - to=parent_page, - update_attrs={ - 'title': form.cleaned_data['new_title'], - 'slug': form.cleaned_data['new_slug'], - }, - keep_live=keep_live, - user=request.user, - ) + # Note that only users who can publish in the new parent page can create an alias. + # This is because alias pages must always match their original page's state. + if can_publish and form.cleaned_data['alias']: + new_page = page.specific.create_alias( + recursive=form.cleaned_data.get('copy_subpages'), + parent=parent_page, + update_slug=form.cleaned_data['new_slug'], + user=request.user, + ) + else: + new_page = page.specific.copy( + recursive=form.cleaned_data.get('copy_subpages'), + to=parent_page, + update_attrs={ + 'title': form.cleaned_data['new_title'], + 'slug': form.cleaned_data['new_slug'], + }, + keep_live=keep_live, + user=request.user, + ) # Give a success message back to the user if form.cleaned_data.get('copy_subpages'):