plone.restapi/plone-only-login: Make breadcrumbs component expandable

Thomas Buchberger <jenkins-z4DKO/[email protected]> Wed, 26 Jul 2017 07:45:36 -0700 (PDT)
Newsgroups gmane.comp.web.zope.plone.cvs
Message-ID <[email protected]>
Repository: plone.restapi
Branch: refs/heads/plone-only-login
Date: 2017-07-25T11:48:31+02:00
Author: Thomas Buchberger (buchi) <t.buchberger-/8aE7x569Db/[email protected]>
Commit: https://github.com/plone/plone.restapi/commit/526b397c9c2562072e55a056d01d16f24d1b7568

Make breadcrumbs component expandable

Files changed:
A src/plone/restapi/services/components/breadcrumbs.py
M docs/source/_json/collection.resp
M docs/source/_json/content_get.resp
M docs/source/_json/content_post.resp
M docs/source/_json/document.resp
M docs/source/_json/event.resp
M docs/source/_json/file.resp
M docs/source/_json/folder.resp
M docs/source/_json/image.resp
M docs/source/_json/jwt_logged_in.resp
M docs/source/_json/link.resp
M docs/source/_json/newsitem.resp
M docs/source/_json/siteroot.resp
M src/plone/restapi/serializer/expansion.py
M src/plone/restapi/services/components/configure.zcml
M src/plone/restapi/services/components/get.py
M src/plone/restapi/tests/test_components.py

