plone.api 2.2.5__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.
- {plone_api-2.2.5 → plone_api-2.3.0}/CHANGES.rst +19 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/PKG-INFO +20 -1
- {plone_api-2.2.5 → plone_api-2.3.0}/docs/content.md +56 -3
- {plone_api-2.2.5 → plone_api-2.3.0}/docs/contribute.md +26 -49
- {plone_api-2.2.5/src/plone/api/tests/doctests → plone_api-2.3.0/docs}/portal.md +43 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/setup.py +1 -1
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/content.py +30 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/portal.py +41 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/tests/doctests/content.md +56 -3
- plone_api-2.3.0/src/plone/api/tests/doctests/contribute.md +217 -0
- {plone_api-2.2.5/docs → plone_api-2.3.0/src/plone/api/tests/doctests}/portal.md +43 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/tests/test_content.py +63 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/tests/test_doctests.py +1 -1
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/tests/test_portal.py +69 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone.api.egg-info/PKG-INFO +20 -1
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone.api.egg-info/SOURCES.txt +1 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/CONTRIBUTING.rst +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/LICENSE +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/MANIFEST.in +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/README.md +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/docs/about.md +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/docs/env.md +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/docs/group.md +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/docs/index.md +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/docs/relation.md +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/docs/user.md +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/pyproject.toml +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/setup.cfg +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/__init__.py +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/__init__.py +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/configure.zcml +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/env.py +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/exc.py +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/group.py +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/profiles/testfixture/metadata.xml +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/profiles/testfixture/types/Dexterity_Folder.xml +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/profiles/testfixture/types/Dexterity_Item.xml +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/profiles/testfixture/types.xml +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/relation.py +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/testing.zcml +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/tests/Dexterity_Folder.xml +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/tests/Dexterity_Item.xml +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/tests/__init__.py +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/tests/base.py +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/tests/doctests/about.md +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/tests/doctests/env.md +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/tests/doctests/group.md +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/tests/doctests/relation.md +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/tests/doctests/user.md +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/tests/test_env.py +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/tests/test_group.py +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/tests/test_relation.py +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/tests/test_user.py +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/tests/test_validation.py +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/user.py +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/validation.py +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone.api.egg-info/dependency_links.txt +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone.api.egg-info/namespace_packages.txt +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone.api.egg-info/not-zip-safe +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone.api.egg-info/requires.txt +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/src/plone.api.egg-info/top_level.txt +0 -0
- {plone_api-2.2.5 → plone_api-2.3.0}/tox.ini +0 -0
|
@@ -8,6 +8,25 @@ 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
|
+
|
|
11
30
|
2.2.5 (2025-01-24)
|
|
12
31
|
------------------
|
|
13
32
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: plone.api
|
|
3
|
-
Version: 2.
|
|
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
|
|
@@ -110,6 +110,25 @@ 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
|
+
|
|
113
132
|
2.2.5 (2025-01-24)
|
|
114
133
|
------------------
|
|
115
134
|
|
|
@@ -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,
|
|
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
|
|
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
|
|
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
|
-
|
|
37
|
+
Run `make help` to see the available `make` commands.
|
|
62
38
|
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
48
|
+
The `make` commands `check`, `livehtml`, and `test` will create a development environment, if it does not already exist.
|
|
69
49
|
|
|
70
|
-
|
|
71
|
-
Some helpful `tox` commands are shown below.
|
|
50
|
+
Test your code changes with the following command.
|
|
72
51
|
|
|
73
52
|
```shell
|
|
74
|
-
|
|
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,22 +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
|
-
|
|
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
|
-
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Alternatively, you can automatically reload changes to the documentation as you edit it in a web browser.
|
|
119
|
-
|
|
120
|
-
```shell
|
|
121
|
-
tox -e livehtml
|
|
93
|
+
make livehtml
|
|
122
94
|
```
|
|
123
95
|
|
|
124
96
|
You can run a link checker on documentation.
|
|
125
97
|
|
|
126
98
|
```shell
|
|
127
|
-
|
|
99
|
+
make linkcheck
|
|
128
100
|
```
|
|
129
101
|
|
|
130
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/).
|
|
@@ -172,6 +144,10 @@ def foo(path=None, UID=None):
|
|
|
172
144
|
% InvalidParameterError,
|
|
173
145
|
% lambda: foo("/plone/blog", "abcd001")
|
|
174
146
|
% )
|
|
147
|
+
%
|
|
148
|
+
% # Make it available for testing below
|
|
149
|
+
% from plone import api
|
|
150
|
+
% api.content.foo = foo
|
|
175
151
|
|
|
176
152
|
Add documentation in {file}`docs/api/content.md`.
|
|
177
153
|
Narrative documentation should describe what your function does.
|
|
@@ -198,7 +174,8 @@ blog_foo = api.content.foo(path="/plone/blog")
|
|
|
198
174
|
|
|
199
175
|
% invisible-code-block: python
|
|
200
176
|
%
|
|
201
|
-
% self.assertEqual(blog_foo,"foo")
|
|
177
|
+
% self.assertEqual(blog_foo, "foo")
|
|
178
|
+
|
|
202
179
|
````
|
|
203
180
|
|
|
204
181
|
Code blocks are rendered in documentation.
|
|
@@ -215,7 +192,7 @@ Invisible code blocks are not rendered in documentation and can be used for test
|
|
|
215
192
|
```markdown
|
|
216
193
|
% invisible-code-block: python
|
|
217
194
|
%
|
|
218
|
-
% self.assertEqual(blog_foo,"foo")
|
|
195
|
+
% self.assertEqual(blog_foo, "foo")
|
|
219
196
|
```
|
|
220
197
|
|
|
221
198
|
Invisible code blocks are also handy for enriching the namespace without cluttering the narrative documentation.
|
|
@@ -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.
|
|
@@ -569,6 +569,36 @@ def get_uuid(obj=None):
|
|
|
569
569
|
return IUUID(obj)
|
|
570
570
|
|
|
571
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
|
+
|
|
572
602
|
def _parse_object_provides_query(query):
|
|
573
603
|
"""Create a query for the object_provides index.
|
|
574
604
|
|
|
@@ -12,11 +12,14 @@ 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
|
|
@@ -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)])
|
|
@@ -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,
|
|
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.
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
---
|
|
2
|
+
myst:
|
|
3
|
+
html_meta:
|
|
4
|
+
"description": "Contribute to plone.api"
|
|
5
|
+
"property=og:description": "Contribute to plone.api"
|
|
6
|
+
"property=og:title": "Contribute to plone.api"
|
|
7
|
+
"keywords": "plone.api, contribute, Plone, API, development"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Contribute to `plone.api`
|
|
11
|
+
|
|
12
|
+
This section describes how to contribute to the `plone.api` project.
|
|
13
|
+
It extends {doc}`plone:contributing/index`.
|
|
14
|
+
|
|
15
|
+
## Prerequisites
|
|
16
|
+
|
|
17
|
+
Prepare your system by installing prerequisites.
|
|
18
|
+
|
|
19
|
+
### System libraries
|
|
20
|
+
|
|
21
|
+
You need to install system libraries, as described in {ref}`plone:plone-prerequisites-label`.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## Create development environment
|
|
25
|
+
|
|
26
|
+
After satisfying the prerequisites, you are ready to create your development environment.
|
|
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.
|
|
29
|
+
|
|
30
|
+
Start by changing your working directory to your project folder, and check out the latest `plone.api` source code.
|
|
31
|
+
|
|
32
|
+
```shell
|
|
33
|
+
cd <your_project_folder>
|
|
34
|
+
git clone https://github.com/plone/plone.api.git
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Run `make help` to see the available `make` commands.
|
|
38
|
+
|
|
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
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The `make` commands `check`, `livehtml`, and `test` will create a development environment, if it does not already exist.
|
|
49
|
+
|
|
50
|
+
Test your code changes with the following command.
|
|
51
|
+
|
|
52
|
+
```shell
|
|
53
|
+
make test
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
(git-workflow)=
|
|
58
|
+
|
|
59
|
+
## git
|
|
60
|
+
|
|
61
|
+
Use the following git branches when contributing to `plone.api`.
|
|
62
|
+
|
|
63
|
+
feature branches
|
|
64
|
+
: All development for a new feature or bug fix must be done on a new branch.
|
|
65
|
+
|
|
66
|
+
`main`
|
|
67
|
+
: Pull requests should be made from a feature branch against the `main` branch.
|
|
68
|
+
When features and bug fixes are complete and approved, they are merged into the `main` branch.
|
|
69
|
+
|
|
70
|
+
```{seealso}
|
|
71
|
+
{ref}`plone:contributing-core-work-with-git-label`
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Continuous integration
|
|
75
|
+
|
|
76
|
+
`plone.api` uses GitHub workflows for continuous integration.
|
|
77
|
+
On every push to the `main` branch, GitHub runs its workflows for all tests and code quality checks.
|
|
78
|
+
GitHub workflows are configured in the directory `.github/workflows` at the root of this package.
|
|
79
|
+
|
|
80
|
+
## Documentation
|
|
81
|
+
|
|
82
|
+
For every feature change or addition to `plone.api`, you must add documentation of it.
|
|
83
|
+
`plone.api` uses [MyST](https://myst-parser.readthedocs.io/en/latest/) for documentation syntax.
|
|
84
|
+
|
|
85
|
+
```{seealso}
|
|
86
|
+
{doc}`plone:contributing/documentation/index`
|
|
87
|
+
```
|
|
88
|
+
|
|
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.
|
|
91
|
+
|
|
92
|
+
```shell
|
|
93
|
+
make livehtml
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
You can run a link checker on documentation.
|
|
97
|
+
|
|
98
|
+
```shell
|
|
99
|
+
make linkcheck
|
|
100
|
+
```
|
|
101
|
+
|
|
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/).
|
|
103
|
+
|
|
104
|
+
## Add a function to an existing module
|
|
105
|
+
|
|
106
|
+
This section describes how to add a new function `foo` to `plone.api`.
|
|
107
|
+
|
|
108
|
+
The function would go in the module `plone.api.content`, located in the file {file}`src/plone/api/content.py`.
|
|
109
|
+
|
|
110
|
+
% invisible-code-block: python
|
|
111
|
+
%
|
|
112
|
+
% from plone.api.validation import at_least_one_of
|
|
113
|
+
% from plone.api.validation import mutually_exclusive_parameters
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
@mutually_exclusive_parameters('path', 'UID')
|
|
117
|
+
@at_least_one_of('path', 'UID')
|
|
118
|
+
def foo(path=None, UID=None):
|
|
119
|
+
"""Do foo.
|
|
120
|
+
|
|
121
|
+
:param path: Path to the object we want to get,
|
|
122
|
+
relative to the portal root.
|
|
123
|
+
:type path: string
|
|
124
|
+
|
|
125
|
+
:param UID: UID of the object we want to get.
|
|
126
|
+
:type UID: string
|
|
127
|
+
|
|
128
|
+
:returns: String
|
|
129
|
+
:raises:
|
|
130
|
+
:class:`~plone.api.exc.MissingParameterError`,
|
|
131
|
+
:class:`~plone.api.exc.InvalidParameterError`
|
|
132
|
+
:Example: :ref:`content-foo-example`
|
|
133
|
+
"""
|
|
134
|
+
return "foo"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
% invisible-code-block: python
|
|
138
|
+
%
|
|
139
|
+
% bar = foo('/plone/blog')
|
|
140
|
+
% self.assertEqual(bar,"foo")
|
|
141
|
+
%
|
|
142
|
+
% from plone.api.exc import InvalidParameterError
|
|
143
|
+
% self.assertRaises(
|
|
144
|
+
% InvalidParameterError,
|
|
145
|
+
% lambda: foo("/plone/blog", "abcd001")
|
|
146
|
+
% )
|
|
147
|
+
%
|
|
148
|
+
% # Make it available for testing below
|
|
149
|
+
% from plone import api
|
|
150
|
+
% api.content.foo = foo
|
|
151
|
+
|
|
152
|
+
Add documentation in {file}`docs/api/content.md`.
|
|
153
|
+
Narrative documentation should describe what your function does.
|
|
154
|
+
|
|
155
|
+
You should also write some tests in code blocks.
|
|
156
|
+
`TestCase` methods, such as `self.assertEqual()`, are available in `doctests`.
|
|
157
|
+
See [unittest.TestCase assert methods](https://docs.python.org/3/library/unittest.html#unittest.TestCase.debug) for all available methods.
|
|
158
|
+
The file is linked in `/src/plone/api/tests/doctests/`, which includes the doctests in `plone.api`'s test setup.
|
|
159
|
+
The package `manuel` allows you to write doctests as common Python code in code blocks.
|
|
160
|
+
|
|
161
|
+
The following example shows narrative documentation and doctests.
|
|
162
|
+
|
|
163
|
+
````markdown
|
|
164
|
+
(content-foo-example)=
|
|
165
|
+
|
|
166
|
+
## Get the foo of an object
|
|
167
|
+
|
|
168
|
+
You can use the {meth}`api.content.foo` function to get the `foo` of an object.
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
from plone import api
|
|
172
|
+
blog_foo = api.content.foo(path="/plone/blog")
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
% invisible-code-block: python
|
|
176
|
+
%
|
|
177
|
+
% self.assertEqual(blog_foo, "foo")
|
|
178
|
+
|
|
179
|
+
````
|
|
180
|
+
|
|
181
|
+
Code blocks are rendered in documentation.
|
|
182
|
+
|
|
183
|
+
````markdown
|
|
184
|
+
```python
|
|
185
|
+
from plone import api
|
|
186
|
+
blog_foo = api.content.foo(path="/plone/blog")
|
|
187
|
+
```
|
|
188
|
+
````
|
|
189
|
+
|
|
190
|
+
Invisible code blocks are not rendered in documentation and can be used for tests.
|
|
191
|
+
|
|
192
|
+
```markdown
|
|
193
|
+
% invisible-code-block: python
|
|
194
|
+
%
|
|
195
|
+
% self.assertEqual(blog_foo, "foo")
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Invisible code blocks are also handy for enriching the namespace without cluttering the narrative documentation.
|
|
199
|
+
|
|
200
|
+
```markdown
|
|
201
|
+
% invisible-code-block: python
|
|
202
|
+
%
|
|
203
|
+
% portal = api.portal.get()
|
|
204
|
+
% image = api.content.create(type='Image', id='image', container=portal)
|
|
205
|
+
% blog = api.content.create(type='Link', id='blog', container=portal)
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Functions and examples in documentation are mutually referenced.
|
|
209
|
+
The function references the narrative documentation via the label `content-foo-example`.
|
|
210
|
+
The narrative documentation references the API function documentation via `` {meth}`api.content.foo` ``.
|
|
211
|
+
The documentation is rendered with a link from the API reference to the narrative documentation, which in turn links back to the API reference.
|
|
212
|
+
|
|
213
|
+
## Resources
|
|
214
|
+
|
|
215
|
+
- {doc}`plone:index`
|
|
216
|
+
- [Source code](https://github.com/plone/plone.api)
|
|
217
|
+
- [Issue tracker](https://github.com/plone/plone.api/issues)
|
|
@@ -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.
|
|
@@ -1447,3 +1447,66 @@ class TestPloneApiContent(unittest.TestCase):
|
|
|
1447
1447
|
|
|
1448
1448
|
for should_be_there in should_be_theres:
|
|
1449
1449
|
self.assertIn((should_be_there + "\n"), str(cm.exception))
|
|
1450
|
+
|
|
1451
|
+
def test_get_path_absolute(self):
|
|
1452
|
+
"""Test getting the path of a content object with relative parameter set to False."""
|
|
1453
|
+
from plone.api.exc import InvalidParameterError
|
|
1454
|
+
|
|
1455
|
+
portal = self.layer["portal"]
|
|
1456
|
+
|
|
1457
|
+
# Test portal root
|
|
1458
|
+
self.assertEqual(
|
|
1459
|
+
api.content.get_path(portal), "/plone" # This assumes default Plone site id
|
|
1460
|
+
)
|
|
1461
|
+
|
|
1462
|
+
# Test folder structure
|
|
1463
|
+
folder = api.content.create(container=portal, type="Folder", id="test-folder")
|
|
1464
|
+
self.assertEqual(api.content.get_path(folder), "/plone/test-folder")
|
|
1465
|
+
|
|
1466
|
+
# Test nested content
|
|
1467
|
+
document = api.content.create(
|
|
1468
|
+
container=folder, type="Document", id="test-document"
|
|
1469
|
+
)
|
|
1470
|
+
self.assertEqual(
|
|
1471
|
+
api.content.get_path(document), "/plone/test-folder/test-document"
|
|
1472
|
+
)
|
|
1473
|
+
|
|
1474
|
+
# Test invalid object
|
|
1475
|
+
invalid_obj = object()
|
|
1476
|
+
with self.assertRaises(InvalidParameterError):
|
|
1477
|
+
api.content.get_path(invalid_obj)
|
|
1478
|
+
self.assertRaisesRegex(
|
|
1479
|
+
InvalidParameterError,
|
|
1480
|
+
r"^Cannot get path of object <object object at 0x[0-9a-f]+>$",
|
|
1481
|
+
)
|
|
1482
|
+
|
|
1483
|
+
def test_get_path_relative(self):
|
|
1484
|
+
from plone.api.exc import InvalidParameterError
|
|
1485
|
+
|
|
1486
|
+
portal = self.layer["portal"]
|
|
1487
|
+
|
|
1488
|
+
# Test portal root
|
|
1489
|
+
self.assertEqual(api.content.get_path(portal, relative=True), "")
|
|
1490
|
+
|
|
1491
|
+
# Test folder structure
|
|
1492
|
+
folder = api.content.create(container=portal, type="Folder", id="test-folder")
|
|
1493
|
+
self.assertEqual(api.content.get_path(folder, relative=True), "test-folder")
|
|
1494
|
+
|
|
1495
|
+
# Test nested content
|
|
1496
|
+
document = api.content.create(
|
|
1497
|
+
container=folder, type="Document", id="test-document"
|
|
1498
|
+
)
|
|
1499
|
+
self.assertEqual(
|
|
1500
|
+
api.content.get_path(document, relative=True),
|
|
1501
|
+
"test-folder/test-document",
|
|
1502
|
+
)
|
|
1503
|
+
|
|
1504
|
+
# Test object outside portal
|
|
1505
|
+
class FauxObject:
|
|
1506
|
+
def getPhysicalPath(self):
|
|
1507
|
+
return ("", "foo", "bar")
|
|
1508
|
+
|
|
1509
|
+
outside_obj = FauxObject()
|
|
1510
|
+
with self.assertRaises(InvalidParameterError) as cm:
|
|
1511
|
+
api.content.get_path(outside_obj, relative=True)
|
|
1512
|
+
self.assertIn("Object not in portal path", str(cm.exception))
|
|
@@ -115,7 +115,7 @@ def DocFileSuite(
|
|
|
115
115
|
|
|
116
116
|
|
|
117
117
|
def test_suite():
|
|
118
|
-
"""Find .
|
|
118
|
+
"""Find .md files and test code examples in them."""
|
|
119
119
|
path = "doctests"
|
|
120
120
|
doctests = []
|
|
121
121
|
docs_path = os.path.join(os.path.dirname(__file__), path)
|
|
@@ -19,6 +19,7 @@ from zope import schema
|
|
|
19
19
|
from zope.component import getUtility
|
|
20
20
|
from zope.component.hooks import setSite
|
|
21
21
|
from zope.interface import Interface
|
|
22
|
+
from zope.schema.vocabulary import SimpleVocabulary
|
|
22
23
|
from zope.site import LocalSiteManager
|
|
23
24
|
|
|
24
25
|
import DateTime
|
|
@@ -894,3 +895,71 @@ class TestPloneApiPortal(unittest.TestCase):
|
|
|
894
895
|
),
|
|
895
896
|
"Página",
|
|
896
897
|
)
|
|
898
|
+
|
|
899
|
+
def test_get_vocabulary(self):
|
|
900
|
+
"""Test getting a vocabulary by name."""
|
|
901
|
+
from plone.api.exc import InvalidParameterError
|
|
902
|
+
from plone.api.exc import MissingParameterError
|
|
903
|
+
|
|
904
|
+
# The vocabulary name must be given as parameter
|
|
905
|
+
with self.assertRaises(MissingParameterError):
|
|
906
|
+
portal.get_vocabulary()
|
|
907
|
+
|
|
908
|
+
# Test getting a commonly available vocabulary
|
|
909
|
+
vocabulary = portal.get_vocabulary(name="plone.app.vocabularies.PortalTypes")
|
|
910
|
+
self.assertIsInstance(vocabulary, SimpleVocabulary)
|
|
911
|
+
|
|
912
|
+
# Test with invalid vocabulary name
|
|
913
|
+
with self.assertRaises(InvalidParameterError) as cm:
|
|
914
|
+
portal.get_vocabulary(name="non.existing.vocabulary")
|
|
915
|
+
|
|
916
|
+
expected_msg = (
|
|
917
|
+
"Cannot find a vocabulary with name 'non.existing.vocabulary'.\n"
|
|
918
|
+
"Available vocabularies are:\n"
|
|
919
|
+
)
|
|
920
|
+
self.assertTrue(str(cm.exception).startswith(expected_msg))
|
|
921
|
+
|
|
922
|
+
# Test with context
|
|
923
|
+
vocabulary_with_context = portal.get_vocabulary(
|
|
924
|
+
name="plone.app.vocabularies.PortalTypes", context=self.portal
|
|
925
|
+
)
|
|
926
|
+
self.assertIsInstance(vocabulary_with_context, SimpleVocabulary)
|
|
927
|
+
|
|
928
|
+
def test_get_vocabulary_names(self):
|
|
929
|
+
"""Test getting list of vocabulary names."""
|
|
930
|
+
names = portal.get_vocabulary_names()
|
|
931
|
+
|
|
932
|
+
# Test we get a list of strings
|
|
933
|
+
self.assertIsInstance(names, list)
|
|
934
|
+
self.assertTrue(len(names) > 0)
|
|
935
|
+
self.assertIsInstance(names[0], str)
|
|
936
|
+
|
|
937
|
+
# Test that common vocabularies are included
|
|
938
|
+
common_vocabularies = [
|
|
939
|
+
"plone.app.vocabularies.PortalTypes",
|
|
940
|
+
"plone.app.vocabularies.WorkflowStates",
|
|
941
|
+
"plone.app.vocabularies.WorkflowTransitions",
|
|
942
|
+
]
|
|
943
|
+
|
|
944
|
+
for vocabulary_name in common_vocabularies:
|
|
945
|
+
self.assertIn(vocabulary_name, names)
|
|
946
|
+
|
|
947
|
+
def test_vocabulary_terms(self):
|
|
948
|
+
"""Test the actual content of retrieved vocabularies."""
|
|
949
|
+
# Get portal types vocabulary
|
|
950
|
+
types_vocabulary = portal.get_vocabulary("plone.app.vocabularies.PortalTypes")
|
|
951
|
+
|
|
952
|
+
# Check that we have some common content types
|
|
953
|
+
types = [term.value for term in types_vocabulary]
|
|
954
|
+
self.assertIn("Document", types)
|
|
955
|
+
self.assertIn("Folder", types)
|
|
956
|
+
|
|
957
|
+
# Get workflow states vocabulary
|
|
958
|
+
states_vocabulary = portal.get_vocabulary(
|
|
959
|
+
"plone.app.vocabularies.WorkflowStates"
|
|
960
|
+
)
|
|
961
|
+
|
|
962
|
+
# Check that we have some common workflow states
|
|
963
|
+
states = [term.value for term in states_vocabulary]
|
|
964
|
+
self.assertIn("private", states)
|
|
965
|
+
self.assertIn("published", states)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: plone.api
|
|
3
|
-
Version: 2.
|
|
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
|
|
@@ -110,6 +110,25 @@ 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
|
+
|
|
113
132
|
2.2.5 (2025-01-24)
|
|
114
133
|
------------------
|
|
115
134
|
|
|
@@ -53,6 +53,7 @@ src/plone/api/tests/test_user.py
|
|
|
53
53
|
src/plone/api/tests/test_validation.py
|
|
54
54
|
src/plone/api/tests/doctests/about.md
|
|
55
55
|
src/plone/api/tests/doctests/content.md
|
|
56
|
+
src/plone/api/tests/doctests/contribute.md
|
|
56
57
|
src/plone/api/tests/doctests/env.md
|
|
57
58
|
src/plone/api/tests/doctests/group.md
|
|
58
59
|
src/plone/api/tests/doctests/portal.md
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/profiles/testfixture/types/Dexterity_Folder.xml
RENAMED
|
File without changes
|
{plone_api-2.2.5 → plone_api-2.3.0}/src/plone/api/profiles/testfixture/types/Dexterity_Item.xml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|