diff --git a/wagtail/admin/tests/pages/test_content_type_use_view.py b/wagtail/admin/tests/pages/test_content_type_use_view.py index d9e00cb619..d9ed598bfa 100644 --- a/wagtail/admin/tests/pages/test_content_type_use_view.py +++ b/wagtail/admin/tests/pages/test_content_type_use_view.py @@ -79,7 +79,7 @@ class TestContentTypeUse(WagtailTestUtils, TestCase): self.assertContains(response, delete_url) self.assertContains(response, "data-bulk-action-select-all-checkbox") - with self.assertNumQueries(38): + with self.assertNumQueries(33): self.client.get(request_url) def test_content_type_use_results(self): diff --git a/wagtail/admin/ui/tables/pages.py b/wagtail/admin/ui/tables/pages.py index dde536bcb4..59b00e0db7 100644 --- a/wagtail/admin/ui/tables/pages.py +++ b/wagtail/admin/ui/tables/pages.py @@ -56,6 +56,8 @@ class ParentPageColumn(Column): cell_template_name = "wagtailadmin/pages/listing/_parent_page_cell.html" def get_value(self, instance): + if parent := getattr(instance, "_parent_page", None): + return parent return instance.get_parent() diff --git a/wagtail/admin/views/pages/listing.py b/wagtail/admin/views/pages/listing.py index fb55b4e274..ca3980ee65 100644 --- a/wagtail/admin/views/pages/listing.py +++ b/wagtail/admin/views/pages/listing.py @@ -265,6 +265,14 @@ class PageListingMixin: kwargs["actions_next_url"] = self.index_url return kwargs + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + if any(isinstance(column, ParentPageColumn) for column in self.columns): + Page.objects.annotate_parent_page(context["object_list"]) + + return context + class IndexView(PageListingMixin, generic.IndexView): template_name = "wagtailadmin/pages/index.html" @@ -439,19 +447,12 @@ class ExplorableIndexView(IndexView): context = super().get_context_data(**kwargs) if self.is_searching: - # postprocess this page of results to annotate each result with its parent page - parent_page_paths = { - page.path[: -page.steplen] for page in context["object_list"] - } - parent_pages_by_path = { - page.path: page - for page in Page.objects.filter(path__in=parent_page_paths).specific() - } + Page.objects.annotate_parent_page(context["object_list"]) for page in context["object_list"]: - parent_page = parent_pages_by_path.get(page.path[: -page.steplen]) # add annotation if parent page is found and is not the currently viewed parent - if parent_page and parent_page != self.parent_page: - page.annotated_parent_page = parent_page + # to be used by PageTitleColumn instead of a dedicated ParentPageColumn + if page._parent_page and page._parent_page != self.parent_page: + page.annotated_parent_page = page._parent_page context.update( { diff --git a/wagtail/models/__init__.py b/wagtail/models/__init__.py index 7d451d58ce..940a276627 100644 --- a/wagtail/models/__init__.py +++ b/wagtail/models/__init__.py @@ -235,6 +235,36 @@ class BasePageManager(models.Manager): return self.get(path=common_parent_path) + def annotate_parent_page(self, pages): + """ + Annotates each page with its parent page. This is implemented as a + manager-only method instead of a QuerySet method so it can be used with + search results. + + If given a QuerySet, this method will evaluate it. Only use this method + when you are ready to consume the queryset, e.g. after pagination has + been applied. This is typically done in the view's `get_context_data` + using `context["object_list"]`. + + This method does not return a new queryset, but modifies the existing one, + to ensure any references to the queryset in the view's context are updated + (e.g. when using `context_object_name`). + """ + parent_page_paths = { + Page._get_parent_path_from_path(page.path) for page in pages + } + parent_pages_by_path = { + page.path: page + for page in Page.objects.filter(path__in=parent_page_paths).specific( + defer=True + ) + } + for page in pages: + parent_page = parent_pages_by_path.get( + Page._get_parent_path_from_path(page.path) + ) + page._parent_page = parent_page + PageManager = BasePageManager.from_queryset(PageQuerySet)