diff --git a/wagtail/admin/tests/pages/test_reorder_page.py b/wagtail/admin/tests/pages/test_reorder_page.py index 65656f6ebd..3f15b45ca4 100644 --- a/wagtail/admin/tests/pages/test_reorder_page.py +++ b/wagtail/admin/tests/pages/test_reorder_page.py @@ -2,7 +2,12 @@ from django.test import TestCase from django.urls import reverse from wagtail.models import Page -from wagtail.test.testapp.models import BusinessChild, BusinessIndex, SimplePage +from wagtail.test.testapp.models import ( + BusinessChild, + BusinessIndex, + NoCreatableSubpageTypesPage, + SimplePage, +) from wagtail.test.utils import WagtailTestUtils @@ -140,3 +145,45 @@ class TestPageReorderWithParentPageRestrictions(TestPageReorder): # Login self.user = self.login() + + +class TestPageReorderWithNoCreatableSubPageTypes(TestPageReorder): + """ + This TestCase is the same as the TestPageReorder class above, but uses a + NoCreatableSubpageTypesPage instance as the parent page instead of SimplePage. + + This ensures that a parent page having no 'creatable' page types in + its `subpage_types` list does not prevent the reorder pre-existing or + automatically generated pages beneath it. + """ + + def setUp(self): + child_content = "
This is content
" + + # Find root page + self.root_page = Page.objects.get(id=2) + + # root + # |- parent_page (NoCreatableSubpageTypesPage) + # | |- child_1 (SimplePage) + # | |- child_2 (SimplePage) + # | |- child_3 (SimplePage) + + self.index_page = NoCreatableSubpageTypesPage(title="Index", slug="index") + self.root_page.add_child(instance=self.index_page) + + self.child_1 = SimplePage( + title="Child 1", slug="child-1", content=child_content + ) + self.index_page.add_child(instance=self.child_1) + self.child_2 = SimplePage( + title="Child 2", slug="child-2", content=child_content + ) + self.index_page.add_child(instance=self.child_2) + self.child_3 = SimplePage( + title="Child 3", slug="child-3", content=child_content + ) + self.index_page.add_child(instance=self.child_3) + + # Login + self.user = self.login() diff --git a/wagtail/models/__init__.py b/wagtail/models/__init__.py index 831fd467f3..087b39ce56 100644 --- a/wagtail/models/__init__.py +++ b/wagtail/models/__init__.py @@ -3228,10 +3228,13 @@ class PagePermissionTester: def can_reorder_children(self): """ - Keep reorder permissions the same as publishing, since it immediately affects published pages - (and the use-cases for a non-admin needing to do it are fairly obscure...) + Reorder permission checking is similar to publishing a subpage, since it immediately + affects published pages. However, it shouldn't care about the 'creatability' of + page types, because the action only ever updates existing pages. """ - return self.can_publish_subpage() + if not self.user.is_active: + return False + return self.user.is_superuser or ("publish" in self.permissions) def can_move(self): """ diff --git a/wagtail/test/testapp/migrations/0040_nocreatablesubpagetypespage_nosubpagetypespage.py b/wagtail/test/testapp/migrations/0040_nocreatablesubpagetypespage_nosubpagetypespage.py new file mode 100644 index 0000000000..c2486aa961 --- /dev/null +++ b/wagtail/test/testapp/migrations/0040_nocreatablesubpagetypespage_nosubpagetypespage.py @@ -0,0 +1,54 @@ +# Generated by Django 4.2.11 on 2024-05-10 12:41 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + dependencies = [ + ("wagtailcore", "0093_uploadedfile"), + ("tests", "0039_alter_eventcategory_locale_and_more"), + ] + + operations = [ + migrations.CreateModel( + name="NoCreatableSubpageTypesPage", + 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="NoSubpageTypesPage", + 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",), + ), + ] diff --git a/wagtail/test/testapp/models.py b/wagtail/test/testapp/models.py index 0d8811169d..5fa463fc86 100644 --- a/wagtail/test/testapp/models.py +++ b/wagtail/test/testapp/models.py @@ -1668,6 +1668,14 @@ class MTIChildPage(MTIBasePage): pass +class NoCreatableSubpageTypesPage(Page): + subpage_types = [MTIBasePage] + + +class NoSubpageTypesPage(Page): + subpage_types = [] + + class AbstractPage(Page): class Meta: abstract = True diff --git a/wagtail/tests/test_page_permissions.py b/wagtail/tests/test_page_permissions.py index 33d15045ce..df4a3e74b5 100644 --- a/wagtail/tests/test_page_permissions.py +++ b/wagtail/tests/test_page_permissions.py @@ -20,6 +20,8 @@ from wagtail.test.testapp.models import ( CustomPermissionTester, EventIndex, EventPage, + NoCreatableSubpageTypesPage, + NoSubpageTypesPage, SingletonPageViaMaxCount, ) @@ -39,6 +41,19 @@ class TestPagePermission(TestCase): def test_nonpublisher_page_permissions(self): event_editor = get_user_model().objects.get(email="eventeditor@example.com") homepage = Page.objects.get(url_path="/home/") + + # As this is a direct child of the homepage, permission checks should + # mostly mirror those for the homepage itself + no_creatable_subpages_page = NoCreatableSubpageTypesPage( + title="No creatable subpages", slug="no-creatable-subpages" + ) + homepage.add_child(instance=no_creatable_subpages_page) + + # As this is a direct child of the homepage, permission checks should + # mostly mirror those for the homepage itself + no_subpages_page = NoSubpageTypesPage(title="No subpages", slug="no-subpages") + homepage.add_child(instance=no_subpages_page) + christmas_page = EventPage.objects.get(url_path="/home/events/christmas/") unpublished_event_page = EventPage.objects.get( url_path="/home/events/tentative-unpublished-event/" @@ -51,6 +66,10 @@ class TestPagePermission(TestCase): ) homepage_perms = homepage.permissions_for_user(event_editor) + no_creatable_subpages_perms = no_creatable_subpages_page.permissions_for_user( + event_editor + ) + no_subpages_perms = no_subpages_page.permissions_for_user(event_editor) christmas_page_perms = christmas_page.permissions_for_user(event_editor) unpub_perms = unpublished_event_page.permissions_for_user(event_editor) someone_elses_event_perms = someone_elses_event_page.permissions_for_user( @@ -59,17 +78,23 @@ class TestPagePermission(TestCase): board_meetings_perms = board_meetings_page.permissions_for_user(event_editor) self.assertFalse(homepage_perms.can_add_subpage()) + self.assertFalse(no_creatable_subpages_perms.can_add_subpage()) + self.assertFalse(no_subpages_perms.can_add_subpage()) self.assertTrue(christmas_page_perms.can_add_subpage()) self.assertTrue(unpub_perms.can_add_subpage()) self.assertTrue(someone_elses_event_perms.can_add_subpage()) self.assertFalse(homepage_perms.can_edit()) + self.assertFalse(no_creatable_subpages_perms.can_edit()) + self.assertFalse(no_subpages_perms.can_edit()) self.assertTrue(christmas_page_perms.can_edit()) self.assertTrue(unpub_perms.can_edit()) # basic 'add' permission doesn't allow editing pages owned by someone else self.assertFalse(someone_elses_event_perms.can_edit()) self.assertFalse(homepage_perms.can_delete()) + self.assertFalse(no_creatable_subpages_perms.can_delete()) + self.assertFalse(no_subpages_perms.can_delete()) self.assertFalse( christmas_page_perms.can_delete() ) # cannot delete because it is published @@ -77,22 +102,32 @@ class TestPagePermission(TestCase): self.assertFalse(someone_elses_event_perms.can_delete()) self.assertFalse(homepage_perms.can_publish()) + self.assertFalse(no_creatable_subpages_perms.can_publish()) + self.assertFalse(no_subpages_perms.can_publish()) self.assertFalse(christmas_page_perms.can_publish()) self.assertFalse(unpub_perms.can_publish()) self.assertFalse(homepage_perms.can_unpublish()) + self.assertFalse(no_creatable_subpages_perms.can_unpublish()) + self.assertFalse(no_subpages_perms.can_unpublish()) self.assertFalse(christmas_page_perms.can_unpublish()) self.assertFalse(unpub_perms.can_unpublish()) self.assertFalse(homepage_perms.can_publish_subpage()) + self.assertFalse(no_creatable_subpages_perms.can_publish_subpage()) + self.assertFalse(no_subpages_perms.can_publish_subpage()) self.assertFalse(christmas_page_perms.can_publish_subpage()) self.assertFalse(unpub_perms.can_publish_subpage()) self.assertFalse(homepage_perms.can_reorder_children()) + self.assertFalse(no_creatable_subpages_perms.can_reorder_children()) + self.assertFalse(no_subpages_perms.can_reorder_children()) self.assertFalse(christmas_page_perms.can_reorder_children()) self.assertFalse(unpub_perms.can_reorder_children()) self.assertFalse(homepage_perms.can_move()) + self.assertFalse(no_creatable_subpages_perms.can_move()) + self.assertFalse(no_subpages_perms.can_move()) # cannot move because this would involve unpublishing from its current location self.assertFalse(christmas_page_perms.can_move()) self.assertTrue(unpub_perms.can_move()) @@ -119,6 +154,19 @@ class TestPagePermission(TestCase): email="eventmoderator@example.com" ) homepage = Page.objects.get(url_path="/home/") + + # As this is a direct child of the homepage, permission checks should + # mostly mirror those for the homepage itself + no_creatable_subpages_page = NoCreatableSubpageTypesPage( + title="No creatable subpages", slug="no-creatable-subpages" + ) + homepage.add_child(instance=no_creatable_subpages_page) + + # As this is a direct child of the homepage, permission checks should + # mostly mirror those for the homepage itself + no_subpages_page = NoSubpageTypesPage(title="No subpages", slug="no-subpages") + homepage.add_child(instance=no_subpages_page) + christmas_page = EventPage.objects.get(url_path="/home/events/christmas/") unpublished_event_page = EventPage.objects.get( url_path="/home/events/tentative-unpublished-event/" @@ -128,42 +176,61 @@ class TestPagePermission(TestCase): ) homepage_perms = homepage.permissions_for_user(event_moderator) + no_creatable_subpages_perms = no_creatable_subpages_page.permissions_for_user( + event_moderator + ) + no_subpages_perms = no_subpages_page.permissions_for_user(event_moderator) christmas_page_perms = christmas_page.permissions_for_user(event_moderator) unpub_perms = unpublished_event_page.permissions_for_user(event_moderator) board_meetings_perms = board_meetings_page.permissions_for_user(event_moderator) self.assertFalse(homepage_perms.can_add_subpage()) + self.assertFalse(no_creatable_subpages_perms.can_add_subpage()) + self.assertFalse(no_subpages_perms.can_add_subpage()) self.assertTrue(christmas_page_perms.can_add_subpage()) self.assertTrue(unpub_perms.can_add_subpage()) self.assertFalse(homepage_perms.can_edit()) + self.assertFalse(no_creatable_subpages_perms.can_edit()) + self.assertFalse(no_subpages_perms.can_edit()) self.assertTrue(christmas_page_perms.can_edit()) self.assertTrue(unpub_perms.can_edit()) self.assertFalse(homepage_perms.can_delete()) + self.assertFalse(no_creatable_subpages_perms.can_delete()) + self.assertFalse(no_subpages_perms.can_delete()) # can delete a published page because we have publish permission self.assertTrue(christmas_page_perms.can_delete()) self.assertTrue(unpub_perms.can_delete()) self.assertFalse(homepage_perms.can_publish()) + self.assertFalse(no_creatable_subpages_perms.can_publish()) + self.assertFalse(no_subpages_perms.can_publish()) self.assertTrue(christmas_page_perms.can_publish()) self.assertTrue(unpub_perms.can_publish()) self.assertFalse(homepage_perms.can_unpublish()) + self.assertFalse(no_creatable_subpages_perms.can_unpublish()) self.assertTrue(christmas_page_perms.can_unpublish()) self.assertFalse( unpub_perms.can_unpublish() ) # cannot unpublish a page that isn't published self.assertFalse(homepage_perms.can_publish_subpage()) + self.assertFalse(no_creatable_subpages_perms.can_publish_subpage()) + self.assertFalse(no_subpages_perms.can_publish_subpage()) self.assertTrue(christmas_page_perms.can_publish_subpage()) self.assertTrue(unpub_perms.can_publish_subpage()) self.assertFalse(homepage_perms.can_reorder_children()) + self.assertFalse(no_creatable_subpages_perms.can_reorder_children()) + self.assertFalse(no_subpages_perms.can_reorder_children()) self.assertTrue(christmas_page_perms.can_reorder_children()) self.assertTrue(unpub_perms.can_reorder_children()) self.assertFalse(homepage_perms.can_move()) + self.assertFalse(no_creatable_subpages_perms.can_move()) + self.assertFalse(no_subpages_perms.can_move()) self.assertTrue(christmas_page_perms.can_move()) self.assertTrue(unpub_perms.can_move()) @@ -325,6 +392,19 @@ class TestPagePermission(TestCase): def test_superuser_has_full_permissions(self): user = get_user_model().objects.get(email="superuser@example.com") homepage = Page.objects.get(url_path="/home/").specific + + # As this is a direct child of the homepage, permission checks should + # mostly mirror those for the homepage itself + no_creatable_subpages_page = NoCreatableSubpageTypesPage( + title="No creatable subpages", slug="no-creatable-subpages" + ) + homepage.add_child(instance=no_creatable_subpages_page) + + # As this is a direct child of the homepage, permission checks should + # mostly mirror those for the homepage itself + no_subpages_page = NoSubpageTypesPage(title="No subpages", slug="no-subpages") + homepage.add_child(instance=no_subpages_page) + root = Page.objects.get(url_path="/").specific unpublished_event_page = EventPage.objects.get( url_path="/home/events/tentative-unpublished-event/" @@ -334,35 +414,64 @@ class TestPagePermission(TestCase): ) homepage_perms = homepage.permissions_for_user(user) + no_creatable_subpages_perms = no_creatable_subpages_page.permissions_for_user( + user + ) + no_subpages_perms = no_subpages_page.permissions_for_user(user) root_perms = root.permissions_for_user(user) unpub_perms = unpublished_event_page.permissions_for_user(user) board_meetings_perms = board_meetings_page.permissions_for_user(user) self.assertTrue(homepage_perms.can_add_subpage()) + self.assertFalse( + no_creatable_subpages_perms.can_add_subpage() + ) # There are no 'creatable' subpage types + self.assertFalse( + no_subpages_perms.can_add_subpage() + ) # There are no subpage types self.assertTrue(root_perms.can_add_subpage()) self.assertTrue(homepage_perms.can_edit()) + self.assertTrue(no_creatable_subpages_perms.can_edit()) + self.assertTrue(no_subpages_perms.can_edit()) self.assertFalse( root_perms.can_edit() ) # root is not a real editable page, even to superusers self.assertTrue(homepage_perms.can_delete()) + self.assertTrue(no_creatable_subpages_perms.can_delete()) + self.assertTrue(no_subpages_perms.can_delete()) self.assertFalse(root_perms.can_delete()) self.assertTrue(homepage_perms.can_publish()) + self.assertTrue(no_creatable_subpages_perms.can_publish()) + self.assertTrue(no_subpages_perms.can_publish()) self.assertFalse(root_perms.can_publish()) self.assertTrue(homepage_perms.can_unpublish()) + self.assertTrue(no_creatable_subpages_perms.can_unpublish()) + self.assertTrue(no_subpages_perms.can_unpublish()) self.assertFalse(root_perms.can_unpublish()) self.assertFalse(unpub_perms.can_unpublish()) self.assertTrue(homepage_perms.can_publish_subpage()) + self.assertFalse( + no_creatable_subpages_perms.can_publish_subpage() + ) # There are no 'creatable' subpages, so a new one cannot be 'created and published' here + self.assertFalse( + no_subpages_perms.can_publish_subpage() + ) # There are no subpages, so a new one cannot be 'created and published' here + self.assertTrue(root_perms.can_publish_subpage()) self.assertTrue(homepage_perms.can_reorder_children()) + self.assertTrue(no_creatable_subpages_perms.can_reorder_children()) + self.assertTrue(no_subpages_perms.can_reorder_children()) self.assertTrue(root_perms.can_reorder_children()) self.assertTrue(homepage_perms.can_move()) + self.assertTrue(no_creatable_subpages_perms.can_move()) + self.assertTrue(no_subpages_perms.can_move()) self.assertFalse(root_perms.can_move()) self.assertTrue(homepage_perms.can_move_to(root)) diff --git a/wagtail/tests/test_tests.py b/wagtail/tests/test_tests.py index 81faa32138..530ccf84cf 100644 --- a/wagtail/tests/test_tests.py +++ b/wagtail/tests/test_tests.py @@ -13,6 +13,8 @@ from wagtail.test.testapp.models import ( BusinessSubIndex, EventIndex, EventPage, + NoCreatableSubpageTypesPage, + NoSubpageTypesPage, SectionedRichTextPage, SimpleChildPage, SimplePage, @@ -287,6 +289,8 @@ class TestWagtailPageTests(WagtailPageTests): BusinessSubIndex, BusinessChild, BusinessIndex, + NoCreatableSubpageTypesPage, + NoSubpageTypesPage, SimpleParentPage, } self.assertAllowedParentPageTypes(BusinessIndex, all_but_business)