diff --git a/docs/source/_json/collection.resp b/docs/source/_json/collection.resp
index ac162329..7640e732 100644
--- a/docs/source/_json/collection.resp
+++ b/docs/source/_json/collection.resp
@@ -3,6 +3,9 @@ Content-Type: application/json
 
 {
   "@components": {
+    "breadcrumbs": {
+      "@id": "http://localhost:55001/plone/collection/@components/breadcrumbs"
+    }, 
     "navigation": {
       "@id": "http://localhost:55001/plone/collection/@components/navigation"
     }
diff --git a/docs/source/_json/content_get.resp b/docs/source/_json/content_get.resp
index ad1e0345..2a7b48ee 100644
--- a/docs/source/_json/content_get.resp
+++ b/docs/source/_json/content_get.resp
@@ -3,6 +3,9 @@ Content-Type: application/json
 
 {
   "@components": {
+    "breadcrumbs": {
+      "@id": "http://localhost:55001/plone/folder/my-document/@components/breadcrumbs"
+    }, 
     "navigation": {
       "@id": "http://localhost:55001/plone/folder/my-document/@components/navigation"
     }
diff --git a/docs/source/_json/content_post.resp b/docs/source/_json/content_post.resp
index 4cde4d25..36da1d20 100644
--- a/docs/source/_json/content_post.resp
+++ b/docs/source/_json/content_post.resp
@@ -4,6 +4,9 @@ Location: http://localhost:55001/plone/folder/my-document
 
 {
   "@components": {
+    "breadcrumbs": {
+      "@id": "http://localhost:55001/plone/folder/my-document/@components/breadcrumbs"
+    }, 
     "navigation": {
       "@id": "http://localhost:55001/plone/folder/my-document/@components/navigation"
     }
diff --git a/docs/source/_json/document.resp b/docs/source/_json/document.resp
index 87a16856..e3aadc15 100644
--- a/docs/source/_json/document.resp
+++ b/docs/source/_json/document.resp
@@ -3,6 +3,9 @@ Content-Type: application/json
 
 {
   "@components": {
+    "breadcrumbs": {
+      "@id": "http://localhost:55001/plone/front-page/@components/breadcrumbs"
+    }, 
     "navigation": {
       "@id": "http://localhost:55001/plone/front-page/@components/navigation"
     }
diff --git a/docs/source/_json/event.resp b/docs/source/_json/event.resp
index d749122d..974db379 100644
--- a/docs/source/_json/event.resp
+++ b/docs/source/_json/event.resp
@@ -3,6 +3,9 @@ Content-Type: application/json
 
 {
   "@components": {
+    "breadcrumbs": {
+      "@id": "http://localhost:55001/plone/event/@components/breadcrumbs"
+    }, 
     "navigation": {
       "@id": "http://localhost:55001/plone/event/@components/navigation"
     }
diff --git a/docs/source/_json/file.resp b/docs/source/_json/file.resp
index ed46fc3c..515cfeb8 100644
--- a/docs/source/_json/file.resp
+++ b/docs/source/_json/file.resp
@@ -3,6 +3,9 @@ Content-Type: application/json
 
 {
   "@components": {
+    "breadcrumbs": {
+      "@id": "http://localhost:55001/plone/file/@components/breadcrumbs"
+    }, 
     "navigation": {
       "@id": "http://localhost:55001/plone/file/@components/navigation"
     }
diff --git a/docs/source/_json/folder.resp b/docs/source/_json/folder.resp
index 19685167..4a12e3eb 100644
--- a/docs/source/_json/folder.resp
+++ b/docs/source/_json/folder.resp
@@ -3,6 +3,9 @@ Content-Type: application/json
 
 {
   "@components": {
+    "breadcrumbs": {
+      "@id": "http://localhost:55001/plone/folder/@components/breadcrumbs"
+    }, 
     "navigation": {
       "@id": "http://localhost:55001/plone/folder/@components/navigation"
     }
diff --git a/docs/source/_json/image.resp b/docs/source/_json/image.resp
index 4d2b0751..df1bd765 100644
--- a/docs/source/_json/image.resp
+++ b/docs/source/_json/image.resp
@@ -3,6 +3,9 @@ Content-Type: application/json
 
 {
   "@components": {
+    "breadcrumbs": {
+      "@id": "http://localhost:55001/plone/image/@components/breadcrumbs"
+    }, 
     "navigation": {
       "@id": "http://localhost:55001/plone/image/@components/navigation"
     }
diff --git a/docs/source/_json/jwt_logged_in.resp b/docs/source/_json/jwt_logged_in.resp
index 331ee0c9..d9a441ca 100644
--- a/docs/source/_json/jwt_logged_in.resp
+++ b/docs/source/_json/jwt_logged_in.resp
@@ -3,6 +3,9 @@ Content-Type: application/json
 
 {
   "@components": {
+    "breadcrumbs": {
+      "@id": "http://localhost:55001/plone/@components/breadcrumbs"
+    }, 
     "navigation": {
       "@id": "http://localhost:55001/plone/@components/navigation"
     }
diff --git a/docs/source/_json/link.resp b/docs/source/_json/link.resp
index 05360f3e..6eba0e69 100644
--- a/docs/source/_json/link.resp
+++ b/docs/source/_json/link.resp
@@ -3,6 +3,9 @@ Content-Type: application/json
 
 {
   "@components": {
+    "breadcrumbs": {
+      "@id": "http://localhost:55001/plone/link/@components/breadcrumbs"
+    }, 
     "navigation": {
       "@id": "http://localhost:55001/plone/link/@components/navigation"
     }
diff --git a/docs/source/_json/newsitem.resp b/docs/source/_json/newsitem.resp
index b0013e66..ae9f6d09 100644
--- a/docs/source/_json/newsitem.resp
+++ b/docs/source/_json/newsitem.resp
@@ -3,6 +3,9 @@ Content-Type: application/json
 
 {
   "@components": {
+    "breadcrumbs": {
+      "@id": "http://localhost:55001/plone/newsitem/@components/breadcrumbs"
+    }, 
     "navigation": {
       "@id": "http://localhost:55001/plone/newsitem/@components/navigation"
     }
diff --git a/docs/source/_json/siteroot.resp b/docs/source/_json/siteroot.resp
index 46705593..30894897 100644
--- a/docs/source/_json/siteroot.resp
+++ b/docs/source/_json/siteroot.resp
@@ -3,6 +3,9 @@ Content-Type: application/json
 
 {
   "@components": {
+    "breadcrumbs": {
+      "@id": "http://localhost:55001/plone/@components/breadcrumbs"
+    }, 
     "navigation": {
       "@id": "http://localhost:55001/plone/@components/navigation"
     }
diff --git a/src/plone/restapi/serializer/expansion.py b/src/plone/restapi/serializer/expansion.py
index 6253f69c..825d34ba 100644
--- a/src/plone/restapi/serializer/expansion.py
+++ b/src/plone/restapi/serializer/expansion.py
@@ -11,7 +11,17 @@ def expandable_elements(context, request):
     res = {}
     for element in elements:
         if element[0] in expands:
-            res.update(element[1](expand=True))
+            update_dict_recursively(res, element[1](expand=True))
         else:
-            res.update(element[1](expand=False))
+            update_dict_recursively(res, element[1](expand=False))
     return res
+
+
+def update_dict_recursively(d, u):
+    for key, value in u.iteritems():
+        if isinstance(value, dict):
+            r = update_dict_recursively(d.get(key, {}), value)
+            d[key] = r
+        else:
+            d[key] = u[key]
+    return d
diff --git a/src/plone/restapi/services/components/breadcrumbs.py b/src/plone/restapi/services/components/breadcrumbs.py
new file mode 100644
index 00000000..24287963
--- /dev/null
+++ b/src/plone/restapi/services/components/breadcrumbs.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+from plone.restapi.interfaces import IExpandableElement
+from zope.component import adapter
+from zope.component import getMultiAdapter
+from zope.interface import Interface
+from zope.interface import implementer
+
+
+@implementer(IExpandableElement)
+@adapter(Interface, Interface)
+class Breadcrumbs(object):
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    def __call__(self, expand=False):
+        if not expand:
+            return {'@components': {'breadcrumbs': {
+                '@id': '{}/@components/breadcrumbs'.format(
+                    self.context.absolute_url()),
+            }}}
+
+        breadcrumbs_view = getMultiAdapter((self.context, self.request),
+                                           name="breadcrumbs_view")
+        result = []
+        for crumb in breadcrumbs_view.breadcrumbs():
+            result.append({
+                'title': crumb['Title'],
+                'url': crumb['absolute_url']
+            })
+        return {'@components': {'breadcrumbs': result}}
diff --git a/src/plone/restapi/services/components/configure.zcml b/src/plone/restapi/services/components/configure.zcml
index fa6faebd..da8a360e 100644
--- a/src/plone/restapi/services/components/configure.zcml
+++ b/src/plone/restapi/services/components/configure.zcml
@@ -11,5 +11,6 @@
     />
 
     <adapter factory=".navigation.Navigation" name="navigation"/>
+    <adapter factory=".breadcrumbs.Breadcrumbs" name="breadcrumbs"/>
 
 </configure>
\ No newline at end of file
diff --git a/src/plone/restapi/services/components/get.py b/src/plone/restapi/services/components/get.py
index f580e3a1..5947d0c3 100644
--- a/src/plone/restapi/services/components/get.py
+++ b/src/plone/restapi/services/components/get.py
@@ -1,8 +1,8 @@
 # -*- coding: utf-8 -*-
 from plone.restapi.services import Service
+from plone.restapi.services.components.breadcrumbs import Breadcrumbs
 from plone.restapi.services.components.navigation import Navigation
 from zope.deprecation import deprecate
-from zope.component import getMultiAdapter
 from zope.interface import implements
 from zope.publisher.interfaces import IPublishTraverse
 
@@ -34,23 +34,13 @@ def _wrap_component_items(self, items, component_id):
         }
         return component
 
-    def get_breadcrumbs(self):
-        breadcrumbs_view = getMultiAdapter((self.context, self.request),
-                                           name="breadcrumbs_view")
-        result = []
-        for crumb in breadcrumbs_view.breadcrumbs():
-            result.append({
-                'title': crumb['Title'],
-                'url': crumb['absolute_url']
-            })
-        return result
-
     def _render_component(self, component_id):
         if component_id == 'navigation':
             items = Navigation(self.context, self.request)(expand=True)[
                 '@components']['navigation']
         elif component_id == 'breadcrumbs':
-            items = self.get_breadcrumbs()
+            items = Breadcrumbs(self.context, self.request)(expand=True)[
+                '@components']['breadcrumbs']
         else:
             raise NotImplementedError(
                 'This endpoint does not currently support the '
diff --git a/src/plone/restapi/tests/test_components.py b/src/plone/restapi/tests/test_components.py
index 3006146a..1c7e232b 100644
--- a/src/plone/restapi/tests/test_components.py
+++ b/src/plone/restapi/tests/test_components.py
@@ -54,6 +54,26 @@ def test_breadcrumb(self):
             }]
         )
 
+    def test_collapsed_breadcrumbs_in_content_serialization(self):
+        obj = self.api_session.get('/folder').json()
+        self.assertIn('@components', obj)
+        self.assertIn('breadcrumbs', obj['@components'])
+        self.assertIn('@id', obj['@components']['breadcrumbs'])
+
+    def test_expanded_breadcrumbs_in_content_serialization(self):
+        obj = self.api_session.get('/folder/doc1?expand=breadcrumbs').json()
+        self.assertIn('@components', obj)
+        self.assertIn('breadcrumbs', obj['@components'])
+        self.assertEqual(
+            [{
+                u'url': u'http://localhost:55001/plone/folder',
+                u'title': u'Some Folder'
+            }, {
+                u'url': u'http://localhost:55001/plone/folder/doc1',
+                u'title': u'A document'
+            }],
+            obj['@components']['breadcrumbs'])
+
     def test_navigation(self):
         response = self.api_session.get('/folder/@components/navigation')
 



------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot