Repository: wagtail/wagtail

Issue:
Release note about performance implications of new private pages in API functionality
Resolve #7923 

I don't know if anyone has ideas for a more performant way to do #7385; it may be the only way to achieve the desired functionality. But we do need to add a note regarding possible performance changes in the 2.15 -> 2.16 upgrade.


Discussion:
<h2>Manage this branch in <a href="https://app.squash.io/accounts/">Squash</a></h2>Test this branch here: <a href='https://cnkpagesapiviewset-performance-47pk5.squash.io' target='_blank'>https://cnkpagesapiviewset-performance-47pk5.squash.io</a><br/>

Resolving patch:
diff --git a/wagtail/api/v2/views.py b/wagtail/api/v2/views.py
--- a/wagtail/api/v2/views.py
+++ b/wagtail/api/v2/views.py
@@ -12,7 +12,7 @@
 from rest_framework.viewsets import GenericViewSet
 
 from wagtail.api import APIField
-from wagtail.core.models import Page, Site
+from wagtail.core.models import Page, PageViewRestriction, Site
 
 from .filters import (
     AncestorOfFilter, ChildOfFilter, DescendantOfFilter, FieldsFilter, LocaleFilter, OrderingFilter,
@@ -443,18 +443,17 @@ def get_base_queryset(self):
         """
         # Get all live pages
         queryset = Page.objects.all().live()
-        # if user is not authenticated return only public pages
-        if not self.request.user.is_authenticated:
-            queryset &= queryset.public()
-        else:  # get pages that are accessible to logged-in users or users in specific groups
-            for page in queryset:
-                restrictions = page.get_view_restrictions().order_by('page__depth')
-                if restrictions:
-                    if restrictions[0].restriction_type == "groups":
-                        if not restrictions[0].groups.filter(id__in=self.request.user.groups.all()).exists():
-                            queryset &= queryset.exclude(id=page.id)
-                    # if restriction_type is login, nothing to do since user is_authenticated
-        # TODO: handle "Private, accessible with the following password" privacy option
+
+        # Exclude pages that the user doesn't have access to
+        restricted_pages = [
+            restriction.page
+            for restriction in PageViewRestriction.objects.all().select_related('page')
+            if not restriction.accept_request(self.request)
+        ]
+
+        # Exclude the restricted pages and their descendants from the queryset
+        for restricted_page in restricted_pages:
+            queryset = queryset.not_descendant_of(restricted_page, inclusive=True)
 
         # Filter by site
         site = Site.find_for_request(self.request)


Test patch:
diff --git a/wagtail/api/v2/tests/test_pages.py b/wagtail/api/v2/tests/test_pages.py
--- a/wagtail/api/v2/tests/test_pages.py
+++ b/wagtail/api/v2/tests/test_pages.py
@@ -12,6 +12,7 @@
 
 from wagtail.api.v2 import signal_handlers
 from wagtail.core.models import Locale, Page, Site
+from wagtail.core.models.view_restrictions import BaseViewRestriction
 from wagtail.tests.demosite import models
 from wagtail.tests.testapp.models import StreamPage
 from wagtail.tests.utils import WagtailTestUtils
@@ -75,7 +76,7 @@ def test_private_pages_dont_appear_in_list(self):
         total_count = get_total_page_count()
 
         page = models.BlogIndexPage.objects.get(id=5)
-        page.view_restrictions.create(password='test')
+        page.view_restrictions.create(restriction_type=BaseViewRestriction.PASSWORD, password='test')
 
         new_total_count = get_total_page_count()
         self.assertNotEqual(total_count, new_total_count)