plone.api 2.4.0__tar.gz → 2.5.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.4.0 → plone_api-2.5.0}/CHANGES.md +14 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/PKG-INFO +15 -1
- plone_api-2.5.0/docs/addon.md +155 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/docs/index.md +9 -7
- {plone_api-2.4.0 → plone_api-2.5.0}/pyproject.toml +1 -1
- {plone_api-2.4.0 → plone_api-2.5.0}/setup.py +1 -1
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/__init__.py +1 -0
- plone_api-2.5.0/src/plone/api/addon.py +303 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/content.py +17 -2
- plone_api-2.5.0/src/plone/api/tests/doctests/addon.md +155 -0
- plone_api-2.5.0/src/plone/api/tests/test_addon.py +106 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone.api.egg-info/PKG-INFO +15 -1
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone.api.egg-info/SOURCES.txt +4 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/tox.ini +7 -5
- {plone_api-2.4.0 → plone_api-2.5.0}/CONTRIBUTING.md +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/LICENSE +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/MANIFEST.in +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/README.md +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/docs/about.md +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/docs/content.md +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/docs/contribute.md +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/docs/env.md +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/docs/group.md +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/docs/portal.md +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/docs/relation.md +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/docs/user.md +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/setup.cfg +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/__init__.py +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/configure.zcml +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/env.py +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/exc.py +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/group.py +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/portal.py +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/profiles/testfixture/metadata.xml +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/profiles/testfixture/types/Dexterity_Folder.xml +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/profiles/testfixture/types/Dexterity_Item.xml +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/profiles/testfixture/types.xml +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/relation.py +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/testing.zcml +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/tests/Dexterity_Folder.xml +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/tests/Dexterity_Item.xml +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/tests/__init__.py +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/tests/base.py +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/tests/doctests/about.md +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/tests/doctests/content.md +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/tests/doctests/contribute.md +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/tests/doctests/env.md +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/tests/doctests/group.md +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/tests/doctests/portal.md +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/tests/doctests/relation.md +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/tests/doctests/user.md +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/tests/test_content.py +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/tests/test_doctests.py +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/tests/test_env.py +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/tests/test_group.py +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/tests/test_portal.py +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/tests/test_relation.py +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/tests/test_user.py +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/tests/test_validation.py +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/user.py +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/validation.py +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone.api.egg-info/dependency_links.txt +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone.api.egg-info/namespace_packages.txt +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone.api.egg-info/not-zip-safe +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone.api.egg-info/requires.txt +0 -0
- {plone_api-2.4.0 → plone_api-2.5.0}/src/plone.api.egg-info/top_level.txt +0 -0
|
@@ -9,6 +9,20 @@
|
|
|
9
9
|
|
|
10
10
|
<!-- towncrier release notes start -->
|
|
11
11
|
|
|
12
|
+
## 2.5.0 (2025-03-25)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### New features:
|
|
16
|
+
|
|
17
|
+
- Implement `plone.api.addon` module. @ericof, @ujsquared, @stevepiercy #505
|
|
18
|
+
|
|
19
|
+
## 2.4.1 (2025-03-17)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Bug fixes:
|
|
23
|
+
|
|
24
|
+
- Attempt to generate a random temporary id for a content type up to 100 times, else continue to raise a `zExceptions.BadRequest` error. @rohnsha0 #445
|
|
25
|
+
|
|
12
26
|
## 2.4.0 (2025-03-14)
|
|
13
27
|
|
|
14
28
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: plone.api
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: A Plone API.
|
|
5
5
|
Home-page: https://github.com/plone/plone.api
|
|
6
6
|
Author: Plone Foundation
|
|
@@ -127,6 +127,20 @@ Continuous Integration
|
|
|
127
127
|
|
|
128
128
|
<!-- towncrier release notes start -->
|
|
129
129
|
|
|
130
|
+
## 2.5.0 (2025-03-25)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
### New features:
|
|
134
|
+
|
|
135
|
+
- Implement `plone.api.addon` module. @ericof, @ujsquared, @stevepiercy #505
|
|
136
|
+
|
|
137
|
+
## 2.4.1 (2025-03-17)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
### Bug fixes:
|
|
141
|
+
|
|
142
|
+
- Attempt to generate a random temporary id for a content type up to 100 times, else continue to raise a `zExceptions.BadRequest` error. @rohnsha0 #445
|
|
143
|
+
|
|
130
144
|
## 2.4.0 (2025-03-14)
|
|
131
145
|
|
|
132
146
|
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
---
|
|
2
|
+
myst:
|
|
3
|
+
html_meta:
|
|
4
|
+
"description": "Get, modify, and manage Plone add-ons"
|
|
5
|
+
"property=og:description": "Get, modify, and manage Plone add-ons"
|
|
6
|
+
"property=og:title": "Get, modify, and manage Plone add-ons"
|
|
7
|
+
"keywords": "Plone, API, development, add-on, manage"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
```{eval-rst}
|
|
11
|
+
.. module:: plone
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
(chapter-addons)=
|
|
15
|
+
|
|
16
|
+
# Add-ons
|
|
17
|
+
|
|
18
|
+
This chapter describes how to get, update, install, uninstall, and manage Plone add-ons.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
(addons-get-addons)=
|
|
22
|
+
|
|
23
|
+
## Get add-ons
|
|
24
|
+
|
|
25
|
+
To get all the add-ons present in the current Plone site, use the {func}`api.addon.get_addons` function.
|
|
26
|
+
The function accepts an optional `limit` parameter to filter the returned add-ons.
|
|
27
|
+
`limit` may be one of the following strings.
|
|
28
|
+
|
|
29
|
+
`available`
|
|
30
|
+
: Products that are not installed, but could be.
|
|
31
|
+
|
|
32
|
+
`broken`
|
|
33
|
+
: Uninstallable products with broken dependencies.
|
|
34
|
+
|
|
35
|
+
`installed`
|
|
36
|
+
: Only products that are installed and not hidden.
|
|
37
|
+
|
|
38
|
+
`non_installable`
|
|
39
|
+
: Non-installable products.
|
|
40
|
+
|
|
41
|
+
`upgradable`
|
|
42
|
+
: Only products with upgrades.
|
|
43
|
+
|
|
44
|
+
The following examples demonstrate usage of the {func}`api.addon.get_addons` function.
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from plone import api
|
|
48
|
+
|
|
49
|
+
# Get all add-ons
|
|
50
|
+
addons = api.addon.get_addons()
|
|
51
|
+
|
|
52
|
+
# Get only installed add-ons
|
|
53
|
+
installed = api.addon.get_addons(limit="installed")
|
|
54
|
+
|
|
55
|
+
# Get only upgradable add-ons
|
|
56
|
+
upgradable = api.addon.get_addons(limit="upgradable")
|
|
57
|
+
|
|
58
|
+
# Get only broken add-ons
|
|
59
|
+
broken = api.addon.get_addons(limit="broken")
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
(addons-get-addon-ids)=
|
|
63
|
+
|
|
64
|
+
## Get add-on IDs
|
|
65
|
+
|
|
66
|
+
To get the IDs of all the add-ons present in the current Plone site, use the {func}`api.addon.get_addon_ids` function.
|
|
67
|
+
The function accepts an optional `limit` parameter to filter the returned add-ons, exactly the same as the {func}`api.addon.get_addons` function.
|
|
68
|
+
`limit` may be one of the following strings.
|
|
69
|
+
|
|
70
|
+
`available`
|
|
71
|
+
: Products that are not installed, but could be.
|
|
72
|
+
|
|
73
|
+
`broken`
|
|
74
|
+
: Uninstallable products with broken dependencies.
|
|
75
|
+
|
|
76
|
+
`installed`
|
|
77
|
+
: Only products that are installed and not hidden.
|
|
78
|
+
|
|
79
|
+
`non_installable`
|
|
80
|
+
: Non-installable products.
|
|
81
|
+
|
|
82
|
+
`upgradable`
|
|
83
|
+
: Only products with upgrades.
|
|
84
|
+
|
|
85
|
+
The following example demonstrates usage of the {func}`api.addon.get_addon_ids` function.
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
# Get IDs of installed add-ons
|
|
89
|
+
addon_ids = api.addon.get_addon_ids(limit="installed")
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Get add-on information
|
|
93
|
+
|
|
94
|
+
To get information about a specific add-on, use the {func}`api.addon.get` function, passing in the name of the add-on as a string.
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from plone import api
|
|
98
|
+
|
|
99
|
+
addon = api.addon.get("plone.session")
|
|
100
|
+
print(addon.id) # ID of the add-on
|
|
101
|
+
print(addon.version) # Version string
|
|
102
|
+
print(addon.title) # Display title
|
|
103
|
+
print(addon.description) # Description
|
|
104
|
+
print(addon.flags) # List of flags like ["installed", "upgradable"]
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Install and uninstall add-ons
|
|
108
|
+
|
|
109
|
+
To install an add-on, use the {func}`api.addon.install` function, passing in the name of the add-on as a string, as shown in the following example.
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from plone import api
|
|
113
|
+
|
|
114
|
+
success = api.addon.install("plone.session")
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
This function returns a `false` boolean value in the following cases.
|
|
118
|
+
- The installation fails due to an error.
|
|
119
|
+
- The add-on is already installed.
|
|
120
|
+
- The add-on is not found among the available add-ons.
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
To uninstall an add-on, use the {func}`api.addon.uninstall` function, passing in the name of the add-on as a string, as shown in the following example.
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from plone import api
|
|
128
|
+
|
|
129
|
+
success = api.addon.uninstall("plone.session")
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
This function returns a `false` boolean value in the following cases.
|
|
133
|
+
- The removal of add-on fails due to an error.
|
|
134
|
+
- The add-on is not installed.
|
|
135
|
+
|
|
136
|
+
## Get add-on version
|
|
137
|
+
|
|
138
|
+
To get the version of an add-on, use the {func}`api.addon.get_version` function, passing in the name of the add-on as a string, as shown in the following example.
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from plone import api
|
|
142
|
+
|
|
143
|
+
version = api.addon.get_version("plone.session")
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Note that this returns the version of the Python package installed from _PyPI_, not the version of the add-on's _GenericSetup_ profile.
|
|
147
|
+
|
|
148
|
+
(addons-exceptions)=
|
|
149
|
+
|
|
150
|
+
## Exceptions
|
|
151
|
+
|
|
152
|
+
The {exc}`~plone.api.exc.InvalidParameterError` exception may be raised in the following cases.
|
|
153
|
+
|
|
154
|
+
- When using the {func}`api.addon.get` function, trying to get information about a non-existent add-on.
|
|
155
|
+
- When using either function {func}`api.addon.get_addons` or {func}`api.addon.get_addon_ids`, using an invalid `limit` parameter value.
|
|
@@ -39,12 +39,13 @@ Backward-incompatible changes to the API will be restricted to major versions (1
|
|
|
39
39
|
:maxdepth: 2
|
|
40
40
|
|
|
41
41
|
about
|
|
42
|
-
|
|
42
|
+
addon
|
|
43
43
|
content
|
|
44
|
-
user
|
|
45
|
-
group
|
|
46
44
|
env
|
|
45
|
+
group
|
|
46
|
+
portal
|
|
47
47
|
relation
|
|
48
|
+
user
|
|
48
49
|
```
|
|
49
50
|
|
|
50
51
|
|
|
@@ -58,13 +59,14 @@ api/index
|
|
|
58
59
|
```
|
|
59
60
|
|
|
60
61
|
- {doc}`api/index`
|
|
61
|
-
- [`plone.api.
|
|
62
|
+
- [`plone.api.addon`](api/addon)
|
|
62
63
|
- [`plone.api.content`](api/content)
|
|
63
|
-
- [`plone.api.user`](api/user)
|
|
64
|
-
- [`plone.api.group`](api/group)
|
|
65
64
|
- [`plone.api.env`](api/env)
|
|
66
|
-
- [`plone.api.relation`](api/relation)
|
|
67
65
|
- [`plone.api.exceptions`](api/exceptions)
|
|
66
|
+
- [`plone.api.group`](api/group)
|
|
67
|
+
- [`plone.api.portal`](api/portal)
|
|
68
|
+
- [`plone.api.relation`](api/relation)
|
|
69
|
+
- [`plone.api.user`](api/user)
|
|
68
70
|
|
|
69
71
|
|
|
70
72
|
## Contribute
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"""API to handle add-on management."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from functools import lru_cache
|
|
5
|
+
from plone.api import portal
|
|
6
|
+
from plone.api.exc import InvalidParameterError
|
|
7
|
+
from plone.api.validation import required_parameters
|
|
8
|
+
from Products.CMFPlone.controlpanel.browser.quickinstaller import InstallerView
|
|
9
|
+
from Products.CMFPlone.interfaces import INonInstallable
|
|
10
|
+
from Products.CMFPlone.utils import get_installer
|
|
11
|
+
from Products.GenericSetup import EXTENSION
|
|
12
|
+
from typing import Dict
|
|
13
|
+
from typing import List
|
|
14
|
+
from typing import Tuple
|
|
15
|
+
from zope.component import getAllUtilitiesRegisteredFor
|
|
16
|
+
from zope.globalrequest import getRequest
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
import pkg_resources
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger("plone.api.addon")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"AddonInformation",
|
|
27
|
+
"NonInstallableAddons",
|
|
28
|
+
"get_addons",
|
|
29
|
+
"get_addon_ids",
|
|
30
|
+
"get_version",
|
|
31
|
+
"get",
|
|
32
|
+
"install",
|
|
33
|
+
"uninstall",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class NonInstallableAddons:
|
|
39
|
+
"""Set of add-ons not available for installation."""
|
|
40
|
+
|
|
41
|
+
profiles: List[str]
|
|
42
|
+
products: List[str]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class AddonInformation:
|
|
47
|
+
"""Add-on information."""
|
|
48
|
+
|
|
49
|
+
id: str # noQA
|
|
50
|
+
version: str
|
|
51
|
+
title: str
|
|
52
|
+
description: str
|
|
53
|
+
|
|
54
|
+
upgrade_profiles: Dict
|
|
55
|
+
other_profiles: List[List]
|
|
56
|
+
install_profile: Dict
|
|
57
|
+
uninstall_profile: Dict
|
|
58
|
+
profile_type: str
|
|
59
|
+
upgrade_info: Dict
|
|
60
|
+
valid: bool
|
|
61
|
+
flags: List[str]
|
|
62
|
+
|
|
63
|
+
def __repr__(self) -> str:
|
|
64
|
+
"""Return a string representation of this object."""
|
|
65
|
+
return f"<AddonInformation id='{self.id}' flags='{self.flags}'>"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _get_installer() -> InstallerView:
|
|
69
|
+
"""Return the InstallerView."""
|
|
70
|
+
portal_obj = portal.get()
|
|
71
|
+
return get_installer(portal_obj, getRequest())
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@lru_cache(maxsize=1)
|
|
75
|
+
def _get_non_installable_addons() -> NonInstallableAddons:
|
|
76
|
+
"""Return information about non installable add-ons.
|
|
77
|
+
|
|
78
|
+
We cache this on first use, as those utilities are registered
|
|
79
|
+
during the application startup
|
|
80
|
+
|
|
81
|
+
:returns: NonInstallableAddons instance.
|
|
82
|
+
"""
|
|
83
|
+
ignore_profiles = []
|
|
84
|
+
ignore_products = []
|
|
85
|
+
utils = getAllUtilitiesRegisteredFor(INonInstallable)
|
|
86
|
+
for util in utils:
|
|
87
|
+
ni_profiles = getattr(util, "getNonInstallableProfiles", None)
|
|
88
|
+
if ni_profiles is not None:
|
|
89
|
+
ignore_profiles.extend(ni_profiles())
|
|
90
|
+
ni_products = getattr(util, "getNonInstallableProducts", None)
|
|
91
|
+
if ni_products is not None:
|
|
92
|
+
ignore_products.extend(ni_products())
|
|
93
|
+
return NonInstallableAddons(
|
|
94
|
+
profiles=ignore_profiles,
|
|
95
|
+
products=ignore_products,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@lru_cache(maxsize=1)
|
|
100
|
+
def _cached_addons() -> Tuple[Tuple[str, AddonInformation]]:
|
|
101
|
+
"""Return information about add-ons in this installation.
|
|
102
|
+
|
|
103
|
+
:returns: Tuple of tuples with add-on id and AddonInformation.
|
|
104
|
+
:rtype: Tuple
|
|
105
|
+
"""
|
|
106
|
+
installer = _get_installer()
|
|
107
|
+
setup_tool = installer.ps
|
|
108
|
+
addons = {}
|
|
109
|
+
non_installable = _get_non_installable_addons()
|
|
110
|
+
# Known profiles:
|
|
111
|
+
profiles = setup_tool.listProfileInfo()
|
|
112
|
+
|
|
113
|
+
for profile in profiles:
|
|
114
|
+
if profile["type"] != EXTENSION:
|
|
115
|
+
continue
|
|
116
|
+
|
|
117
|
+
pid = profile["id"]
|
|
118
|
+
if pid in non_installable.profiles:
|
|
119
|
+
continue
|
|
120
|
+
pid_parts = pid.split(":")
|
|
121
|
+
if len(pid_parts) != 2:
|
|
122
|
+
logger.error(f"Profile with id '{pid}' is invalid.")
|
|
123
|
+
# Which package (product) is this from?
|
|
124
|
+
product_id = profile["product"]
|
|
125
|
+
flags = []
|
|
126
|
+
is_broken = not installer.is_product_installable(product_id, allow_hidden=True)
|
|
127
|
+
is_non_installable = product_id in non_installable.products
|
|
128
|
+
valid = not (is_broken or is_non_installable)
|
|
129
|
+
if is_broken:
|
|
130
|
+
flags.append("broken")
|
|
131
|
+
if is_non_installable:
|
|
132
|
+
flags.append("non_installable")
|
|
133
|
+
profile_type = pid_parts[-1]
|
|
134
|
+
if product_id not in addons:
|
|
135
|
+
# get some basic information on the product
|
|
136
|
+
product = {
|
|
137
|
+
"id": product_id,
|
|
138
|
+
"version": get_version(product_id),
|
|
139
|
+
"title": product_id,
|
|
140
|
+
"description": "",
|
|
141
|
+
"upgrade_profiles": {},
|
|
142
|
+
"other_profiles": [],
|
|
143
|
+
"install_profile": {},
|
|
144
|
+
"uninstall_profile": {},
|
|
145
|
+
"upgrade_info": {},
|
|
146
|
+
"profile_type": profile_type,
|
|
147
|
+
"valid": valid,
|
|
148
|
+
"flags": flags,
|
|
149
|
+
}
|
|
150
|
+
install_profile = installer.get_install_profile(product_id)
|
|
151
|
+
if install_profile is not None:
|
|
152
|
+
product["title"] = install_profile["title"]
|
|
153
|
+
product["description"] = install_profile["description"]
|
|
154
|
+
product["install_profile"] = install_profile
|
|
155
|
+
product["profile_type"] = "default"
|
|
156
|
+
uninstall_profile = installer.get_uninstall_profile(product_id)
|
|
157
|
+
if uninstall_profile is not None:
|
|
158
|
+
product["uninstall_profile"] = uninstall_profile
|
|
159
|
+
# Do not override profile_type.
|
|
160
|
+
if not product["profile_type"]:
|
|
161
|
+
product["profile_type"] = "uninstall"
|
|
162
|
+
if "version" in profile:
|
|
163
|
+
product["upgrade_profiles"][profile["version"]] = profile
|
|
164
|
+
else:
|
|
165
|
+
product["other_profiles"].append(profile)
|
|
166
|
+
addons[product_id] = AddonInformation(**product)
|
|
167
|
+
return tuple(addons.items())
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _update_addon_info(
|
|
171
|
+
addon: AddonInformation, installer: InstallerView
|
|
172
|
+
) -> AddonInformation:
|
|
173
|
+
"""Update information about an add-on.
|
|
174
|
+
|
|
175
|
+
:param addon: [required] Add-on object to be updated
|
|
176
|
+
:type addon: AddonInformation object
|
|
177
|
+
:param installer: InstallerView object to check for add-on info
|
|
178
|
+
:type installer: InstallerView object
|
|
179
|
+
|
|
180
|
+
:returns: Updated AddonInformation object
|
|
181
|
+
:rtype: AddonInformation object
|
|
182
|
+
"""
|
|
183
|
+
addon_id = addon.id
|
|
184
|
+
if addon.valid:
|
|
185
|
+
flags = []
|
|
186
|
+
# Update only what could be changed
|
|
187
|
+
is_installed = installer.is_product_installed(addon_id)
|
|
188
|
+
if is_installed:
|
|
189
|
+
addon.upgrade_info = installer.upgrade_info(addon_id) or {}
|
|
190
|
+
if addon.upgrade_info.get("available"):
|
|
191
|
+
flags.append("upgradable")
|
|
192
|
+
else:
|
|
193
|
+
flags.append("installed")
|
|
194
|
+
else:
|
|
195
|
+
flags.append("available")
|
|
196
|
+
addon.flags = flags
|
|
197
|
+
return addon
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _get_addons() -> List[AddonInformation]:
|
|
201
|
+
"""Return an updated list of add-on information.
|
|
202
|
+
|
|
203
|
+
:returns: List of AddonInformation.
|
|
204
|
+
:rtype: List
|
|
205
|
+
"""
|
|
206
|
+
installer = _get_installer()
|
|
207
|
+
addons = dict(_cached_addons())
|
|
208
|
+
result = []
|
|
209
|
+
for addon in addons.values():
|
|
210
|
+
result.append(_update_addon_info(addon, installer))
|
|
211
|
+
return result
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def get_addons(limit: str = "") -> List[AddonInformation]:
|
|
215
|
+
"""List add-ons in this Plone site.
|
|
216
|
+
|
|
217
|
+
:param limit: Limit list of add-ons.
|
|
218
|
+
'installed': only products that are installed and not hidden
|
|
219
|
+
'upgradable': only products with upgrades
|
|
220
|
+
'available': products that are not installed but could be
|
|
221
|
+
'non_installable': Non installable products
|
|
222
|
+
'broken': uninstallable products with broken dependencies
|
|
223
|
+
:type limit: string
|
|
224
|
+
:returns: List of AddonInformation.
|
|
225
|
+
:raises:
|
|
226
|
+
InvalidParameterError
|
|
227
|
+
:Example: :ref:`addons-get-addons`
|
|
228
|
+
"""
|
|
229
|
+
addons = _get_addons()
|
|
230
|
+
if limit in ("non_installable", "broken"):
|
|
231
|
+
return [addon for addon in addons if limit in addon.flags]
|
|
232
|
+
|
|
233
|
+
addons = [addon for addon in addons if addon.valid]
|
|
234
|
+
if limit in ("installed", "upgradable", "available"):
|
|
235
|
+
addons = [addon for addon in addons if limit in addon.flags]
|
|
236
|
+
elif limit != "":
|
|
237
|
+
raise InvalidParameterError(f"Parameter limit='{limit}' is not valid.")
|
|
238
|
+
return addons
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def get_addon_ids(limit: str = "") -> List[str]:
|
|
242
|
+
"""List add-ons ids in this Plone site.
|
|
243
|
+
|
|
244
|
+
:param limit: Limit list of add-ons.
|
|
245
|
+
'installed': only products that are installed and not hidden
|
|
246
|
+
'upgradable': only products with upgrades
|
|
247
|
+
'available': products that are not installed but could be
|
|
248
|
+
'non_installable': Non installable products
|
|
249
|
+
'broken': uninstallable products with broken dependencies
|
|
250
|
+
:type limit: string
|
|
251
|
+
:returns: List of add-on ids.
|
|
252
|
+
"""
|
|
253
|
+
addons = get_addons(limit=limit)
|
|
254
|
+
return [addon.id for addon in addons]
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@required_parameters("addon")
|
|
258
|
+
def get_version(addon: str) -> str:
|
|
259
|
+
"""Return the version of the product (package)."""
|
|
260
|
+
try:
|
|
261
|
+
dist = pkg_resources.get_distribution(addon)
|
|
262
|
+
return dist.version
|
|
263
|
+
except pkg_resources.DistributionNotFound:
|
|
264
|
+
if "." in addon:
|
|
265
|
+
return ""
|
|
266
|
+
return get_version(f"Products.{addon}")
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@required_parameters("addon")
|
|
270
|
+
def get(addon: str) -> AddonInformation:
|
|
271
|
+
"""Information about an Add-on.
|
|
272
|
+
|
|
273
|
+
:param addon: ID of the add-on to be retrieved.
|
|
274
|
+
:returns: Add-on information.
|
|
275
|
+
:rtype: string
|
|
276
|
+
"""
|
|
277
|
+
addons = dict(_cached_addons())
|
|
278
|
+
if addon not in addons:
|
|
279
|
+
raise InvalidParameterError(f"No add-on {addon} found.")
|
|
280
|
+
return _update_addon_info(addons.get(addon), _get_installer())
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
@required_parameters("addon")
|
|
284
|
+
def install(addon: str) -> bool:
|
|
285
|
+
"""Install an add-on.
|
|
286
|
+
|
|
287
|
+
:param addon: ID of the add-on to be installed.
|
|
288
|
+
:returns: Status of the installation.
|
|
289
|
+
"""
|
|
290
|
+
installer = _get_installer()
|
|
291
|
+
return installer.install_product(addon)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@required_parameters("addon")
|
|
295
|
+
def uninstall(addon: str) -> bool:
|
|
296
|
+
"""Uninstall an add-on.
|
|
297
|
+
|
|
298
|
+
:param addon: ID of the add-on to be uninstalled.
|
|
299
|
+
:returns: Status of the uninstallation.
|
|
300
|
+
:rtype: Boolean value representing the status of the uninstallation.
|
|
301
|
+
"""
|
|
302
|
+
installer = _get_installer()
|
|
303
|
+
return installer.uninstall_product(addon)
|
|
@@ -22,12 +22,15 @@ from zope.globalrequest import getRequest
|
|
|
22
22
|
from zope.interface import Interface
|
|
23
23
|
from zope.interface import providedBy
|
|
24
24
|
|
|
25
|
-
import random
|
|
26
25
|
import transaction
|
|
26
|
+
import uuid
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
_marker = []
|
|
30
30
|
|
|
31
|
+
# Maximum number of attempts to generate a unique random ID
|
|
32
|
+
MAX_UNIQUE_ID_ATTEMPTS = 100
|
|
33
|
+
|
|
31
34
|
|
|
32
35
|
@required_parameters("container", "type")
|
|
33
36
|
@at_least_one_of("id", "title")
|
|
@@ -67,7 +70,19 @@ def create(
|
|
|
67
70
|
:Example: :ref:`content-create-example`
|
|
68
71
|
"""
|
|
69
72
|
# Create a temporary id if the id is not given
|
|
70
|
-
|
|
73
|
+
if not safe_id and id:
|
|
74
|
+
content_id = id
|
|
75
|
+
else:
|
|
76
|
+
# Try to generate a unique random ID using UUID4
|
|
77
|
+
attempts = 0
|
|
78
|
+
while attempts < MAX_UNIQUE_ID_ATTEMPTS:
|
|
79
|
+
content_id = str(uuid.uuid4())
|
|
80
|
+
if content_id not in container:
|
|
81
|
+
break
|
|
82
|
+
attempts += 1
|
|
83
|
+
# If we couldn't find a unique ID after max attempts, raise ValueError
|
|
84
|
+
if attempts >= MAX_UNIQUE_ID_ATTEMPTS:
|
|
85
|
+
raise ValueError("Could not find unique id while creating content.")
|
|
71
86
|
|
|
72
87
|
if title:
|
|
73
88
|
kwargs["title"] = title
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
---
|
|
2
|
+
myst:
|
|
3
|
+
html_meta:
|
|
4
|
+
"description": "Get, modify, and manage Plone add-ons"
|
|
5
|
+
"property=og:description": "Get, modify, and manage Plone add-ons"
|
|
6
|
+
"property=og:title": "Get, modify, and manage Plone add-ons"
|
|
7
|
+
"keywords": "Plone, API, development, add-on, manage"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
```{eval-rst}
|
|
11
|
+
.. module:: plone
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
(chapter-addons)=
|
|
15
|
+
|
|
16
|
+
# Add-ons
|
|
17
|
+
|
|
18
|
+
This chapter describes how to get, update, install, uninstall, and manage Plone add-ons.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
(addons-get-addons)=
|
|
22
|
+
|
|
23
|
+
## Get add-ons
|
|
24
|
+
|
|
25
|
+
To get all the add-ons present in the current Plone site, use the {func}`api.addon.get_addons` function.
|
|
26
|
+
The function accepts an optional `limit` parameter to filter the returned add-ons.
|
|
27
|
+
`limit` may be one of the following strings.
|
|
28
|
+
|
|
29
|
+
`available`
|
|
30
|
+
: Products that are not installed, but could be.
|
|
31
|
+
|
|
32
|
+
`broken`
|
|
33
|
+
: Uninstallable products with broken dependencies.
|
|
34
|
+
|
|
35
|
+
`installed`
|
|
36
|
+
: Only products that are installed and not hidden.
|
|
37
|
+
|
|
38
|
+
`non_installable`
|
|
39
|
+
: Non-installable products.
|
|
40
|
+
|
|
41
|
+
`upgradable`
|
|
42
|
+
: Only products with upgrades.
|
|
43
|
+
|
|
44
|
+
The following examples demonstrate usage of the {func}`api.addon.get_addons` function.
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from plone import api
|
|
48
|
+
|
|
49
|
+
# Get all add-ons
|
|
50
|
+
addons = api.addon.get_addons()
|
|
51
|
+
|
|
52
|
+
# Get only installed add-ons
|
|
53
|
+
installed = api.addon.get_addons(limit="installed")
|
|
54
|
+
|
|
55
|
+
# Get only upgradable add-ons
|
|
56
|
+
upgradable = api.addon.get_addons(limit="upgradable")
|
|
57
|
+
|
|
58
|
+
# Get only broken add-ons
|
|
59
|
+
broken = api.addon.get_addons(limit="broken")
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
(addons-get-addon-ids)=
|
|
63
|
+
|
|
64
|
+
## Get add-on IDs
|
|
65
|
+
|
|
66
|
+
To get the IDs of all the add-ons present in the current Plone site, use the {func}`api.addon.get_addon_ids` function.
|
|
67
|
+
The function accepts an optional `limit` parameter to filter the returned add-ons, exactly the same as the {func}`api.addon.get_addons` function.
|
|
68
|
+
`limit` may be one of the following strings.
|
|
69
|
+
|
|
70
|
+
`available`
|
|
71
|
+
: Products that are not installed, but could be.
|
|
72
|
+
|
|
73
|
+
`broken`
|
|
74
|
+
: Uninstallable products with broken dependencies.
|
|
75
|
+
|
|
76
|
+
`installed`
|
|
77
|
+
: Only products that are installed and not hidden.
|
|
78
|
+
|
|
79
|
+
`non_installable`
|
|
80
|
+
: Non-installable products.
|
|
81
|
+
|
|
82
|
+
`upgradable`
|
|
83
|
+
: Only products with upgrades.
|
|
84
|
+
|
|
85
|
+
The following example demonstrates usage of the {func}`api.addon.get_addon_ids` function.
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
# Get IDs of installed add-ons
|
|
89
|
+
addon_ids = api.addon.get_addon_ids(limit="installed")
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Get add-on information
|
|
93
|
+
|
|
94
|
+
To get information about a specific add-on, use the {func}`api.addon.get` function, passing in the name of the add-on as a string.
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from plone import api
|
|
98
|
+
|
|
99
|
+
addon = api.addon.get("plone.session")
|
|
100
|
+
print(addon.id) # ID of the add-on
|
|
101
|
+
print(addon.version) # Version string
|
|
102
|
+
print(addon.title) # Display title
|
|
103
|
+
print(addon.description) # Description
|
|
104
|
+
print(addon.flags) # List of flags like ["installed", "upgradable"]
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Install and uninstall add-ons
|
|
108
|
+
|
|
109
|
+
To install an add-on, use the {func}`api.addon.install` function, passing in the name of the add-on as a string, as shown in the following example.
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from plone import api
|
|
113
|
+
|
|
114
|
+
success = api.addon.install("plone.session")
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
This function returns a `false` boolean value in the following cases.
|
|
118
|
+
- The installation fails due to an error.
|
|
119
|
+
- The add-on is already installed.
|
|
120
|
+
- The add-on is not found among the available add-ons.
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
To uninstall an add-on, use the {func}`api.addon.uninstall` function, passing in the name of the add-on as a string, as shown in the following example.
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from plone import api
|
|
128
|
+
|
|
129
|
+
success = api.addon.uninstall("plone.session")
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
This function returns a `false` boolean value in the following cases.
|
|
133
|
+
- The removal of add-on fails due to an error.
|
|
134
|
+
- The add-on is not installed.
|
|
135
|
+
|
|
136
|
+
## Get add-on version
|
|
137
|
+
|
|
138
|
+
To get the version of an add-on, use the {func}`api.addon.get_version` function, passing in the name of the add-on as a string, as shown in the following example.
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from plone import api
|
|
142
|
+
|
|
143
|
+
version = api.addon.get_version("plone.session")
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Note that this returns the version of the Python package installed from _PyPI_, not the version of the add-on's _GenericSetup_ profile.
|
|
147
|
+
|
|
148
|
+
(addons-exceptions)=
|
|
149
|
+
|
|
150
|
+
## Exceptions
|
|
151
|
+
|
|
152
|
+
The {exc}`~plone.api.exc.InvalidParameterError` exception may be raised in the following cases.
|
|
153
|
+
|
|
154
|
+
- When using the {func}`api.addon.get` function, trying to get information about a non-existent add-on.
|
|
155
|
+
- When using either function {func}`api.addon.get_addons` or {func}`api.addon.get_addon_ids`, using an invalid `limit` parameter value.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Tests for plone.api.addon methods."""
|
|
2
|
+
|
|
3
|
+
from plone import api
|
|
4
|
+
from plone.api.addon import AddonInformation
|
|
5
|
+
from plone.api.tests.base import INTEGRATION_TESTING
|
|
6
|
+
|
|
7
|
+
import unittest
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
ADDON = "plone.session"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestAPIAddonGetAddons(unittest.TestCase):
|
|
14
|
+
"""TestCase for plone.api.addon.get_addons."""
|
|
15
|
+
|
|
16
|
+
layer = INTEGRATION_TESTING
|
|
17
|
+
|
|
18
|
+
def setUp(self):
|
|
19
|
+
"""Set up TestCase."""
|
|
20
|
+
self.portal = self.layer["portal"]
|
|
21
|
+
# Install plone.app.multilingual
|
|
22
|
+
api.addon.install(ADDON)
|
|
23
|
+
|
|
24
|
+
def test_api_get_addons(self):
|
|
25
|
+
"""Test api.addon.get_addons without any filter."""
|
|
26
|
+
result = api.addon.get_addons()
|
|
27
|
+
self.assertIsInstance(result, list)
|
|
28
|
+
addon_ids = [addon.id for addon in result]
|
|
29
|
+
self.assertIn(ADDON, addon_ids)
|
|
30
|
+
|
|
31
|
+
def test_api_get_addons_limit_broken(self):
|
|
32
|
+
"""Test api.addon.get_addons filtering for broken add-ons."""
|
|
33
|
+
result = api.addon.get_addons(limit="broken")
|
|
34
|
+
self.assertEqual(len(result), 0)
|
|
35
|
+
|
|
36
|
+
def test_api_get_addons_limit_non_installable(self):
|
|
37
|
+
"""Test api.addon.get_addons filtering for non_installable add-ons."""
|
|
38
|
+
result = api.addon.get_addons(limit="non_installable")
|
|
39
|
+
self.assertNotEqual(len(result), 0)
|
|
40
|
+
addon_ids = [addon.id for addon in result]
|
|
41
|
+
self.assertIn("plone.app.dexterity", addon_ids)
|
|
42
|
+
|
|
43
|
+
def test_api_get_addons_limit_installed(self):
|
|
44
|
+
"""Test api.addon.get_addons filtering for installed add-ons."""
|
|
45
|
+
result = api.addon.get_addons(limit="installed")
|
|
46
|
+
self.assertEqual(len(result), 2)
|
|
47
|
+
addon_ids = [addon.id for addon in result]
|
|
48
|
+
self.assertIn(ADDON, addon_ids)
|
|
49
|
+
|
|
50
|
+
def test_api_get_addons_limit_upgradable(self):
|
|
51
|
+
"""Test api.addon.get_addons filtering for add-ons with upgradable."""
|
|
52
|
+
result = api.addon.get_addons(limit="upgradable")
|
|
53
|
+
self.assertEqual(len(result), 0)
|
|
54
|
+
|
|
55
|
+
def test_api_get_addons_limit_invalid(self):
|
|
56
|
+
"""Test api.addon.get_addons filtering with an invalid parameter."""
|
|
57
|
+
with self.assertRaises(api.exc.InvalidParameterError) as cm:
|
|
58
|
+
api.addon.get_addons(limit="foobar")
|
|
59
|
+
self.assertIn("Parameter limit='foobar' is not valid.", str(cm.exception))
|
|
60
|
+
|
|
61
|
+
def test_api_get_addon_ids(self):
|
|
62
|
+
"""Test api.addon.get_addon_ids."""
|
|
63
|
+
result = api.addon.get_addon_ids(limit="installed")
|
|
64
|
+
self.assertEqual(len(result), 2)
|
|
65
|
+
self.assertIn(ADDON, result)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class TestAPIAddon(unittest.TestCase):
|
|
69
|
+
"""TestCase for plone.api.addon."""
|
|
70
|
+
|
|
71
|
+
layer = INTEGRATION_TESTING
|
|
72
|
+
|
|
73
|
+
def setUp(self):
|
|
74
|
+
"""Set up TestCase."""
|
|
75
|
+
self.portal = self.layer["portal"]
|
|
76
|
+
|
|
77
|
+
def test_api_install(self):
|
|
78
|
+
"""Test api.addon.install."""
|
|
79
|
+
result = api.addon.install(ADDON)
|
|
80
|
+
self.assertTrue(result)
|
|
81
|
+
|
|
82
|
+
def test_api_uninstall(self):
|
|
83
|
+
"""Test api.addon.uninstall."""
|
|
84
|
+
# First install the add-on
|
|
85
|
+
api.addon.install(ADDON)
|
|
86
|
+
# Then uninstall the add-on
|
|
87
|
+
result = api.addon.uninstall(ADDON)
|
|
88
|
+
self.assertTrue(result)
|
|
89
|
+
|
|
90
|
+
def test_api_uninstall_unavailable(self):
|
|
91
|
+
"""Test api.addon.uninstall unavailable add-on."""
|
|
92
|
+
result = api.addon.uninstall("Foobar")
|
|
93
|
+
self.assertFalse(result)
|
|
94
|
+
|
|
95
|
+
def test_api_get(self):
|
|
96
|
+
"""Test api.addon.get."""
|
|
97
|
+
result = api.addon.get(ADDON)
|
|
98
|
+
self.assertIsInstance(result, AddonInformation)
|
|
99
|
+
self.assertEqual(result.id, ADDON)
|
|
100
|
+
self.assertTrue(result.valid)
|
|
101
|
+
self.assertEqual(result.description, "Optional plone.session refresh support.")
|
|
102
|
+
self.assertEqual(result.profile_type, "default")
|
|
103
|
+
self.assertIsInstance(result.version, str)
|
|
104
|
+
self.assertIsInstance(result.install_profile, dict)
|
|
105
|
+
self.assertIsInstance(result.uninstall_profile, dict)
|
|
106
|
+
self.assertIsInstance(result.upgrade_info, dict)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: plone.api
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: A Plone API.
|
|
5
5
|
Home-page: https://github.com/plone/plone.api
|
|
6
6
|
Author: Plone Foundation
|
|
@@ -127,6 +127,20 @@ Continuous Integration
|
|
|
127
127
|
|
|
128
128
|
<!-- towncrier release notes start -->
|
|
129
129
|
|
|
130
|
+
## 2.5.0 (2025-03-25)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
### New features:
|
|
134
|
+
|
|
135
|
+
- Implement `plone.api.addon` module. @ericof, @ujsquared, @stevepiercy #505
|
|
136
|
+
|
|
137
|
+
## 2.4.1 (2025-03-17)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
### Bug fixes:
|
|
141
|
+
|
|
142
|
+
- Attempt to generate a random temporary id for a content type up to 100 times, else continue to raise a `zExceptions.BadRequest` error. @rohnsha0 #445
|
|
143
|
+
|
|
130
144
|
## 2.4.0 (2025-03-14)
|
|
131
145
|
|
|
132
146
|
|
|
@@ -8,6 +8,7 @@ setup.cfg
|
|
|
8
8
|
setup.py
|
|
9
9
|
tox.ini
|
|
10
10
|
docs/about.md
|
|
11
|
+
docs/addon.md
|
|
11
12
|
docs/content.md
|
|
12
13
|
docs/contribute.md
|
|
13
14
|
docs/env.md
|
|
@@ -25,6 +26,7 @@ src/plone.api.egg-info/not-zip-safe
|
|
|
25
26
|
src/plone.api.egg-info/requires.txt
|
|
26
27
|
src/plone.api.egg-info/top_level.txt
|
|
27
28
|
src/plone/api/__init__.py
|
|
29
|
+
src/plone/api/addon.py
|
|
28
30
|
src/plone/api/configure.zcml
|
|
29
31
|
src/plone/api/content.py
|
|
30
32
|
src/plone/api/env.py
|
|
@@ -43,6 +45,7 @@ src/plone/api/tests/Dexterity_Folder.xml
|
|
|
43
45
|
src/plone/api/tests/Dexterity_Item.xml
|
|
44
46
|
src/plone/api/tests/__init__.py
|
|
45
47
|
src/plone/api/tests/base.py
|
|
48
|
+
src/plone/api/tests/test_addon.py
|
|
46
49
|
src/plone/api/tests/test_content.py
|
|
47
50
|
src/plone/api/tests/test_doctests.py
|
|
48
51
|
src/plone/api/tests/test_env.py
|
|
@@ -52,6 +55,7 @@ src/plone/api/tests/test_relation.py
|
|
|
52
55
|
src/plone/api/tests/test_user.py
|
|
53
56
|
src/plone/api/tests/test_validation.py
|
|
54
57
|
src/plone/api/tests/doctests/about.md
|
|
58
|
+
src/plone/api/tests/doctests/addon.md
|
|
55
59
|
src/plone/api/tests/doctests/content.md
|
|
56
60
|
src/plone/api/tests/doctests/contribute.md
|
|
57
61
|
src/plone/api/tests/doctests/env.md
|
|
@@ -107,7 +107,7 @@ commands =
|
|
|
107
107
|
description = run the distribution tests
|
|
108
108
|
use_develop = true
|
|
109
109
|
skip_install = false
|
|
110
|
-
constrain_package_deps =
|
|
110
|
+
constrain_package_deps = True
|
|
111
111
|
set_env =
|
|
112
112
|
ROBOT_BROWSER=headlesschrome
|
|
113
113
|
|
|
@@ -153,7 +153,7 @@ extras =
|
|
|
153
153
|
description = get a test coverage report
|
|
154
154
|
use_develop = true
|
|
155
155
|
skip_install = false
|
|
156
|
-
constrain_package_deps =
|
|
156
|
+
constrain_package_deps = True
|
|
157
157
|
set_env =
|
|
158
158
|
ROBOT_BROWSER=headlesschrome
|
|
159
159
|
|
|
@@ -201,7 +201,7 @@ use_develop = true
|
|
|
201
201
|
skip_install = false
|
|
202
202
|
# Here we must always constrain the package deps to what is already installed,
|
|
203
203
|
# otherwise we simply get the latest from PyPI, which may not work.
|
|
204
|
-
constrain_package_deps =
|
|
204
|
+
constrain_package_deps = True
|
|
205
205
|
set_env =
|
|
206
206
|
|
|
207
207
|
##
|
|
@@ -259,6 +259,7 @@ allowlist_externals =
|
|
|
259
259
|
# See [testenv:docs] for classic documentation
|
|
260
260
|
basepython = python3.11
|
|
261
261
|
skip_install = False
|
|
262
|
+
constrain_package_deps = True
|
|
262
263
|
package = editable
|
|
263
264
|
allowlist_externals =
|
|
264
265
|
mkdir
|
|
@@ -275,6 +276,7 @@ commands =
|
|
|
275
276
|
# Build docs on Read the Docs to preview pull requests using plone-sphinx-theme
|
|
276
277
|
basepython = python3.11
|
|
277
278
|
skip_install = False
|
|
279
|
+
constrain_package_deps = True
|
|
278
280
|
extras =
|
|
279
281
|
tests
|
|
280
282
|
deps =
|
|
@@ -306,6 +308,7 @@ whitelist_externals =
|
|
|
306
308
|
[testenv:linkcheck]
|
|
307
309
|
basepython = python3.11
|
|
308
310
|
skip_install = False
|
|
311
|
+
constrain_package_deps = True
|
|
309
312
|
package = editable
|
|
310
313
|
allowlist_externals =
|
|
311
314
|
mkdir
|
|
@@ -321,7 +324,7 @@ commands =
|
|
|
321
324
|
[testenv:livehtml]
|
|
322
325
|
basepython = python3.11
|
|
323
326
|
skip_install = False
|
|
324
|
-
constrain_package_deps =
|
|
327
|
+
constrain_package_deps = True
|
|
325
328
|
package = editable
|
|
326
329
|
allowlist_externals =
|
|
327
330
|
mkdir
|
|
@@ -329,7 +332,6 @@ extras =
|
|
|
329
332
|
{[testenv:plone6docs]extras}
|
|
330
333
|
deps =
|
|
331
334
|
{[testenv:plone6docs]deps}
|
|
332
|
-
-c https://dist.plone.org/release/6.0-dev/constraints.txt
|
|
333
335
|
commands =
|
|
334
336
|
python -VV
|
|
335
337
|
mkdir -p {toxinidir}/_build/plone6docs
|
|
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
|
{plone_api-2.4.0 → plone_api-2.5.0}/src/plone/api/profiles/testfixture/types/Dexterity_Folder.xml
RENAMED
|
File without changes
|
{plone_api-2.4.0 → plone_api-2.5.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|