plone.api 2.2.4__tar.gz → 2.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. {plone_api-2.2.4 → plone_api-2.3.0}/CHANGES.rst +42 -0
  2. {plone_api-2.2.4 → plone_api-2.3.0}/MANIFEST.in +2 -0
  3. {plone_api-2.2.4 → plone_api-2.3.0}/PKG-INFO +44 -2
  4. {plone_api-2.2.4 → plone_api-2.3.0}/docs/content.md +56 -3
  5. {plone_api-2.2.4 → plone_api-2.3.0}/docs/contribute.md +27 -44
  6. {plone_api-2.2.4 → plone_api-2.3.0}/docs/portal.md +44 -1
  7. {plone_api-2.2.4 → plone_api-2.3.0}/pyproject.toml +2 -2
  8. {plone_api-2.2.4 → plone_api-2.3.0}/setup.py +2 -2
  9. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/content.py +36 -1
  10. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/portal.py +43 -2
  11. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/relation.py +1 -1
  12. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/testing.zcml +1 -1
  13. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/tests/doctests/content.md +56 -3
  14. plone_api-2.3.0/src/plone/api/tests/doctests/contribute.md +217 -0
  15. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/tests/doctests/portal.md +44 -1
  16. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/tests/test_content.py +75 -2
  17. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/tests/test_doctests.py +1 -1
  18. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/tests/test_portal.py +70 -1
  19. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone.api.egg-info/PKG-INFO +44 -2
  20. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone.api.egg-info/SOURCES.txt +1 -0
  21. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone.api.egg-info/requires.txt +1 -1
  22. {plone_api-2.2.4 → plone_api-2.3.0}/tox.ini +13 -1
  23. {plone_api-2.2.4 → plone_api-2.3.0}/CONTRIBUTING.rst +0 -0
  24. {plone_api-2.2.4 → plone_api-2.3.0}/LICENSE +0 -0
  25. {plone_api-2.2.4 → plone_api-2.3.0}/README.md +0 -0
  26. {plone_api-2.2.4 → plone_api-2.3.0}/docs/about.md +0 -0
  27. {plone_api-2.2.4 → plone_api-2.3.0}/docs/env.md +0 -0
  28. {plone_api-2.2.4 → plone_api-2.3.0}/docs/group.md +0 -0
  29. {plone_api-2.2.4 → plone_api-2.3.0}/docs/index.md +0 -0
  30. {plone_api-2.2.4 → plone_api-2.3.0}/docs/relation.md +0 -0
  31. {plone_api-2.2.4 → plone_api-2.3.0}/docs/user.md +0 -0
  32. {plone_api-2.2.4 → plone_api-2.3.0}/setup.cfg +0 -0
  33. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/__init__.py +0 -0
  34. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/__init__.py +0 -0
  35. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/configure.zcml +0 -0
  36. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/env.py +0 -0
  37. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/exc.py +0 -0
  38. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/group.py +0 -0
  39. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/profiles/testfixture/metadata.xml +0 -0
  40. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/profiles/testfixture/types/Dexterity_Folder.xml +0 -0
  41. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/profiles/testfixture/types/Dexterity_Item.xml +0 -0
  42. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/profiles/testfixture/types.xml +0 -0
  43. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/tests/Dexterity_Folder.xml +0 -0
  44. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/tests/Dexterity_Item.xml +0 -0
  45. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/tests/__init__.py +0 -0
  46. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/tests/base.py +0 -0
  47. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/tests/doctests/about.md +0 -0
  48. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/tests/doctests/env.md +0 -0
  49. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/tests/doctests/group.md +0 -0
  50. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/tests/doctests/relation.md +0 -0
  51. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/tests/doctests/user.md +0 -0
  52. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/tests/test_env.py +0 -0
  53. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/tests/test_group.py +0 -0
  54. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/tests/test_relation.py +0 -0
  55. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/tests/test_user.py +0 -0
  56. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/tests/test_validation.py +0 -0
  57. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/user.py +0 -0
  58. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone/api/validation.py +0 -0
  59. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone.api.egg-info/dependency_links.txt +0 -0
  60. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone.api.egg-info/namespace_packages.txt +0 -0
  61. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone.api.egg-info/not-zip-safe +0 -0
  62. {plone_api-2.2.4 → plone_api-2.3.0}/src/plone.api.egg-info/top_level.txt +0 -0
