mirror of
https://github.com/Hopiu/wagtail.git
synced 2026-04-25 00:54:52 +00:00
Initial work on the Admin API (#2507)
This commit is contained in:
parent
bdd80dd33e
commit
2f8d00222d
21 changed files with 1112 additions and 2 deletions
|
|
@ -54,6 +54,15 @@ class BaseAPIEndpoint(GenericViewSet):
|
|||
default_fields = []
|
||||
name = None # Set on subclass.
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(BaseAPIEndpoint, self).__init__(*args, **kwargs)
|
||||
|
||||
# seen_types is a mapping of type name strings (format: "app_label.ModelName")
|
||||
# to model classes. When an object is serialised in the API, its model
|
||||
# is added to this mapping. This is used by the Admin API which appends a
|
||||
# summary of the used types to the response.
|
||||
self.seen_types = OrderedDict()
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.all().order_by('id')
|
||||
|
||||
|
|
@ -187,6 +196,15 @@ class BaseAPIEndpoint(GenericViewSet):
|
|||
url(r'^(?P<pk>\d+)/$', cls.as_view({'get': 'detail_view'}), name='detail'),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def get_model_listing_urlpath(cls, model, namespace=''):
|
||||
if namespace:
|
||||
url_name = namespace + ':listing'
|
||||
else:
|
||||
url_name = 'listing'
|
||||
|
||||
return reverse(url_name)
|
||||
|
||||
@classmethod
|
||||
def get_object_detail_urlpath(cls, model, pk, namespace=''):
|
||||
if namespace:
|
||||
|
|
|
|||
|
|
@ -30,6 +30,20 @@ class WagtailAPIRouter(object):
|
|||
if issubclass(model, class_.model):
|
||||
return name, class_
|
||||
|
||||
def get_model_listing_urlpath(self, model):
|
||||
"""
|
||||
Returns a URL path (excluding scheme and hostname) to the listing
|
||||
page of a model
|
||||
|
||||
Returns None if the model is not represented by any endpoints.
|
||||
"""
|
||||
endpoint = self.get_model_endpoint(model)
|
||||
|
||||
if endpoint:
|
||||
endpoint_name, endpoint_class = endpoint[0], endpoint[1]
|
||||
url_namespace = self.url_namespace + ':' + endpoint_name
|
||||
return endpoint_class.get_model_listing_urlpath(model, namespace=url_namespace)
|
||||
|
||||
def get_object_detail_urlpath(self, model, pk):
|
||||
"""
|
||||
Returns a URL path (excluding scheme and hostname) to the detail
|
||||
|
|
|
|||
|
|
@ -40,7 +40,9 @@ class TypeField(Field):
|
|||
return instance
|
||||
|
||||
def to_representation(self, obj):
|
||||
return type(obj)._meta.app_label + '.' + type(obj).__name__
|
||||
name = type(obj)._meta.app_label + '.' + type(obj).__name__
|
||||
self.context['view'].seen_types[name] = type(obj)
|
||||
return name
|
||||
|
||||
|
||||
class DetailUrlField(Field):
|
||||
|
|
@ -95,7 +97,9 @@ class PageTypeField(Field):
|
|||
return instance
|
||||
|
||||
def to_representation(self, page):
|
||||
return page.specific_class._meta.app_label + '.' + page.specific_class.__name__
|
||||
name = page.specific_class._meta.app_label + '.' + page.specific_class.__name__
|
||||
self.context['view'].seen_types[name] = page.specific_class
|
||||
return name
|
||||
|
||||
|
||||
class RelatedField(relations.RelatedField):
|
||||
|
|
|
|||
0
wagtail/wagtailadmin/api/__init__.py
Normal file
0
wagtail/wagtailadmin/api/__init__.py
Normal file
92
wagtail/wagtailadmin/api/endpoints.py
Normal file
92
wagtail/wagtailadmin/api/endpoints.py
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
from wagtail.api.v2.endpoints import 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 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',
|
||||
'descendants',
|
||||
'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
|
||||
26
wagtail/wagtailadmin/api/filters.py
Normal file
26
wagtail/wagtailadmin/api/filters.py
Normal file
|
|
@ -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
|
||||
90
wagtail/wagtailadmin/api/serializers.py
Normal file
90
wagtail/wagtailadmin/api/serializers.py
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
from rest_framework.fields import Field
|
||||
|
||||
from wagtail.api.v2.serializers import PageSerializer
|
||||
from wagtail.api.v2.utils import get_full_url
|
||||
from wagtail.wagtailcore.models import Page
|
||||
|
||||
|
||||
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 PageDescendantsField(Field):
|
||||
"""
|
||||
Serializes the "descendants" field.
|
||||
|
||||
Example:
|
||||
"descendants": {
|
||||
"count": 10,
|
||||
"listing_url": "/api/v1/pages/?descendant_of=2"
|
||||
}
|
||||
"""
|
||||
def get_attribute(self, instance):
|
||||
return instance
|
||||
|
||||
def to_representation(self, page):
|
||||
return OrderedDict([
|
||||
('count', page.get_descendants().count()),
|
||||
('listing_url', get_model_listing_url(self.context, Page) + '?descendant_of=' + str(page.id)),
|
||||
])
|
||||
|
||||
|
||||
class AdminPageSerializer(PageSerializer):
|
||||
status = PageStatusField(read_only=True)
|
||||
children = PageChildrenField(read_only=True)
|
||||
descendants = PageDescendantsField(read_only=True)
|
||||
|
||||
meta_fields = PageSerializer.meta_fields + [
|
||||
'status',
|
||||
'children',
|
||||
'descendants',
|
||||
]
|
||||
18
wagtail/wagtailadmin/api/urls.py
Normal file
18
wagtail/wagtailadmin/api/urls.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from wagtail.api.v2.router import WagtailAPIRouter
|
||||
from wagtail.wagtailcore import hooks
|
||||
|
||||
from .endpoints import PagesAdminAPIEndpoint
|
||||
|
||||
admin_api = WagtailAPIRouter('wagtailadmin_api_v1')
|
||||
admin_api.register_endpoint('pages', PagesAdminAPIEndpoint)
|
||||
|
||||
for fn in hooks.get_hooks('construct_admin_api'):
|
||||
fn(admin_api)
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^v2beta/', admin_api.urls),
|
||||
]
|
||||
0
wagtail/wagtailadmin/tests/api/__init__.py
Normal file
0
wagtail/wagtailadmin/tests/api/__init__.py
Normal file
115
wagtail/wagtailadmin/tests/api/test_documents.py
Normal file
115
wagtail/wagtailadmin/tests/api/test_documents.py
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import json
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from wagtail.api.v2.tests.test_documents import TestDocumentDetail, TestDocumentListing
|
||||
from wagtail.wagtaildocs.models import Document
|
||||
|
||||
from .utils import AdminAPITestCase
|
||||
|
||||
|
||||
class TestAdminDocumentListing(AdminAPITestCase, TestDocumentListing):
|
||||
fixtures = ['demosite.json']
|
||||
|
||||
def get_response(self, **params):
|
||||
return self.client.get(reverse('wagtailadmin_api_v1:documents:listing'), params)
|
||||
|
||||
def get_document_id_list(self, content):
|
||||
return [document['id'] for document in content['items']]
|
||||
|
||||
|
||||
# BASIC TESTS
|
||||
|
||||
def test_basic(self):
|
||||
response = self.get_response()
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response['Content-type'], 'application/json')
|
||||
|
||||
# Will crash if the JSON is invalid
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
|
||||
# Check that the meta section is there
|
||||
self.assertIn('meta', content)
|
||||
self.assertIsInstance(content['meta'], dict)
|
||||
|
||||
# Check that the total count is there and correct
|
||||
self.assertIn('total_count', content['meta'])
|
||||
self.assertIsInstance(content['meta']['total_count'], int)
|
||||
self.assertEqual(content['meta']['total_count'], Document.objects.count())
|
||||
|
||||
# Check that the items section is there
|
||||
self.assertIn('items', content)
|
||||
self.assertIsInstance(content['items'], list)
|
||||
|
||||
# Check that each document has a meta section with type, detail_url and tags attributes
|
||||
for document in content['items']:
|
||||
self.assertIn('meta', document)
|
||||
self.assertIsInstance(document['meta'], dict)
|
||||
self.assertEqual(set(document['meta'].keys()), {'type', 'detail_url', 'download_url', 'tags'})
|
||||
|
||||
# Type should always be wagtaildocs.Document
|
||||
self.assertEqual(document['meta']['type'], 'wagtaildocs.Document')
|
||||
|
||||
# Check detail_url
|
||||
self.assertEqual(document['meta']['detail_url'], 'http://localhost/admin/api/v2beta/documents/%d/' % document['id'])
|
||||
|
||||
# Check download_url
|
||||
self.assertTrue(document['meta']['download_url'].startswith('http://localhost/documents/%d/' % document['id']))
|
||||
|
||||
|
||||
# FIELDS
|
||||
|
||||
def test_fields_default(self):
|
||||
response = self.get_response()
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
|
||||
for document in content['items']:
|
||||
self.assertEqual(set(document.keys()), {'id', 'meta', 'title'})
|
||||
self.assertEqual(set(document['meta'].keys()), {'type', 'detail_url', 'download_url', 'tags'})
|
||||
|
||||
|
||||
class TestAdminDocumentDetail(AdminAPITestCase, TestDocumentDetail):
|
||||
fixtures = ['demosite.json']
|
||||
|
||||
def get_response(self, image_id, **params):
|
||||
return self.client.get(reverse('wagtailadmin_api_v1:documents:detail', args=(image_id, )), params)
|
||||
|
||||
def test_basic(self):
|
||||
response = self.get_response(1)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response['Content-type'], 'application/json')
|
||||
|
||||
# Will crash if the JSON is invalid
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
|
||||
# Check the id field
|
||||
self.assertIn('id', content)
|
||||
self.assertEqual(content['id'], 1)
|
||||
|
||||
# Check that the meta section is there
|
||||
self.assertIn('meta', content)
|
||||
self.assertIsInstance(content['meta'], dict)
|
||||
|
||||
# Check the meta type
|
||||
self.assertIn('type', content['meta'])
|
||||
self.assertEqual(content['meta']['type'], 'wagtaildocs.Document')
|
||||
|
||||
# Check the meta detail_url
|
||||
self.assertIn('detail_url', content['meta'])
|
||||
self.assertEqual(content['meta']['detail_url'], 'http://localhost/admin/api/v2beta/documents/1/')
|
||||
|
||||
# Check the meta download_url
|
||||
self.assertIn('download_url', content['meta'])
|
||||
self.assertEqual(content['meta']['download_url'], 'http://localhost/documents/1/wagtail_by_markyharky.jpg')
|
||||
|
||||
# Check the title field
|
||||
self.assertIn('title', content)
|
||||
self.assertEqual(content['title'], "Wagtail by mark Harkin")
|
||||
|
||||
# Check the tags field
|
||||
self.assertIn('tags', content['meta'])
|
||||
self.assertEqual(content['meta']['tags'], [])
|
||||
143
wagtail/wagtailadmin/tests/api/test_images.py
Normal file
143
wagtail/wagtailadmin/tests/api/test_images.py
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import json
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from wagtail.api.v2.tests.test_images import TestImageDetail, TestImageListing
|
||||
from wagtail.wagtailimages.models import get_image_model
|
||||
from wagtail.wagtailimages.tests.utils import get_test_image_file
|
||||
|
||||
from .utils import AdminAPITestCase
|
||||
|
||||
|
||||
class TestAdminImageListing(AdminAPITestCase, TestImageListing):
|
||||
fixtures = ['demosite.json']
|
||||
|
||||
def get_response(self, **params):
|
||||
return self.client.get(reverse('wagtailadmin_api_v1:images:listing'), params)
|
||||
|
||||
def get_image_id_list(self, content):
|
||||
return [image['id'] for image in content['items']]
|
||||
|
||||
|
||||
# BASIC TESTS
|
||||
|
||||
def test_basic(self):
|
||||
response = self.get_response()
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response['Content-type'], 'application/json')
|
||||
|
||||
# Will crash if the JSON is invalid
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
|
||||
# Check that the meta section is there
|
||||
self.assertIn('meta', content)
|
||||
self.assertIsInstance(content['meta'], dict)
|
||||
|
||||
# Check that the total count is there and correct
|
||||
self.assertIn('total_count', content['meta'])
|
||||
self.assertIsInstance(content['meta']['total_count'], int)
|
||||
self.assertEqual(content['meta']['total_count'], get_image_model().objects.count())
|
||||
|
||||
# Check that the items section is there
|
||||
self.assertIn('items', content)
|
||||
self.assertIsInstance(content['items'], list)
|
||||
|
||||
# Check that each image has a meta section with type, detail_url and tags attributes
|
||||
for image in content['items']:
|
||||
self.assertIn('meta', image)
|
||||
self.assertIsInstance(image['meta'], dict)
|
||||
self.assertEqual(set(image['meta'].keys()), {'type', 'detail_url', 'tags'})
|
||||
|
||||
# Type should always be wagtailimages.Image
|
||||
self.assertEqual(image['meta']['type'], 'wagtailimages.Image')
|
||||
|
||||
# Check detail url
|
||||
self.assertEqual(image['meta']['detail_url'], 'http://localhost/admin/api/v2beta/images/%d/' % image['id'])
|
||||
|
||||
|
||||
# FIELDS
|
||||
|
||||
def test_fields_default(self):
|
||||
response = self.get_response()
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
|
||||
for image in content['items']:
|
||||
self.assertEqual(set(image.keys()), {'id', 'meta', 'title', 'width', 'height', 'thumbnail'})
|
||||
self.assertEqual(set(image['meta'].keys()), {'type', 'detail_url', 'tags'})
|
||||
|
||||
|
||||
class TestAdminImageDetail(AdminAPITestCase, TestImageDetail):
|
||||
fixtures = ['demosite.json']
|
||||
|
||||
def get_response(self, image_id, **params):
|
||||
return self.client.get(reverse('wagtailadmin_api_v1:images:detail', args=(image_id, )), params)
|
||||
|
||||
|
||||
def test_basic(self):
|
||||
response = self.get_response(5)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response['Content-type'], 'application/json')
|
||||
|
||||
# Will crash if the JSON is invalid
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
|
||||
# Check the id field
|
||||
self.assertIn('id', content)
|
||||
self.assertEqual(content['id'], 5)
|
||||
|
||||
# Check that the meta section is there
|
||||
self.assertIn('meta', content)
|
||||
self.assertIsInstance(content['meta'], dict)
|
||||
|
||||
# Check the meta type
|
||||
self.assertIn('type', content['meta'])
|
||||
self.assertEqual(content['meta']['type'], 'wagtailimages.Image')
|
||||
|
||||
# Check the meta detail_url
|
||||
self.assertIn('detail_url', content['meta'])
|
||||
self.assertEqual(content['meta']['detail_url'], 'http://localhost/admin/api/v2beta/images/5/')
|
||||
|
||||
# Check the thumbnail
|
||||
|
||||
# Note: This is None because the source image doesn't exist
|
||||
# See test_thumbnail below for working example
|
||||
self.assertIn('thumbnail', content)
|
||||
self.assertEqual(content['thumbnail'], {'error': 'SourceImageIOError'})
|
||||
|
||||
# Check the title field
|
||||
self.assertIn('title', content)
|
||||
self.assertEqual(content['title'], "James Joyce")
|
||||
|
||||
# Check the width and height fields
|
||||
self.assertIn('width', content)
|
||||
self.assertIn('height', content)
|
||||
self.assertEqual(content['width'], 500)
|
||||
self.assertEqual(content['height'], 392)
|
||||
|
||||
# Check the tags field
|
||||
self.assertIn('tags', content['meta'])
|
||||
self.assertEqual(content['meta']['tags'], [])
|
||||
|
||||
def test_thumbnail(self):
|
||||
# Add a new image with source file
|
||||
image = get_image_model().objects.create(
|
||||
title="Test image",
|
||||
file=get_test_image_file(),
|
||||
)
|
||||
|
||||
response = self.get_response(image.id)
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
|
||||
self.assertIn('thumbnail', content)
|
||||
self.assertEqual(content['thumbnail'], {
|
||||
'url': '/media/images/test.max-165x165.png',
|
||||
'width': 165,
|
||||
'height': 123
|
||||
})
|
||||
|
||||
# Check that source_image_error didn't appear
|
||||
self.assertNotIn('source_image_error', content['meta'])
|
||||
487
wagtail/wagtailadmin/tests/api/test_pages.py
Normal file
487
wagtail/wagtailadmin/tests/api/test_pages.py
Normal file
|
|
@ -0,0 +1,487 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import collections
|
||||
import datetime
|
||||
import json
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
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.wagtailcore.models import Page
|
||||
|
||||
from .utils import AdminAPITestCase
|
||||
|
||||
|
||||
def get_total_page_count():
|
||||
# Need to take away 1 as the root page is invisible over the API by default
|
||||
return Page.objects.count() - 1
|
||||
|
||||
|
||||
class TestAdminPageListing(AdminAPITestCase, TestPageListing):
|
||||
fixtures = ['demosite.json']
|
||||
|
||||
def get_response(self, **params):
|
||||
return self.client.get(reverse('wagtailadmin_api_v1:pages:listing'), params)
|
||||
|
||||
def get_page_id_list(self, content):
|
||||
return [page['id'] for page in content['items']]
|
||||
|
||||
|
||||
# BASIC TESTS
|
||||
|
||||
def test_basic(self):
|
||||
response = self.get_response()
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response['Content-type'], 'application/json')
|
||||
|
||||
# Will crash if the JSON is invalid
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
|
||||
# Check that the meta section is there
|
||||
self.assertIn('meta', content)
|
||||
self.assertIsInstance(content['meta'], dict)
|
||||
|
||||
# Check that the total count is there and correct
|
||||
self.assertIn('total_count', content['meta'])
|
||||
self.assertIsInstance(content['meta']['total_count'], int)
|
||||
self.assertEqual(content['meta']['total_count'], get_total_page_count())
|
||||
|
||||
# Check that the items section is there
|
||||
self.assertIn('items', content)
|
||||
self.assertIsInstance(content['items'], list)
|
||||
|
||||
# Check that each page has a meta section with type, detail_url, html_url, status and children attributes
|
||||
for page in content['items']:
|
||||
self.assertIn('meta', page)
|
||||
self.assertIsInstance(page['meta'], dict)
|
||||
self.assertEqual(set(page['meta'].keys()), {'type', 'detail_url', 'html_url', 'status', 'children', 'slug', 'first_published_at', 'latest_revision_created_at'})
|
||||
|
||||
# Check the type info
|
||||
self.assertIsInstance(content['__types'], dict)
|
||||
self.assertEqual(set(content['__types'].keys()), {
|
||||
'demosite.EventPage',
|
||||
'demosite.StandardIndexPage',
|
||||
'demosite.PersonPage',
|
||||
'demosite.HomePage',
|
||||
'demosite.StandardPage',
|
||||
'demosite.EventIndexPage',
|
||||
'demosite.ContactPage',
|
||||
'demosite.BlogEntryPage',
|
||||
'demosite.BlogIndexPage',
|
||||
})
|
||||
self.assertEqual(set(content['__types']['demosite.EventPage'].keys()), {'verbose_name', 'verbose_name_plural'})
|
||||
self.assertEqual(content['__types']['demosite.EventPage']['verbose_name'], 'event page')
|
||||
self.assertEqual(content['__types']['demosite.EventPage']['verbose_name_plural'], 'event pages')
|
||||
|
||||
# Not applicable to the admin API
|
||||
test_unpublished_pages_dont_appear_in_list = None
|
||||
test_private_pages_dont_appear_in_list = None
|
||||
|
||||
def test_unpublished_pages_appear_in_list(self):
|
||||
total_count = get_total_page_count()
|
||||
|
||||
page = models.BlogEntryPage.objects.get(id=16)
|
||||
page.unpublish()
|
||||
|
||||
response = self.get_response()
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
self.assertEqual(content['meta']['total_count'], total_count)
|
||||
|
||||
def test_private_pages_appear_in_list(self):
|
||||
total_count = get_total_page_count()
|
||||
|
||||
page = models.BlogIndexPage.objects.get(id=5)
|
||||
page.view_restrictions.create(password='test')
|
||||
|
||||
new_total_count = get_total_page_count()
|
||||
self.assertEqual(total_count, total_count)
|
||||
|
||||
response = self.get_response()
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
self.assertEqual(content['meta']['total_count'], new_total_count)
|
||||
|
||||
|
||||
# FIELDS
|
||||
|
||||
def test_fields_default(self):
|
||||
response = self.get_response(type='demosite.BlogEntryPage')
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
|
||||
for page in content['items']:
|
||||
self.assertEqual(set(page.keys()), {'id', 'meta', 'title'})
|
||||
self.assertEqual(set(page['meta'].keys()), {'type', 'detail_url', 'html_url', 'children', 'status', 'slug', 'first_published_at', 'latest_revision_created_at'})
|
||||
|
||||
def test_fields_foreign_key(self):
|
||||
# Only the base the detail_url is different here from the public API
|
||||
response = self.get_response(type='demosite.BlogEntryPage', fields='title,date,feed_image')
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
|
||||
for page in content['items']:
|
||||
feed_image = page['feed_image']
|
||||
|
||||
if feed_image is not None:
|
||||
self.assertIsInstance(feed_image, dict)
|
||||
self.assertEqual(set(feed_image.keys()), {'id', 'meta'})
|
||||
self.assertIsInstance(feed_image['id'], int)
|
||||
self.assertIsInstance(feed_image['meta'], dict)
|
||||
self.assertEqual(set(feed_image['meta'].keys()), {'type', 'detail_url'})
|
||||
self.assertEqual(feed_image['meta']['type'], 'wagtailimages.Image')
|
||||
self.assertEqual(feed_image['meta']['detail_url'], 'http://localhost/admin/api/v2beta/images/%d/' % feed_image['id'])
|
||||
|
||||
def test_fields_parent(self):
|
||||
response = self.get_response(type='demosite.BlogEntryPage', fields='parent')
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
|
||||
for page in content['items']:
|
||||
parent = page['meta']['parent']
|
||||
|
||||
# All blog entry pages have the same parent
|
||||
self.assertEqual(parent, {
|
||||
'id': 5,
|
||||
'meta': {
|
||||
'type': 'demosite.BlogIndexPage',
|
||||
'detail_url': 'http://localhost/admin/api/v2beta/pages/5/',
|
||||
'html_url': 'http://localhost/blog-index/',
|
||||
}
|
||||
})
|
||||
|
||||
def test_fields_descendants(self):
|
||||
response = self.get_response(fields='descendants')
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
|
||||
for page in content['items']:
|
||||
descendants = page['meta']['descendants']
|
||||
self.assertEqual(set(descendants.keys()), {'count', 'listing_url'})
|
||||
self.assertIsInstance(descendants['count'], int)
|
||||
self.assertEqual(descendants['listing_url'], 'http://localhost/admin/api/v2beta/pages/?descendant_of=%d' % page['id'])
|
||||
|
||||
|
||||
# CHILD OF FILTER
|
||||
|
||||
# Not applicable to the admin API
|
||||
test_child_of_page_thats_not_in_same_site_gives_error = None
|
||||
|
||||
def test_child_of_root(self):
|
||||
# Only return the homepage as that's the only child of the "root" node
|
||||
# in the tree. This is different to the public API which pretends the
|
||||
# homepage of the current site is the root page.
|
||||
response = self.get_response(child_of='root')
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
|
||||
page_id_list = self.get_page_id_list(content)
|
||||
self.assertEqual(page_id_list, [2])
|
||||
|
||||
def test_child_of_page_1(self):
|
||||
# Public API doesn't allow this, as it's the root page
|
||||
response = self.get_response(child_of=1)
|
||||
json.loads(response.content.decode('UTF-8'))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
# DESCENDANT OF FILTER
|
||||
|
||||
# Not applicable to the admin API
|
||||
test_descendant_of_page_thats_not_in_same_site_gives_error = None
|
||||
|
||||
def test_descendant_of_root(self):
|
||||
response = self.get_response(descendant_of='root')
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
|
||||
page_id_list = self.get_page_id_list(content)
|
||||
self.assertEqual(page_id_list, [2, 4, 8, 9, 5, 16, 18, 19, 6, 10, 15, 17, 21, 22, 23, 20, 13, 14, 12])
|
||||
|
||||
def test_descendant_of_root_doesnt_give_error(self):
|
||||
# Public API doesn't allow this
|
||||
response = self.get_response(descendant_of=1)
|
||||
json.loads(response.content.decode('UTF-8'))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
# HAS CHILDREN FILTER
|
||||
|
||||
def test_has_children_filter(self):
|
||||
response = self.get_response(has_children=1)
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
|
||||
page_id_list = self.get_page_id_list(content)
|
||||
self.assertEqual(page_id_list, [2, 4, 5, 6, 21, 20])
|
||||
|
||||
def test_has_children_filter_off(self):
|
||||
response = self.get_response(has_children=0)
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
|
||||
page_id_list = self.get_page_id_list(content)
|
||||
self.assertEqual(page_id_list, [8, 9, 16, 18, 19, 10, 15, 17, 22, 23, 13, 14, 12])
|
||||
|
||||
def test_has_children_filter_invalid_integer(self):
|
||||
response = self.get_response(has_children=3)
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertEqual(content, {'message': "has_children must be 1 or 0"})
|
||||
|
||||
def test_has_children_filter_invalid_value(self):
|
||||
response = self.get_response(has_children='yes')
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertEqual(content, {'message': "has_children must be 1 or 0"})
|
||||
|
||||
|
||||
class TestAdminPageDetail(AdminAPITestCase, TestPageDetail):
|
||||
fixtures = ['demosite.json']
|
||||
|
||||
def get_response(self, page_id, **params):
|
||||
return self.client.get(reverse('wagtailadmin_api_v1:pages:detail', args=(page_id, )), params)
|
||||
|
||||
def test_basic(self):
|
||||
response = self.get_response(16)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response['Content-type'], 'application/json')
|
||||
|
||||
# Will crash if the JSON is invalid
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
|
||||
# Check the id field
|
||||
self.assertIn('id', content)
|
||||
self.assertEqual(content['id'], 16)
|
||||
|
||||
# Check that the meta section is there
|
||||
self.assertIn('meta', content)
|
||||
self.assertIsInstance(content['meta'], dict)
|
||||
|
||||
# Check the meta type
|
||||
self.assertIn('type', content['meta'])
|
||||
self.assertEqual(content['meta']['type'], 'demosite.BlogEntryPage')
|
||||
|
||||
# Check the meta detail_url
|
||||
self.assertIn('detail_url', content['meta'])
|
||||
self.assertEqual(content['meta']['detail_url'], 'http://localhost/admin/api/v2beta/pages/16/')
|
||||
|
||||
# Check the meta html_url
|
||||
self.assertIn('html_url', content['meta'])
|
||||
self.assertEqual(content['meta']['html_url'], 'http://localhost/blog-index/blog-post/')
|
||||
|
||||
# Check the meta status
|
||||
|
||||
self.assertIn('status', content['meta'])
|
||||
self.assertEqual(content['meta']['status'], {
|
||||
'status': 'live',
|
||||
'live': True,
|
||||
'has_unpublished_changes': False
|
||||
})
|
||||
|
||||
# Check the meta children
|
||||
|
||||
self.assertIn('children', content['meta'])
|
||||
self.assertEqual(content['meta']['children'], {
|
||||
'count': 0,
|
||||
'listing_url': 'http://localhost/admin/api/v2beta/pages/?child_of=16'
|
||||
})
|
||||
|
||||
# Check the parent field
|
||||
self.assertIn('parent', content['meta'])
|
||||
self.assertIsInstance(content['meta']['parent'], dict)
|
||||
self.assertEqual(set(content['meta']['parent'].keys()), {'id', 'meta'})
|
||||
self.assertEqual(content['meta']['parent']['id'], 5)
|
||||
self.assertIsInstance(content['meta']['parent']['meta'], dict)
|
||||
self.assertEqual(set(content['meta']['parent']['meta'].keys()), {'type', 'detail_url', 'html_url'})
|
||||
self.assertEqual(content['meta']['parent']['meta']['type'], 'demosite.BlogIndexPage')
|
||||
self.assertEqual(content['meta']['parent']['meta']['detail_url'], 'http://localhost/admin/api/v2beta/pages/5/')
|
||||
self.assertEqual(content['meta']['parent']['meta']['html_url'], 'http://localhost/blog-index/')
|
||||
|
||||
# Check that the custom fields are included
|
||||
self.assertIn('date', content)
|
||||
self.assertIn('body', content)
|
||||
self.assertIn('tags', content)
|
||||
self.assertIn('feed_image', content)
|
||||
self.assertIn('related_links', content)
|
||||
self.assertIn('carousel_items', content)
|
||||
|
||||
# Check that the date was serialised properly
|
||||
self.assertEqual(content['date'], '2013-12-02')
|
||||
|
||||
# Check that the tags were serialised properly
|
||||
self.assertEqual(content['tags'], ['bird', 'wagtail'])
|
||||
|
||||
# Check that the feed image was serialised properly
|
||||
self.assertIsInstance(content['feed_image'], dict)
|
||||
self.assertEqual(set(content['feed_image'].keys()), {'id', 'meta'})
|
||||
self.assertEqual(content['feed_image']['id'], 7)
|
||||
self.assertIsInstance(content['feed_image']['meta'], dict)
|
||||
self.assertEqual(set(content['feed_image']['meta'].keys()), {'type', 'detail_url'})
|
||||
self.assertEqual(content['feed_image']['meta']['type'], 'wagtailimages.Image')
|
||||
self.assertEqual(content['feed_image']['meta']['detail_url'], 'http://localhost/admin/api/v2beta/images/7/')
|
||||
|
||||
# Check that the child relations were serialised properly
|
||||
self.assertEqual(content['related_links'], [])
|
||||
for carousel_item in content['carousel_items']:
|
||||
self.assertEqual(set(carousel_item.keys()), {'id', 'meta', 'embed_url', 'link', 'caption', 'image'})
|
||||
self.assertEqual(set(carousel_item['meta'].keys()), {'type'})
|
||||
|
||||
# Check the type info
|
||||
self.assertIsInstance(content['__types'], dict)
|
||||
self.assertEqual(set(content['__types'].keys()), {
|
||||
'demosite.BlogIndexPage',
|
||||
'demosite.BlogEntryPageCarouselItem',
|
||||
'demosite.BlogEntryPage',
|
||||
'wagtailimages.Image'
|
||||
})
|
||||
self.assertEqual(set(content['__types']['demosite.BlogIndexPage'].keys()), {'verbose_name', 'verbose_name_plural'})
|
||||
self.assertEqual(content['__types']['demosite.BlogIndexPage']['verbose_name'], 'blog index page')
|
||||
self.assertEqual(content['__types']['demosite.BlogIndexPage']['verbose_name_plural'], 'blog index pages')
|
||||
|
||||
def test_field_ordering(self):
|
||||
# Need to override this as the admin API has a __types field
|
||||
|
||||
response = self.get_response(16)
|
||||
|
||||
# Will crash if the JSON is invalid
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
|
||||
# Test field order
|
||||
content = json.JSONDecoder(object_pairs_hook=collections.OrderedDict).decode(response.content.decode('UTF-8'))
|
||||
field_order = [
|
||||
'id',
|
||||
'meta',
|
||||
'title',
|
||||
'body',
|
||||
'tags',
|
||||
'date',
|
||||
'feed_image',
|
||||
'carousel_items',
|
||||
'related_links',
|
||||
'__types',
|
||||
]
|
||||
self.assertEqual(list(content.keys()), field_order)
|
||||
|
||||
def test_meta_status_draft(self):
|
||||
# Unpublish the page
|
||||
Page.objects.get(id=16).unpublish()
|
||||
|
||||
response = self.get_response(16)
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
|
||||
self.assertIn('status', content['meta'])
|
||||
self.assertEqual(content['meta']['status'], {
|
||||
'status': 'draft',
|
||||
'live': False,
|
||||
'has_unpublished_changes': True
|
||||
})
|
||||
|
||||
def test_meta_status_live_draft(self):
|
||||
# Save revision without republish
|
||||
Page.objects.get(id=16).save_revision()
|
||||
|
||||
response = self.get_response(16)
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
|
||||
self.assertIn('status', content['meta'])
|
||||
self.assertEqual(content['meta']['status'], {
|
||||
'status': 'live + draft',
|
||||
'live': True,
|
||||
'has_unpublished_changes': True
|
||||
})
|
||||
|
||||
def test_meta_status_scheduled(self):
|
||||
# Unpublish and save revision with go live date in the future
|
||||
Page.objects.get(id=16).unpublish()
|
||||
tomorrow = timezone.now() + datetime.timedelta(days=1)
|
||||
Page.objects.get(id=16).save_revision(approved_go_live_at=tomorrow)
|
||||
|
||||
response = self.get_response(16)
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
|
||||
self.assertIn('status', content['meta'])
|
||||
self.assertEqual(content['meta']['status'], {
|
||||
'status': 'scheduled',
|
||||
'live': False,
|
||||
'has_unpublished_changes': True
|
||||
})
|
||||
|
||||
def test_meta_status_expired(self):
|
||||
# Unpublish and set expired flag
|
||||
Page.objects.get(id=16).unpublish()
|
||||
Page.objects.filter(id=16).update(expired=True)
|
||||
|
||||
response = self.get_response(16)
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
|
||||
self.assertIn('status', content['meta'])
|
||||
self.assertEqual(content['meta']['status'], {
|
||||
'status': 'expired',
|
||||
'live': False,
|
||||
'has_unpublished_changes': True
|
||||
})
|
||||
|
||||
def test_meta_children_for_parent(self):
|
||||
# Homepage should have children
|
||||
response = self.get_response(2)
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
|
||||
self.assertIn('children', content['meta'])
|
||||
self.assertEqual(content['meta']['children'], {
|
||||
'count': 5,
|
||||
'listing_url': 'http://localhost/admin/api/v2beta/pages/?child_of=2'
|
||||
})
|
||||
|
||||
def test_meta_descendants(self):
|
||||
# Homepage should have children
|
||||
response = self.get_response(2)
|
||||
content = json.loads(response.content.decode('UTF-8'))
|
||||
|
||||
self.assertIn('descendants', content['meta'])
|
||||
self.assertEqual(content['meta']['descendants'], {
|
||||
'count': 18,
|
||||
'listing_url': 'http://localhost/admin/api/v2beta/pages/?descendant_of=2'
|
||||
})
|
||||
|
||||
|
||||
class TestAdminPageDetailWithStreamField(AdminAPITestCase):
|
||||
fixtures = ['test.json']
|
||||
|
||||
def setUp(self):
|
||||
super(TestAdminPageDetailWithStreamField, self).setUp()
|
||||
|
||||
self.homepage = Page.objects.get(url_path='/home/')
|
||||
|
||||
def make_stream_page(self, body):
|
||||
stream_page = StreamPage(
|
||||
title='stream page',
|
||||
slug='stream-page',
|
||||
body=body
|
||||
)
|
||||
return self.homepage.add_child(instance=stream_page)
|
||||
|
||||
def test_can_fetch_streamfield_content(self):
|
||||
stream_page = self.make_stream_page('[{"type": "text", "value": "foo"}]')
|
||||
|
||||
response_url = reverse('wagtailadmin_api_v1:pages:detail', args=(stream_page.id, ))
|
||||
response = self.client.get(response_url)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response['content-type'], 'application/json')
|
||||
|
||||
content = json.loads(response.content.decode('utf-8'))
|
||||
|
||||
self.assertIn('id', content)
|
||||
self.assertEqual(content['id'], stream_page.id)
|
||||
self.assertIn('body', content)
|
||||
self.assertEqual(content['body'], [{'type': 'text', 'value': 'foo'}])
|
||||
|
||||
def test_image_block(self):
|
||||
stream_page = self.make_stream_page('[{"type": "image", "value": 1}]')
|
||||
|
||||
response_url = reverse('wagtailadmin_api_v1:pages:detail', args=(stream_page.id, ))
|
||||
response = self.client.get(response_url)
|
||||
content = json.loads(response.content.decode('utf-8'))
|
||||
|
||||
# ForeignKeys in a StreamField shouldn't be translated into dictionary representation
|
||||
self.assertEqual(content['body'], [{'type': 'image', 'value': 1}])
|
||||
10
wagtail/wagtailadmin/tests/api/utils.py
Normal file
10
wagtail/wagtailadmin/tests/api/utils.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from wagtail.tests.utils import WagtailTestUtils
|
||||
|
||||
|
||||
class AdminAPITestCase(TestCase, WagtailTestUtils):
|
||||
def setUp(self):
|
||||
self.login()
|
||||
|
|
@ -5,6 +5,7 @@ from wagtail.wagtailadmin.urls import pages as wagtailadmin_pages_urls
|
|||
from wagtail.wagtailadmin.urls import collections as wagtailadmin_collections_urls
|
||||
from wagtail.wagtailadmin.urls import password_reset as wagtailadmin_password_reset_urls
|
||||
from wagtail.wagtailadmin.views import account, chooser, home, pages, tags, userbar
|
||||
from wagtail.wagtailadmin.api import urls as api_urls
|
||||
from wagtail.wagtailcore import hooks
|
||||
from wagtail.utils.urlpatterns import decorate_urlpatterns
|
||||
from wagtail.wagtailadmin.decorators import require_admin_access
|
||||
|
|
@ -13,6 +14,8 @@ from wagtail.wagtailadmin.decorators import require_admin_access
|
|||
urlpatterns = [
|
||||
url(r'^$', home.home, name='wagtailadmin_home'),
|
||||
|
||||
url(r'api/', include(api_urls)),
|
||||
|
||||
url(r'^failwhale/$', home.error_test, name='wagtailadmin_error_test'),
|
||||
|
||||
url(r'^explorer-nav/$', pages.explorer_nav, name='wagtailadmin_explorer_nav'),
|
||||
|
|
|
|||
0
wagtail/wagtaildocs/api/admin/__init__.py
Normal file
0
wagtail/wagtaildocs/api/admin/__init__.py
Normal file
7
wagtail/wagtaildocs/api/admin/endpoints.py
Normal file
7
wagtail/wagtaildocs/api/admin/endpoints.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from ..v2.endpoints import DocumentsAPIEndpoint
|
||||
|
||||
|
||||
class DocumentsAdminAPIEndpoint(DocumentsAPIEndpoint):
|
||||
pass
|
||||
|
|
@ -12,6 +12,7 @@ from wagtail.wagtailadmin.search import SearchArea
|
|||
from wagtail.wagtailadmin.site_summary import SummaryItem
|
||||
from wagtail.wagtailcore import hooks
|
||||
from wagtail.wagtaildocs import admin_urls
|
||||
from wagtail.wagtaildocs.api.admin.endpoints import DocumentsAdminAPIEndpoint
|
||||
from wagtail.wagtaildocs.forms import GroupDocumentPermissionFormSet
|
||||
from wagtail.wagtaildocs.models import get_document_model
|
||||
from wagtail.wagtaildocs.permissions import permission_policy
|
||||
|
|
@ -25,6 +26,11 @@ def register_admin_urls():
|
|||
]
|
||||
|
||||
|
||||
@hooks.register('construct_admin_api')
|
||||
def construct_admin_api(router):
|
||||
router.register_endpoint('documents', DocumentsAdminAPIEndpoint)
|
||||
|
||||
|
||||
class DocumentsMenuItem(MenuItem):
|
||||
def is_shown(self, request):
|
||||
return permission_policy.user_has_any_permission(
|
||||
|
|
|
|||
0
wagtail/wagtailimages/api/admin/__init__.py
Normal file
0
wagtail/wagtailimages/api/admin/__init__.py
Normal file
18
wagtail/wagtailimages/api/admin/endpoints.py
Normal file
18
wagtail/wagtailimages/api/admin/endpoints.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from ..v2.endpoints import ImagesAPIEndpoint
|
||||
from .serializers import AdminImageSerializer
|
||||
|
||||
|
||||
class ImagesAdminAPIEndpoint(ImagesAPIEndpoint):
|
||||
base_serializer_class = AdminImageSerializer
|
||||
|
||||
extra_body_fields = ImagesAPIEndpoint.extra_body_fields + [
|
||||
'thumbnail',
|
||||
]
|
||||
|
||||
default_fields = ImagesAPIEndpoint.default_fields + [
|
||||
'width',
|
||||
'height',
|
||||
'thumbnail',
|
||||
]
|
||||
53
wagtail/wagtailimages/api/admin/serializers.py
Normal file
53
wagtail/wagtailimages/api/admin/serializers.py
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
from rest_framework.fields import Field
|
||||
|
||||
from ...models import SourceImageIOError
|
||||
from ..v2.serializers import ImageSerializer
|
||||
|
||||
|
||||
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)
|
||||
|
|
@ -12,6 +12,7 @@ from wagtail.wagtailadmin.search import SearchArea
|
|||
from wagtail.wagtailadmin.site_summary import SummaryItem
|
||||
from wagtail.wagtailcore import hooks
|
||||
from wagtail.wagtailimages import admin_urls, image_operations
|
||||
from wagtail.wagtailimages.api.admin.endpoints import ImagesAdminAPIEndpoint
|
||||
from wagtail.wagtailimages.forms import GroupImagePermissionFormSet
|
||||
from wagtail.wagtailimages.models import get_image_model
|
||||
from wagtail.wagtailimages.permissions import permission_policy
|
||||
|
|
@ -25,6 +26,11 @@ def register_admin_urls():
|
|||
]
|
||||
|
||||
|
||||
@hooks.register('construct_admin_api')
|
||||
def construct_admin_api(router):
|
||||
router.register_endpoint('images', ImagesAdminAPIEndpoint)
|
||||
|
||||
|
||||
class ImagesMenuItem(MenuItem):
|
||||
def is_shown(self, request):
|
||||
return permission_policy.user_has_any_permission(
|
||||
|
|
|
|||
Loading…
Reference in a new issue