-
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial work on the Admin API (#2485)
* feat(admin/api): Added Admin API * test(admin/api): Tests for admin API * feat(admin/api): Show all pages in admin API (except root) * refactor(admin/api): Restructure to match wagtail.api module * feat(admin/api): Added "status" to pages * feat(admin/api): Added "has_children" to pages * feat(admin/api): Added thumbnail to images * doc(admin/api): Fixes to docstring * feat(admin/api): Changed representation of page meta status Now includes live and has_unpublished_changes split out into separate fields * feat(api): Added get_model_listing_urlpath to router * feat(admin/api): Replaced page has_children with children This allows us to return the count and listing_url of the child pages * feat(admin/api): Bump API version to v2 Since the API is based on WagtailAPI v2. This should hopefully reduce risk of confusion. There won't be an Admin API v1. * Fixed some import paths * Added extra fields into tests * Updated admin API tests for items rename * Revert "feat(api/2): Removed meta from top of listings" This reverts commit fb61033. * Rewrite admin meta fields * Fixed admin api tests post rebase * Control which fields are shown in the admin API User defined api_fields no longer affect the admin API * Use unrestricted child_of/descendant_of filters * Revert "Control which fields are shown in the admin API" This reverts commit 90a6758. * Updates to admin API for compatibility with changes to api v2 meta field handling * Added type information to admin API * Added latest_revision_created_at field * Added has_children filter * Add parent field * Removed duplicate tests between admin api and public api * isort
- Loading branch information
Showing
13 changed files
with
1,019 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
from __future__ import absolute_import, unicode_literals | ||
|
||
from collections import OrderedDict | ||
|
||
from wagtail.api.v2.endpoints import DocumentsAPIEndpoint, ImagesAPIEndpoint, PagesAPIEndpoint | ||
from wagtail.api.v2.filters import ( | ||
ChildOfFilter, DescendantOfFilter, FieldsFilter, OrderingFilter, SearchFilter) | ||
from wagtail.api.v2.utils import BadRequestError, filter_page_type, page_models_from_string | ||
from wagtail.wagtailcore.models import Page | ||
|
||
from .filters import HasChildrenFilter | ||
from .serializers import AdminImageSerializer, AdminPageSerializer | ||
|
||
|
||
class PagesAdminAPIEndpoint(PagesAPIEndpoint): | ||
base_serializer_class = AdminPageSerializer | ||
|
||
# Use unrestricted child_of/descendant_of filters | ||
# Add has_children filter | ||
filter_backends = [ | ||
FieldsFilter, | ||
ChildOfFilter, | ||
DescendantOfFilter, | ||
HasChildrenFilter, | ||
OrderingFilter, | ||
SearchFilter, | ||
] | ||
|
||
extra_meta_fields = PagesAPIEndpoint.extra_meta_fields + [ | ||
'latest_revision_created_at', | ||
'status', | ||
'children', | ||
'parent', | ||
] | ||
|
||
default_fields = PagesAPIEndpoint.default_fields + [ | ||
'latest_revision_created_at', | ||
'status', | ||
'children', | ||
] | ||
|
||
known_query_parameters = PagesAPIEndpoint.known_query_parameters.union([ | ||
'has_children' | ||
]) | ||
|
||
def get_queryset(self): | ||
request = self.request | ||
|
||
# Allow pages to be filtered to a specific type | ||
try: | ||
models = page_models_from_string(request.GET.get('type', 'wagtailcore.Page')) | ||
except (LookupError, ValueError): | ||
raise BadRequestError("type doesn't exist") | ||
|
||
if not models: | ||
models = [Page] | ||
|
||
if len(models) == 1: | ||
queryset = models[0].objects.all() | ||
else: | ||
queryset = Page.objects.all() | ||
|
||
# Filter pages by specified models | ||
queryset = filter_page_type(queryset, models) | ||
|
||
# Hide root page | ||
# TODO: Add "include_root" flag | ||
queryset = queryset.exclude(depth=1) | ||
|
||
return queryset | ||
|
||
def get_type_info(self): | ||
types = OrderedDict() | ||
|
||
for name, model in self.seen_types.items(): | ||
types[name] = OrderedDict([ | ||
('verbose_name', model._meta.verbose_name), | ||
('verbose_name_plural', model._meta.verbose_name_plural), | ||
]) | ||
|
||
return types | ||
|
||
def listing_view(self, request): | ||
response = super(PagesAdminAPIEndpoint, self).listing_view(request) | ||
response.data['__types'] = self.get_type_info() | ||
return response | ||
|
||
def detail_view(self, request, pk): | ||
response = super(PagesAdminAPIEndpoint, self).detail_view(request, pk) | ||
response.data['__types'] = self.get_type_info() | ||
return response | ||
|
||
|
||
class ImagesAdminAPIEndpoint(ImagesAPIEndpoint): | ||
base_serializer_class = AdminImageSerializer | ||
|
||
extra_body_fields = ImagesAPIEndpoint.extra_body_fields + [ | ||
'thumbnail', | ||
] | ||
|
||
default_fields = ImagesAPIEndpoint.default_fields + [ | ||
'width', | ||
'height', | ||
'thumbnail', | ||
] | ||
|
||
|
||
|
||
class DocumentsAdminAPIEndpoint(DocumentsAPIEndpoint): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
from __future__ import absolute_import, unicode_literals | ||
|
||
from rest_framework.filters import BaseFilterBackend | ||
|
||
from wagtail.api.v2.utils import BadRequestError | ||
|
||
|
||
class HasChildrenFilter(BaseFilterBackend): | ||
""" | ||
Filters the queryset by checking if the pages have children or not. | ||
This is useful when you want to get just the branches or just the leaves. | ||
""" | ||
def filter_queryset(self, request, queryset, view): | ||
if 'has_children' in request.GET: | ||
try: | ||
has_children_filter = int(request.GET['has_children']) | ||
assert has_children_filter is 1 or has_children_filter is 0 | ||
except (ValueError, AssertionError): | ||
raise BadRequestError("has_children must be 1 or 0") | ||
|
||
if has_children_filter == 1: | ||
return queryset.filter(numchild__gt=0) | ||
else: | ||
return queryset.filter(numchild=0) | ||
|
||
return queryset |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
from __future__ import absolute_import, unicode_literals | ||
|
||
from collections import OrderedDict | ||
|
||
from rest_framework.fields import Field | ||
|
||
from wagtail.api.v2.serializers import ImageSerializer, PageSerializer | ||
from wagtail.api.v2.utils import get_full_url | ||
from wagtail.wagtailcore.models import Page | ||
from wagtail.wagtailimages.models import SourceImageIOError | ||
|
||
|
||
def get_model_listing_url(context, model): | ||
url_path = context['router'].get_model_listing_urlpath(model) | ||
|
||
if url_path: | ||
return get_full_url(context['request'], url_path) | ||
|
||
|
||
class PageStatusField(Field): | ||
""" | ||
Serializes the "status" field. | ||
Example: | ||
"status": { | ||
"status": "live", | ||
"live": true, | ||
"has_unpublished_changes": false | ||
}, | ||
""" | ||
def get_attribute(self, instance): | ||
return instance | ||
|
||
def to_representation(self, page): | ||
return OrderedDict([ | ||
('status', page.status_string), | ||
('live', page.live), | ||
('has_unpublished_changes', page.has_unpublished_changes), | ||
]) | ||
|
||
|
||
class PageChildrenField(Field): | ||
""" | ||
Serializes the "children" field. | ||
Example: | ||
"children": { | ||
"count": 1, | ||
"listing_url": "/api/v1/pages/?child_of=2" | ||
} | ||
""" | ||
def get_attribute(self, instance): | ||
return instance | ||
|
||
def to_representation(self, page): | ||
return OrderedDict([ | ||
('count', page.numchild), | ||
('listing_url', get_model_listing_url(self.context, Page) + '?child_of=' + str(page.id)), | ||
]) | ||
|
||
|
||
class AdminPageSerializer(PageSerializer): | ||
status = PageStatusField(read_only=True) | ||
children = PageChildrenField(read_only=True) | ||
|
||
meta_fields = PageSerializer.meta_fields + [ | ||
'status', | ||
'children', | ||
] | ||
|
||
|
||
class ImageRenditionField(Field): | ||
""" | ||
A field that generates a rendition with the specified filter spec, and serialises | ||
details of that rendition. | ||
Example: | ||
"thumbnail": { | ||
"url": "/media/images/myimage.max-165x165.jpg", | ||
"width": 165, | ||
"height": 100 | ||
} | ||
If there is an error with the source image. The dict will only contain a single | ||
key, "error", indicating this error: | ||
"thumbnail": { | ||
"error": "SourceImageIOError" | ||
} | ||
""" | ||
def __init__(self, filter_spec, *args, **kwargs): | ||
self.filter_spec = filter_spec | ||
super(ImageRenditionField, self).__init__(*args, **kwargs) | ||
|
||
def get_attribute(self, instance): | ||
return instance | ||
|
||
def to_representation(self, image): | ||
try: | ||
thumbnail = image.get_rendition(self.filter_spec) | ||
|
||
return OrderedDict([ | ||
('url', thumbnail.url), | ||
('width', thumbnail.width), | ||
('height', thumbnail.height), | ||
]) | ||
except SourceImageIOError: | ||
return OrderedDict([ | ||
('error', 'SourceImageIOError'), | ||
]) | ||
|
||
|
||
class AdminImageSerializer(ImageSerializer): | ||
thumbnail = ImageRenditionField('max-165x165', read_only=True) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
from __future__ import absolute_import, unicode_literals | ||
|
||
from django.conf.urls import url | ||
|
||
from wagtail.api.v2.router import WagtailAPIRouter | ||
|
||
from .endpoints import DocumentsAdminAPIEndpoint, ImagesAdminAPIEndpoint, PagesAdminAPIEndpoint | ||
|
||
v1 = WagtailAPIRouter('wagtailadmin_api_v1') | ||
v1.register_endpoint('pages', PagesAdminAPIEndpoint) | ||
v1.register_endpoint('images', ImagesAdminAPIEndpoint) | ||
v1.register_endpoint('documents', DocumentsAdminAPIEndpoint) | ||
|
||
urlpatterns = [ | ||
url(r'^v2beta/', v1.urls), | ||
] |
Empty file.
Oops, something went wrong.