plone.restapi/plone-only-login: Merge pull request #379 from plone/root-reordering
GitHub <jenkins-z4DKO/[email protected]>
| Newsgroups | gmane.comp.web.zope.plone.cvs |
|---|---|
| Message-ID | <[email protected]> |
Repository: plone.restapi Branch: refs/heads/plone-only-login Date: 2017-07-14T11:34:31+02:00 Author: Víctor Fernández de Alba (sneridagh) <[email protected]> Commit: https://github.com/plone/plone.restapi/commit/b0d2a87641419831ccc71447165dda770e88fcad Merge pull request #379 from plone/root-reordering Site root reordering + set layout Files changed: A src/plone/restapi/deserializer/site.py A src/plone/restapi/tests/mixin_ordering.py A src/plone/restapi/tests/test_site_deserializer.py M CHANGES.rst M docs/source/content.rst M src/plone/restapi/deserializer/configure.zcml M src/plone/restapi/deserializer/mixins.py M src/plone/restapi/services/content/configure.zcml M src/plone/restapi/tests/test_atcontent_deserializer.py M src/plone/restapi/tests/test_dxcontent_deserializer.py diff --git a/CHANGES.rst b/CHANGES.rst index 66f57a27..f99d1992 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,6 +8,12 @@ Changelog and breadcrumbs as components. [timo] +- Support content reordering on the site root. + [jaroel] + +- Support setting Layout on the site root. + [jaroel] + 1.0a19 (2017-06-25) ------------------- diff --git a/docs/source/content.rst b/docs/source/content.rst index 396f0cf0..5595bc70 100644 --- a/docs/source/content.rst +++ b/docs/source/content.rst @@ -328,8 +328,12 @@ The resources contained within a resource can be reordered using the `ordering` Use the `obj_id` subkey to specify which resource to reorder. The subkey `delta` can be 'top', 'bottom', or a negative or positive integer for moving up or down. + Reordering resources within a subset of resources can be done using the `subset_ids` subkey. + A response 400 BadRequest with a message 'Client/server ordering mismatch' will be returned if the value is not in the same order as serverside. +A response 400 BadRequest with a message 'Content ordering is not supported by this resource' will be returned if the container does not support ordering. + .. http:example:: curl httpie python-requests :request: _json/content_reorder.req diff --git a/src/plone/restapi/deserializer/configure.zcml b/src/plone/restapi/deserializer/configure.zcml index 006d4482..21fcf196 100644 --- a/src/plone/restapi/deserializer/configure.zcml +++ b/src/plone/restapi/deserializer/configure.zcml @@ -3,6 +3,8 @@ xmlns:zcml="http://namespaces.zope.org/zcml" i18n_domain="plone.restapi"> + <adapter factory=".site.DeserializeSiteRootFromJson" /> + <adapter factory=".dxcontent.DeserializeFromJson" /> <adapter factory=".dxfields.DefaultFieldDeserializer" /> <adapter factory=".dxfields.DatetimeFieldDeserializer" /> diff --git a/src/plone/restapi/deserializer/mixins.py b/src/plone/restapi/deserializer/mixins.py index c72f7ef5..98739ed3 100644 --- a/src/plone/restapi/deserializer/mixins.py +++ b/src/plone/restapi/deserializer/mixins.py @@ -12,11 +12,20 @@ def handle_ordering(self, data): delta = data['ordering']['delta'] subset_ids = data['ordering'].get('subset_ids') + # The REST api returns only content items and a Zope resource + # may contain non-content items. We need to set the subset_ids + # so we'll move items relative to each other. + if not subset_ids: + subset_ids = self.context.contentIds() + self.reorderItems(obj_id, delta, subset_ids) - def reorderItems(self, obj_id, delta, subset_ids=None): + def reorderItems(self, obj_id, delta, subset_ids): # Based on wildcard.foldercontents.viewsItemOrder ordering = self.getOrdering() + if ordering is None: + msg = 'Content ordering is not supported by this resource' + raise BadRequest(msg) # Make sure we're seeing the same order as the client is. if subset_ids: @@ -38,7 +47,7 @@ def reorderItems(self, obj_id, delta, subset_ids=None): def getOrdering(self): if IPloneSiteRoot.providedBy(self.context): return self.context - else: + elif getattr(self.context, 'getOrdering', None): ordering = self.context.getOrdering() if not IExplicitOrdering.providedBy(ordering): return None diff --git a/src/plone/restapi/deserializer/site.py b/src/plone/restapi/deserializer/site.py new file mode 100644 index 00000000..7b749383 --- /dev/null +++ b/src/plone/restapi/deserializer/site.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +from Products.CMFPlone.interfaces import IPloneSiteRoot +from plone.restapi.deserializer import json_body +from plone.restapi.interfaces import IDeserializeFromJson +from zope.component import adapter +from zope.interface import implementer + +from plone.restapi.deserializer.mixins import OrderingMixin +from zope.publisher.interfaces import IRequest + + +@implementer(IDeserializeFromJson) +@adapter(IPloneSiteRoot, IRequest) +class DeserializeSiteRootFromJson(OrderingMixin, object): + """JSON deserializer for the Plone site root + """ + + def __init__(self, context, request): + self.context = context + self.request = request + + def __call__(self, validate_all=False): + # Currently we only do layout and ordering, as the plone site root + # has no schema or something like that. + data = json_body(self.request) + + if 'layout' in data: + layout = data['layout'] + self.context.setLayout(layout) + + # OrderingMixin + if 'ordering' in data and 'subset_ids' not in data['ordering']: + data['ordering']['subset_ids'] = self.context.contentIds() + self.handle_ordering(data) + + return self.context diff --git a/src/plone/restapi/services/content/configure.zcml b/src/plone/restapi/services/content/configure.zcml index 9e338f5f..f3cbde14 100644 --- a/src/plone/restapi/services/content/configure.zcml +++ b/src/plone/restapi/services/content/configure.zcml @@ -19,6 +19,13 @@ <plone:service method="PATCH" + for="Products.CMFPlone.interfaces.IPloneSiteRoot" + factory=".update.ContentPatch" + permission="cmf.ModifyPortalContent" + /> + + <plone:service + method="PATCH" for="Products.CMFCore.interfaces.IContentish" factory=".update.ContentPatch" permission="cmf.ModifyPortalContent" diff --git a/src/plone/restapi/tests/mixin_ordering.py b/src/plone/restapi/tests/mixin_ordering.py new file mode 100644 index 00000000..180cc0e8 --- /dev/null +++ b/src/plone/restapi/tests/mixin_ordering.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +import json + +from zExceptions import BadRequest + + +class OrderingMixin(): + # This assumes there is a self.folder with 9 documents in it. + + def test_reorder(self): + # We run all this in one test, because of dependend ordering. + # initial situation + self.assertEquals( + ['doc1', 'doc2', 'doc3', 'doc4', 'doc5', 'doc6', 'doc7', 'doc8', 'doc9', ], # noqa + self.folder.contentIds() + ) + + # Normal + # Move to top + data = {'ordering': {'delta': 'top', 'obj_id': 'doc9'}} + self.deserialize(body=json.dumps(data), context=self.folder) + self.assertEquals( + ['doc9', 'doc1', 'doc2', 'doc3', 'doc4', 'doc5', 'doc6', 'doc7', 'doc8', ], # noqa + self.folder.contentIds() + ) + + # Move to bottom + data = {'ordering': {'delta': 'bottom', 'obj_id': 'doc9'}} + self.deserialize(body=json.dumps(data), context=self.folder) + self.assertEquals( + ['doc1', 'doc2', 'doc3', 'doc4', 'doc5', 'doc6', 'doc7', 'doc8', 'doc9', ], # noqa + self.folder.contentIds() + ) + + # Delta up + data = {'ordering': {'delta': -2, 'obj_id': 'doc5'}} + self.deserialize(body=json.dumps(data), context=self.folder) + self.assertEquals( + ['doc1', 'doc2', 'doc5', 'doc3', 'doc4', 'doc6', 'doc7', 'doc8', 'doc9', ], # noqa + self.folder.contentIds() + ) + + # Delta down + data = {'ordering': {'delta': 2, 'obj_id': 'doc6'}} + self.deserialize(body=json.dumps(data), context=self.folder) + self.assertEquals( + ['doc1', 'doc2', 'doc5', 'doc3', 'doc4', 'doc7', 'doc8', 'doc6', 'doc9', ], # noqa + self.folder.contentIds() + ) + + # subset ids + # Move to top + data = {'ordering': {'delta': 'top', 'obj_id': 'doc8', 'subset_ids': ['doc2', 'doc3', 'doc8']}} # noqa + self.deserialize(body=json.dumps(data), context=self.folder) + self.assertEquals( + ['doc1', 'doc8', 'doc5', 'doc2', 'doc4', 'doc7', 'doc3', 'doc6', 'doc9'], # noqa + self.folder.contentIds() + ) + + # Move to bottom + data = {'ordering': {'delta': 'bottom', 'obj_id': 'doc8', 'subset_ids': ['doc8', 'doc2', 'doc3']}} # noqa + self.deserialize(body=json.dumps(data), context=self.folder) + self.assertEquals( + ['doc1', 'doc2', 'doc5', 'doc3', 'doc4', 'doc7', 'doc8', 'doc6', 'doc9'], # noqa + self.folder.contentIds() + ) + + # Delta up + data = {'ordering': {'delta': -1, 'obj_id': 'doc8', 'subset_ids': ['doc2', 'doc3', 'doc8']}} # noqa + self.deserialize(body=json.dumps(data), context=self.folder) + self.assertEquals( + ['doc1', 'doc2', 'doc5', 'doc8', 'doc4', 'doc7', 'doc3', 'doc6', 'doc9'], # noqa + self.folder.contentIds() + ) + + # Delta down + data = {'ordering': {'delta': 1, 'obj_id': 'doc2', 'subset_ids': ['doc2', 'doc8', 'doc3']}} # noqa + self.deserialize(body=json.dumps(data), context=self.folder) + self.assertEquals( + ['doc1', 'doc8', 'doc5', 'doc2', 'doc4', 'doc7', 'doc3', 'doc6', 'doc9'], # noqa + self.folder.contentIds() + ) + + def test_reorder_subsetids(self): + # sanity check, initial situation + self.assertEquals( + ['doc1', 'doc2', 'doc3', 'doc4', 'doc5', 'doc6', 'doc7', 'doc8', 'doc9'], # noqa + self.folder.contentIds() + ) + + data = {'ordering': {'delta': 1, 'obj_id': 'doc8', 'subset_ids': ['doc2', 'doc8', 'doc6']}} # noqa + + with self.assertRaises(BadRequest) as cm: + self.deserialize(body=json.dumps(data), context=self.folder) + + self.assertEquals( + u'Client/server ordering mismatch', + cm.exception.message + ) diff --git a/src/plone/restapi/tests/test_atcontent_deserializer.py b/src/plone/restapi/tests/test_atcontent_deserializer.py index 2a7d5a2d..a97f3cbd 100644 --- a/src/plone/restapi/tests/test_atcontent_deserializer.py +++ b/src/plone/restapi/tests/test_atcontent_deserializer.py @@ -7,6 +7,7 @@ from plone.restapi.deserializer.atcontent import ValidationRequest from plone.restapi.interfaces import IDeserializeFromJson from plone.restapi.testing import PLONE_RESTAPI_AT_INTEGRATION_TESTING +from plone.restapi.tests.mixin_ordering import OrderingMixin from zExceptions import BadRequest from zope.component import getMultiAdapter from zope.component import provideHandler @@ -17,11 +18,10 @@ from Products.Archetypes.interfaces import IObjectPreValidation -import json import unittest -class TestATContentDeserializer(unittest.TestCase): +class TestATContentDeserializer(unittest.TestCase, OrderingMixin): layer = PLONE_RESTAPI_AT_INTEGRATION_TESTING @@ -152,97 +152,6 @@ def test_set_layout(self): self.deserialize(body='{"layout": "my_new_layout"}') self.assertEquals('my_new_layout', self.doc1.getLayout()) - def test_reorder(self): - # We run all this in one test, because of dependend ordering. - # initial situation - self.assertEquals( - ['doc1', 'doc2', 'doc3', 'doc4', 'doc5', 'doc6', 'doc7', 'doc8', 'doc9', ], # noqa - self.folder.objectIds() - ) - - # Normal - # Move to top - data = {'ordering': {'delta': 'top', 'obj_id': 'doc9'}} - self.deserialize(body=json.dumps(data), context=self.folder) - self.assertEquals( - ['doc9', 'doc1', 'doc2', 'doc3', 'doc4', 'doc5', 'doc6', 'doc7', 'doc8', ], # noqa - self.folder.objectIds() - ) - - # Move to bottom - data = {'ordering': {'delta': 'bottom', 'obj_id': 'doc9'}} - self.deserialize(body=json.dumps(data), context=self.folder) - self.assertEquals( - ['doc1', 'doc2', 'doc3', 'doc4', 'doc5', 'doc6', 'doc7', 'doc8', 'doc9', ], # noqa - self.folder.objectIds() - ) - - # Delta up - data = {'ordering': {'delta': -2, 'obj_id': 'doc5'}} - self.deserialize(body=json.dumps(data), context=self.folder) - self.assertEquals( - ['doc1', 'doc2', 'doc5', 'doc3', 'doc4', 'doc6', 'doc7', 'doc8', 'doc9', ], # noqa - self.folder.objectIds() - ) - - # Delta down - data = {'ordering': {'delta': 2, 'obj_id': 'doc6'}} - self.deserialize(body=json.dumps(data), context=self.folder) - self.assertEquals( - ['doc1', 'doc2', 'doc5', 'doc3', 'doc4', 'doc7', 'doc8', 'doc6', 'doc9', ], # noqa - self.folder.objectIds() - ) - - # subset ids - # Move to top - data = {'ordering': {'delta': 'top', 'obj_id': 'doc8', 'subset_ids': ['doc2', 'doc3', 'doc8']}} # noqa - self.deserialize(body=json.dumps(data), context=self.folder) - self.assertEquals( - ['doc1', 'doc8', 'doc5', 'doc2', 'doc4', 'doc7', 'doc3', 'doc6', 'doc9'], # noqa - self.folder.objectIds() - ) - - # Move to bottom - data = {'ordering': {'delta': 'bottom', 'obj_id': 'doc8', 'subset_ids': ['doc8', 'doc2', 'doc3']}} # noqa - self.deserialize(body=json.dumps(data), context=self.folder) - self.assertEquals( - ['doc1', 'doc2', 'doc5', 'doc3', 'doc4', 'doc7', 'doc8', 'doc6', 'doc9'], # noqa - self.folder.objectIds() - ) - - # Delta up - data = {'ordering': {'delta': -1, 'obj_id': 'doc8', 'subset_ids': ['doc2', 'doc3', 'doc8']}} # noqa - self.deserialize(body=json.dumps(data), context=self.folder) - self.assertEquals( - ['doc1', 'doc2', 'doc5', 'doc8', 'doc4', 'doc7', 'doc3', 'doc6', 'doc9'], # noqa - self.folder.objectIds() - ) - - # Delta down - data = {'ordering': {'delta': 1, 'obj_id': 'doc2', 'subset_ids': ['doc2', 'doc8', 'doc3']}} # noqa - self.deserialize(body=json.dumps(data), context=self.folder) - self.assertEquals( - ['doc1', 'doc8', 'doc5', 'doc2', 'doc4', 'doc7', 'doc3', 'doc6', 'doc9'], # noqa - self.folder.objectIds() - ) - - def test_reorder_subsetids(self): - # sanity check, initial situation - self.assertEquals( - ['doc1', 'doc2', 'doc3', 'doc4', 'doc5', 'doc6', 'doc7', 'doc8', 'doc9'], # noqa - self.folder.objectIds() - ) - - data = {'ordering': {'delta': 1, 'obj_id': 'doc8', 'subset_ids': ['doc2', 'doc8', 'doc6']}} # noqa - - with self.assertRaises(BadRequest) as cm: - self.deserialize(body=json.dumps(data), context=self.folder) - - self.assertEquals( - u'Client/server ordering mismatch', - cm.exception.message - ) - class TestValidationRequest(unittest.TestCase): diff --git a/src/plone/restapi/tests/test_dxcontent_deserializer.py b/src/plone/restapi/tests/test_dxcontent_deserializer.py index 7e8dd906..26fd2411 100644 --- a/src/plone/restapi/tests/test_dxcontent_deserializer.py +++ b/src/plone/restapi/tests/test_dxcontent_deserializer.py @@ -6,16 +6,16 @@ from plone.restapi.interfaces import IDeserializeFromJson from plone.restapi.testing import PLONE_RESTAPI_DX_INTEGRATION_TESTING from plone.restapi.tests.dxtypes import ITestAnnotationsBehavior +from plone.restapi.tests.mixin_ordering import OrderingMixin from zExceptions import BadRequest from zope.component import getMultiAdapter from zope.component import provideHandler from zope.lifecycleevent.interfaces import IObjectModifiedEvent -import json import unittest -class TestDXContentDeserializer(unittest.TestCase): +class TestDXContentDeserializer(unittest.TestCase, OrderingMixin): layer = PLONE_RESTAPI_DX_INTEGRATION_TESTING @@ -147,93 +147,3 @@ def test_set_layout(self): self.assertNotEquals(current_layout, "my_new_layout") self.deserialize(body='{"layout": "my_new_layout"}') self.assertEquals('my_new_layout', self.portal.doc1.getLayout()) - - def test_reorder(self): # noqa: ignore=E501 - # We run all this in one test, because of dependend ordering. - # initial situation - self.assertEquals( - ['doc1', 'doc2', 'doc3', 'doc4', 'doc5', 'doc6', 'doc7', 'doc8', 'doc9', ], # noqa - self.folder.objectIds() - ) - - # Normal - # Move to top - data = {'ordering': {'delta': 'top', 'obj_id': 'doc9'}} - self.deserialize(body=json.dumps(data), context=self.folder) - self.assertEquals( - ['doc9', 'doc1', 'doc2', 'doc3', 'doc4', 'doc5', 'doc6', 'doc7', 'doc8', ], # noqa - self.folder.objectIds()) - - # Move to bottom - data = {'ordering': {'delta': 'bottom', 'obj_id': 'doc9'}} - self.deserialize(body=json.dumps(data), context=self.folder) - self.assertEquals( - ['doc1', 'doc2', 'doc3', 'doc4', 'doc5', 'doc6', 'doc7', 'doc8', 'doc9', ], # noqa - self.folder.objectIds() - ) - - # Delta up - data = {'ordering': {'delta': -2, 'obj_id': 'doc5'}} - self.deserialize(body=json.dumps(data), context=self.folder) - self.assertEquals( - ['doc1', 'doc2', 'doc5', 'doc3', 'doc4', 'doc6', 'doc7', 'doc8', 'doc9', ], # noqa - self.folder.objectIds() - ) - - # Delta down - data = {'ordering': {'delta': 2, 'obj_id': 'doc6'}} - self.deserialize(body=json.dumps(data), context=self.folder) - self.assertEquals( - ['doc1', 'doc2', 'doc5', 'doc3', 'doc4', 'doc7', 'doc8', 'doc6', 'doc9', ], # noqa - self.folder.objectIds() - ) - - # subset ids - # Move to top - data = {'ordering': {'delta': 'top', 'obj_id': 'doc8', 'subset_ids': ['doc2', 'doc3', 'doc8']}} # noqa - self.deserialize(body=json.dumps(data), context=self.folder) - self.assertEquals( - ['doc1', 'doc8', 'doc5', 'doc2', 'doc4', 'doc7', 'doc3', 'doc6', 'doc9'], # noqa - self.folder.objectIds() - ) - - # Move to bottom - data = {'ordering': {'delta': 'bottom', 'obj_id': 'doc8', 'subset_ids': ['doc8', 'doc2', 'doc3']}} # noqa - self.deserialize(body=json.dumps(data), context=self.folder) - self.assertEquals( - ['doc1', 'doc2', 'doc5', 'doc3', 'doc4', 'doc7', 'doc8', 'doc6', 'doc9'], # noqa - self.folder.objectIds() - ) - - # Delta up - data = {'ordering': {'delta': -1, 'obj_id': 'doc8', 'subset_ids': ['doc2', 'doc3', 'doc8']}} # noqa - self.deserialize(body=json.dumps(data), context=self.folder) - self.assertEquals( - ['doc1', 'doc2', 'doc5', 'doc8', 'doc4', 'doc7', 'doc3', 'doc6', 'doc9'], # noqa - self.folder.objectIds() - ) - - # Delta down - data = {'ordering': {'delta': 1, 'obj_id': 'doc2', 'subset_ids': ['doc2', 'doc8', 'doc3']}} # noqa - self.deserialize(body=json.dumps(data), context=self.folder) - self.assertEquals( - ['doc1', 'doc8', 'doc5', 'doc2', 'doc4', 'doc7', 'doc3', 'doc6', 'doc9'], # noqa - self.folder.objectIds() - ) - - def test_reorder_subsetids(self): - # sanity check, initial situation - self.assertEquals( - ['doc1', 'doc2', 'doc3', 'doc4', 'doc5', 'doc6', 'doc7', 'doc8', 'doc9'], # noqa - self.folder.objectIds() - ) - - data = {'ordering': {'delta': 1, 'obj_id': 'doc8', 'subset_ids': ['doc2', 'doc8', 'doc6']}} # noqa - - with self.assertRaises(BadRequest) as cm: - self.deserialize(body=json.dumps(data), context=self.folder) - - self.assertEquals( - u'Client/server ordering mismatch', - cm.exception.message - ) diff --git a/src/plone/restapi/tests/test_site_deserializer.py b/src/plone/restapi/tests/test_site_deserializer.py new file mode 100644 index 00000000..4374a46b --- /dev/null +++ b/src/plone/restapi/tests/test_site_deserializer.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +from plone.restapi.interfaces import IDeserializeFromJson +from plone.restapi.testing import PLONE_RESTAPI_DX_INTEGRATION_TESTING +from plone.restapi.tests.mixin_ordering import OrderingMixin +from zope.component import getMultiAdapter + +import unittest + + +class TestDXContentDeserializer(unittest.TestCase, OrderingMixin): + + layer = PLONE_RESTAPI_DX_INTEGRATION_TESTING + + def setUp(self): + self.portal = self.layer['portal'] + self.request = self.layer['request'] + + # ordering setup + self.folder = self.portal + + for x in range(1, 10): + self.folder.invokeFactory( + 'Document', + id='doc' + str(x), + title='Test doc ' + str(x) + ) + + def deserialize(self, body='{}', validate_all=False, context=None): + context = context or self.portal + self.request['BODY'] = body + deserializer = getMultiAdapter((context, self.request), + IDeserializeFromJson) + return deserializer(validate_all=validate_all) + + def test_set_layout(self): + current_layout = self.portal.getLayout() + self.assertNotEquals(current_layout, "my_new_layout") + self.deserialize(body='{"layout": "my_new_layout"}') + self.assertEquals('my_new_layout', self.portal.getLayout()) ------------------------------------------------------------------------------ Check out the vibrant tech community on one of the world's most engaging tech sites, Slashdot.org! http://sdm.link/slashdot