@@ -8,6 +8,48 @@ Changelog
8
8
 
9
9
  .. towncrier release notes start
10
10
 
11
+ 2.3.0 (2025-02-21)
12
+ ------------------
13
+
14
+ New features:
15
+
16
+
17
+ - Added the content API helper function ``api.content.get_path``, which gets either the relative or absolute path of an object. @ujsquared (#532)
18
+ - Added two new portal API functions:
19
+ - ``api.portal.get_vocabulary``: Get a vocabulary by name
20
+ - ``api.portal.get_vocabulary_names``: Get a list of all available vocabulary names
21
+ @ujsquared (#533)
22
+
23
+
24
+ Internal:
25
+
26
+
27
+ - Making it easier for new contributors to get started with a simple Makefile and a tidy 'contributing' chapter. @ksuess (#558)
28
+
29
+
30
+ 2.2.5 (2025-01-24)
31
+ ------------------
32
+
33
+ Bug fixes:
34
+
35
+
36
+ - Fix api.content.get(path=path) when a item in the path is not accessible to the user.
37
+ [pbauer] (#549)
38
+ - Fix DeprecationWarnings. [maurits] (#4090)
39
+
40
+
41
+ Documentation:
42
+
43
+
44
+ - Preview docs on Read the Docs instead of Netlify. @stevepiercy (#545)
45
+ - Remove Netlify stuff, follow up to #545. @stevepiercy
46
+ - Sort and remove duplicate entries in `pyproject.toml`
47
+ - Remove unused docs requirements.
48
+ - Fix comments and remove unnecessary steps from `tox.ini`.
49
+ - Enable copy button for code blocks.
50
+ - Add linkcheck to documentation of documentation. (#546)
51
+
52
+
11
53
  2.2.4 (2024-12-16)
12
54
  ------------------
13
55
 
@@ -13,6 +13,8 @@ global-exclude *.pyc
13
13
  include pyproject.toml
14
14
  recursive-exclude news *
15
15
  exclude news
16
+ recursive-exclude .vscode *
17
+ exclude .readthedocs.yaml
16
18
 
17
19
  # added by check-manifest
18
20
  recursive-include src *.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: plone.api
3
- Version: 2.2.4
3
+ Version: 2.3.0
4
4
  Summary: A Plone API.
5
5
  Home-page: https://github.com/plone/plone.api
6
6
  Author: Plone Foundation
@@ -34,8 +34,8 @@ Requires-Dist: decorator
34
34
  Requires-Dist: plone.app.uuid
35
35
  Requires-Dist: plone.app.dexterity
36
36
  Requires-Dist: plone.app.intid
37
- Requires-Dist: plone.app.layout
38
37
  Requires-Dist: plone.app.linkintegrity
38
+ Requires-Dist: plone.base
39
39
  Requires-Dist: plone.dexterity
40
40
  Requires-Dist: plone.i18n
41
41
  Requires-Dist: plone.registry
@@ -110,6 +110,48 @@ Changelog
110
110
 
111
111
  .. towncrier release notes start
112
112
 
113
+ 2.3.0 (2025-02-21)
114
+ ------------------
115
+
116
+ New features:
117
+
118
+
119
+ - Added the content API helper function ``api.content.get_path``, which gets either the relative or absolute path of an object. @ujsquared (#532)
120
+ - Added two new portal API functions:
121
+ - ``api.portal.get_vocabulary``: Get a vocabulary by name
122
+ - ``api.portal.get_vocabulary_names``: Get a list of all available vocabulary names
123
+ @ujsquared (#533)
124
+
125
+
126
+ Internal:
127
+
128
+
129
+ - Making it easier for new contributors to get started with a simple Makefile and a tidy 'contributing' chapter. @ksuess (#558)
130
+
131
+
132
+ 2.2.5 (2025-01-24)
133
+ ------------------
134
+
135
+ Bug fixes:
136
+
137
+
138
+ - Fix api.content.get(path=path) when a item in the path is not accessible to the user.
139
+ [pbauer] (#549)
140
+ - Fix DeprecationWarnings. [maurits] (#4090)
141
+
142
+
143
+ Documentation:
144
+
145
+
146
+ - Preview docs on Read the Docs instead of Netlify. @stevepiercy (#545)
147
+ - Remove Netlify stuff, follow up to #545. @stevepiercy
148
+ - Sort and remove duplicate entries in `pyproject.toml`
149
+ - Remove unused docs requirements.
150
+ - Fix comments and remove unnecessary steps from `tox.ini`.
151
+ - Enable copy button for code blocks.
152
+ - Add linkcheck to documentation of documentation. (#546)
153
+
154
+
113
155
  2.2.4 (2024-12-16)
114
156
  ------------------
115
157
 
@@ -78,7 +78,7 @@ plone (portal root)
78
78
  % api.content.create(container=events, type='Event', id='conference')
79
79
  % api.content.create(container=events, type='Event', id='sprint')
80
80
 
81
- The following operations will get objects from the structure above, including using {meth}`api.content.get`.
81
+ The following operations will get objects from the structure above, using {meth}`api.content.get`.
82
82
 
83
83
  ```python
84
84
  # let's first get the portal object
@@ -118,7 +118,7 @@ not_found = api.content.get(UID='notfound')
118
118
 
119
119
  ## Find content objects
120
120
 
121
- You can use the find function to search for content.
121
+ You can use the {func}`api.content.find` function to search for content.
122
122
 
123
123
  Finding all Documents:
124
124
 
@@ -460,7 +460,6 @@ portal = api.portal.get()
460
460
  api.content.transition(obj=portal['about'], transition='reject', comment='You had a typo on your page.')
461
461
  ```
462
462
 
463
-
464
463
  (content-disable-roles-acquisition-example)=
465
464
 
466
465
  ## Disable local roles acquisition
@@ -536,6 +535,60 @@ view = api.content.get_view(
536
535
  %
537
536
  % self.assertEqual(view.__name__, u'plone')
538
537
 
538
+ (content-get-path-example)=
539
+
540
+ ## Get content path
541
+
542
+ To get the path of a content object, use {func}`api.content.get_path`.
543
+ This function accepts an object for which you want to get its path as the required parameter `obj`, and an optional boolean parameter `relative` whose default is `False`.
544
+
545
+ It returns either an absolute path from the Zope root by default or when `relative` is set to `False`, or a relative path from the portal root when `relative` is set to `True`.
546
+
547
+ The following example shows how to get the absolute path from the Zope root.
548
+
549
+ ```python
550
+ from plone import api
551
+ portal = api.portal.get()
552
+
553
+ folder = portal['events']['training']
554
+ path = api.content.get_path(obj=folder)
555
+ assert path == '/plone/events/training'
556
+ ```
557
+
558
+ The following example shows how to get the portal-relative path.
559
+
560
+ ```python
561
+ rel_path = api.content.get_path(obj=folder, relative=True)
562
+ assert rel_path == 'events/training'
563
+ ```
564
+
565
+ If the API is used to fetch an object with the `relative` parameter set as `True`, and the object is outside the portal, it throws an `InvalidParameterError` error.
566
+
567
+ % invisible-code-block: python
568
+ %
569
+ % # Setup an object outside portal for testing error case
570
+ % app = portal.aq_parent
571
+ % app.manage_addFolder('outside_folder')
572
+ %
573
+ % # Test that getting relative path for object outside portal raises error
574
+ % from plone.api.exc import InvalidParameterError
575
+ % with self.assertRaises(InvalidParameterError):
576
+ % api.content.get_path(obj=app.outside_folder, relative=True)
577
+
578
+ ```python
579
+ from plone.api.exc import InvalidParameterError
580
+
581
+ # Getting path of an object outside portal raises InvalidParameterError
582
+ try:
583
+ outside_path = api.content.get_path(
584
+ obj=app.outside_folder,
585
+ relative=True
586
+ )
587
+ assert False, "Should raise InvalidParameterError and not reach this code"
588
+ except InvalidParameterError as e:
589
+ assert "Object not in portal path" in str(e)
590
+ ```
591
+
539
592
  ## Further reading
540
593
 
541
594
  For more information on possible flags and usage options please see the full {ref}`plone-api-content` specification.
@@ -18,65 +18,42 @@ Prepare your system by installing prerequisites.
18
18
 
19
19
  ### System libraries
20
20
 
21
- You need to install system libraries, as described in {ref}`plone:plone-prerequisites-label`, with the exception of GNU make.
21
+ You need to install system libraries, as described in {ref}`plone:plone-prerequisites-label`.
22
22
 
23
- ### tox
24
-
25
- [tox](https://tox.wiki/en/stable/index.html) automates and standardizes testing in Python.
26
- Install tox into your Python user space with the following command.
27
-
28
- ```shell
29
- python -m pip install --user tox
30
- ```
31
-
32
- ### pre-commit
33
-
34
- `plone.api` uses [pre-commit](https://pre-commit.com/) to automate code quality checks before every commit.
35
-
36
- Install pre-commit either with your system package manager.
37
- Alternatively you can install pre-commit into your Python user.
38
-
39
- ```shell
40
- python -m pip install --user pre-commit
41
- ```
42
-
43
- Once installed, set up the git hook scripts to run on every commit.
44
-
45
- ```shell
46
- pre-commit install
47
- ```
48
23
 
49
24
  ## Create development environment
50
25
 
51
26
  After satisfying the prerequisites, you are ready to create your development environment.
52
27
  `plone.api` uses `tox` as a wrapper around `coredev.buildout` to simplify development, whereas Plone core uses `coredev.buildout` directly.
28
+ Additionally `plone.api` uses `make` commands that invoke the `tox` commands as a convenience and for consistency across Plone packages.
53
29
 
54
- Start by changing your working directory to your project folder, and download the latest `plone.api` source code.
30
+ Start by changing your working directory to your project folder, and check out the latest `plone.api` source code.
55
31
 
56
32
  ```shell
57
33
  cd <your_project_folder>
58
34
  git clone https://github.com/plone/plone.api.git
59
35
  ```
60
36
 
61
- Next go into the newly created directory, and build your environment.
37
+ Run `make help` to see the available `make` commands.
62
38
 
63
- ```shell
64
- cd plone.api
65
- tox
39
+ ```console
40
+ check Check code base according to Plone standards
41
+ clean Clean environment
42
+ help This help message
43
+ linkcheck Check links in documentation
44
+ livehtml Build docs and watch for changes
45
+ test Run tests
66
46
  ```
67
47
 
68
- Go make some tea while `tox` runs all tasks listed by issuing the command `tox -l`.
48
+ The `make` commands `check`, `livehtml`, and `test` will create a development environment, if it does not already exist.
69
49
 
70
- Open `tox.ini` in your code editor to see all configured commands and what they do.
71
- Some helpful `tox` commands are shown below.
50
+ Test your code changes with the following command.
72
51
 
73
52
  ```shell
74
- tox -e py39-plone-60 # run all tests for Python 3.9 and Plone 6
75
- tox -e plone6docs # build documentation
76
- tox -e livehtml # build, serve, and reload changes to documentation
77
- tox -l # list all tox environments
53
+ make test
78
54
  ```
79
55
 
56
+
80
57
  (git-workflow)=
81
58
 
82
59
  ## git
@@ -109,16 +86,17 @@ For every feature change or addition to `plone.api`, you must add documentation
109
86
  {doc}`plone:contributing/documentation/index`
110
87
  ```
111
88
 
112
- After adding or modifying documentation, you can build the documentation with the following command.
89
+ When adding or modifying documentation, you can build the documentation with the following command.
90
+ As you edit the documentation, the started process automatically reloads your changes in the web browser.
113
91
 
114
92
  ```shell
115
- tox -e plone6docs
93
+ make livehtml
116
94
  ```
117
95
 
118
- Alternatively, you can automatically reload changes to the documentation as you edit it in a web browser.
96
+ You can run a link checker on documentation.
119
97
 
120
98
  ```shell
121
- tox -e livehtml
99
+ make linkcheck
122
100
  ```
123
101
 
124
102
  The [`plone.api` documentation](https://6.docs.plone.org/plone.api) is automatically generated from the documentation source files when its submodule is updated in the [main Plone `documentation` repository](https://github.com/plone/documentation/).
@@ -166,6 +144,10 @@ def foo(path=None, UID=None):
166
144
  % InvalidParameterError,
167
145
  % lambda: foo("/plone/blog", "abcd001")
168
146
  % )
147
+ %
148
+ % # Make it available for testing below
149
+ % from plone import api
150
+ % api.content.foo = foo
169
151
 
170
152
  Add documentation in {file}`docs/api/content.md`.
171
153
  Narrative documentation should describe what your function does.
@@ -192,7 +174,8 @@ blog_foo = api.content.foo(path="/plone/blog")
192
174
 
193
175
  % invisible-code-block: python
194
176
  %
195
- % self.assertEqual(blog_foo,"foo")
177
+ % self.assertEqual(blog_foo, "foo")
178
+
196
179
  ````
197
180
 
198
181
  Code blocks are rendered in documentation.
@@ -209,7 +192,7 @@ Invisible code blocks are not rendered in documentation and can be used for test
209
192
  ```markdown
210
193
  % invisible-code-block: python
211
194
  %
212
- % self.assertEqual(blog_foo,"foo")
195
+ % self.assertEqual(blog_foo, "foo")
213
196
  ```
214
197
 
215
198
  Invisible code blocks are also handy for enriching the namespace without cluttering the narrative documentation.
@@ -45,7 +45,7 @@ Assuming there is a document `english_page` in a folder `en`, which is the navig
45
45
  % invisible-code-block: python
46
46
  %
47
47
  % from plone import api
48
- % from plone.app.layout.navigation.interfaces import INavigationRoot
48
+ % from plone.base.interfaces import INavigationRoot
49
49
  % from zope.interface import alsoProvides
50
50
  %
51
51
  % portal = api.portal.get()
@@ -416,6 +416,49 @@ api.portal.set_registry_record('field_one', 'new value', interface=IMyRegistrySe
416
416
  % 'new value'
417
417
  % )
418
418
 
419
+ (portal-get-vocabulary-example)=
420
+
421
+ ## Get vocabulary
422
+
423
+ To get a vocabulary by name, use {func}`api.portal.get_vocabulary`.
424
+
425
+ ```python
426
+ from plone import api
427
+
428
+ # Get vocabulary using default portal context
429
+ vocabulary = api.portal.get_vocabulary(name='plone.app.vocabularies.PortalTypes')
430
+
431
+ # Get vocabulary with specific context
432
+ context = api.portal.get()
433
+ states_vocabulary = api.portal.get_vocabulary(
434
+ name='plone.app.vocabularies.WorkflowStates',
435
+ context=context
436
+ )
437
+ ```
438
+
439
+ (portal-get-all-vocabulary-names-example)=
440
+
441
+ ## Get all vocabulary names
442
+
443
+ To get a list of all available vocabulary names in your Plone site, use {meth}`api.portal.get_vocabulary_names`.
444
+
445
+ ```python
446
+ from plone import api
447
+
448
+ # Get all vocabulary names
449
+ vocabulary_names = api.portal.get_vocabulary_names()
450
+
451
+ # Common vocabularies that should be available
452
+ common_vocabularies = [
453
+ 'plone.app.vocabularies.PortalTypes',
454
+ 'plone.app.vocabularies.WorkflowStates',
455
+ 'plone.app.vocabularies.WorkflowTransitions'
456
+ ]
457
+
458
+ for vocabulary_name in common_vocabularies:
459
+ assert vocabulary_name in vocabulary_names
460
+ ```
461
+
419
462
  ## Further reading
420
463
 
421
464
  For more information on possible flags and usage options please see the full {ref}`plone-api-portal` specification.
@@ -140,14 +140,14 @@ ignore = [
140
140
  "dependabot.yml",
141
141
  "mx.ini",
142
142
  "tox.ini",
143
- ".editorconfig",
144
143
  "*.cfg",
144
+ ".editorconfig",
145
+ ".readthedocs.yaml",
145
146
  "constraints_plone52.txt",
146
147
  "constraints_plone60.txt",
147
148
  "constraints.txt",
148
149
  "fix-converted-myst.py",
149
150
  "Makefile",
150
- "netlify.toml",
151
151
  "requirements-docs.txt",
152
152
  "requirements.txt",
153
153
 
@@ -3,7 +3,7 @@ from setuptools import find_packages
3
3
  from setuptools import setup
4
4
 
5
5
 
6
- version = "2.2.4"
6
+ version = "2.3.0"
7
7
 
8
8
  long_description = (
9
9
  f"{Path('README.md').read_text()}\n"
@@ -36,8 +36,8 @@ setup(
36
36
  "plone.app.uuid",
37
37
  "plone.app.dexterity",
38
38
  "plone.app.intid",
39
- "plone.app.layout",
40
39
  "plone.app.linkintegrity",
40
+ "plone.base",
41
41
  "plone.dexterity",
42
42
  "plone.i18n",
43
43
  "plone.registry",
@@ -133,7 +133,12 @@ def get(path=None, UID=None):
133
133
  relative_path=path,
134
134
  )
135
135
  try:
136
- content = site.restrictedTraverse(path)
136
+ path = path.split("/")
137
+ if len(path) > 1:
138
+ parent = site.unrestrictedTraverse(path[:-1])
139
+ content = parent.restrictedTraverse(path[-1])
140
+ else:
141
+ content = site.restrictedTraverse(path[-1])
137
142
  except (KeyError, AttributeError):
138
143
  return None # When no object is found don't raise an error
139
144
  else:
@@ -564,6 +569,36 @@ def get_uuid(obj=None):
564
569
  return IUUID(obj)
565
570
 
566
571
 
572
+ @required_parameters("obj")
573
+ def get_path(obj=None, relative=False):
574
+ """Get the path of an object.
575
+
576
+ :param obj: [required] Object for which to get its path
577
+ :type obj: Content object
578
+ :param relative: Return a relative path from the portal root
579
+ :type relative: boolean
580
+ :returns: Path to the object
581
+ :rtype: string
582
+ :raises:
583
+ InvalidParameterError
584
+ :Example: :ref:`content-get-path-example`
585
+ """
586
+ if not hasattr(obj, "getPhysicalPath"):
587
+ raise InvalidParameterError(f"Cannot get path of object {obj!r}")
588
+
589
+ if not relative:
590
+ return "/".join(obj.getPhysicalPath())
591
+ site = portal.get()
592
+ site_path = site.getPhysicalPath()
593
+ obj_path = obj.getPhysicalPath()
594
+ if obj_path[: len(site_path)] != site_path:
595
+ raise InvalidParameterError(
596
+ "Object not in portal path. Object path: {}".format("/".join(obj_path))
597
+ )
598
+ rel_path = obj_path[len(site_path) :]
599
+ return "/".join(rel_path) if rel_path else ""
600
+
601
+
567
602
  def _parse_object_provides_query(query):
568
603
  """Create a query for the object_provides index.
569
604
 
@@ -7,16 +7,19 @@ from logging import getLogger
7
7
  from plone.api.exc import CannotGetPortalError
8
8
  from plone.api.exc import InvalidParameterError
9
9
  from plone.api.validation import required_parameters
10
- from plone.app.layout.navigation.root import getNavigationRootObject
10
+ from plone.base.navigationroot import get_navigation_root_object
11
11
  from plone.registry.interfaces import IRegistry
12
12
  from Products.CMFCore.interfaces import ISiteRoot
13
13
  from Products.CMFCore.utils import getToolByName
14
14
  from Products.statusmessages.interfaces import IStatusMessage
15
+ from zope.component import ComponentLookupError
16
+ from zope.component import getUtilitiesFor
15
17
  from zope.component import getUtility
16
18
  from zope.component import providedBy
17
19
  from zope.component.hooks import getSite
18
20
  from zope.globalrequest import getRequest
19
21
  from zope.interface.interfaces import IInterface
22
+ from zope.schema.interfaces import IVocabularyFactory
20
23
 
21
24
  import datetime as dtime
22
25
  import re
@@ -86,7 +89,7 @@ def get_navigation_root(context=None):
86
89
  :Example: :ref:`portal-get-navigation-root-example`
87
90
  """
88
91
  context = aq_inner(context)
89
- return getNavigationRootObject(context, get())
92
+ return get_navigation_root_object(context, get())
90
93
 
91
94
 
92
95
  @required_parameters("name")
@@ -431,3 +434,41 @@ def translate(msgid, domain="plone", lang=None):
431
434
  # Pass the request, so zope.i18n.translate can negotiate the language.
432
435
  query["context"] = getRequest()
433
436
  return translation_service.utranslate(**query)
437
+
438
+
439
+ @required_parameters("name")
440
+ def get_vocabulary(name=None, context=None):
441
+ """Return a vocabulary object with the given name.
442
+
443
+ :param name: Name of the vocabulary.
444
+ :type name: str
445
+ :param context: Context to be applied to the vocabulary. Default: portal root.
446
+ :type context: object
447
+ :returns: A SimpleVocabulary instance that implements IVocabularyTokenized.
448
+ :rtype: zope.schema.vocabulary.SimpleVocabulary
449
+ :Example: :ref:`portal-get-vocabulary-example`
450
+ """
451
+ if context is None:
452
+ context = get()
453
+ try:
454
+ vocabulary = getUtility(IVocabularyFactory, name)
455
+ except ComponentLookupError:
456
+ raise InvalidParameterError(
457
+ "Cannot find a vocabulary with name '{name}'.\n"
458
+ "Available vocabularies are:\n"
459
+ "{vocabularies}".format(
460
+ name=name,
461
+ vocabularies="\n".join(get_vocabulary_names()),
462
+ ),
463
+ )
464
+ return vocabulary(context)
465
+
466
+
467
+ def get_vocabulary_names():
468
+ """Return a list of vocabulary names.
469
+
470
+ :returns: A sorted list of vocabulary names.
471
+ :rtype: list[str]
472
+ :Example: :ref:`portal-get-all-vocabulary-names-example`
473
+ """
474
+ return sorted([name for name, vocabulary in getUtilitiesFor(IVocabularyFactory)])
@@ -10,8 +10,8 @@ from plone.api.validation import at_least_one_of
10
10
  from plone.api.validation import required_parameters
11
11
  from plone.app.linkintegrity.handlers import modifiedContent
12
12
  from plone.app.linkintegrity.utils import referencedRelationship
13
+ from plone.base.utils import base_hasattr
13
14
  from plone.dexterity.utils import iterSchemataForType
14
- from Products.CMFPlone.utils import base_hasattr
15
15
  from z3c.relationfield import event
16
16
  from z3c.relationfield import RelationValue
17
17
  from z3c.relationfield.schema import Relation
@@ -14,7 +14,7 @@
14
14
  title="plone.api: Test fixture"
15
15
  description="Extension profile to configure a test fixture"
16
16
  provides="Products.GenericSetup.interfaces.EXTENSION"
17
- for="Products.CMFPlone.interfaces.ITestCasePloneSiteRoot"
17
+ for="plone.base.interfaces.ITestCasePloneSiteRoot"
18
18
  directory="profiles/testfixture"
19
19
  />
20
20
 
@@ -78,7 +78,7 @@ plone (portal root)
78
78
  % api.content.create(container=events, type='Event', id='conference')
79
79
  % api.content.create(container=events, type='Event', id='sprint')
80
80
 
81
- The following operations will get objects from the structure above, including using {meth}`api.content.get`.
81
+ The following operations will get objects from the structure above, using {meth}`api.content.get`.
82
82
 
83
83
  ```python
84
84
  # let's first get the portal object
@@ -118,7 +118,7 @@ not_found = api.content.get(UID='notfound')
118
118
 
119
119
  ## Find content objects
120
120
 
121
- You can use the find function to search for content.
121
+ You can use the {func}`api.content.find` function to search for content.
122
122
 
123
123
  Finding all Documents:
124
124
 
@@ -460,7 +460,6 @@ portal = api.portal.get()
460
460
  api.content.transition(obj=portal['about'], transition='reject', comment='You had a typo on your page.')
461
461
  ```
462
462
 
463
-
464
463
  (content-disable-roles-acquisition-example)=
465
464
 
466
465
  ## Disable local roles acquisition
@@ -536,6 +535,60 @@ view = api.content.get_view(
536
535
  %
537
536
  % self.assertEqual(view.__name__, u'plone')
538
537
 
538
+ (content-get-path-example)=
539
+
540
+ ## Get content path
541
+
542
+ To get the path of a content object, use {func}`api.content.get_path`.
543
+ This function accepts an object for which you want to get its path as the required parameter `obj`, and an optional boolean parameter `relative` whose default is `False`.
544
+
545
+ It returns either an absolute path from the Zope root by default or when `relative` is set to `False`, or a relative path from the portal root when `relative` is set to `True`.
546
+
547
+ The following example shows how to get the absolute path from the Zope root.
548
+
549
+ ```python
550
+ from plone import api
551
+ portal = api.portal.get()
552
+
553
+ folder = portal['events']['training']
554
+ path = api.content.get_path(obj=folder)
555
+ assert path == '/plone/events/training'
556
+ ```
557
+
558
+ The following example shows how to get the portal-relative path.
559
+
560
+ ```python
561
+ rel_path = api.content.get_path(obj=folder, relative=True)
562
+ assert rel_path == 'events/training'
563
+ ```
564
+
565
+ If the API is used to fetch an object with the `relative` parameter set as `True`, and the object is outside the portal, it throws an `InvalidParameterError` error.
566
+
567
+ % invisible-code-block: python
568
+ %
569
+ % # Setup an object outside portal for testing error case
570
+ % app = portal.aq_parent
571
+ % app.manage_addFolder('outside_folder')
572
+ %
573
+ % # Test that getting relative path for object outside portal raises error
574
+ % from plone.api.exc import InvalidParameterError
575
+ % with self.assertRaises(InvalidParameterError):
576
+ % api.content.get_path(obj=app.outside_folder, relative=True)
577
+
578
+ ```python
579
+ from plone.api.exc import InvalidParameterError
580
+
581
+ # Getting path of an object outside portal raises InvalidParameterError
582
+ try:
583
+ outside_path = api.content.get_path(
584
+ obj=app.outside_folder,
585
+ relative=True
586
+ )
587
+ assert False, "Should raise InvalidParameterError and not reach this code"
588
+ except InvalidParameterError as e:
589
+ assert "Object not in portal path" in str(e)
590
+ ```
591
+
539
592
  ## Further reading
540
593
 
541
594
  For more information on possible flags and usage options please see the full {ref}`plone-api-content` specification.