diff --git a/docs/source/endpoints/index.md b/docs/source/endpoints/index.md
new file mode 100644
index 0000000000..239b6577ad
--- /dev/null
+++ b/docs/source/endpoints/index.md
@@ -0,0 +1,61 @@
+---
+myst:
+ html_meta:
+ "description": "Usage of the Plone REST API."
+ "property=og:description": "Usage of the Plone REST API."
+ "property=og:title": "Usage of the Plone REST API"
+ "keywords": "Plone, plone.restapi, REST, API, Usage"
+---
+
+(restapi-endpoints)=
+
+# Endpoints
+
+This part of the documentation explains the endpoints of Plone REST API.
+
+```{toctree}
+:caption: Table of Contents
+:maxdepth: 2
+
+addons
+aliases
+breadcrumbs
+comments
+content-types
+content-rules
+contextnavigation
+controlpanels
+copymove
+database
+email-notification
+email-send
+expansion
+groups
+history
+linkintegrity
+locking
+navigation
+navroot
+actions
+portrait
+principals
+querystring
+querystringsearch
+registry
+relations
+roles
+searching
+site
+system
+tiles
+transactions
+translations
+tusupload
+types
+upgrade
+users
+userschema
+vocabularies
+workflow
+workingcopy
+```
diff --git a/docs/source/endpoints/navroot.md b/docs/source/endpoints/navroot.md
new file mode 100644
index 0000000000..30fd610177
--- /dev/null
+++ b/docs/source/endpoints/navroot.md
@@ -0,0 +1,158 @@
+---
+html_meta:
+ "description": "Navigation root is a concept that provides a way to root catalog queries, searches, and breadcrumbs in Plone."
+ "property=og:description": "Navigation root is a concept that provides a way to root catalog queries, searches, and breadcrumbs in Plone."
+ "property=og:title": "Navigation Root"
+ "keywords": "Plone, plone.restapi, REST, API, site, navigation root"
+---
+
+(navigation-root-label)=
+
+# Navigation root
+
+Plone has a concept called {term}`navigation root` which provides a way to root catalog queries, searches, breadcrumbs, and so on in a given section of the site.
+This feature is useful when working with subsites or multilingual sites, because it allows the site manager to restrict searches or navigation queries to a specific location in the site.
+
+This navigation root information is different depending on the context of the request.
+For instance, in a default multilingual site when browsing the contents inside a language folder such as `www.domain.com/en`, the context is `en` and its navigation root will be `/en/`.
+In a non-multilingual site, the context is the root of the site such as `www.domain.com` and the navigation root will be `/`.
+
+To get the information about the navigation root, the REST API has a `@navroot` contextual endpoint.
+For instance, send a `GET` request to the `@navroot` endpoint at the root of the site:
+
+```{eval-rst}
+.. http:example:: curl httpie python-requests
+ :request: ../../../src/plone/restapi/tests/http-examples/navroot_standard_site_get.req
+```
+
+The response will contain the navigation root information for the site:
+
+```{literalinclude} ../../../src/plone/restapi/tests/http-examples/navroot_standard_site_get.resp
+:language: http
+```
+
+If you request the `@navroot` of a given content item in the site:
+
+```{eval-rst}
+.. http:example:: curl httpie python-requests
+ :request: ../../../src/plone/restapi/tests/http-examples/navroot_standard_site_content_get.req
+```
+
+The response will contain the navigation root information in the context of that content item:
+
+```{literalinclude} ../../../src/plone/restapi/tests/http-examples/navroot_standard_site_content_get.resp
+:language: http
+```
+
+In a multilingual site, the root of the site will work as usual:
+
+```{eval-rst}
+.. http:example:: curl httpie python-requests
+ :request: ../../../src/plone/restapi/tests/http-examples/navroot_site_get.req
+```
+
+The response will contain the navigation root information of the root of the site:
+
+```{literalinclude} ../../../src/plone/restapi/tests/http-examples/navroot_site_get.resp
+:language: http
+```
+
+In a multilingual site where the language folder is the navigation root, the response has the language folder information:
+
+```{eval-rst}
+.. http:example:: curl httpie python-requests
+ :request: ../../../src/plone/restapi/tests/http-examples/navroot_lang_folder_get.req
+```
+
+The response will contain the navigation root information for the language folder:
+
+```{literalinclude} ../../../src/plone/restapi/tests/http-examples/navroot_lang_folder_get.resp
+:language: http
+```
+
+In a multilingual site, if the navigation root is requested for content inside a language folder:
+
+```{eval-rst}
+.. http:example:: curl httpie python-requests
+ :request: ../../../src/plone/restapi/tests/http-examples/navroot_lang_content_get.req
+```
+
+The response has the language folder information as a navigation root:
+
+```{literalinclude} ../../../src/plone/restapi/tests/http-examples/navroot_lang_content_get.resp
+:language: http
+```
+
+(navigation-root-expansion-label)=
+
+## Expansion
+
+This endpoint can be used with the {doc}`expansion` mechanism which allows getting more information about a content item in one query, avoiding unnecessary requests.
+
+If a simple `GET` request is made on the content item, a new entry will be shown on the `@components` entry with the URL of the `@navroot` endpoint.
+
+In a standard site when querying the site root:
+
+```{eval-rst}
+.. http:example:: curl httpie python-requests
+ :request: ../../../src/plone/restapi/tests/http-examples/navroot_standard_site_get_expansion.req
+```
+
+The response will contain information of the site root with the navigation expanded:
+
+```{literalinclude} ../../../src/plone/restapi/tests/http-examples/navroot_standard_site_get_expansion.resp
+:language: http
+```
+
+When querying a content item inside the root:
+
+```{eval-rst}
+.. http:example:: curl httpie python-requests
+ :request: ../../../src/plone/restapi/tests/http-examples/navroot_standard_site_content_get_expansion.req
+```
+
+The response will contain the information of that content item with its navigation root information expanded:
+
+```{literalinclude} ../../../src/plone/restapi/tests/http-examples/navroot_standard_site_content_get_expansion.resp
+:language: http
+```
+
+In a multilingual site, it works the same.
+Use the request:
+
+```{eval-rst}
+.. http:example:: curl httpie python-requests
+ :request: ../../../src/plone/restapi/tests/http-examples/site_get_expand_navroot.req
+```
+
+And the response will contain the navigation root information pointing to the root of the site:
+
+```{literalinclude} ../../../src/plone/restapi/tests/http-examples/site_get_expand_navroot.resp
+:language: http
+```
+
+It will also work with language root folders that are navigation roots:
+
+```{eval-rst}
+.. http:example:: curl httpie python-requests
+ :request: ../../../src/plone/restapi/tests/http-examples/site_get_expand_lang_folder.req
+```
+
+The response will contain the navigation root information expanded:
+
+```{literalinclude} ../../../src/plone/restapi/tests/http-examples/site_get_expand_lang_folder.resp
+:language: http
+```
+
+And also for content inside the language root folders:
+
+```{eval-rst}
+.. http:example:: curl httpie python-requests
+ :request: ../../../src/plone/restapi/tests/http-examples/site_get_expand_lang_folder_content.req
+```
+
+The response will include the expanded information pointing to the language root:
+
+```{literalinclude} ../../../src/plone/restapi/tests/http-examples/site_get_expand_lang_folder_content.resp
+:language: http
+```
diff --git a/docs/source/endpoints/site.md b/docs/source/endpoints/site.md
new file mode 100644
index 0000000000..40e76bf37c
--- /dev/null
+++ b/docs/source/endpoints/site.md
@@ -0,0 +1,25 @@
+---
+html_meta:
+ "description": "Site endpoint for Plone REST API"
+ "property=og:title": "Site endpoint for Plone REST API"
+ "property=og:description": "Site endpoint for Plone REST API"
+ "keywords": "Plone, plone.restapi, REST, API, site, navigation root"
+---
+
+# Site
+
+The `@site` endpoint provides general site-wide information, such as the site title, logo, and other information.
+It uses the `zope2.View` permission, which requires appropriate authorization.
+
+Send a `GET` request to the `@site` endpoint:
+
+```{eval-rst}
+.. http:example:: curl httpie python-requests
+ :request: ../../../src/plone/restapi/tests/http-examples/site_get.req
+```
+
+The response will contain the site information:
+
+```{literalinclude} ../../../src/plone/restapi/tests/http-examples/site_get.resp
+:language: http
+```
diff --git a/docs/source/glossary.md b/docs/source/glossary.md
new file mode 100644
index 0000000000..74adaaa75b
--- /dev/null
+++ b/docs/source/glossary.md
@@ -0,0 +1,67 @@
+---
+myst:
+ html_meta:
+ "description": "plone.restapi Glossary"
+ "property=og:description": "plone.restapi Glossary"
+ "property=og:title": "Glossary"
+ "keywords": "Plone, plone.restapi, REST, API, Glossary"
+---
+
+# Glossary
+
+```{glossary}
+:sorted: true
+
+REST
+ REST stands for [Representational State Transfer](https://en.wikipedia.org/wiki/Representational_state_transfer). It is a software architectural principle to create loosely coupled web APIs.
+
+workflow
+ A concept in Plone (and other CMS's) whereby a content object can be in a number of states (private, public, etcetera) and uses transitions to change between them (e.g. "publish", "approve", "reject", "retract"). See the [Plone docs on Workflow](https://5.docs.plone.org/working-with-content/collaboration-and-workflow/)
+
+HTTP-Request
+HTTP Request
+Request
+Requests
+ The initial action performed by a web client to communicate with a server. The {term}`Request` is usually followed by a {term}`Response` by the server, either synchronous or asynchronous (which is more complex to handle on the user side)
+
+HTTP-Response
+HTTP Response
+Response
+ Answer of or action by the server that is executed or send to the client after the {term}`Request` is processed.
+
+HTTP-Header
+HTTP Header
+Header
+ The part of the communication of the client with the server that provides the initialisation of the communication of a {term}`Request`.
+
+HTTP-Verb
+HTTP Verb
+Verb
+ One of the basic actions that can be requested to be executed by the server (on an object) based on the {term}`Request`.
+
+Object URL
+ The target object of the {term}`Request`
+
+Authorization Header
+ Part of the {term}`Request` that is responsible for the authentication related to the right user or service to ask for a {term}`Response`.
+
+Accept Header
+ Part of the {term}`Request` that is responsible to define the expected type of data to be accepted by the client in the {term}`Response`.
+
+Authentication Method
+ Access restriction provided by the connection chain to the server exposed to the client.
+
+Basic Auth
+ A simple {term}`Authentication Method` referenced in the {term}`Authorization Header` that needs to be provided by the server.
+
+content rule
+ A content rule will automatically perform an action when a certain event, known as a {term}`trigger`, takes place.
+
+trigger
+ A trigger is an event in Plone that causes the execution of defined actions.
+ Example triggers include object modified, user logged in, and workflow state changed.
+
+navigation root
+ An object marked as a navigation root provides a way to root catalog queries, searches, breadcrumbs, and so on, into that object.
+
+```
diff --git a/docs/source/index.md b/docs/source/index.md
new file mode 100644
index 0000000000..1560bc7bd4
--- /dev/null
+++ b/docs/source/index.md
@@ -0,0 +1,43 @@
+---
+myst:
+ html_meta:
+ "description": "A RESTful API for Plone."
+ "property=og:description": "A RESTful API for Plone."
+ "property=og:title": "REST API"
+ "keywords": "Plone, plone.restapi, REST, API"
+---
+
+% plone.restapi documentation master file, created by
+% sphinx-quickstart on Mon Apr 28 13:04:12 2014.
+% You can adapt this file completely to your liking, but it should at least
+% contain the root `toctree` directive.
+
+# REST API
+
+A RESTful API for Plone.
+
+```{toctree}
+:caption: Table of Contents
+:maxdepth: 2
+
+introduction
+usage/index
+endpoints/index
+upgrade-guide
+contributing/index
+```
+
+```{eval-rst}
+.. include:: ../../README.rst
+```
+
+## Appendix and Glossary
+
+```{toctree}
+http-status-codes
+/glossary
+```
+
+## Index
+
+- {ref}`genindex`
diff --git a/news/1464.feature b/news/1464.feature
new file mode 100644
index 0000000000..5e52088d8b
--- /dev/null
+++ b/news/1464.feature
@@ -0,0 +1 @@
+Added `@site` and `@navroot` endpoints. @erral
diff --git a/src/plone/restapi/services/configure.zcml b/src/plone/restapi/services/configure.zcml
index b9f4fac9db..0a526c7690 100644
--- a/src/plone/restapi/services/configure.zcml
+++ b/src/plone/restapi/services/configure.zcml
@@ -23,6 +23,7 @@
If you're seeing this instead of the web site you were expecting, the owner of this web site has just installed Plone. Do not contact the Plone Team or the Plone mailing lists about this.
", + "encoding": "utf-8" + }, + "title": "Welcome to Plone", + "type_title": "Page", + "version": "current", + "versioning_enabled": true, + "working_copy": null, + "working_copy_of": null +} diff --git a/src/plone/restapi/tests/http-examples/navroot_standard_site_get.req b/src/plone/restapi/tests/http-examples/navroot_standard_site_get.req new file mode 100644 index 0000000000..cc9c5ca1e8 --- /dev/null +++ b/src/plone/restapi/tests/http-examples/navroot_standard_site_get.req @@ -0,0 +1,3 @@ +GET /plone/@navroot HTTP/1.1 +Accept: application/json +Authorization: Basic YWRtaW46c2VjcmV0 diff --git a/src/plone/restapi/tests/http-examples/navroot_standard_site_get.resp b/src/plone/restapi/tests/http-examples/navroot_standard_site_get.resp new file mode 100644 index 0000000000..532ec7d6ca --- /dev/null +++ b/src/plone/restapi/tests/http-examples/navroot_standard_site_get.resp @@ -0,0 +1,76 @@ +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "@id": "http://localhost:55001/plone/@navroot", + "navroot": { + "@components": { + "actions": { + "@id": "http://localhost:55001/plone/@actions" + }, + "aliases": { + "@id": "http://localhost:55001/plone/@aliases" + }, + "breadcrumbs": { + "@id": "http://localhost:55001/plone/@breadcrumbs" + }, + "contextnavigation": { + "@id": "http://localhost:55001/plone/@contextnavigation" + }, + "navigation": { + "@id": "http://localhost:55001/plone/@navigation" + }, + "navroot": { + "@id": "http://localhost:55001/plone/@navroot" + }, + "types": { + "@id": "http://localhost:55001/plone/@types" + }, + "workflow": { + "@id": "http://localhost:55001/plone/@workflow" + } + }, + "@id": "http://localhost:55001/plone", + "@type": "Plone Site", + "UID": "55c25ebc220d400393574f37d648727c", + "allow_discussion": null, + "contributors": [], + "creators": [ + "admin" + ], + "description": "", + "effective": null, + "exclude_from_nav": false, + "expires": null, + "id": "plone", + "is_folderish": true, + "items": [ + { + "@id": "http://localhost:55001/plone/front-page", + "@type": "Document", + "description": "Congratulations! You have successfully installed Plone.", + "review_state": "private", + "title": "Welcome to Plone", + "type_title": "Page" + } + ], + "items_total": 1, + "language": { + "title": "English", + "token": "en" + }, + "lock": { + "locked": false, + "stealable": true + }, + "parent": {}, + "relatedItems": [], + "review_state": null, + "rights": "", + "subjects": [], + "table_of_contents": null, + "text": null, + "title": "Plone site", + "type_title": "Plone Site" + } +} diff --git a/src/plone/restapi/tests/http-examples/navroot_standard_site_get_expansion.req b/src/plone/restapi/tests/http-examples/navroot_standard_site_get_expansion.req new file mode 100644 index 0000000000..36c802e247 --- /dev/null +++ b/src/plone/restapi/tests/http-examples/navroot_standard_site_get_expansion.req @@ -0,0 +1,3 @@ +GET /plone/?expand=navroot HTTP/1.1 +Accept: application/json +Authorization: Basic YWRtaW46c2VjcmV0 diff --git a/src/plone/restapi/tests/http-examples/navroot_standard_site_get_expansion.resp b/src/plone/restapi/tests/http-examples/navroot_standard_site_get_expansion.resp new file mode 100644 index 0000000000..0a33c7475c --- /dev/null +++ b/src/plone/restapi/tests/http-examples/navroot_standard_site_get_expansion.resp @@ -0,0 +1,143 @@ +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "@components": { + "actions": { + "@id": "http://localhost:55001/plone/@actions" + }, + "aliases": { + "@id": "http://localhost:55001/plone/@aliases" + }, + "breadcrumbs": { + "@id": "http://localhost:55001/plone/@breadcrumbs" + }, + "contextnavigation": { + "@id": "http://localhost:55001/plone/@contextnavigation" + }, + "navigation": { + "@id": "http://localhost:55001/plone/@navigation" + }, + "navroot": { + "@id": "http://localhost:55001/plone/@navroot", + "navroot": { + "@components": { + "actions": { + "@id": "http://localhost:55001/plone/@actions" + }, + "aliases": { + "@id": "http://localhost:55001/plone/@aliases" + }, + "breadcrumbs": { + "@id": "http://localhost:55001/plone/@breadcrumbs" + }, + "contextnavigation": { + "@id": "http://localhost:55001/plone/@contextnavigation" + }, + "navigation": { + "@id": "http://localhost:55001/plone/@navigation" + }, + "navroot": { + "@id": "http://localhost:55001/plone/@navroot" + }, + "types": { + "@id": "http://localhost:55001/plone/@types" + }, + "workflow": { + "@id": "http://localhost:55001/plone/@workflow" + } + }, + "@id": "http://localhost:55001/plone", + "@type": "Plone Site", + "UID": "55c25ebc220d400393574f37d648727c", + "allow_discussion": null, + "contributors": [], + "creators": [ + "admin" + ], + "description": "", + "effective": null, + "exclude_from_nav": false, + "expires": null, + "id": "plone", + "is_folderish": true, + "items": [ + { + "@id": "http://localhost:55001/plone/front-page", + "@type": "Document", + "description": "Congratulations! You have successfully installed Plone.", + "review_state": "private", + "title": "Welcome to Plone", + "type_title": "Page" + } + ], + "items_total": 1, + "language": { + "title": "English", + "token": "en" + }, + "lock": { + "locked": false, + "stealable": true + }, + "parent": {}, + "relatedItems": [], + "review_state": null, + "rights": "", + "subjects": [], + "table_of_contents": null, + "text": null, + "title": "Plone site", + "type_title": "Plone Site" + } + }, + "types": { + "@id": "http://localhost:55001/plone/@types" + }, + "workflow": { + "@id": "http://localhost:55001/plone/@workflow" + } + }, + "@id": "http://localhost:55001/plone", + "@type": "Plone Site", + "UID": "55c25ebc220d400393574f37d648727c", + "allow_discussion": null, + "contributors": [], + "creators": [ + "admin" + ], + "description": "", + "effective": null, + "exclude_from_nav": false, + "expires": null, + "id": "plone", + "is_folderish": true, + "items": [ + { + "@id": "http://localhost:55001/plone/front-page", + "@type": "Document", + "description": "Congratulations! You have successfully installed Plone.", + "review_state": "private", + "title": "Welcome to Plone", + "type_title": "Page" + } + ], + "items_total": 1, + "language": { + "title": "English", + "token": "en" + }, + "lock": { + "locked": false, + "stealable": true + }, + "parent": {}, + "relatedItems": [], + "review_state": null, + "rights": "", + "subjects": [], + "table_of_contents": null, + "text": null, + "title": "Plone site", + "type_title": "Plone Site" +} diff --git a/src/plone/restapi/tests/http-examples/newsitem.resp b/src/plone/restapi/tests/http-examples/newsitem.resp index 7b13607f99..ddf4779998 100644 --- a/src/plone/restapi/tests/http-examples/newsitem.resp +++ b/src/plone/restapi/tests/http-examples/newsitem.resp @@ -15,6 +15,9 @@ Content-Type: application/json "navigation": { "@id": "http://localhost:55001/plone/newsitem/@navigation" }, + "navroot": { + "@id": "http://localhost:55001/plone/newsitem/@navroot" + }, "types": { "@id": "http://localhost:55001/plone/newsitem/@types" }, diff --git a/src/plone/restapi/tests/http-examples/search_fullobjects.resp b/src/plone/restapi/tests/http-examples/search_fullobjects.resp index 58edeb566a..a32e3555a4 100644 --- a/src/plone/restapi/tests/http-examples/search_fullobjects.resp +++ b/src/plone/restapi/tests/http-examples/search_fullobjects.resp @@ -18,6 +18,9 @@ Content-Type: application/json "navigation": { "@id": "http://localhost:55001/plone/doc1/@navigation" }, + "navroot": { + "@id": "http://localhost:55001/plone/doc1/@navroot" + }, "types": { "@id": "http://localhost:55001/plone/doc1/@types" }, diff --git a/src/plone/restapi/tests/http-examples/site_get.req b/src/plone/restapi/tests/http-examples/site_get.req new file mode 100644 index 0000000000..2c0e40f0e2 --- /dev/null +++ b/src/plone/restapi/tests/http-examples/site_get.req @@ -0,0 +1,3 @@ +GET /plone/@site HTTP/1.1 +Accept: application/json +Authorization: Basic YWRtaW46c2VjcmV0 diff --git a/src/plone/restapi/tests/http-examples/site_get.resp b/src/plone/restapi/tests/http-examples/site_get.resp new file mode 100644 index 0000000000..e5f274d3de --- /dev/null +++ b/src/plone/restapi/tests/http-examples/site_get.resp @@ -0,0 +1,22 @@ +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "@id": "http://localhost:55001/plone/@site", + "plone.allowed_sizes": [ + "huge 1600:65536", + "great 1200:65536", + "larger 1000:65536", + "large 800:65536", + "teaser 600:65536", + "preview 400:65536", + "mini 200:65536", + "thumb 128:128", + "tile 64:64", + "icon 32:32", + "listing 16:16" + ], + "plone.robots_txt": "Sitemap: {portal_url}/sitemap.xml.gz\n\n# Define access-restrictions for robots/spiders\n# http://www.robotstxt.org/wc/norobots.html\n\n\n\n# By default we allow robots to access all areas of our site\n# already accessible to anonymous users\n\nUser-agent: *\nDisallow:\n\n\n\n# Add Googlebot-specific syntax extension to exclude forms\n# that are repeated for each piece of content in the site\n# the wildcard is only supported by Googlebot\n# http://www.google.com/support/webmasters/bin/answer.py?answer=40367&ctx=sibling\n\nUser-Agent: Googlebot\nDisallow: /*?\nDisallow: /*atct_album_view$\nDisallow: /*folder_factories$\nDisallow: /*folder_summary_view$\nDisallow: /*login_form$\nDisallow: /*mail_password_form$\nDisallow: /@@search\nDisallow: /*search_rss$\nDisallow: /*sendto_form$\nDisallow: /*summary_view$\nDisallow: /*thumbnail_view$\nDisallow: /*view$\n", + "plone.site_logo": null, + "plone.site_title": "Plone site" +} diff --git a/src/plone/restapi/tests/http-examples/site_get_expand_lang_folder.req b/src/plone/restapi/tests/http-examples/site_get_expand_lang_folder.req new file mode 100644 index 0000000000..5eae3bdfb1 --- /dev/null +++ b/src/plone/restapi/tests/http-examples/site_get_expand_lang_folder.req @@ -0,0 +1,3 @@ +GET /plone/en?expand=navroot HTTP/1.1 +Accept: application/json +Authorization: Basic YWRtaW46c2VjcmV0 diff --git a/src/plone/restapi/tests/http-examples/site_get_expand_lang_folder.resp b/src/plone/restapi/tests/http-examples/site_get_expand_lang_folder.resp new file mode 100644 index 0000000000..d5c1a0f186 --- /dev/null +++ b/src/plone/restapi/tests/http-examples/site_get_expand_lang_folder.resp @@ -0,0 +1,173 @@ +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "@components": { + "actions": { + "@id": "http://localhost:55001/plone/en/@actions" + }, + "breadcrumbs": { + "@id": "http://localhost:55001/plone/en/@breadcrumbs" + }, + "contextnavigation": { + "@id": "http://localhost:55001/plone/en/@contextnavigation" + }, + "navigation": { + "@id": "http://localhost:55001/plone/en/@navigation" + }, + "navroot": { + "@id": "http://localhost:55001/plone/en/@navroot", + "navroot": { + "@components": { + "actions": { + "@id": "http://localhost:55001/plone/en/@actions" + }, + "breadcrumbs": { + "@id": "http://localhost:55001/plone/en/@breadcrumbs" + }, + "contextnavigation": { + "@id": "http://localhost:55001/plone/en/@contextnavigation" + }, + "navigation": { + "@id": "http://localhost:55001/plone/en/@navigation" + }, + "navroot": { + "@id": "http://localhost:55001/plone/en/@navroot" + }, + "translations": { + "@id": "http://localhost:55001/plone/en/@translations" + }, + "types": { + "@id": "http://localhost:55001/plone/en/@types" + }, + "workflow": { + "@id": "http://localhost:55001/plone/en/@workflow" + } + }, + "@id": "http://localhost:55001/plone/en", + "@type": "LRF", + "UID": "00000000000000000000000000000001", + "allow_discussion": false, + "contributors": [], + "created": "1995-07-31T13:45:00", + "creators": [ + "admin" + ], + "description": "", + "effective": null, + "exclude_from_nav": true, + "expires": null, + "id": "en", + "is_folderish": true, + "items": [ + { + "@id": "http://localhost:55001/plone/en/assets", + "@type": "LIF", + "description": "", + "review_state": "published", + "title": "Assets" + }, + { + "@id": "http://localhost:55001/plone/en/test-document", + "@type": "Document", + "description": "", + "review_state": "private", + "title": "Test document" + } + ], + "items_total": 2, + "language": { + "title": "English", + "token": "en" + }, + "layout": "folder_listing", + "lock": {}, + "modified": "1995-07-31T17:30:00", + "next_item": { + "@id": "http://localhost:55001/plone/de", + "@type": "LRF", + "description": "", + "title": "Deutsch" + }, + "parent": { + "@id": "http://localhost:55001/plone", + "@type": "Plone Site", + "description": "", + "title": "Plone site" + }, + "previous_item": {}, + "review_state": "published", + "rights": "", + "subjects": [], + "title": "English", + "version": "current" + } + }, + "translations": { + "@id": "http://localhost:55001/plone/en/@translations" + }, + "types": { + "@id": "http://localhost:55001/plone/en/@types" + }, + "workflow": { + "@id": "http://localhost:55001/plone/en/@workflow" + } + }, + "@id": "http://localhost:55001/plone/en", + "@type": "LRF", + "UID": "00000000000000000000000000000001", + "allow_discussion": false, + "contributors": [], + "created": "1995-07-31T13:45:00", + "creators": [ + "admin" + ], + "description": "", + "effective": null, + "exclude_from_nav": true, + "expires": null, + "id": "en", + "is_folderish": true, + "items": [ + { + "@id": "http://localhost:55001/plone/en/assets", + "@type": "LIF", + "description": "", + "review_state": "published", + "title": "Assets" + }, + { + "@id": "http://localhost:55001/plone/en/test-document", + "@type": "Document", + "description": "", + "review_state": "private", + "title": "Test document" + } + ], + "items_total": 2, + "language": { + "title": "English", + "token": "en" + }, + "layout": "folder_listing", + "lock": {}, + "modified": "1995-07-31T17:30:00", + "next_item": { + "@id": "http://localhost:55001/plone/de", + "@type": "LRF", + "description": "", + "title": "Deutsch" + }, + "parent": { + "@id": "http://localhost:55001/plone", + "@type": "Plone Site", + "description": "", + "title": "Plone site" + }, + "previous_item": {}, + "review_state": "published", + "rights": "", + "subjects": [], + "title": "English", + "version": "current" +} diff --git a/src/plone/restapi/tests/http-examples/site_get_expand_lang_folder_content.req b/src/plone/restapi/tests/http-examples/site_get_expand_lang_folder_content.req new file mode 100644 index 0000000000..3574d72d7e --- /dev/null +++ b/src/plone/restapi/tests/http-examples/site_get_expand_lang_folder_content.req @@ -0,0 +1,3 @@ +GET /plone/en/test-document?expand=navroot HTTP/1.1 +Accept: application/json +Authorization: Basic YWRtaW46c2VjcmV0 diff --git a/src/plone/restapi/tests/http-examples/site_get_expand_lang_folder_content.resp b/src/plone/restapi/tests/http-examples/site_get_expand_lang_folder_content.resp new file mode 100644 index 0000000000..32745d5534 --- /dev/null +++ b/src/plone/restapi/tests/http-examples/site_get_expand_lang_folder_content.resp @@ -0,0 +1,165 @@ +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "@components": { + "actions": { + "@id": "http://localhost:55001/plone/en/test-document/@actions" + }, + "breadcrumbs": { + "@id": "http://localhost:55001/plone/en/test-document/@breadcrumbs" + }, + "contextnavigation": { + "@id": "http://localhost:55001/plone/en/test-document/@contextnavigation" + }, + "navigation": { + "@id": "http://localhost:55001/plone/en/test-document/@navigation" + }, + "navroot": { + "@id": "http://localhost:55001/plone/en/test-document/@navroot", + "navroot": { + "@components": { + "actions": { + "@id": "http://localhost:55001/plone/en/@actions" + }, + "breadcrumbs": { + "@id": "http://localhost:55001/plone/en/@breadcrumbs" + }, + "contextnavigation": { + "@id": "http://localhost:55001/plone/en/@contextnavigation" + }, + "navigation": { + "@id": "http://localhost:55001/plone/en/@navigation" + }, + "navroot": { + "@id": "http://localhost:55001/plone/en/@navroot" + }, + "translations": { + "@id": "http://localhost:55001/plone/en/@translations" + }, + "types": { + "@id": "http://localhost:55001/plone/en/@types" + }, + "workflow": { + "@id": "http://localhost:55001/plone/en/@workflow" + } + }, + "@id": "http://localhost:55001/plone/en", + "@type": "LRF", + "UID": "00000000000000000000000000000001", + "allow_discussion": false, + "contributors": [], + "created": "1995-07-31T13:45:00", + "creators": [ + "admin" + ], + "description": "", + "effective": null, + "exclude_from_nav": true, + "expires": null, + "id": "en", + "is_folderish": true, + "items": [ + { + "@id": "http://localhost:55001/plone/en/assets", + "@type": "LIF", + "description": "", + "review_state": "published", + "title": "Assets" + }, + { + "@id": "http://localhost:55001/plone/en/test-document", + "@type": "Document", + "description": "", + "review_state": "private", + "title": "Test document" + } + ], + "items_total": 2, + "language": { + "title": "English", + "token": "en" + }, + "layout": "folder_listing", + "lock": {}, + "modified": "1995-07-31T17:30:00", + "next_item": { + "@id": "http://localhost:55001/plone/de", + "@type": "LRF", + "description": "", + "title": "Deutsch" + }, + "parent": { + "@id": "http://localhost:55001/plone", + "@type": "Plone Site", + "description": "", + "title": "Plone site" + }, + "previous_item": {}, + "review_state": "published", + "rights": "", + "subjects": [], + "title": "English", + "version": "current" + } + }, + "translations": { + "@id": "http://localhost:55001/plone/en/test-document/@translations" + }, + "types": { + "@id": "http://localhost:55001/plone/en/test-document/@types" + }, + "workflow": { + "@id": "http://localhost:55001/plone/en/test-document/@workflow" + } + }, + "@id": "http://localhost:55001/plone/en/test-document", + "@type": "Document", + "UID": "SomeUUID000000000000000000000001", + "allow_discussion": false, + "changeNote": "", + "contributors": [], + "created": "1995-07-31T13:45:00", + "creators": [ + "test_user_1_" + ], + "description": "", + "effective": null, + "exclude_from_nav": false, + "expires": null, + "id": "test-document", + "is_folderish": false, + "language": { + "title": "English", + "token": "en" + }, + "layout": "document_view", + "lock": { + "locked": false, + "stealable": true + }, + "modified": "1995-07-31T17:30:00", + "next_item": {}, + "parent": { + "@id": "http://localhost:55001/plone/en", + "@type": "LRF", + "description": "", + "review_state": "published", + "title": "English" + }, + "previous_item": { + "@id": "http://localhost:55001/plone/en/assets", + "@type": "LIF", + "description": "", + "title": "Assets" + }, + "relatedItems": [], + "review_state": "private", + "rights": "", + "subjects": [], + "table_of_contents": null, + "text": null, + "title": "Test document", + "version": "current", + "versioning_enabled": true +} diff --git a/src/plone/restapi/tests/http-examples/site_get_expand_navroot.req b/src/plone/restapi/tests/http-examples/site_get_expand_navroot.req new file mode 100644 index 0000000000..36c802e247 --- /dev/null +++ b/src/plone/restapi/tests/http-examples/site_get_expand_navroot.req @@ -0,0 +1,3 @@ +GET /plone/?expand=navroot HTTP/1.1 +Accept: application/json +Authorization: Basic YWRtaW46c2VjcmV0 diff --git a/src/plone/restapi/tests/http-examples/site_get_expand_navroot.resp b/src/plone/restapi/tests/http-examples/site_get_expand_navroot.resp new file mode 100644 index 0000000000..b28d008a7a --- /dev/null +++ b/src/plone/restapi/tests/http-examples/site_get_expand_navroot.resp @@ -0,0 +1,121 @@ +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "@components": { + "actions": { + "@id": "http://localhost:55001/plone/@actions" + }, + "breadcrumbs": { + "@id": "http://localhost:55001/plone/@breadcrumbs" + }, + "contextnavigation": { + "@id": "http://localhost:55001/plone/@contextnavigation" + }, + "navigation": { + "@id": "http://localhost:55001/plone/@navigation" + }, + "navroot": { + "@id": "http://localhost:55001/plone/@navroot", + "navroot": { + "@components": { + "actions": { + "@id": "http://localhost:55001/plone/@actions" + }, + "breadcrumbs": { + "@id": "http://localhost:55001/plone/@breadcrumbs" + }, + "contextnavigation": { + "@id": "http://localhost:55001/plone/@contextnavigation" + }, + "navigation": { + "@id": "http://localhost:55001/plone/@navigation" + }, + "navroot": { + "@id": "http://localhost:55001/plone/@navroot" + } + }, + "@id": "http://localhost:55001/plone/?expand=navroot", + "@type": "Plone Site", + "blocks": {}, + "blocks_layout": {}, + "description": "", + "id": "plone", + "is_folderish": true, + "items": [ + { + "@id": "http://localhost:55001/plone/en", + "@type": "LRF", + "description": "", + "review_state": "published", + "title": "English" + }, + { + "@id": "http://localhost:55001/plone/de", + "@type": "LRF", + "description": "", + "review_state": "published", + "title": "Deutsch" + }, + { + "@id": "http://localhost:55001/plone/es", + "@type": "LRF", + "description": "", + "review_state": "published", + "title": "Espa\u00f1ol" + }, + { + "@id": "http://localhost:55001/plone/fr", + "@type": "LRF", + "description": "", + "review_state": null, + "title": "Fran\u00e7ais" + } + ], + "items_total": 4, + "parent": {}, + "title": "Plone site" + } + } + }, + "@id": "http://localhost:55001/plone/?expand=navroot", + "@type": "Plone Site", + "blocks": {}, + "blocks_layout": {}, + "description": "", + "id": "plone", + "is_folderish": true, + "items": [ + { + "@id": "http://localhost:55001/plone/en", + "@type": "LRF", + "description": "", + "review_state": "published", + "title": "English" + }, + { + "@id": "http://localhost:55001/plone/de", + "@type": "LRF", + "description": "", + "review_state": "published", + "title": "Deutsch" + }, + { + "@id": "http://localhost:55001/plone/es", + "@type": "LRF", + "description": "", + "review_state": "published", + "title": "Espa\u00f1ol" + }, + { + "@id": "http://localhost:55001/plone/fr", + "@type": "LRF", + "description": "", + "review_state": null, + "title": "Fran\u00e7ais" + } + ], + "items_total": 4, + "parent": {}, + "title": "Plone site" +} diff --git a/src/plone/restapi/tests/http-examples/siteroot.resp b/src/plone/restapi/tests/http-examples/siteroot.resp index 4c00477092..a528d73376 100644 --- a/src/plone/restapi/tests/http-examples/siteroot.resp +++ b/src/plone/restapi/tests/http-examples/siteroot.resp @@ -14,6 +14,9 @@ Content-Type: application/json }, "navigation": { "@id": "http://localhost:55001/plone/@navigation" + }, + "navroot": { + "@id": "http://localhost:55001/plone/@navroot" } }, "@id": "http://localhost:55001/plone", diff --git a/src/plone/restapi/tests/http-examples/translations_link_on_post.resp b/src/plone/restapi/tests/http-examples/translations_link_on_post.resp index f82e131dfe..836ce9429d 100644 --- a/src/plone/restapi/tests/http-examples/translations_link_on_post.resp +++ b/src/plone/restapi/tests/http-examples/translations_link_on_post.resp @@ -16,6 +16,9 @@ Location: http://localhost:55001/plone/de/mydocument "navigation": { "@id": "http://localhost:55001/plone/de/mydocument/@navigation" }, + "navroot": { + "@id": "http://localhost:55001/plone/de/mydocument/@navroot" + }, "translations": { "@id": "http://localhost:55001/plone/de/mydocument/@translations" }, diff --git a/src/plone/restapi/tests/http-examples/workingcopy_baseline_get.resp b/src/plone/restapi/tests/http-examples/workingcopy_baseline_get.resp index 92a8ab1818..bb6eb48071 100644 --- a/src/plone/restapi/tests/http-examples/workingcopy_baseline_get.resp +++ b/src/plone/restapi/tests/http-examples/workingcopy_baseline_get.resp @@ -5,80 +5,80 @@ Content-Type: application/json "@components": { "actions": { "@id": "http://localhost:55001/plone/document/@actions" - }, + }, "breadcrumbs": { "@id": "http://localhost:55001/plone/document/@breadcrumbs" - }, + }, "contextnavigation": { "@id": "http://localhost:55001/plone/document/@contextnavigation" - }, + }, "navigation": { "@id": "http://localhost:55001/plone/document/@navigation" - }, + }, "types": { "@id": "http://localhost:55001/plone/document/@types" - }, + }, "workflow": { "@id": "http://localhost:55001/plone/document/@workflow" } - }, - "@id": "http://localhost:55001/plone/document", - "@type": "Document", - "UID": "SomeUUID000000000000000000000001", - "allow_discussion": false, - "contributors": [], - "created": "1995-07-31T13:45:00", + }, + "@id": "http://localhost:55001/plone/document", + "@type": "Document", + "UID": "SomeUUID000000000000000000000001", + "allow_discussion": false, + "contributors": [], + "created": "1995-07-31T13:45:00", "creators": [ "test_user_1_" - ], - "description": "", - "effective": null, - "exclude_from_nav": false, - "expires": null, - "id": "document", - "is_folderish": false, - "language": "", - "layout": "document_view", + ], + "description": "", + "effective": null, + "exclude_from_nav": false, + "expires": null, + "id": "document", + "is_folderish": false, + "language": "", + "layout": "document_view", "lock": { - "created": "1995-07-31T17:30:00", - "creator": "admin", - "creator_name": "admin", - "creator_url": "http://localhost:55001/plone/author/admin", - "locked": true, - "name": "iterate.lock", - "stealable": false, - "time": 807201000.0, - "timeout": 4294967280, + "created": "1995-07-31T17:30:00", + "creator": "admin", + "creator_name": "admin", + "creator_url": "http://localhost:55001/plone/author/admin", + "locked": true, + "name": "iterate.lock", + "stealable": false, + "time": 807201000.0, + "timeout": 4294967280, "token": "0.3217605825198149-0.016714612599692202-00105A989226:1629895144.744" - }, - "modified": "1995-07-31T17:30:00", + }, + "modified": "1995-07-31T17:30:00", "next_item": { - "@id": "http://localhost:55001/plone/copy_of_document", - "@type": "Document", - "description": "", + "@id": "http://localhost:55001/plone/copy_of_document", + "@type": "Document", + "description": "", "title": "Test document" - }, + }, "parent": { - "@id": "http://localhost:55001/plone", - "@type": "Plone Site", - "description": "", + "@id": "http://localhost:55001/plone", + "@type": "Plone Site", + "description": "", "title": "Plone site" - }, - "previous_item": {}, - "relatedItems": [], - "review_state": "private", - "rights": "", - "subjects": [], - "table_of_contents": null, - "text": null, - "title": "Test document", - "version": "current", + }, + "previous_item": {}, + "relatedItems": [], + "review_state": "private", + "rights": "", + "subjects": [], + "table_of_contents": null, + "text": null, + "title": "Test document", + "version": "current", "working_copy": { - "@id": "http://localhost:55001/plone/copy_of_document", - "created": "1995-07-31T13:45:00", - "creator_name": "admin", - "creator_url": "http://localhost:55001/plone/author/admin", + "@id": "http://localhost:55001/plone/copy_of_document", + "created": "1995-07-31T13:45:00", + "creator_name": "admin", + "creator_url": "http://localhost:55001/plone/author/admin", "title": "Test document" - }, + }, "working_copy_of": null -} \ No newline at end of file +} diff --git a/src/plone/restapi/tests/http-examples/workingcopy_wc_get.resp b/src/plone/restapi/tests/http-examples/workingcopy_wc_get.resp index 9c4216f49b..cb7e871387 100644 --- a/src/plone/restapi/tests/http-examples/workingcopy_wc_get.resp +++ b/src/plone/restapi/tests/http-examples/workingcopy_wc_get.resp @@ -2,78 +2,142 @@ HTTP/1.1 200 OK Content-Type: application/json { - "@components": { - "actions": { - "@id": "http://localhost:55001/plone/copy_of_document/@actions" - }, - "breadcrumbs": { - "@id": "http://localhost:55001/plone/copy_of_document/@breadcrumbs" - }, - "contextnavigation": { - "@id": "http://localhost:55001/plone/copy_of_document/@contextnavigation" - }, - "navigation": { - "@id": "http://localhost:55001/plone/copy_of_document/@navigation" - }, - "types": { - "@id": "http://localhost:55001/plone/copy_of_document/@types" - }, - "workflow": { - "@id": "http://localhost:55001/plone/copy_of_document/@workflow" + "@components": { + "actions": { + "@id": "http://localhost:55001/plone/copy_of_document/@actions" + }, + "aliases": { + "@id": "http://localhost:55001/plone/copy_of_document/@aliases" + }, + "breadcrumbs": { + "@id": "http://localhost:55001/plone/copy_of_document/@breadcrumbs" + }, + "contextnavigation": { + "@id": "http://localhost:55001/plone/copy_of_document/@contextnavigation" + }, + "navigation": { + "@id": "http://localhost:55001/plone/copy_of_document/@navigation" + }, + "navroot": { + "@id": "http://localhost:55001/plone/copy_of_document/@navroot" + }, + "types": { + "@id": "http://localhost:55001/plone/copy_of_document/@types" + }, + "workflow": { + "@id": "http://localhost:55001/plone/copy_of_document/@workflow" + } + }, + "@id": "http://localhost:55001/plone/copy_of_document", + "@type": "Document", + "UID": "SomeUUID000000000000000000000002", + "allow_discussion": false, + "contributors": [], + "created": "1995-07-31T13:45:00", + "creators": [ + "test_user_1_" + ], + "description": "", + "effective": null, + "exclude_from_nav": false, + "expires": null, + "id": "copy_of_document", + "is_folderish": false, + "language": "", + "layout": "document_view", + "lock": { + "locked": false, + "stealable": true + }, + "modified": "1995-07-31T17:30:00", + "next_item": {}, + "parent": { + "@id": "http://localhost:55001/plone", + "@type": "Plone Site", + "description": "", + "title": "Plone site", + "type_title": "Plone Site" + }, + "previous_item": { + "@id": "http://localhost:55001/plone/document", + "@type": "Document", + "description": "", + "title": "Test document", + "type_title": "Page" + }, + "relatedItems": [], + "review_state": "private", + "rights": "", + "subjects": [], + "table_of_contents": null, + "text": null, + "title": "Test document", + "type_title": "Page", + "version": "current", + "working_copy": { + "@id": "http://localhost:55001/plone/copy_of_document", + "created": "1995-07-31T13:45:00", + "creator_name": "admin", + "creator_url": "http://localhost:55001/plone/author/admin", + "title": "Test document" + }, + "working_copy_of": { + "@id": "http://localhost:55001/plone/document", + "title": "Test document" } - }, - "@id": "http://localhost:55001/plone/copy_of_document", - "@type": "Document", - "UID": "SomeUUID000000000000000000000002", - "allow_discussion": false, - "contributors": [], - "created": "1995-07-31T13:45:00", + }, + "@id": "http://localhost:55001/plone/copy_of_document", + "@type": "Document", + "UID": "SomeUUID000000000000000000000002", + "allow_discussion": false, + "contributors": [], + "created": "1995-07-31T13:45:00", "creators": [ "test_user_1_" - ], - "description": "", - "effective": null, - "exclude_from_nav": false, - "expires": null, - "id": "copy_of_document", - "is_folderish": false, - "language": "", - "layout": "document_view", + ], + "description": "", + "effective": null, + "exclude_from_nav": false, + "expires": null, + "id": "copy_of_document", + "is_folderish": false, + "language": "", + "layout": "document_view", "lock": { - "locked": false, + "locked": false, "stealable": true - }, - "modified": "1995-07-31T17:30:00", - "next_item": {}, + }, + "modified": "1995-07-31T17:30:00", + "next_item": {}, "parent": { - "@id": "http://localhost:55001/plone", - "@type": "Plone Site", - "description": "", + "@id": "http://localhost:55001/plone", + "@type": "Plone Site", + "description": "", "title": "Plone site" - }, + }, "previous_item": { - "@id": "http://localhost:55001/plone/document", - "@type": "Document", - "description": "", + "@id": "http://localhost:55001/plone/document", + "@type": "Document", + "description": "", "title": "Test document" - }, - "relatedItems": [], - "review_state": "private", - "rights": "", - "subjects": [], - "table_of_contents": null, - "text": null, - "title": "Test document", - "version": "current", + }, + "relatedItems": [], + "review_state": "private", + "rights": "", + "subjects": [], + "table_of_contents": null, + "text": null, + "title": "Test document", + "version": "current", "working_copy": { - "@id": "http://localhost:55001/plone/copy_of_document", - "created": "1995-07-31T13:45:00", - "creator_name": "admin", - "creator_url": "http://localhost:55001/plone/author/admin", + "@id": "http://localhost:55001/plone/copy_of_document", + "created": "1995-07-31T13:45:00", + "creator_name": "admin", + "creator_url": "http://localhost:55001/plone/author/admin", "title": "Test document" - }, + }, "working_copy_of": { - "@id": "http://localhost:55001/plone/document", + "@id": "http://localhost:55001/plone/document", "title": "Test document" } } \ No newline at end of file diff --git a/src/plone/restapi/tests/test_documentation.py b/src/plone/restapi/tests/test_documentation.py index f4b40c675e..5912a1f0f3 100644 --- a/src/plone/restapi/tests/test_documentation.py +++ b/src/plone/restapi/tests/test_documentation.py @@ -8,7 +8,7 @@ from plone.app.discussion.interfaces import IConversation from plone.app.discussion.interfaces import IDiscussionSettings from plone.app.discussion.interfaces import IReplies -from plone.app.testing import applyProfile + from plone.app.testing import popGlobalRegistry from plone.app.testing import pushGlobalRegistry from plone.app.testing import setRoles @@ -32,9 +32,8 @@ from six.moves import range from zope.component import createObject from zope.component import getUtility -from zope.component.hooks import getSite from zope.interface import alsoProvides - +from zope.component.hooks import getSite import collections import json import os @@ -203,7 +202,8 @@ def setUp(self): self.browser = Browser(self.app) self.browser.handleErrors = False self.browser.addHeader( - "Authorization", "Basic %s:%s" % (SITE_OWNER_NAME, SITE_OWNER_PASSWORD) + "Authorization", + "Basic %s:%s" % (SITE_OWNER_NAME, SITE_OWNER_PASSWORD), ) setRoles(self.portal, TEST_USER_ID, ["Manager"]) @@ -234,7 +234,6 @@ def setUp(self): super(TestDocumentation, self).setUp() self.document = self.create_document() alsoProvides(self.document, ITTWLockable) - transaction.commit() def tearDown(self): @@ -271,7 +270,8 @@ def test_documentation_content_crud(self): transaction.commit() response = self.api_session.post( - folder.absolute_url(), json={"@type": "Document", "title": "My Document"} + folder.absolute_url(), + json={"@type": "Document", "title": "My Document"}, ) save_request_and_response_for_docs("content_post", response) @@ -432,7 +432,11 @@ def test_documentation_search_options(self): self.portal.invokeFactory("Folder", id="folder1", title="Folder 1") self.portal.folder1.invokeFactory("Folder", id="folder2", title="Folder 2") transaction.commit() - query = {"sort_on": "path", "path.query": "/plone/folder1", "path.depth": "1"} + query = { + "sort_on": "path", + "path.query": "/plone/folder1", + "path.depth": "1", + } response = self.api_session.get("/@search", params=query) save_request_and_response_for_docs("search_options", response) @@ -453,7 +457,10 @@ def test_documentation_search_multiple_paths(self): def test_documentation_search_metadata_fields(self): self.portal.invokeFactory("Document", id="doc1", title="Lorem Ipsum") transaction.commit() - query = {"SearchableText": "lorem", "metadata_fields": ["modified", "created"]} + query = { + "SearchableText": "lorem", + "metadata_fields": ["modified", "created"], + } response = self.api_session.get("/@search", params=query) save_request_and_response_for_docs("search_metadata_fields", response) @@ -498,7 +505,8 @@ def test_documentation_registry_get(self): def test_documentation_registry_update(self): response = self.api_session.patch( - "/@registry/", json={"plone.app.querystring.field.path.title": "Value"} + "/@registry/", + json={"plone.app.querystring.field.path.title": "Value"}, ) save_request_and_response_for_docs("registry_update", response) @@ -782,7 +790,9 @@ def test_documentation_batching(self): ] for i in range(7): folder.invokeFactory( - "Document", id="doc-%s" % str(i + 1), title="Document %s" % str(i + 1) + "Document", + id="doc-%s" % str(i + 1), + title="Document %s" % str(i + 1), ) transaction.commit() @@ -860,7 +870,9 @@ def test_documentation_users_get(self): "location": "Cambridge, MA", } api.user.create( - email="noam.chomsky@example.com", username="noam", properties=properties + email="noam.chomsky@example.com", + username="noam", + properties=properties, ) transaction.commit() response = self.api_session.get("@users/noam") @@ -876,7 +888,9 @@ def test_documentation_users_anonymous_get(self): "location": "Cambridge, MA", } api.user.create( - email="noam.chomsky@example.com", username="noam", properties=properties + email="noam.chomsky@example.com", + username="noam", + properties=properties, ) transaction.commit() @@ -954,7 +968,9 @@ def test_documentation_users_filtered_get(self): "location": "Cambridge, MA", } api.user.create( - email="noam.chomsky@example.com", username="noam", properties=properties + email="noam.chomsky@example.com", + username="noam", + properties=properties, ) transaction.commit() response = self.api_session.get("@users", params={"query": "noa"}) @@ -1003,7 +1019,9 @@ def test_documentation_users_update(self): "location": "Cambridge, MA", } api.user.create( - email="noam.chomsky@example.com", username="noam", properties=properties + email="noam.chomsky@example.com", + username="noam", + properties=properties, ) transaction.commit() @@ -1061,7 +1079,9 @@ def test_documentation_users_delete(self): "location": "Cambridge, MA", } api.user.create( - email="noam.chomsky@example.com", username="noam", properties=properties + email="noam.chomsky@example.com", + username="noam", + properties=properties, ) transaction.commit() @@ -1161,7 +1181,10 @@ def test_documentation_groups_update(self): response = self.api_session.patch( "/@groups/ploneteam", - json={"email": "ploneteam2@plone.org", "users": {TEST_USER_ID: False}}, + json={ + "email": "ploneteam2@plone.org", + "users": {TEST_USER_ID: False}, + }, ) save_request_and_response_for_docs("groups_update", response) @@ -1211,7 +1234,10 @@ def test_documentation_navigation_tree(self): folder, u"Folder", id=u"subfolder2", title=u"SubFolder 2" ) thirdlevelfolder = createContentInContainer( - subfolder1, u"Folder", id=u"thirdlevelfolder", title=u"Third Level Folder" + subfolder1, + u"Folder", + id=u"thirdlevelfolder", + title=u"Third Level Folder", ) createContentInContainer( thirdlevelfolder, @@ -1242,7 +1268,10 @@ def test_documentation_contextnavigation(self): folder, u"Folder", id=u"subfolder2", title=u"SubFolder 2" ) thirdlevelfolder = createContentInContainer( - subfolder1, u"Folder", id=u"thirdlevelfolder", title=u"Third Level Folder" + subfolder1, + u"Folder", + id=u"thirdlevelfolder", + title=u"Third Level Folder", ) createContentInContainer( thirdlevelfolder, @@ -1289,7 +1318,12 @@ def test_documentation_copy_multiple(self): response = self.api_session.post( "/@copy", - json={"source": [self.document.absolute_url(), newsitem.absolute_url()]}, + json={ + "source": [ + self.document.absolute_url(), + newsitem.absolute_url(), + ] + }, ) save_request_and_response_for_docs("copy_multiple", response) @@ -1334,7 +1368,10 @@ def test_documentation_vocabularies_get_filtered_by_token(self): def test_documentation_sources_get(self): api.content.create( - container=self.portal, id="doc", type="DXTestDocument", title=u"DX Document" + container=self.portal, + id="doc", + type="DXTestDocument", + title=u"DX Document", ) transaction.commit() response = self.api_session.get("/doc/@sources/test_choice_with_source") @@ -1526,7 +1563,8 @@ def test_locking_lock(self): # Replace dynamic lock token with a static one response._content = re.sub( b'"token": "[^"]+"', - b'"token": "0.684672730996-0.25195226375-00105A989226:1477076400.000"', # noqa + b'"token":' + b' "0.684672730996-0.25195226375-00105A989226:1477076400.000"', # noqa response.content, ) save_request_and_response_for_docs("lock", response) @@ -1539,7 +1577,8 @@ def test_locking_lock_nonstealable_and_timeout(self): # Replace dynamic lock token with a static one response._content = re.sub( b'"token": "[^"]+"', - b'"token": "0.684672730996-0.25195226375-00105A989226:1477076400.000"', # noqa + b'"token":' + b' "0.684672730996-0.25195226375-00105A989226:1477076400.000"', # noqa response.content, ) save_request_and_response_for_docs("lock_nonstealable_timeout", response) @@ -1565,7 +1604,8 @@ def test_locking_refresh_lock(self): # Replace dynamic lock token with a static one response._content = re.sub( b'"token": "[^"]+"', - b'"token": "0.684672730996-0.25195226375-00105A989226:1477076400.000"', # noqa + b'"token":' + b' "0.684672730996-0.25195226375-00105A989226:1477076400.000"', # noqa response.content, ) save_request_and_response_for_docs("refresh_lock", response) @@ -1877,7 +1917,8 @@ def test_controlpanels_crud_dexterity(self): @unittest.skipUnless( - PAM_INSTALLED, "plone.app.multilingual is installed by default only in Plone 5" + PAM_INSTALLED, + "plone.app.multilingual is installed by default only in Plone 5", ) # NOQA class TestPAMDocumentation(TestDocumentationBase): @@ -1886,11 +1927,16 @@ class TestPAMDocumentation(TestDocumentationBase): def setUp(self): super(TestPAMDocumentation, self).setUp() - language_tool = api.portal.get_tool("portal_languages") - language_tool.addSupportedLanguage("en") - language_tool.addSupportedLanguage("es") - language_tool.addSupportedLanguage("de") - applyProfile(self.portal, "plone.app.multilingual:default") + # + # We manually set the UIDs for LRFs here because the static uuid + # generator is not applied for LRFs. + # When we have tried to apply it for LRFs we have had several + # utility registration problems. + # + setattr(self.portal.en, "_plone.uuid", "00000000000000000000000000000001") + setattr(self.portal.es, "_plone.uuid", "00000000000000000000000000000002") + setattr(self.portal.fr, "_plone.uuid", "00000000000000000000000000000003") + setattr(self.portal.de, "_plone.uuid", "00000000000000000000000000000004") en_id = self.portal["en"].invokeFactory( "Document", id="test-document", title="Test document" @@ -1902,9 +1948,6 @@ def setUp(self): self.es_content = self.portal["es"].get(es_id) transaction.commit() - def tearDown(self): - super(TestPAMDocumentation, self).tearDown() - def test_documentation_translations_post(self): response = self.api_session.post( "{}/@translations".format(self.en_content.absolute_url()), @@ -1929,6 +1972,7 @@ def test_documentation_translations_post_by_uid(self): def test_documentation_translations_get(self): ITranslationManager(self.en_content).register_translation("es", self.es_content) transaction.commit() + response = self.api_session.get( "{}/@translations".format(self.en_content.absolute_url()) ) @@ -1966,3 +2010,29 @@ def test_documentation_translation_locator(self): auth=(SITE_OWNER_NAME, SITE_OWNER_PASSWORD), ) save_request_and_response_for_docs("translation_locator", response) + + def test_site_navroot_get(self): + response = self.api_session.get("/@navroot") + save_request_and_response_for_docs("navroot_site_get", response) + + def test_site_navroot_language_folder_get(self): + response = self.api_session.get("/en/@navroot") + save_request_and_response_for_docs("navroot_lang_folder_get", response) + + def test_site_navroot_language_content_get(self): + response = self.api_session.get("/en/test-document/@navroot") + save_request_and_response_for_docs("navroot_lang_content_get", response) + + def test_site_expansion_navroot(self): + response = self.api_session.get("?expand=navroot") + save_request_and_response_for_docs("site_get_expand_navroot", response) + + def test_site_expansion_navroot_language_folder(self): + response = self.api_session.get("/en?expand=navroot") + save_request_and_response_for_docs("site_get_expand_lang_folder", response) + + def test_site_expansion_navroot_language_folder_content(self): + response = self.api_session.get("/en/test-document?expand=navroot") + save_request_and_response_for_docs( + "site_get_expand_lang_folder_content", response + ) diff --git a/src/plone/restapi/tests/test_services_navroot.py b/src/plone/restapi/tests/test_services_navroot.py new file mode 100644 index 0000000000..0e2e7b844b --- /dev/null +++ b/src/plone/restapi/tests/test_services_navroot.py @@ -0,0 +1,268 @@ +# -*- coding: utf-8 -*- +from plone.app.testing import setRoles +from plone.app.testing import TEST_USER_ID +from plone.restapi.testing import ( + PLONE_RESTAPI_DX_FUNCTIONAL_TESTING, + PLONE_RESTAPI_DX_PAM_FUNCTIONAL_TESTING, +) +from plone.restapi.testing import PAM_INSTALLED +from plone.restapi.testing import RelativeSession +from zope.component import getMultiAdapter +from zope.interface import alsoProvides +from plone.app.layout.navigation.interfaces import INavigationRoot + +import unittest +from plone import api +import transaction + + +class TestServicesNavroot(unittest.TestCase): + + layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING + + def setUp(self): + self.app = self.layer["app"] + self.portal = self.layer["portal"] + self.portal_url = self.portal.absolute_url() + setRoles(self.portal, TEST_USER_ID, ["Manager"]) + + api.content.create( + container=self.portal, + id="news", + title="News", + type="Folder", + ) + api.content.create( + container=self.portal.news, + id="document", + title="Document", + type="Document", + ) + api.content.transition(obj=self.portal.news, transition="publish") + api.content.transition(obj=self.portal.news.document, transition="publish") + transaction.commit() + + self.api_session = RelativeSession(self.portal_url) + self.api_session.headers.update({"Accept": "application/json"}) + + def test_get_navroot(self): + response = self.api_session.get( + "/@navroot", + ) + + self.assertEqual(response.status_code, 200) + portal_state = getMultiAdapter( + (self.portal, self.layer["request"]), name="plone_portal_state" + ) + + self.assertIn("navroot", response.json()) + self.assertEqual( + response.json()["navroot"]["title"], + portal_state.navigation_root_title(), + ) + self.assertEqual(response.json()["@id"], self.portal_url + "/@navroot") + + def test_get_navroot_non_multilingual_navigation_root(self): + """test that the navroot is computed correctly when a section + implements INavigationRoot + """ + alsoProvides(self.portal.news, INavigationRoot) + transaction.commit() + + response = self.api_session.get( + "/news/@navroot", + ) + + self.assertEqual(response.status_code, 200) + portal_state = getMultiAdapter( + (self.portal.news, self.layer["request"]), + name="plone_portal_state", + ) + + self.assertIn("navroot", response.json()) + + self.assertEqual( + response.json()["navroot"]["title"], + portal_state.navigation_root_title(), + ) + self.assertEqual( + response.json()["navroot"]["@id"], + portal_state.navigation_root_url(), + ) + + def test_get_navroot_non_multilingual_navigation_root_content(self): + """test that the navroot is computed correctly in a content inside a section + that implements INavigationRoot + """ + alsoProvides(self.portal.news, INavigationRoot) + transaction.commit() + + response = self.api_session.get( + "/news/document/@navroot", + ) + + self.assertEqual(response.status_code, 200) + portal_state = getMultiAdapter( + (self.portal.news, self.layer["request"]), + name="plone_portal_state", + ) + + self.assertIn("navroot", response.json()) + + self.assertEqual( + response.json()["navroot"]["title"], + portal_state.navigation_root_title(), + ) + self.assertEqual( + response.json()["navroot"]["@id"], + portal_state.navigation_root_url(), + ) + + +@unittest.skipUnless( + PAM_INSTALLED, "plone.app.multilingual is installed by default only in Plone 5" +) # NOQA +class TestServicesNavrootMultilingual(unittest.TestCase): + + layer = PLONE_RESTAPI_DX_PAM_FUNCTIONAL_TESTING + + def setUp(self): + self.app = self.layer["app"] + self.portal = self.layer["portal"] + self.portal_url = self.portal.absolute_url() + setRoles(self.portal, TEST_USER_ID, ["Manager"]) + + api.content.create( + container=self.portal.en, + id="news", + title="News", + type="Folder", + ) + api.content.create( + container=self.portal.en.news, + id="document", + title="Document", + type="Document", + ) + api.content.transition(obj=self.portal.en.news, transition="publish") + api.content.transition(obj=self.portal.en.news.document, transition="publish") + + self.api_session = RelativeSession(self.portal_url) + self.api_session.headers.update({"Accept": "application/json"}) + + transaction.commit() + + def test_get_navroot_site(self): + response = self.api_session.get( + "/@navroot", + ) + + self.assertEqual(response.status_code, 200) + portal_state = getMultiAdapter( + (self.portal, self.layer["request"]), name="plone_portal_state" + ) + self.assertIn("navroot", response.json()) + + self.assertEqual( + response.json()["navroot"]["title"], + portal_state.navigation_root_title(), + ) + self.assertEqual( + response.json()["@id"], + portal_state.navigation_root_url() + "/@navroot", + ) + + def test_get_navroot_language_folder(self): + response = self.api_session.get( + "/en/@navroot", + ) + + self.assertEqual(response.status_code, 200) + portal_state = getMultiAdapter( + (self.portal.en, self.layer["request"]), name="plone_portal_state" + ) + self.assertIn("navroot", response.json()) + + self.assertEqual( + response.json()["navroot"]["title"], + portal_state.navigation_root_title(), + ) + self.assertEqual( + response.json()["@id"], + portal_state.navigation_root_url() + "/@navroot", + ) + + def test_get_navroot_language_content(self): + response = self.api_session.get( + "/en/news/@navroot", + ) + + self.assertEqual(response.status_code, 200) + portal_state = getMultiAdapter( + (self.portal.en.news, self.layer["request"]), + name="plone_portal_state", + ) + self.assertIn("navroot", response.json()) + + self.assertEqual( + response.json()["navroot"]["title"], + portal_state.navigation_root_title(), + ) + self.assertEqual( + response.json()["navroot"]["@id"], + portal_state.navigation_root_url(), + ) + + def test_get_navroot_non_multilingual_navigation_root(self): + """test that the navroot is computed correctly when a section + implements INavigationRoot + """ + alsoProvides(self.portal.en.news, INavigationRoot) + transaction.commit() + + response = self.api_session.get( + "/en/news/@navroot", + ) + + self.assertEqual(response.status_code, 200) + portal_state = getMultiAdapter( + (self.portal.en.news, self.layer["request"]), + name="plone_portal_state", + ) + self.assertIn("navroot", response.json()) + + self.assertEqual( + response.json()["navroot"]["title"], + portal_state.navigation_root_title(), + ) + self.assertEqual( + response.json()["navroot"]["@id"], + portal_state.navigation_root_url(), + ) + + def test_get_navroot_non_multilingual_navigation_root_content(self): + """test that the navroot is computed correctly in a content inside a section + that implements INavigationRoot + """ + alsoProvides(self.portal.en.news, INavigationRoot) + transaction.commit() + + response = self.api_session.get( + "/en/news/document/@navroot", + ) + + self.assertEqual(response.status_code, 200) + portal_state = getMultiAdapter( + (self.portal.en.news, self.layer["request"]), + name="plone_portal_state", + ) + self.assertIn("navroot", response.json()) + + self.assertEqual( + response.json()["navroot"]["title"], + portal_state.navigation_root_title(), + ) + self.assertEqual( + response.json()["navroot"]["@id"], + portal_state.navigation_root_url(), + ) diff --git a/src/plone/restapi/tests/test_services_site.py b/src/plone/restapi/tests/test_services_site.py new file mode 100644 index 0000000000..145f81c0ac --- /dev/null +++ b/src/plone/restapi/tests/test_services_site.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +from plone.app.testing import setRoles +from plone.app.testing import TEST_USER_ID +from plone.restapi.testing import PLONE_RESTAPI_DX_FUNCTIONAL_TESTING +from plone.restapi.testing import RelativeSession +from zope.component import getMultiAdapter + +import unittest + +IS_PLONE4 = False + +try: + from Products.CMFPlone.interfaces import IImagingSchema # noqa + from Products.CMFPlone.interfaces import ISiteSchema # noqa +except ImportError: + IS_PLONE4 = True + + +class TestServicesSite(unittest.TestCase): + + layer = PLONE_RESTAPI_DX_FUNCTIONAL_TESTING + + def setUp(self): + self.app = self.layer["app"] + self.portal = self.layer["portal"] + self.portal_url = self.portal.absolute_url() + setRoles(self.portal, TEST_USER_ID, ["Manager"]) + + self.api_session = RelativeSession(self.portal_url) + self.api_session.headers.update({"Accept": "application/json"}) + + def test_get_site_title(self): + response = self.api_session.get( + "/@site", + ) + + self.assertEqual(response.status_code, 200) + portal_state = getMultiAdapter( + (self.portal, self.layer["request"]), name="plone_portal_state" + ) + self.assertEqual( + response.json()["plone.site_title"], portal_state.portal_title() + ) + + @unittest.skipIf( + IS_PLONE4, + "The information can only be extracted from the ISiteSchema and IImagingSchema values in registry, which are only available in Plone 5", + ) # NOQA + def test_get_site_other(self): + response = self.api_session.get( + "/@site", + ) + + self.assertEqual(response.status_code, 200) + self.assertIn("plone.site_logo", response.json()) + self.assertIn("plone.robots_txt", response.json()) + self.assertIn("plone.allowed_sizes", response.json())