diff --git a/CHANGES.rst b/CHANGES.rst
index 1687046a4c..9f4255fafd 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,7 +1,7 @@
Changelog
=========
-1.3.2 (unreleased)
+1.6.1 (unreleased)
------------------
- Expose the tagged values for widgets in the @types endpoint.
@@ -10,6 +10,48 @@ Changelog
- Render subject vocabulary as items for subjects field.
[jaroel]
+Bugfixes:
+
+- Add VHM support to @search
+ [csenger]
+
+
+1.6.0 (2018-04-17)
+------------------
+
+New Features:
+
+- Add `expand.navigation.depth` parameter to the `@navigation` endpoint.
+ [fulv, sneridagh]
+
+
+1.5.0 (2018-04-03)
+------------------
+
+New Features:
+
+- Allow users to update their own properties and password.
+ [sneridagh]
+
+
+1.4.1 (2018-03-22)
+------------------
+
+Bugfixes:
+
+- Fix serialization of `Discussion Item` and `Collection` content types when
+ called with `fullobjects` parameter.
+ [sneridagh]
+
+
+1.4.0 (2018-03-19)
+------------------
+
+New Features:
+
+- Add expandable @actions endpoint to retrieve portal_actions.
+ [csenger,timo,sneridagh]
+
1.3.1 (2018-03-14)
------------------
@@ -38,6 +80,20 @@ New Features:
1.2.0 (2018-02-28)
------------------
+Breaking Changes:
+
+- Make `@translations` endpoint expandable
+ [erral]
+
+- Rename the results attribute in `@translations` endpoint to be 'items'
+ [erral]
+
+- Remove 'language' attribute in `@translations` endpoint from the
+ top-level response entry
+ [erral]
+
+
+
New Features:
- Allow users to get their own user information.
diff --git a/base.cfg b/base.cfg
index 1b7de9a50c..c212abe5a9 100644
--- a/base.cfg
+++ b/base.cfg
@@ -141,6 +141,7 @@ interpreter = ${buildout:directory}/bin/${sphinx-python:interpreter}
[sphinx-python]
recipe = zc.recipe.egg
eggs =
+ sphinx_rtd_theme
sphinxcontrib-httpdomain
sphinxcontrib-httpexample
interpreter = sphinxPython
diff --git a/docs/source/_json/actions_get.req b/docs/source/_json/actions_get.req
new file mode 100644
index 0000000000..369875e288
--- /dev/null
+++ b/docs/source/_json/actions_get.req
@@ -0,0 +1,3 @@
+GET /plone/@actions HTTP/1.1
+Accept: application/json
+Authorization: Basic YWRtaW46c2VjcmV0
diff --git a/docs/source/_json/actions_get.resp b/docs/source/_json/actions_get.resp
new file mode 100644
index 0000000000..d7e6bbbf9b
--- /dev/null
+++ b/docs/source/_json/actions_get.resp
@@ -0,0 +1,71 @@
+HTTP/1.1 200 OK
+Content-Type: application/json
+
+{
+ "object": [
+ {
+ "icon": "",
+ "id": "view",
+ "title": "View"
+ },
+ {
+ "icon": "",
+ "id": "edit",
+ "title": "Edit"
+ },
+ {
+ "icon": "",
+ "id": "folderContents",
+ "title": "Contents"
+ },
+ {
+ "icon": "",
+ "id": "history",
+ "title": "History"
+ },
+ {
+ "icon": "",
+ "id": "local_roles",
+ "title": "Sharing"
+ }
+ ],
+ "object_buttons": [
+ {
+ "icon": "",
+ "id": "cut",
+ "title": "Cut"
+ },
+ {
+ "icon": "",
+ "id": "copy",
+ "title": "Copy"
+ },
+ {
+ "icon": "",
+ "id": "delete",
+ "title": "Delete"
+ },
+ {
+ "icon": "",
+ "id": "rename",
+ "title": "Rename"
+ }
+ ],
+ "user": [
+ {
+ "icon": "",
+ "id": "preferences",
+ "title": "Preferences"
+ },
+ {
+ "icon": "",
+ "id": "plone_setup",
+ "title": "Site Setup"
+ },
+ {
+ "icon": "",
+ "id": "logout",
+ "title": "Log out"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/docs/source/_json/collection.resp b/docs/source/_json/collection.resp
index 65d38338e2..86c6c4ced3 100644
--- a/docs/source/_json/collection.resp
+++ b/docs/source/_json/collection.resp
@@ -3,6 +3,9 @@ Content-Type: application/json
{
"@components": {
+ "actions": {
+ "@id": "http://localhost:55001/plone/collection/@actions"
+ },
"breadcrumbs": {
"@id": "http://localhost:55001/plone/collection/@breadcrumbs"
},
diff --git a/docs/source/_json/content_get.resp b/docs/source/_json/content_get.resp
index f327f3e57b..c25c48353a 100644
--- a/docs/source/_json/content_get.resp
+++ b/docs/source/_json/content_get.resp
@@ -3,6 +3,9 @@ Content-Type: application/json
{
"@components": {
+ "actions": {
+ "@id": "http://localhost:55001/plone/folder/my-document/@actions"
+ },
"breadcrumbs": {
"@id": "http://localhost:55001/plone/folder/my-document/@breadcrumbs"
},
diff --git a/docs/source/_json/content_patch_representation.resp b/docs/source/_json/content_patch_representation.resp
index d0043d7612..ee859de341 100644
--- a/docs/source/_json/content_patch_representation.resp
+++ b/docs/source/_json/content_patch_representation.resp
@@ -3,6 +3,9 @@ Content-Type: application/json
{
"@components": {
+ "actions": {
+ "@id": "http://localhost:55001/plone/folder/my-document/@actions"
+ },
"breadcrumbs": {
"@id": "http://localhost:55001/plone/folder/my-document/@breadcrumbs"
},
diff --git a/docs/source/_json/content_post.resp b/docs/source/_json/content_post.resp
index 89063e1527..89664953cf 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": {
+ "actions": {
+ "@id": "http://localhost:55001/plone/folder/my-document/@actions"
+ },
"breadcrumbs": {
"@id": "http://localhost:55001/plone/folder/my-document/@breadcrumbs"
},
diff --git a/docs/source/_json/document.resp b/docs/source/_json/document.resp
index f6b2bd9397..3429a2df07 100644
--- a/docs/source/_json/document.resp
+++ b/docs/source/_json/document.resp
@@ -3,6 +3,9 @@ Content-Type: application/json
{
"@components": {
+ "actions": {
+ "@id": "http://localhost:55001/plone/front-page/@actions"
+ },
"breadcrumbs": {
"@id": "http://localhost:55001/plone/front-page/@breadcrumbs"
},
diff --git a/docs/source/_json/event.resp b/docs/source/_json/event.resp
index cf73c3ccbf..5aaa2269b9 100644
--- a/docs/source/_json/event.resp
+++ b/docs/source/_json/event.resp
@@ -3,6 +3,9 @@ Content-Type: application/json
{
"@components": {
+ "actions": {
+ "@id": "http://localhost:55001/plone/event/@actions"
+ },
"breadcrumbs": {
"@id": "http://localhost:55001/plone/event/@breadcrumbs"
},
diff --git a/docs/source/_json/expansion.resp b/docs/source/_json/expansion.resp
index f6b2bd9397..3429a2df07 100644
--- a/docs/source/_json/expansion.resp
+++ b/docs/source/_json/expansion.resp
@@ -3,6 +3,9 @@ Content-Type: application/json
{
"@components": {
+ "actions": {
+ "@id": "http://localhost:55001/plone/front-page/@actions"
+ },
"breadcrumbs": {
"@id": "http://localhost:55001/plone/front-page/@breadcrumbs"
},
diff --git a/docs/source/_json/expansion_expanded.resp b/docs/source/_json/expansion_expanded.resp
index e8507d4e19..ac46fbd7d3 100644
--- a/docs/source/_json/expansion_expanded.resp
+++ b/docs/source/_json/expansion_expanded.resp
@@ -3,6 +3,9 @@ Content-Type: application/json
{
"@components": {
+ "actions": {
+ "@id": "http://localhost:55001/plone/front-page/@actions"
+ },
"breadcrumbs": {
"@id": "http://localhost:55001/plone/front-page/@breadcrumbs",
"items": [
diff --git a/docs/source/_json/expansion_expanded_full.req b/docs/source/_json/expansion_expanded_full.req
index 258f61044c..f0d7135fac 100644
--- a/docs/source/_json/expansion_expanded_full.req
+++ b/docs/source/_json/expansion_expanded_full.req
@@ -1,3 +1,3 @@
-GET /plone/front-page?expand=breadcrumbs,navigation,schema,workflow HTTP/1.1
+GET /plone/front-page?expand=actions,breadcrumbs,navigation,schema,workflow HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0
diff --git a/docs/source/_json/expansion_expanded_full.resp b/docs/source/_json/expansion_expanded_full.resp
index f9d1835eaf..940cec1366 100644
--- a/docs/source/_json/expansion_expanded_full.resp
+++ b/docs/source/_json/expansion_expanded_full.resp
@@ -3,6 +3,99 @@ Content-Type: application/json
{
"@components": {
+ "actions": {
+ "document_actions": [],
+ "object": [
+ {
+ "icon": "",
+ "id": "view",
+ "title": "View"
+ },
+ {
+ "icon": "",
+ "id": "edit",
+ "title": "Edit"
+ },
+ {
+ "icon": "",
+ "id": "folderContents",
+ "title": "Contents"
+ },
+ {
+ "icon": "",
+ "id": "history",
+ "title": "History"
+ },
+ {
+ "icon": "",
+ "id": "local_roles",
+ "title": "Sharing"
+ }
+ ],
+ "object_buttons": [
+ {
+ "icon": "",
+ "id": "cut",
+ "title": "Cut"
+ },
+ {
+ "icon": "",
+ "id": "copy",
+ "title": "Copy"
+ },
+ {
+ "icon": "",
+ "id": "delete",
+ "title": "Delete"
+ },
+ {
+ "icon": "",
+ "id": "rename",
+ "title": "Rename"
+ }
+ ],
+ "portal_tabs": [
+ {
+ "icon": "",
+ "id": "index_html",
+ "title": "Home"
+ }
+ ],
+ "site_actions": [
+ {
+ "icon": "",
+ "id": "sitemap",
+ "title": "Site Map"
+ },
+ {
+ "icon": "",
+ "id": "accessibility",
+ "title": "Accessibility"
+ },
+ {
+ "icon": "",
+ "id": "contact",
+ "title": "Contact"
+ }
+ ],
+ "user": [
+ {
+ "icon": "",
+ "id": "preferences",
+ "title": "Preferences"
+ },
+ {
+ "icon": "",
+ "id": "plone_setup",
+ "title": "Site Setup"
+ },
+ {
+ "icon": "",
+ "id": "logout",
+ "title": "Log out"
+ }
+ ]
+ },
"breadcrumbs": {
"@id": "http://localhost:55001/plone/front-page/@breadcrumbs",
"items": [
@@ -17,10 +110,12 @@ Content-Type: application/json
"items": [
{
"@id": "http://localhost:55001/plone",
+ "description": "",
"title": "Home"
},
{
"@id": "http://localhost:55001/plone/front-page",
+ "description": "Congratulations! You have successfully installed Plone.",
"title": "Welcome to Plone"
}
]
diff --git a/docs/source/_json/file.resp b/docs/source/_json/file.resp
index f7b4592466..b76c1f956c 100644
--- a/docs/source/_json/file.resp
+++ b/docs/source/_json/file.resp
@@ -3,6 +3,9 @@ Content-Type: application/json
{
"@components": {
+ "actions": {
+ "@id": "http://localhost:55001/plone/file/@actions"
+ },
"breadcrumbs": {
"@id": "http://localhost:55001/plone/file/@breadcrumbs"
},
diff --git a/docs/source/_json/folder.resp b/docs/source/_json/folder.resp
index 87486953af..2905c0a2e3 100644
--- a/docs/source/_json/folder.resp
+++ b/docs/source/_json/folder.resp
@@ -3,6 +3,9 @@ Content-Type: application/json
{
"@components": {
+ "actions": {
+ "@id": "http://localhost:55001/plone/folder/@actions"
+ },
"breadcrumbs": {
"@id": "http://localhost:55001/plone/folder/@breadcrumbs"
},
diff --git a/docs/source/_json/image.resp b/docs/source/_json/image.resp
index dc1966b3d8..ab96607b54 100644
--- a/docs/source/_json/image.resp
+++ b/docs/source/_json/image.resp
@@ -3,6 +3,9 @@ Content-Type: application/json
{
"@components": {
+ "actions": {
+ "@id": "http://localhost:55001/plone/image/@actions"
+ },
"breadcrumbs": {
"@id": "http://localhost:55001/plone/image/@breadcrumbs"
},
diff --git a/docs/source/_json/jwt_logged_in.resp b/docs/source/_json/jwt_logged_in.resp
index 3eae74e64e..8ce3cff3a8 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": {
+ "actions": {
+ "@id": "http://localhost:55001/plone/@actions"
+ },
"breadcrumbs": {
"@id": "http://localhost:55001/plone/@breadcrumbs"
},
diff --git a/docs/source/_json/link.resp b/docs/source/_json/link.resp
index 367ed230d5..38238edc50 100644
--- a/docs/source/_json/link.resp
+++ b/docs/source/_json/link.resp
@@ -3,6 +3,9 @@ Content-Type: application/json
{
"@components": {
+ "actions": {
+ "@id": "http://localhost:55001/plone/link/@actions"
+ },
"breadcrumbs": {
"@id": "http://localhost:55001/plone/link/@breadcrumbs"
},
diff --git a/docs/source/_json/navigation.resp b/docs/source/_json/navigation.resp
index ded5c66e58..81f1be2eb2 100644
--- a/docs/source/_json/navigation.resp
+++ b/docs/source/_json/navigation.resp
@@ -6,10 +6,12 @@ Content-Type: application/json
"items": [
{
"@id": "http://localhost:55001/plone",
+ "description": "",
"title": "Home"
},
{
"@id": "http://localhost:55001/plone/front-page",
+ "description": "Congratulations! You have successfully installed Plone.",
"title": "Welcome to Plone"
}
]
diff --git a/docs/source/_json/navigation_tree.req b/docs/source/_json/navigation_tree.req
new file mode 100644
index 0000000000..f839596b69
--- /dev/null
+++ b/docs/source/_json/navigation_tree.req
@@ -0,0 +1,3 @@
+GET /plone/front-page/@navigation?expand.navigation.depth=4 HTTP/1.1
+Accept: application/json
+Authorization: Basic YWRtaW46c2VjcmV0
diff --git a/docs/source/_json/navigation_tree.resp b/docs/source/_json/navigation_tree.resp
new file mode 100644
index 0000000000..67370a3ce1
--- /dev/null
+++ b/docs/source/_json/navigation_tree.resp
@@ -0,0 +1,62 @@
+HTTP/1.1 200 OK
+Content-Type: application/json
+
+{
+ "@id": "http://localhost:55001/plone/front-page/@navigation",
+ "items": [
+ {
+ "@id": "http://localhost:55001/plone",
+ "description": "",
+ "items": "",
+ "title": "Home"
+ },
+ {
+ "@id": "http://localhost:55001/plone/front-page",
+ "description": "Congratulations! You have successfully installed Plone.",
+ "items": [],
+ "title": "Welcome to Plone"
+ },
+ {
+ "@id": "http://localhost:55001/plone/folder",
+ "description": "",
+ "items": [
+ {
+ "@id": "http://localhost:55001/plone/folder/subfolder1",
+ "description": "",
+ "items": [
+ {
+ "@id": "http://localhost:55001/plone/folder/subfolder1/thirdlevelfolder",
+ "description": "",
+ "items": [
+ {
+ "@id": "http://localhost:55001/plone/folder/subfolder1/thirdlevelfolder/fourthlevelfolder",
+ "description": "",
+ "title": "Fourth Level Folder"
+ }
+ ],
+ "title": "Third Level Folder"
+ }
+ ],
+ "title": "SubFolder 1"
+ },
+ {
+ "@id": "http://localhost:55001/plone/folder/subfolder2",
+ "description": "",
+ "title": "SubFolder 2"
+ },
+ {
+ "@id": "http://localhost:55001/plone/folder/doc1",
+ "description": "",
+ "title": "A document"
+ }
+ ],
+ "title": "Some Folder"
+ },
+ {
+ "@id": "http://localhost:55001/plone/folder2",
+ "description": "",
+ "items": [],
+ "title": "Some Folder 2"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/docs/source/_json/newsitem.resp b/docs/source/_json/newsitem.resp
index bea32707e5..dc8228aa32 100644
--- a/docs/source/_json/newsitem.resp
+++ b/docs/source/_json/newsitem.resp
@@ -3,6 +3,9 @@ Content-Type: application/json
{
"@components": {
+ "actions": {
+ "@id": "http://localhost:55001/plone/newsitem/@actions"
+ },
"breadcrumbs": {
"@id": "http://localhost:55001/plone/newsitem/@breadcrumbs"
},
diff --git a/docs/source/_json/siteroot.resp b/docs/source/_json/siteroot.resp
index ffabcb8568..0b2f02311d 100644
--- a/docs/source/_json/siteroot.resp
+++ b/docs/source/_json/siteroot.resp
@@ -3,6 +3,9 @@ Content-Type: application/json
{
"@components": {
+ "actions": {
+ "@id": "http://localhost:55001/plone/@actions"
+ },
"breadcrumbs": {
"@id": "http://localhost:55001/plone/@breadcrumbs"
},
diff --git a/docs/source/_json/translations_get.resp b/docs/source/_json/translations_get.resp
index f27b50ee45..b6b78eaf9c 100644
--- a/docs/source/_json/translations_get.resp
+++ b/docs/source/_json/translations_get.resp
@@ -2,9 +2,8 @@ HTTP/1.1 200 OK
Content-Type: application/json
{
- "@id": "http://localhost:55001/plone/en/test-document",
- "language": "en",
- "translations": [
+ "@id": "http://localhost:55001/plone/en/test-document/@translations",
+ "items": [
{
"@id": "http://localhost:55001/plone/es/test-document",
"language": "es"
diff --git a/docs/source/_json/users_add.req b/docs/source/_json/users_add.req
new file mode 100644
index 0000000000..eab44415e5
--- /dev/null
+++ b/docs/source/_json/users_add.req
@@ -0,0 +1,14 @@
+POST /plone/@users HTTP/1.1
+Accept: application/json
+Authorization: Basic YWRtaW46c2VjcmV0
+Content-Type: application/json
+
+{
+ "description": "Professor of Linguistics",
+ "email": "noam.chomsky@example.com",
+ "fullname": "Noam Avram Chomsky",
+ "home_page": "web.mit.edu/chomsky",
+ "location": "Cambridge, MA",
+ "sendPasswordReset": true,
+ "username": "noamchomsky"
+}
\ No newline at end of file
diff --git a/docs/source/_json/users_add.resp b/docs/source/_json/users_add.resp
new file mode 100644
index 0000000000..4e0df86726
--- /dev/null
+++ b/docs/source/_json/users_add.resp
@@ -0,0 +1,17 @@
+HTTP/1.1 201 Created
+Content-Type: application/json
+Location: http://localhost:55001/plone/@users/noamchomsky
+
+{
+ "@id": "http://localhost:55001/plone/@users/noamchomsky",
+ "description": "Professor of Linguistics",
+ "email": "noam.chomsky@example.com",
+ "fullname": "Noam Avram Chomsky",
+ "home_page": "web.mit.edu/chomsky",
+ "id": "noamchomsky",
+ "location": "Cambridge, MA",
+ "roles": [
+ "Member"
+ ],
+ "username": "noamchomsky"
+}
\ No newline at end of file
diff --git a/docs/source/_static/img/postman_basic_auth.png b/docs/source/_static/img/postman_basic_auth.png
index 2b0281c311..4483dcc993 100644
Binary files a/docs/source/_static/img/postman_basic_auth.png and b/docs/source/_static/img/postman_basic_auth.png differ
diff --git a/docs/source/_static/img/postman_headers.png b/docs/source/_static/img/postman_headers.png
index 1cc0409dc5..0943eb3c57 100644
Binary files a/docs/source/_static/img/postman_headers.png and b/docs/source/_static/img/postman_headers.png differ
diff --git a/docs/source/_static/img/postman_request.png b/docs/source/_static/img/postman_request.png
index 5bf20f0162..cab67e3e4f 100644
Binary files a/docs/source/_static/img/postman_request.png and b/docs/source/_static/img/postman_request.png differ
diff --git a/docs/source/_static/img/postman_response.png b/docs/source/_static/img/postman_response.png
new file mode 100644
index 0000000000..a02f083173
Binary files /dev/null and b/docs/source/_static/img/postman_response.png differ
diff --git a/docs/source/_static/img/postman_retain_headers.png b/docs/source/_static/img/postman_retain_headers.png
index 314ede8e36..6a2208a194 100644
Binary files a/docs/source/_static/img/postman_retain_headers.png and b/docs/source/_static/img/postman_retain_headers.png differ
diff --git a/docs/source/actions.rst b/docs/source/actions.rst
new file mode 100644
index 0000000000..2b57686cbd
--- /dev/null
+++ b/docs/source/actions.rst
@@ -0,0 +1,34 @@
+Portal Actions
+==============
+
+Plone has the concept of configurable actions (called "portal_actions").
+Each actions defines an id, a title, the required
+permissions and a condition that will be checked to decide if the action
+will be available for a user.
+Actions are sorted by categories.
+
+Actions can be used to build UI elements that adapt to the available actions.
+An example is the Plone toolbar where the "object_tabs" (view, edit, folder contents, sharing)
+and the "user_actions" (login, logout, preferences) are used to display the user only the actions that are allowed for the currently logged in user.
+
+The available actions for the currently logged in user can be retrieved
+by calling the @actions endpoint on a specific context.
+This also works for not authenticated users.
+
+Listing available actions
+-------------------------
+
+To list the available actions, send a GET request to the '@actions' endpoint on a specific content object:
+
+.. http:example:: curl httpie python-requests
+ :request: _json/actions_get.req
+
+The server will respond with a `200 OK` status code.
+The JSON response contains the available actions categories (object, object_buttons, user) on the top level.
+Each category contains a list of the available actions in that category:
+
+.. literalinclude:: _json/actions_get.resp
+ :language: http
+
+If you want to limit the categories that are retured, pass one or more parameters
+``categories:list``, i.e. ``@action?categories:list=object&categories:list=user``.
diff --git a/docs/source/expansion.rst b/docs/source/expansion.rst
index a8f5052ab2..aeab6d3e26 100644
--- a/docs/source/expansion.rst
+++ b/docs/source/expansion.rst
@@ -1,3 +1,5 @@
+.. _`expansion name`:
+
Expansion
=========
@@ -21,10 +23,12 @@ in the reponse of any content GET request::
"@id": "http://localhost:55001/plone/front-page",
"@type": "Document",
"@components": [
+ {"@id": "http://localhost:55001/plone/front-page/@actions"},
{"@id": "http://localhost:55001/plone/front-page/@breadcrumbs"},
{"@id": "http://localhost:55001/plone/front-page/@navigation"},
{"@id": "http://localhost:55001/plone/front-page/@schema"},
- {"@id": "http://localhost:55001/plone/front-page/@workflow"}
+ {"@id": "http://localhost:55001/plone/front-page/@workflow"},
+ ...
},
"UID": "1f699ffa110e45afb1ba502f75f7ec33",
"title": "Welcome to Plone",
@@ -54,6 +58,9 @@ component::
"@id": "http://localhost:55001/plone/front-page",
"@type": "Document",
"@components": {
+ "actions": {
+ "@id": "http://localhost:55001/plone/front-page/@actions"
+ },
"breadcrumbs": {
"@id": "http://localhost:55001/plone/front-page/@components/breadcrumbs",
"items": [
@@ -63,28 +70,14 @@ component::
}
]
},
- "navigation": "http://localhost:55001/plone/front-page/@navigation",
- "schema": "http://localhost:55001/plone/front-page/@schema",
+ "navigation": {
+ "@id": "http://localhost:55001/plone/front-page/@navigation"
+ },
+ "schema": {
+ "@id": "http://localhost:55001/plone/front-page/@schema"
+ },
"workflow": {
- "history": [
- {
- "action": null,
- "actor": "test_user_1_",
- "comments": "",
- "review_state": "private",
- "time": "2016-10-21T19:00:00+00:00"
- }
- ],
- "transitions": [
- {
- "@id": "http://localhost:55001/plone/front-page/@workflow/publish",
- "title": "Publish"
- },
- {
- "@id": "http://localhost:55001/plone/front-page/@workflow/submit",
- "title": "Submit for publication"
- }
- ]
+ "@id": http://localhost:55001/plone/front-page/@workflow"
},
},
"UID": "1f699ffa110e45afb1ba502f75f7ec33",
diff --git a/docs/source/exploring.rst b/docs/source/exploring.rst
index 7b3499a411..a2405dc14e 100644
--- a/docs/source/exploring.rst
+++ b/docs/source/exploring.rst
@@ -3,7 +3,11 @@
Explore the API using Postman
=============================
-To discover the API interactively, the Chrome-Extension Postman_ is a suitable solution.
+To discover the API interactively, using Postman_ is recommended.
+
+.. admonition:: Note
+
+ The Chrome-Extension version of Postman is deprecated and it is recommended to use the native app available instead.
Configuration
-------------
@@ -45,7 +49,7 @@ You have to select
* in the drop-down menu :menuselection:`Basic Auth ->` the term :term:`Basic Auth` as the authentication method
* A valid existing user with appropriate permissions
-After providing these parameters you can create the resulting :term:`Authorization Header` and insert it into the prepared request by clicking on :guilabel:`Update Request`.
+After providing these parameters you can create the resulting :term:`Authorization Header` and insert it into the prepared request by clicking on :guilabel:`Preview Request`.
|postman-basic-auth|
@@ -60,6 +64,9 @@ The request is now ready and can be send by clicking on :guilabel:`Send` button.
The :term:`Response` of the server is now displayed below the :term:`Request`. You can easily follow the links on the ``@id`` attributes by clicking on them. For every link Postman_ has prepared another request sharing the same headers that can be send again by licking on the :guilabel:`Send` button.
+|postman-response|
+
+
.. admonition:: Conclusion
You can now explore the whole stucture of your application easily via the API using `GET` requests.
@@ -78,3 +85,4 @@ The :term:`Response` of the server is now displayed below the :term:`Request`. Y
.. |postman-request| image:: ./_static/img/postman_request.png
.. |postman-basic-auth| image:: ./_static/img/postman_basic_auth.png
.. |postman-headers| image:: ./_static/img/postman_headers.png
+.. |postman-response| image:: ./_static/img/postman_response.png
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 18ed524b79..c5cfc71e19 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -7,8 +7,6 @@
plone.restapi: A RESTful hypermedia API for Plone.
==================================================
-.. include:: /alert-noindex.rst
-
Contents
--------
@@ -27,6 +25,7 @@ Contents
comments
copymove
expansion
+ actions
workflow
locking
sharing
diff --git a/docs/source/navigation.rst b/docs/source/navigation.rst
index 599a6d32e7..e0b5447dec 100644
--- a/docs/source/navigation.rst
+++ b/docs/source/navigation.rst
@@ -3,6 +3,9 @@
Navigation
==========
+Top-Level Navigation
+--------------------
+
Getting the top navigation items:
.. http:example:: curl httpie python-requests
@@ -12,3 +15,17 @@ Example response:
.. literalinclude:: _json/navigation.resp
:language: http
+
+
+Navigation Tree
+---------------
+
+Getting the navigation item tree providing a `expand.navigation.depth` parameter:
+
+.. http:example:: curl httpie python-requests
+ :request: _json/navigation_tree.req
+
+Example response:
+
+.. literalinclude:: _json/navigation_tree.resp
+ :language: http
diff --git a/docs/source/translations.rst b/docs/source/translations.rst
index 4544c6ca79..6a92aa3fd5 100644
--- a/docs/source/translations.rst
+++ b/docs/source/translations.rst
@@ -1,3 +1,5 @@
+.. _`translations`:
+
Translations
============
@@ -65,6 +67,17 @@ endpoint of the content item and provide the language code you want to unlink.:
:language: http
+Expansion
+---------
+
+This endpoint can be used with the :ref:`expansion name` mechanism which allows to get additional
+information about a content item in one query, avoiding unnecesary requests.
+
+If a simple ``GET`` request is done on the content item, a new entry will be shown on the `@components`
+entry with the URL of the `@translations` endpoint:
+
+
+
.. _`plone.app.multilingual`: https://pypi.python.org/pypi/plone.app.multilingual
.. _`Products.LinguaPlone`: https://pypi.python.org/pypi/Products.LinguaPlone.
.. _`documentation`: https://docs.plone.org/develop/plone/i18n/translating_content.html
\ No newline at end of file
diff --git a/docs/source/upgrade-guide.rst b/docs/source/upgrade-guide.rst
index 24d6233c83..056ecaccd2 100644
--- a/docs/source/upgrade-guide.rst
+++ b/docs/source/upgrade-guide.rst
@@ -4,6 +4,52 @@ Upgrade Guide
This upgrade guide lists all breaking changes in plone.restapi and explains the necessary steps that are needed to upgrade to the lastest version.
+Upgrading from plone.restapi 1.x
+--------------------------------
+
+When using the `@translations` endpoint in plone.restapi 1.x, the endpoint returned a `language` key
+with the content object's language and a `translations` key with all its translations.
+
+Now, as the endpoint is expandable we want the endpoint to behave like the other expandable endpoints.
+As top level information we only include the name of the endpoint on the `@id` attribute and the actual
+translations of the content object in an attribute called `items`.
+
+This means that now the JSON response to a GET request to the :ref:`translations` endpoint does not
+include anymore the language of the actual content item and the translations in an attribute called
+`items` instead of `translations`.
+
+Old response::
+
+ HTTP/1.1 200 OK
+ Content-Type: application/json
+
+ {
+ "@id": "http://localhost:55001/plone/en/test-document",
+ "language": "en",
+ "translations": [
+ {
+ "@id": "http://localhost:55001/plone/es/test-document",
+ "language": "es"
+ }
+ ]
+ }
+
+New response::
+
+ HTTP/1.1 200 OK
+ Content-Type: application/json
+
+ {
+ "@id": "http://localhost:55001/plone/en/test-document/@translations",
+ "items": [
+ {
+ "@id": "http://localhost:55001/plone/es/test-document",
+ "language": "es"
+ }
+ ]
+ }
+
+
Upgrading to plone.restapi 1.0b1
--------------------------------
diff --git a/docs/source/users.rst b/docs/source/users.rst
index b5e9c2613b..3dae824a0c 100644
--- a/docs/source/users.rst
+++ b/docs/source/users.rst
@@ -135,6 +135,7 @@ A successful response to a PATCH request will be indicated by a :term:`204 No Co
.. note::
The 'roles' object is a mapping of a role and a boolean indicating adding or removing.
+Any user is able to update their own properties and password (if allowed) by using the same request.
Delete User
-----------
@@ -153,9 +154,23 @@ A successful response will be indicated by a :term:`204 No Content` response:
User registration
-----------------
-Plone allows you to enable the auto registration of users. If it is enabled, then an anonymous user can register a new user using the user creation endpoint.
+Plone allows you to enable the auto registration of users.
+If it is enabled, then an anonymous user can register a new user using the user creation endpoint.
This new user will have the role ``Member`` by default as the Plone registration process also does.
+To create a new user send a POST request to the '@users' endpoint:
+
+.. http:example:: curl httpie python-requests
+ :request: _json/users_add.req
+
+If the user should receive an email to set her password, you should pass 'sendPasswordReset": true' in the JSON body of the request.
+Keep in mind that Plone will send a URL that points to the URL of the Plone site, which might just be your API endpoint.
+
+If the user has been created, the server will respond with a :term:`201 Created` response:
+
+.. literalinclude:: _json/users_add.resp
+ :language: http
+
Reset User Password
-------------------
@@ -169,7 +184,7 @@ Plone allows to reset a password for a user by sending a POST request to the use
The server will respond with a :term:`200 OK` response and send an email to the user to reset her password.
The token that is part of the reset url in the email can be used to
-authorize setting a new password::
+authorize setting a new password:
.. http:example:: curl httpie python-requests
:request: _json/users_reset.req
diff --git a/setup.py b/setup.py
index e4bb2cf962..2463212302 100644
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,6 @@
from setuptools import setup, find_packages
-version = '1.3.2-dev.0'
+version = '1.6.1.dev0'
long_description = (
open('README.rst').read() + '\n' +
diff --git a/src/plone/restapi/search/handler.py b/src/plone/restapi/search/handler.py
index 84f17e01d4..2f65f530a4 100644
--- a/src/plone/restapi/search/handler.py
+++ b/src/plone/restapi/search/handler.py
@@ -33,6 +33,20 @@ def _constrain_query_by_path(self, query):
if 'path' not in query:
query['path'] = {}
+ if isinstance(query['path'], str):
+ query['path'] = {'query': query['path']}
+
+ # If this is accessed through a VHM the client does not know
+ # the complete physical path of an object. But the path index
+ # indexes the complete physical path. Complete the path.
+ vhm_physical_path = self.request.get('VirtualRootPhysicalPath')
+ if vhm_physical_path:
+ path = query['path'].get('query')
+ if path:
+ path = path.lstrip('/')
+ full_path = '/'.join(vhm_physical_path + (path,))
+ query['path']['query'] = full_path
+
if isinstance(query['path'], dict) and 'query' not in query['path']:
# We either had no 'path' parameter at all, or an incomplete
# 'path' query dict (with just ExtendedPathIndex options (like
diff --git a/src/plone/restapi/serializer/collection.py b/src/plone/restapi/serializer/collection.py
index 603d478abc..e6145a5e9f 100644
--- a/src/plone/restapi/serializer/collection.py
+++ b/src/plone/restapi/serializer/collection.py
@@ -14,7 +14,7 @@
@adapter(ICollection, Interface)
class SerializeCollectionToJson(SerializeToJson):
- def __call__(self, version=None):
+ def __call__(self, version=None, include_items=True):
collection_metadata = super(SerializeCollectionToJson, self).__call__(
version=version,
)
diff --git a/src/plone/restapi/serializer/discussion.py b/src/plone/restapi/serializer/discussion.py
index 588b798f0e..22c8176ed2 100644
--- a/src/plone/restapi/serializer/discussion.py
+++ b/src/plone/restapi/serializer/discussion.py
@@ -54,7 +54,7 @@ def __init__(self, context, request):
self.context = context
self.request = request
- def __call__(self):
+ def __call__(self, include_items=True):
content_url = self.context.__parent__.__parent__.absolute_url()
comments_url = '{}/@comments'.format(content_url)
url = '{}/{}'.format(comments_url, self.context.id)
diff --git a/src/plone/restapi/services/actions/__init__.py b/src/plone/restapi/services/actions/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/plone/restapi/services/actions/configure.zcml b/src/plone/restapi/services/actions/configure.zcml
new file mode 100644
index 0000000000..30787c8f10
--- /dev/null
+++ b/src/plone/restapi/services/actions/configure.zcml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
diff --git a/src/plone/restapi/services/actions/get.py b/src/plone/restapi/services/actions/get.py
new file mode 100644
index 0000000000..c707c5bc59
--- /dev/null
+++ b/src/plone/restapi/services/actions/get.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+from Products.CMFCore.interfaces import IActionCategory
+from Products.CMFCore.utils import getToolByName
+from plone.restapi.interfaces import IExpandableElement
+from plone.restapi.services import Service
+from zope.component import adapter
+from zope.component import getMultiAdapter
+from zope.i18n import translate
+from zope.interface import Interface
+from zope.interface import implementer
+
+
+@implementer(IExpandableElement)
+@adapter(Interface, Interface)
+class Actions(object):
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def __call__(self, expand=False):
+ result = {
+ 'actions': {
+ '@id': '{}/@actions'.format(self.context.absolute_url()),
+ },
+ }
+ if not expand:
+ return result
+
+ context_state = getMultiAdapter(
+ (self.context, self.request), name='plone_context_state')
+
+ categories = self.request.form.get(
+ 'categories',
+ self.all_categories
+ )
+ data = {}
+ for category in categories:
+ category_action_data = []
+ actions = context_state.actions(category=category)
+ for action in actions:
+ category_action_data.append({
+ 'title': translate(action['title'], context=self.request),
+ 'id': action['id'],
+ 'icon': action['icon'],
+ })
+ data[category] = category_action_data
+ return {'actions': data}
+
+ @property
+ def all_categories(self):
+ portal_actions = getToolByName(self.context, 'portal_actions')
+ categories = []
+ for id, obj in portal_actions.objectItems():
+ if IActionCategory.providedBy(obj):
+ categories.append(id)
+ return categories
+
+
+class ActionsGet(Service):
+
+ def reply(self):
+ actions = Actions(self.context, self.request)
+ return actions(expand=True)['actions']
diff --git a/src/plone/restapi/services/configure.zcml b/src/plone/restapi/services/configure.zcml
index c864fbff60..2c7a6bd65f 100644
--- a/src/plone/restapi/services/configure.zcml
+++ b/src/plone/restapi/services/configure.zcml
@@ -6,6 +6,7 @@
+
diff --git a/src/plone/restapi/services/multilingual/configure.zcml b/src/plone/restapi/services/multilingual/configure.zcml
index c3ca9f36c7..2c6dae1b09 100644
--- a/src/plone/restapi/services/multilingual/configure.zcml
+++ b/src/plone/restapi/services/multilingual/configure.zcml
@@ -3,6 +3,8 @@
xmlns:plone="http://namespaces.plone.org/plone"
xmlns:zcml="http://namespaces.zope.org/zcml">
+
+
0 and self.bottomLevel > 0 and depth >= self.bottomLevel:
+ return False
+ else:
+ return True
+
+ def getRootPath(self, topLevel=1):
+ rootPath = getNavigationRoot(self.context)
+
+ contextPath = '/'.join(self.context.getPhysicalPath())
+ if not contextPath.startswith(rootPath):
+ return None
+ contextSubPathElements = contextPath[len(rootPath) + 1:]
+ if contextSubPathElements:
+ contextSubPathElements = contextSubPathElements.split('/')
+ if len(contextSubPathElements) < topLevel:
+ return None
+ rootPath = rootPath + '/' + '/'.join(contextSubPathElements[:topLevel]) # noqa
+ else:
+ return None
+
+ return rootPath
@implementer(IExpandableElement)
@@ -14,8 +68,14 @@ class Navigation(object):
def __init__(self, context, request):
self.context = context
self.request = request
+ self.portal = getSite()
def __call__(self, expand=False):
+ if self.request.form.get('expand.navigation.depth', False):
+ self.depth = int(self.request.form['expand.navigation.depth'])
+ else:
+ self.depth = 1
+
result = {
'navigation': {
'@id': '{}/@navigation'.format(self.context.absolute_url()),
@@ -28,13 +88,71 @@ def __call__(self, expand=False):
name="portal_tabs_view")
items = []
for tab in tabs.topLevelTabs():
- items.append({
- 'title': tab.get('title', tab.get('name')),
- '@id': tab['url'] + ''
- })
+ if self.depth > 1:
+ subitems = self.getTabSubTree(tabUrl=tab['url'],
+ tabPath=tab.get('path'))
+ items.append({
+ 'title': tab.get('title', tab.get('name')),
+ '@id': tab['url'] + '',
+ 'description': tab.get('description', ''),
+ 'items': subitems,
+ })
+ else:
+ items.append({
+ 'title': tab.get('title', tab.get('name')),
+ '@id': tab['url'] + '',
+ 'description': tab.get('description', ''),
+ })
result['navigation']['items'] = items
return result
+ def getTabSubTree(self, tabUrl='', tabPath=None):
+ if tabPath is None:
+ # get path for current tab's object
+ tabPath = tabUrl.split(self.portal.absolute_url())[-1]
+
+ if tabPath == '' or '/view' in tabPath:
+ return ''
+
+ if tabPath.startswith('/'):
+ tabPath = tabPath[1:]
+ elif tabPath.endswith('/'):
+ # we need a real path, without a slash that might appear
+ # at the end of the path occasionally
+ tabPath = str(tabPath.split('/')[0])
+
+ if '%20' in tabPath:
+ # we have the space in object's ID that has to be
+ # converted to the real spaces
+ tabPath = tabPath.replace('%20', ' ').strip()
+
+ tabObj = self.portal.restrictedTraverse(tabPath, None)
+ if tabObj is None:
+ return ''
+
+ strategy = CustomNavtreeStrategy(tabObj)
+ queryBuilder = NavigationTreeQueryBuilder(tabObj, self.depth)
+ query = queryBuilder()
+ data = buildFolderTree(
+ tabObj, obj=tabObj, query=query, strategy=strategy)
+
+ return self.recurse(
+ children=data.get('children', []),
+ level=1)
+
+ def recurse(self, children=None, level=0, bottomLevel=0):
+ li = []
+ for node in children:
+ item = {'title': node['Title'], 'description': node['Description']}
+ item['@id'] = node['getURL']
+ if bottomLevel <= 0 or level <= bottomLevel:
+ nc = node['children']
+ nc = self.recurse(nc, level+1, bottomLevel)
+ if nc:
+ item['items'] = nc
+ li.append(item)
+ return li
+
class NavigationGet(Service):
diff --git a/src/plone/restapi/services/users/configure.zcml b/src/plone/restapi/services/users/configure.zcml
index 25d785b23d..ad07d1a5ce 100644
--- a/src/plone/restapi/services/users/configure.zcml
+++ b/src/plone/restapi/services/users/configure.zcml
@@ -16,7 +16,7 @@
name="@users"
for="Products.CMFCore.interfaces.ISiteRoot"
factory=".update.UsersPatch"
- permission="cmf.ManagePortal"
+ permission="zope2.View"
/>