plone.restapi/plone-only-login: Support content reording on site root + support setting layo
Roel Bruggink <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-01T14:28:26+02:00 Author: Roel Bruggink (jaroel) <roel-Ttk/PUy0oENmR6Xm/[email protected]> Commit: https://github.com/plone/plone.restapi/commit/f27564f4ace3872a539042c5ae0c61749a460343 Support content reording on site root + support setting layout on site root 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 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/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..e1336ded --- /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, Interface + +from plone.restapi.deserializer.mixins import OrderingMixin +from zope.publisher.interfaces import IRequest + + +@implementer(IDeserializeFromJson) +@adapter(IPloneSiteRoot, Interface) +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 not 'subset_ids' 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..dcd9c532 --- /dev/null +++ b/src/plone/restapi/tests/mixin_ordering.py @@ -0,0 +1,98 @@ +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 + ) \ No newline at end of file diff --git a/src/plone/restapi/tests/test_atcontent_deserializer.py b/src/plone/restapi/tests/test_atcontent_deserializer.py index 2a7d5a2d..1a8fe094 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 @@ -21,7 +22,7 @@ import unittest -class TestATContentDeserializer(unittest.TestCase): +class TestATContentDeserializer(unittest.TestCase, OrderingMixin): layer = PLONE_RESTAPI_AT_INTEGRATION_TESTING @@ -152,96 +153,7 @@ 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..264691c4 100644 --- a/src/plone/restapi/tests/test_dxcontent_deserializer.py +++ b/src/plone/restapi/tests/test_dxcontent_deserializer.py @@ -6,6 +6,7 @@ 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 @@ -15,7 +16,7 @@ import unittest -class TestDXContentDeserializer(unittest.TestCase): +class TestDXContentDeserializer(unittest.TestCase, OrderingMixin): layer = PLONE_RESTAPI_DX_INTEGRATION_TESTING @@ -147,93 +148,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..ad17514d --- /dev/null +++ b/src/plone/restapi/tests/test_site_deserializer.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +from plone.app.testing import TEST_USER_ID +from plone.app.testing import setRoles +from plone.dexterity.interfaces import IDexterityItem +from plone.restapi.exceptions import DeserializationError +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, 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