kopia lustrzana https://github.com/wagtail/wagtail
Filter pop-out explorer pages using 'construct_explorer_page_queryset'
The pages listed when browsing the /admin/pages/ explorer differed from the pages listed in the new React/admin API powered pop-out explorer. The latter did not pass the queryset through the 'construct_explorer_page_queryset' hook, so pages that should have been hidden were visible. Visiting these pages in the explorer could then lead to unexpected behaviours, as hidden sections of the site became browsable. A new `for_explorer=1` query parameter has been added to the admin API, which will pass the page queryset through the 'construct_explorer_page_queryset' hooks.pull/3984/merge
rodzic
e1fc584f1c
commit
1f2b8ccaf1
|
@ -13,6 +13,7 @@ Changelog
|
|||
* Usage count now shows on delete confirmation page when WAGTAIL_USAGE_COUNT_ENABLED is active (Kees Hink)
|
||||
* Added usage count to snippets (Kees Hink)
|
||||
* Moved usage count to the sidebar on the edit page (Kees Hink)
|
||||
* Explorer menu now reflects customisations to the page listing made via the `construct_explorer_page_queryset` hook and `ModelAdmin.exclude_from_explorer` property (Tim Heap)
|
||||
* Fix: Do not remove stopwords when generating slugs from non-ASCII titles, to avoid issues with incorrect word boundaries (Sævar Öfjörð Magnússon)
|
||||
* Fix: The PostgreSQL search backend now preserves ordering of the `QuerySet` when searching with `order_by_relevance=False` (Bertrand Bordage)
|
||||
* Fix: Using `modeladmin_register` as a decorator no longer replaces the decorated class with `None` (Tim Heap)
|
||||
|
|
|
@ -10,7 +10,7 @@ export const getPage = (id) => {
|
|||
};
|
||||
|
||||
export const getPageChildren = (id, options = {}) => {
|
||||
let url = `${ADMIN_API.PAGES}?child_of=${id}`;
|
||||
let url = `${ADMIN_API.PAGES}?child_of=${id}&for_explorer=1`;
|
||||
|
||||
if (options.fields) {
|
||||
url += `&fields=${global.encodeURIComponent(options.fields.join(','))}`;
|
||||
|
|
|
@ -20,23 +20,23 @@ describe('admin API', () => {
|
|||
describe('getPageChildren', () => {
|
||||
it('works', () => {
|
||||
getPageChildren(3);
|
||||
expect(client.get).toBeCalledWith(`${ADMIN_API.PAGES}?child_of=3`);
|
||||
expect(client.get).toBeCalledWith(`${ADMIN_API.PAGES}?child_of=3&for_explorer=1`);
|
||||
});
|
||||
|
||||
it('#fields', () => {
|
||||
getPageChildren(3, { fields: ['title', 'latest_revision_created_at'] });
|
||||
// eslint-disable-next-line max-len
|
||||
expect(client.get).toBeCalledWith(`${ADMIN_API.PAGES}?child_of=3&fields=title%2Clatest_revision_created_at`);
|
||||
expect(client.get).toBeCalledWith(`${ADMIN_API.PAGES}?child_of=3&for_explorer=1&fields=title%2Clatest_revision_created_at`);
|
||||
});
|
||||
|
||||
it('#onlyWithChildren', () => {
|
||||
getPageChildren(3, { onlyWithChildren: true });
|
||||
expect(client.get).toBeCalledWith(`${ADMIN_API.PAGES}?child_of=3&has_children=1`);
|
||||
expect(client.get).toBeCalledWith(`${ADMIN_API.PAGES}?child_of=3&for_explorer=1&has_children=1`);
|
||||
});
|
||||
|
||||
it('#offset', () => {
|
||||
getPageChildren(3, { offset: 5 });
|
||||
expect(client.get).toBeCalledWith(`${ADMIN_API.PAGES}?child_of=3&offset=5`);
|
||||
expect(client.get).toBeCalledWith(`${ADMIN_API.PAGES}?child_of=3&for_explorer=1&offset=5`);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ Other features
|
|||
* Usage count now shows on delete confirmation page when WAGTAIL_USAGE_COUNT_ENABLED is active (Kees Hink)
|
||||
* Added usage count to snippets (Kees Hink)
|
||||
* Moved usage count to the sidebar on the edit page (Kees Hink)
|
||||
* Explorer menu now reflects customisations to the page listing made via the `construct_explorer_page_queryset` hook and `ModelAdmin.exclude_from_explorer` property (Tim Heap)
|
||||
|
||||
Bug fixes
|
||||
~~~~~~~~~
|
||||
|
|
|
@ -5,6 +5,7 @@ from django.db import models
|
|||
from rest_framework.filters import BaseFilterBackend
|
||||
from taggit.managers import TaggableManager
|
||||
|
||||
from wagtail.wagtailcore import hooks
|
||||
from wagtail.wagtailcore.models import Page
|
||||
from wagtail.wagtailsearch.backends import get_search_backend
|
||||
|
||||
|
@ -150,7 +151,7 @@ class ChildOfFilter(BaseFilterBackend):
|
|||
raise BadRequestError("parent page doesn't exist")
|
||||
|
||||
queryset = queryset.child_of(parent_page)
|
||||
queryset._filtered_by_child_of = True
|
||||
queryset._filtered_by_child_of = parent_page
|
||||
|
||||
return queryset
|
||||
|
||||
|
@ -181,7 +182,7 @@ class DescendantOfFilter(BaseFilterBackend):
|
|||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
if 'descendant_of' in request.GET:
|
||||
if getattr(queryset, '_filtered_by_child_of', False):
|
||||
if hasattr(queryset, '_filtered_by_child_of'):
|
||||
raise BadRequestError("filtering by descendant_of with child_of is not supported")
|
||||
try:
|
||||
parent_page_id = int(request.GET['descendant_of'])
|
||||
|
@ -212,3 +213,16 @@ class RestrictedDescendantOfFilter(DescendantOfFilter):
|
|||
def get_page_by_id(self, request, page_id):
|
||||
site_pages = pages_for_site(request.site)
|
||||
return site_pages.get(id=page_id)
|
||||
|
||||
|
||||
class ForExplorerFilter(BaseFilterBackend):
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
if request.GET.get('for_explorer'):
|
||||
if not hasattr(queryset, '_filtered_by_child_of'):
|
||||
raise BadRequestError("filtering by for_explorer without child_of is not supported")
|
||||
|
||||
parent_page = queryset._filtered_by_child_of
|
||||
for hook in hooks.get_hooks('construct_explorer_page_queryset'):
|
||||
queryset = hook(parent_page, queryset, request)
|
||||
|
||||
return queryset
|
||||
|
|
|
@ -93,6 +93,12 @@ def polite_pages_only(parent_page, pages, request):
|
|||
return pages
|
||||
|
||||
|
||||
@hooks.register('construct_explorer_page_queryset')
|
||||
def hide_hidden_pages(parent_page, pages, request):
|
||||
# Pages with 'hidden' in their title are hidden. Magic!
|
||||
return pages.exclude(title__icontains='hidden')
|
||||
|
||||
|
||||
# register 'blockquote' as a rich text feature supported by a hallo.js plugin
|
||||
@hooks.register('register_rich_text_features')
|
||||
def register_blockquote_feature(features):
|
||||
|
|
|
@ -4,7 +4,8 @@ from collections import OrderedDict
|
|||
|
||||
from wagtail.api.v2.endpoints import PagesAPIEndpoint
|
||||
from wagtail.api.v2.filters import (
|
||||
ChildOfFilter, DescendantOfFilter, FieldsFilter, OrderingFilter, SearchFilter)
|
||||
ChildOfFilter, DescendantOfFilter, FieldsFilter, ForExplorerFilter, OrderingFilter,
|
||||
SearchFilter)
|
||||
from wagtail.api.v2.utils import BadRequestError, filter_page_type, page_models_from_string
|
||||
from wagtail.wagtailcore.models import Page
|
||||
|
||||
|
@ -21,6 +22,7 @@ class PagesAdminAPIEndpoint(PagesAPIEndpoint):
|
|||
FieldsFilter,
|
||||
ChildOfFilter,
|
||||
DescendantOfFilter,
|
||||
ForExplorerFilter,
|
||||
HasChildrenFilter,
|
||||
OrderingFilter,
|
||||
SearchFilter,
|
||||
|
@ -49,6 +51,7 @@ class PagesAdminAPIEndpoint(PagesAPIEndpoint):
|
|||
detail_only_fields = []
|
||||
|
||||
known_query_parameters = PagesAPIEndpoint.known_query_parameters.union([
|
||||
'for_explorer',
|
||||
'has_children'
|
||||
])
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ from django.utils import timezone
|
|||
|
||||
from wagtail.api.v2.tests.test_pages import TestPageDetail, TestPageListing
|
||||
from wagtail.tests.demosite import models
|
||||
from wagtail.tests.testapp.models import StreamPage
|
||||
from wagtail.tests.testapp.models import SimplePage, StreamPage
|
||||
from wagtail.wagtailcore.models import Page
|
||||
|
||||
from .utils import AdminAPITestCase
|
||||
|
@ -301,6 +301,42 @@ class TestAdminPageListing(AdminAPITestCase, TestPageListing):
|
|||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
# FOR EXPLORER FILTER
|
||||
|
||||
def make_simple_page(self, parent, title):
|
||||
return parent.add_child(instance=SimplePage(title=title, content='Simple page'))
|
||||
|
||||
def test_for_explorer_filter(self):
|
||||
movies = self.make_simple_page(Page.objects.get(pk=1), 'Movies')
|
||||
visible_movies = [
|
||||
self.make_simple_page(movies, 'The Way of the Dragon'),
|
||||
self.make_simple_page(movies, 'Enter the Dragon'),
|
||||
self.make_simple_page(movies, 'Dragons Forever'),
|
||||
]
|
||||
hidden_movies = [
|
||||
self.make_simple_page(movies, 'The Hidden Fortress'),
|
||||
self.make_simple_page(movies, 'Crouching Tiger, Hidden Dragon'),
|
||||
self.make_simple_page(movies, 'Crouching Tiger, Hidden Dragon: Sword of Destiny'),
|
||||
]
|
||||
|
||||
response = self.get_response(child_of=movies.pk, for_explorer=1)
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
page_id_list = self.get_page_id_list(content)
|
||||
self.assertEqual(page_id_list, [page.pk for page in visible_movies])
|
||||
|
||||
response = self.get_response(child_of=movies.pk)
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
page_id_list = self.get_page_id_list(content)
|
||||
self.assertEqual(page_id_list, [page.pk for page in visible_movies + hidden_movies])
|
||||
|
||||
def test_for_explorer_no_child_of(self):
|
||||
response = self.get_response(for_explorer=1)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
self.assertEqual(content, {
|
||||
'message': 'filtering by for_explorer without child_of is not supported',
|
||||
})
|
||||
|
||||
# HAS CHILDREN FILTER
|
||||
|
||||
def test_has_children_filter(self):
|
||||
|
|
Ładowanie…
Reference in New Issue