plone.api 2.5.3__tar.gz → 3.0.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.5.3 → plone_api-3.0.0}/CHANGES.md +48 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/PKG-INFO +57 -7
- {plone_api-2.5.3 → plone_api-3.0.0}/docs/portal.md +103 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/pyproject.toml +12 -11
- {plone_api-2.5.3 → plone_api-3.0.0}/setup.py +7 -11
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/addon.py +14 -18
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/content.py +0 -1
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/env.py +0 -1
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/portal.py +0 -1
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/relation.py +0 -1
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/tests/doctests/portal.md +103 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/tests/test_addon.py +0 -1
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/tests/test_content.py +6 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/tests/test_doctests.py +0 -1
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/tests/test_portal.py +0 -1
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/tests/test_user.py +3 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/user.py +24 -4
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone.api.egg-info/PKG-INFO +57 -7
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone.api.egg-info/SOURCES.txt +0 -2
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone.api.egg-info/requires.txt +1 -1
- {plone_api-2.5.3 → plone_api-3.0.0}/tox.ini +85 -108
- plone_api-2.5.3/src/plone/__init__.py +0 -1
- plone_api-2.5.3/src/plone.api.egg-info/namespace_packages.txt +0 -1
- {plone_api-2.5.3 → plone_api-3.0.0}/CONTRIBUTING.md +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/LICENSE +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/MANIFEST.in +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/README.md +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/docs/about.md +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/docs/addon.md +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/docs/content.md +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/docs/contribute.md +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/docs/env.md +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/docs/group.md +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/docs/index.md +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/docs/relation.md +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/docs/user.md +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/setup.cfg +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/__init__.py +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/configure.zcml +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/exc.py +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/group.py +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/profiles/testfixture/metadata.xml +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/profiles/testfixture/types/Dexterity_Folder.xml +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/profiles/testfixture/types/Dexterity_Item.xml +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/profiles/testfixture/types.xml +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/testing.zcml +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/tests/Dexterity_Folder.xml +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/tests/Dexterity_Item.xml +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/tests/__init__.py +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/tests/base.py +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/tests/doctests/about.md +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/tests/doctests/addon.md +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/tests/doctests/content.md +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/tests/doctests/contribute.md +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/tests/doctests/env.md +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/tests/doctests/group.md +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/tests/doctests/relation.md +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/tests/doctests/user.md +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/tests/test_env.py +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/tests/test_group.py +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/tests/test_relation.py +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/tests/test_validation.py +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone/api/validation.py +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone.api.egg-info/dependency_links.txt +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone.api.egg-info/not-zip-safe +0 -0
- {plone_api-2.5.3 → plone_api-3.0.0}/src/plone.api.egg-info/top_level.txt +0 -0
|
@@ -9,6 +9,54 @@
|
|
|
9
9
|
|
|
10
10
|
<!-- towncrier release notes start -->
|
|
11
11
|
|
|
12
|
+
## 3.0.0 (2026-05-07)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Documentation
|
|
16
|
+
|
|
17
|
+
- Added example to construct a complex email. @1letter
|
|
18
|
+
|
|
19
|
+
## 3.0.0a3 (2026-03-25)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Bug fixes
|
|
23
|
+
|
|
24
|
+
- Fix ``plone.api.user.create()`` to respect ``use_uuid_as_userid`` and
|
|
25
|
+
``use_email_as_login`` registry settings, as well as custom ``IUserIdGenerator``
|
|
26
|
+
and ``ILoginNameGenerator`` utilities. #4292
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Internal
|
|
30
|
+
|
|
31
|
+
- Update configuration files @plone
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
### Tests
|
|
35
|
+
|
|
36
|
+
- Fix `test_create_raises_unicodedecodeerror` for deferred indexing queue processing since Products.CMFCore 3.9.
|
|
37
|
+
@jensens #602
|
|
38
|
+
|
|
39
|
+
## 3.0.0a2 (2025-12-18)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
### Bug fixes:
|
|
43
|
+
|
|
44
|
+
- Fix deprecation warnings for `INonInstallable` and `get_installer`. @mauritsvanrees
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
### Internal:
|
|
48
|
+
|
|
49
|
+
- Improve trigger for RTD PR preview builds. @stevepiercy #591
|
|
50
|
+
- Ignore the `/_build` directory from building documentation. @stevepiercy #598
|
|
51
|
+
|
|
52
|
+
## 3.0.0a1 (2025-11-26)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
### Breaking changes:
|
|
56
|
+
|
|
57
|
+
- Replace ``pkg_resources`` namespace with PEP 420 native namespace.
|
|
58
|
+
Support only Plone 6.2 and Python 3.10+. #3928
|
|
59
|
+
|
|
12
60
|
## 2.5.3 (2025-09-10)
|
|
13
61
|
|
|
14
62
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: plone.api
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0
|
|
4
4
|
Summary: A Plone API.
|
|
5
5
|
Home-page: https://github.com/plone/plone.api
|
|
6
6
|
Author: Plone Foundation
|
|
@@ -19,17 +19,18 @@ Platform: Any
|
|
|
19
19
|
Classifier: Development Status :: 5 - Production/Stable
|
|
20
20
|
Classifier: Environment :: Web Environment
|
|
21
21
|
Classifier: Framework :: Plone
|
|
22
|
-
Classifier: Framework :: Plone :: 6.
|
|
22
|
+
Classifier: Framework :: Plone :: 6.2
|
|
23
23
|
Classifier: Framework :: Plone :: Core
|
|
24
24
|
Classifier: Intended Audience :: Developers
|
|
25
25
|
Classifier: License :: OSI Approved :: GNU General Public License v2 (GPLv2)
|
|
26
26
|
Classifier: Operating System :: OS Independent
|
|
27
27
|
Classifier: Programming Language :: Python
|
|
28
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
29
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
30
28
|
Classifier: Programming Language :: Python :: 3.10
|
|
31
29
|
Classifier: Programming Language :: Python :: 3.11
|
|
32
|
-
|
|
30
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
31
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
32
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
33
|
+
Requires-Python: >=3.10
|
|
33
34
|
Description-Content-Type: text/markdown
|
|
34
35
|
License-File: LICENSE
|
|
35
36
|
Requires-Dist: Acquisition
|
|
@@ -37,6 +38,7 @@ Requires-Dist: Products.statusmessages
|
|
|
37
38
|
Requires-Dist: Products.PlonePAS
|
|
38
39
|
Requires-Dist: Products.CMFPlone
|
|
39
40
|
Requires-Dist: decorator
|
|
41
|
+
Requires-Dist: plone.app.users
|
|
40
42
|
Requires-Dist: plone.app.uuid
|
|
41
43
|
Requires-Dist: plone.app.dexterity
|
|
42
44
|
Requires-Dist: plone.app.intid
|
|
@@ -46,7 +48,6 @@ Requires-Dist: plone.dexterity
|
|
|
46
48
|
Requires-Dist: plone.i18n
|
|
47
49
|
Requires-Dist: plone.registry
|
|
48
50
|
Requires-Dist: plone.uuid
|
|
49
|
-
Requires-Dist: setuptools
|
|
50
51
|
Requires-Dist: zope.globalrequest
|
|
51
52
|
Requires-Dist: Products.CMFCore
|
|
52
53
|
Requires-Dist: z3c.relationfield
|
|
@@ -71,6 +72,7 @@ Dynamic: description-content-type
|
|
|
71
72
|
Dynamic: home-page
|
|
72
73
|
Dynamic: keywords
|
|
73
74
|
Dynamic: license
|
|
75
|
+
Dynamic: license-file
|
|
74
76
|
Dynamic: platform
|
|
75
77
|
Dynamic: project-url
|
|
76
78
|
Dynamic: provides-extra
|
|
@@ -132,6 +134,54 @@ Continuous Integration
|
|
|
132
134
|
|
|
133
135
|
<!-- towncrier release notes start -->
|
|
134
136
|
|
|
137
|
+
## 3.0.0 (2026-05-07)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
### Documentation
|
|
141
|
+
|
|
142
|
+
- Added example to construct a complex email. @1letter
|
|
143
|
+
|
|
144
|
+
## 3.0.0a3 (2026-03-25)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
### Bug fixes
|
|
148
|
+
|
|
149
|
+
- Fix ``plone.api.user.create()`` to respect ``use_uuid_as_userid`` and
|
|
150
|
+
``use_email_as_login`` registry settings, as well as custom ``IUserIdGenerator``
|
|
151
|
+
and ``ILoginNameGenerator`` utilities. #4292
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
### Internal
|
|
155
|
+
|
|
156
|
+
- Update configuration files @plone
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
### Tests
|
|
160
|
+
|
|
161
|
+
- Fix `test_create_raises_unicodedecodeerror` for deferred indexing queue processing since Products.CMFCore 3.9.
|
|
162
|
+
@jensens #602
|
|
163
|
+
|
|
164
|
+
## 3.0.0a2 (2025-12-18)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
### Bug fixes:
|
|
168
|
+
|
|
169
|
+
- Fix deprecation warnings for `INonInstallable` and `get_installer`. @mauritsvanrees
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
### Internal:
|
|
173
|
+
|
|
174
|
+
- Improve trigger for RTD PR preview builds. @stevepiercy #591
|
|
175
|
+
- Ignore the `/_build` directory from building documentation. @stevepiercy #598
|
|
176
|
+
|
|
177
|
+
## 3.0.0a1 (2025-11-26)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
### Breaking changes:
|
|
181
|
+
|
|
182
|
+
- Replace ``pkg_resources`` namespace with PEP 420 native namespace.
|
|
183
|
+
Support only Plone 6.2 and Python 3.10+. #3928
|
|
184
|
+
|
|
135
185
|
## 2.5.3 (2025-09-10)
|
|
136
186
|
|
|
137
187
|
|
|
@@ -257,6 +257,109 @@ api.portal.send_email(
|
|
|
257
257
|
% 'attachment; filename="report.xml',
|
|
258
258
|
% payloads[1]['Content-Disposition']
|
|
259
259
|
% )
|
|
260
|
+
% mailhost.messages.clear()
|
|
261
|
+
|
|
262
|
+
The following code is a more complex example that constructs an email with a file attachment, HTML, plain text, and mail headers to control the mail response.
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
from email.encoders import encode_base64
|
|
266
|
+
from email.header import Header
|
|
267
|
+
from email.mime.base import MIMEBase
|
|
268
|
+
from email.mime.multipart import MIMEMultipart
|
|
269
|
+
from email.mime.text import MIMEText
|
|
270
|
+
from plone import api
|
|
271
|
+
|
|
272
|
+
# we need a message part to bundle the HTML and Plain Text
|
|
273
|
+
textmsgpart = MIMEMultipart("alternative")
|
|
274
|
+
|
|
275
|
+
# create plain text part of email
|
|
276
|
+
plaintextpart = MIMEText("Fill out your plain text", "plain", "utf-8")
|
|
277
|
+
|
|
278
|
+
# create html text of email
|
|
279
|
+
html = "<html><body><p>fill out your text in HTML</p></body></html>"
|
|
280
|
+
htmlpart = MIMEText(html, "html", "utf-8")
|
|
281
|
+
|
|
282
|
+
# bundle the parts
|
|
283
|
+
textmsgpart.attach(plaintextpart)
|
|
284
|
+
textmsgpart.attach(htmlpart)
|
|
285
|
+
|
|
286
|
+
# handle the file attachment
|
|
287
|
+
|
|
288
|
+
# Create a dummy PDF as NamedBlobFile
|
|
289
|
+
from plone.namedfile.file import NamedBlobFile
|
|
290
|
+
|
|
291
|
+
# Minimal PDF content (a valid but nearly empty PDF)
|
|
292
|
+
pdf_content = b"""%PDF-1.4
|
|
293
|
+
1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj
|
|
294
|
+
2 0 obj<</Type/Pages/Count 1/Kids[3 0 R]>>endobj
|
|
295
|
+
3 0 obj<</Type/Page/Parent 2 0 R/MediaBox[0 0 612 792]/Contents 4 0 R>>endobj
|
|
296
|
+
4 0 obj<</Length 44>>stream
|
|
297
|
+
BT /F1 12 Tf 100 700 Td (Hello World) Tj ET
|
|
298
|
+
endstream endobj
|
|
299
|
+
xref
|
|
300
|
+
0 5
|
|
301
|
+
0000000000 65535 f
|
|
302
|
+
0000000009 00000 n
|
|
303
|
+
0000000056 00000 n
|
|
304
|
+
0000000115 00000 n
|
|
305
|
+
0000000214 00000 n
|
|
306
|
+
trailer<</Size 5/Root 1 0 R>>
|
|
307
|
+
startxref
|
|
308
|
+
315
|
|
309
|
+
%%EOF"""
|
|
310
|
+
|
|
311
|
+
attachment = NamedBlobFile(
|
|
312
|
+
data=pdf_content,
|
|
313
|
+
contentType='application/pdf',
|
|
314
|
+
filename='document.pdf'
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
filepart = MIMEBase("application", "pdf")
|
|
318
|
+
filepart.set_payload(attachment.data)
|
|
319
|
+
encode_base64(filepart)
|
|
320
|
+
filepart.add_header(
|
|
321
|
+
"Content-Disposition",
|
|
322
|
+
"attachment",
|
|
323
|
+
filename=(Header(attachment.filename, "utf-8").encode()),
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
# we need a mixed Multipart Message to bundle the text bundle and the attachment
|
|
327
|
+
msg = MIMEMultipart("mixed")
|
|
328
|
+
msg["From"] = "sender@abc"
|
|
329
|
+
msg["To"] = "recipient@zzz"
|
|
330
|
+
msg["Subject"] = "The Mail Subject"
|
|
331
|
+
msg["Reply-To"] = "community@plone.org"
|
|
332
|
+
msg["Return-Path"] = "error@xxx"
|
|
333
|
+
|
|
334
|
+
# add the text message bundle
|
|
335
|
+
msg.attach(textmsgpart)
|
|
336
|
+
|
|
337
|
+
# add the file attachment
|
|
338
|
+
msg.attach(filepart)
|
|
339
|
+
|
|
340
|
+
# send with plone.api
|
|
341
|
+
api.portal.send_email(
|
|
342
|
+
sender=msg["From"],
|
|
343
|
+
recipient=msg["To"],
|
|
344
|
+
subject=msg["Subject"],
|
|
345
|
+
body=msg.as_string()
|
|
346
|
+
)
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
% invisible-code-block: python
|
|
350
|
+
%
|
|
351
|
+
% self.assertEqual(len(mailhost.messages), 1)
|
|
352
|
+
%
|
|
353
|
+
% msg = message_from_bytes(mailhost.messages[0])
|
|
354
|
+
% payloads = msg.get_payload()
|
|
355
|
+
% self.assertTrue(len(payloads) == 2)
|
|
356
|
+
% self.assertTrue(msg['Reply-To'] == 'community@plone.org')
|
|
357
|
+
% text_payloads = payloads[0].get_payload()
|
|
358
|
+
% self.assertEqual(len(text_payloads), 2)
|
|
359
|
+
% self.assertIn(
|
|
360
|
+
% 'attachment; filename',
|
|
361
|
+
% payloads[1]['Content-Disposition']
|
|
362
|
+
% )
|
|
260
363
|
% api.portal.PRINTINGMAILHOST_ENABLED = False
|
|
261
364
|
% mailhost.reset()
|
|
262
365
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Generated from:
|
|
2
|
-
# https://github.com/plone/meta/tree/main/
|
|
2
|
+
# https://github.com/plone/meta/tree/main/src/plone/meta/default
|
|
3
3
|
# See the inline comments on how to expand/tweak this configuration file
|
|
4
4
|
[build-system]
|
|
5
|
-
requires = ["setuptools>=68.2,<
|
|
5
|
+
requires = ["setuptools>=68.2,<83", "wheel"]
|
|
6
6
|
|
|
7
7
|
[tool.towncrier]
|
|
8
8
|
directory = "news/"
|
|
@@ -14,27 +14,27 @@ underlines = ["", "", ""]
|
|
|
14
14
|
|
|
15
15
|
[[tool.towncrier.type]]
|
|
16
16
|
directory = "breaking"
|
|
17
|
-
name = "Breaking changes
|
|
17
|
+
name = "Breaking changes"
|
|
18
18
|
showcontent = true
|
|
19
19
|
|
|
20
20
|
[[tool.towncrier.type]]
|
|
21
21
|
directory = "feature"
|
|
22
|
-
name = "New features
|
|
22
|
+
name = "New features"
|
|
23
23
|
showcontent = true
|
|
24
24
|
|
|
25
25
|
[[tool.towncrier.type]]
|
|
26
26
|
directory = "bugfix"
|
|
27
|
-
name = "Bug fixes
|
|
27
|
+
name = "Bug fixes"
|
|
28
28
|
showcontent = true
|
|
29
29
|
|
|
30
30
|
[[tool.towncrier.type]]
|
|
31
31
|
directory = "internal"
|
|
32
|
-
name = "Internal
|
|
32
|
+
name = "Internal"
|
|
33
33
|
showcontent = true
|
|
34
34
|
|
|
35
35
|
[[tool.towncrier.type]]
|
|
36
36
|
directory = "documentation"
|
|
37
|
-
name = "Documentation
|
|
37
|
+
name = "Documentation"
|
|
38
38
|
showcontent = true
|
|
39
39
|
|
|
40
40
|
[[tool.towncrier.type]]
|
|
@@ -62,7 +62,7 @@ profile = "plone"
|
|
|
62
62
|
##
|
|
63
63
|
|
|
64
64
|
[tool.black]
|
|
65
|
-
target-version = ["
|
|
65
|
+
target-version = ["py310"]
|
|
66
66
|
|
|
67
67
|
##
|
|
68
68
|
# Add extra configuration options in .meta.toml:
|
|
@@ -73,7 +73,7 @@ target-version = ["py38"]
|
|
|
73
73
|
##
|
|
74
74
|
|
|
75
75
|
[tool.codespell]
|
|
76
|
-
ignore-words-list = "discreet,manuel"
|
|
76
|
+
ignore-words-list = "discreet,assertin,thet,manuel,checkin"
|
|
77
77
|
skip = "*.po,"
|
|
78
78
|
##
|
|
79
79
|
# Add extra configuration options in .meta.toml:
|
|
@@ -121,6 +121,7 @@ Zope = [
|
|
|
121
121
|
'Products.CMFCore', 'Products.CMFDynamicViewFTI',
|
|
122
122
|
]
|
|
123
123
|
python-dateutil = ['dateutil']
|
|
124
|
+
pytest-plone = ['pytest', 'zope.pytestlayer', 'plone.testing', 'plone.app.testing']
|
|
124
125
|
ignore-packages = ['Products.PrintingMailHost', 'plone.app.iterate',]
|
|
125
126
|
|
|
126
127
|
##
|
|
@@ -142,11 +143,11 @@ ignore = [
|
|
|
142
143
|
"dependabot.yml",
|
|
143
144
|
"mx.ini",
|
|
144
145
|
"tox.ini",
|
|
146
|
+
# From `.meta.toml`, pyproject.check_manifest_ignores.
|
|
145
147
|
"*.cfg",
|
|
146
148
|
".editorconfig",
|
|
147
149
|
".readthedocs.yaml",
|
|
148
|
-
"
|
|
149
|
-
"constraints_plone60.txt",
|
|
150
|
+
"constraints_plone62.txt",
|
|
150
151
|
"constraints.txt",
|
|
151
152
|
"fix-converted-myst.py",
|
|
152
153
|
"Makefile",
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
-
from setuptools import find_packages
|
|
3
2
|
from setuptools import setup
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
version = "2.5.3"
|
|
4
|
+
version = "3.0.0"
|
|
7
5
|
|
|
8
6
|
long_description = "\n".join(
|
|
9
7
|
[Path("README.md").read_text(), Path("CHANGES.md").read_text()]
|
|
@@ -18,20 +16,18 @@ setup(
|
|
|
18
16
|
author="Plone Foundation",
|
|
19
17
|
author_email="plone-developers@lists.sourceforge.net",
|
|
20
18
|
license="GPL version 2",
|
|
21
|
-
packages=find_packages("src"),
|
|
22
|
-
package_dir={"": "src"},
|
|
23
|
-
namespace_packages=["plone"],
|
|
24
19
|
include_package_data=True,
|
|
25
20
|
zip_safe=False,
|
|
26
21
|
url="https://github.com/plone/plone.api",
|
|
27
22
|
keywords="plone api",
|
|
28
|
-
python_requires=">=3.
|
|
23
|
+
python_requires=">=3.10",
|
|
29
24
|
install_requires=[
|
|
30
25
|
"Acquisition",
|
|
31
26
|
"Products.statusmessages",
|
|
32
27
|
"Products.PlonePAS",
|
|
33
28
|
"Products.CMFPlone",
|
|
34
29
|
"decorator",
|
|
30
|
+
"plone.app.users",
|
|
35
31
|
"plone.app.uuid",
|
|
36
32
|
"plone.app.dexterity",
|
|
37
33
|
"plone.app.intid",
|
|
@@ -41,7 +37,6 @@ setup(
|
|
|
41
37
|
"plone.i18n",
|
|
42
38
|
"plone.registry",
|
|
43
39
|
"plone.uuid",
|
|
44
|
-
"setuptools",
|
|
45
40
|
"zope.globalrequest",
|
|
46
41
|
"Products.CMFCore",
|
|
47
42
|
"z3c.relationfield",
|
|
@@ -68,16 +63,17 @@ setup(
|
|
|
68
63
|
"Development Status :: 5 - Production/Stable",
|
|
69
64
|
"Environment :: Web Environment",
|
|
70
65
|
"Framework :: Plone",
|
|
71
|
-
"Framework :: Plone :: 6.
|
|
66
|
+
"Framework :: Plone :: 6.2",
|
|
72
67
|
"Framework :: Plone :: Core",
|
|
73
68
|
"Intended Audience :: Developers",
|
|
74
69
|
"License :: OSI Approved :: GNU General Public License v2 (GPLv2)",
|
|
75
70
|
"Operating System :: OS Independent",
|
|
76
71
|
"Programming Language :: Python",
|
|
77
|
-
"Programming Language :: Python :: 3.8",
|
|
78
|
-
"Programming Language :: Python :: 3.9",
|
|
79
72
|
"Programming Language :: Python :: 3.10",
|
|
80
73
|
"Programming Language :: Python :: 3.11",
|
|
74
|
+
"Programming Language :: Python :: 3.12",
|
|
75
|
+
"Programming Language :: Python :: 3.13",
|
|
76
|
+
"Programming Language :: Python :: 3.14",
|
|
81
77
|
],
|
|
82
78
|
platforms="Any",
|
|
83
79
|
project_urls={
|
|
@@ -7,19 +7,15 @@ from importlib.metadata import PackageNotFoundError
|
|
|
7
7
|
from plone.api import portal
|
|
8
8
|
from plone.api.exc import InvalidParameterError
|
|
9
9
|
from plone.api.validation import required_parameters
|
|
10
|
+
from plone.base.interfaces import INonInstallable
|
|
11
|
+
from plone.base.utils import get_installer
|
|
10
12
|
from Products.CMFPlone.controlpanel.browser.quickinstaller import InstallerView
|
|
11
|
-
from Products.CMFPlone.interfaces import INonInstallable
|
|
12
|
-
from Products.CMFPlone.utils import get_installer
|
|
13
13
|
from Products.GenericSetup import EXTENSION
|
|
14
|
-
from typing import Dict
|
|
15
|
-
from typing import List
|
|
16
|
-
from typing import Tuple
|
|
17
14
|
from zope.component import getAllUtilitiesRegisteredFor
|
|
18
15
|
from zope.globalrequest import getRequest
|
|
19
16
|
|
|
20
17
|
import logging
|
|
21
18
|
|
|
22
|
-
|
|
23
19
|
logger = logging.getLogger("plone.api.addon")
|
|
24
20
|
|
|
25
21
|
|
|
@@ -39,8 +35,8 @@ __all__ = [
|
|
|
39
35
|
class NonInstallableAddons:
|
|
40
36
|
"""Set of add-ons not available for installation."""
|
|
41
37
|
|
|
42
|
-
profiles:
|
|
43
|
-
products:
|
|
38
|
+
profiles: list[str]
|
|
39
|
+
products: list[str]
|
|
44
40
|
|
|
45
41
|
|
|
46
42
|
@dataclass
|
|
@@ -52,14 +48,14 @@ class AddonInformation:
|
|
|
52
48
|
title: str
|
|
53
49
|
description: str
|
|
54
50
|
|
|
55
|
-
upgrade_profiles:
|
|
56
|
-
other_profiles:
|
|
57
|
-
install_profile:
|
|
58
|
-
uninstall_profile:
|
|
51
|
+
upgrade_profiles: dict
|
|
52
|
+
other_profiles: list[list]
|
|
53
|
+
install_profile: dict
|
|
54
|
+
uninstall_profile: dict
|
|
59
55
|
profile_type: str
|
|
60
|
-
upgrade_info:
|
|
56
|
+
upgrade_info: dict
|
|
61
57
|
valid: bool
|
|
62
|
-
flags:
|
|
58
|
+
flags: list[str]
|
|
63
59
|
|
|
64
60
|
def __repr__(self) -> str:
|
|
65
61
|
"""Return a string representation of this object."""
|
|
@@ -98,7 +94,7 @@ def _get_non_installable_addons() -> NonInstallableAddons:
|
|
|
98
94
|
|
|
99
95
|
|
|
100
96
|
@lru_cache(maxsize=1)
|
|
101
|
-
def _cached_addons() ->
|
|
97
|
+
def _cached_addons() -> tuple[tuple[str, AddonInformation]]:
|
|
102
98
|
"""Return information about add-ons in this installation.
|
|
103
99
|
|
|
104
100
|
:returns: Tuple of tuples with add-on id and AddonInformation.
|
|
@@ -198,7 +194,7 @@ def _update_addon_info(
|
|
|
198
194
|
return addon
|
|
199
195
|
|
|
200
196
|
|
|
201
|
-
def _get_addons() ->
|
|
197
|
+
def _get_addons() -> list[AddonInformation]:
|
|
202
198
|
"""Return an updated list of add-on information.
|
|
203
199
|
|
|
204
200
|
:returns: List of AddonInformation.
|
|
@@ -212,7 +208,7 @@ def _get_addons() -> List[AddonInformation]:
|
|
|
212
208
|
return result
|
|
213
209
|
|
|
214
210
|
|
|
215
|
-
def get_addons(limit: str = "") ->
|
|
211
|
+
def get_addons(limit: str = "") -> list[AddonInformation]:
|
|
216
212
|
"""List add-ons in this Plone site.
|
|
217
213
|
|
|
218
214
|
:param limit: Limit list of add-ons.
|
|
@@ -239,7 +235,7 @@ def get_addons(limit: str = "") -> List[AddonInformation]:
|
|
|
239
235
|
return addons
|
|
240
236
|
|
|
241
237
|
|
|
242
|
-
def get_addon_ids(limit: str = "") ->
|
|
238
|
+
def get_addon_ids(limit: str = "") -> list[str]:
|
|
243
239
|
"""List add-ons ids in this Plone site.
|
|
244
240
|
|
|
245
241
|
:param limit: Limit list of add-ons.
|
|
@@ -257,6 +257,109 @@ api.portal.send_email(
|
|
|
257
257
|
% 'attachment; filename="report.xml',
|
|
258
258
|
% payloads[1]['Content-Disposition']
|
|
259
259
|
% )
|
|
260
|
+
% mailhost.messages.clear()
|
|
261
|
+
|
|
262
|
+
The following code is a more complex example that constructs an email with a file attachment, HTML, plain text, and mail headers to control the mail response.
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
from email.encoders import encode_base64
|
|
266
|
+
from email.header import Header
|
|
267
|
+
from email.mime.base import MIMEBase
|
|
268
|
+
from email.mime.multipart import MIMEMultipart
|
|
269
|
+
from email.mime.text import MIMEText
|
|
270
|
+
from plone import api
|
|
271
|
+
|
|
272
|
+
# we need a message part to bundle the HTML and Plain Text
|
|
273
|
+
textmsgpart = MIMEMultipart("alternative")
|
|
274
|
+
|
|
275
|
+
# create plain text part of email
|
|
276
|
+
plaintextpart = MIMEText("Fill out your plain text", "plain", "utf-8")
|
|
277
|
+
|
|
278
|
+
# create html text of email
|
|
279
|
+
html = "<html><body><p>fill out your text in HTML</p></body></html>"
|
|
280
|
+
htmlpart = MIMEText(html, "html", "utf-8")
|
|
281
|
+
|
|
282
|
+
# bundle the parts
|
|
283
|
+
textmsgpart.attach(plaintextpart)
|
|
284
|
+
textmsgpart.attach(htmlpart)
|
|
285
|
+
|
|
286
|
+
# handle the file attachment
|
|
287
|
+
|
|
288
|
+
# Create a dummy PDF as NamedBlobFile
|
|
289
|
+
from plone.namedfile.file import NamedBlobFile
|
|
290
|
+
|
|
291
|
+
# Minimal PDF content (a valid but nearly empty PDF)
|
|
292
|
+
pdf_content = b"""%PDF-1.4
|
|
293
|
+
1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj
|
|
294
|
+
2 0 obj<</Type/Pages/Count 1/Kids[3 0 R]>>endobj
|
|
295
|
+
3 0 obj<</Type/Page/Parent 2 0 R/MediaBox[0 0 612 792]/Contents 4 0 R>>endobj
|
|
296
|
+
4 0 obj<</Length 44>>stream
|
|
297
|
+
BT /F1 12 Tf 100 700 Td (Hello World) Tj ET
|
|
298
|
+
endstream endobj
|
|
299
|
+
xref
|
|
300
|
+
0 5
|
|
301
|
+
0000000000 65535 f
|
|
302
|
+
0000000009 00000 n
|
|
303
|
+
0000000056 00000 n
|
|
304
|
+
0000000115 00000 n
|
|
305
|
+
0000000214 00000 n
|
|
306
|
+
trailer<</Size 5/Root 1 0 R>>
|
|
307
|
+
startxref
|
|
308
|
+
315
|
|
309
|
+
%%EOF"""
|
|
310
|
+
|
|
311
|
+
attachment = NamedBlobFile(
|
|
312
|
+
data=pdf_content,
|
|
313
|
+
contentType='application/pdf',
|
|
314
|
+
filename='document.pdf'
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
filepart = MIMEBase("application", "pdf")
|
|
318
|
+
filepart.set_payload(attachment.data)
|
|
319
|
+
encode_base64(filepart)
|
|
320
|
+
filepart.add_header(
|
|
321
|
+
"Content-Disposition",
|
|
322
|
+
"attachment",
|
|
323
|
+
filename=(Header(attachment.filename, "utf-8").encode()),
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
# we need a mixed Multipart Message to bundle the text bundle and the attachment
|
|
327
|
+
msg = MIMEMultipart("mixed")
|
|
328
|
+
msg["From"] = "sender@abc"
|
|
329
|
+
msg["To"] = "recipient@zzz"
|
|
330
|
+
msg["Subject"] = "The Mail Subject"
|
|
331
|
+
msg["Reply-To"] = "community@plone.org"
|
|
332
|
+
msg["Return-Path"] = "error@xxx"
|
|
333
|
+
|
|
334
|
+
# add the text message bundle
|
|
335
|
+
msg.attach(textmsgpart)
|
|
336
|
+
|
|
337
|
+
# add the file attachment
|
|
338
|
+
msg.attach(filepart)
|
|
339
|
+
|
|
340
|
+
# send with plone.api
|
|
341
|
+
api.portal.send_email(
|
|
342
|
+
sender=msg["From"],
|
|
343
|
+
recipient=msg["To"],
|
|
344
|
+
subject=msg["Subject"],
|
|
345
|
+
body=msg.as_string()
|
|
346
|
+
)
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
% invisible-code-block: python
|
|
350
|
+
%
|
|
351
|
+
% self.assertEqual(len(mailhost.messages), 1)
|
|
352
|
+
%
|
|
353
|
+
% msg = message_from_bytes(mailhost.messages[0])
|
|
354
|
+
% payloads = msg.get_payload()
|
|
355
|
+
% self.assertTrue(len(payloads) == 2)
|
|
356
|
+
% self.assertTrue(msg['Reply-To'] == 'community@plone.org')
|
|
357
|
+
% text_payloads = payloads[0].get_payload()
|
|
358
|
+
% self.assertEqual(len(text_payloads), 2)
|
|
359
|
+
% self.assertIn(
|
|
360
|
+
% 'attachment; filename',
|
|
361
|
+
% payloads[1]['Content-Disposition']
|
|
362
|
+
% )
|
|
260
363
|
% api.portal.PRINTINGMAILHOST_ENABLED = False
|
|
261
364
|
% mailhost.reset()
|
|
262
365
|
|
|
@@ -15,6 +15,7 @@ from plone.base.interfaces import INavigationRoot
|
|
|
15
15
|
from plone.indexer import indexer
|
|
16
16
|
from plone.uuid.interfaces import IMutableUUID
|
|
17
17
|
from plone.uuid.interfaces import IUUIDGenerator
|
|
18
|
+
from Products.CMFCore.indexing import processQueue
|
|
18
19
|
from Products.CMFCore.interfaces import IFolderish
|
|
19
20
|
from Products.CMFCore.WorkflowCore import WorkflowException
|
|
20
21
|
from Products.ZCatalog.interfaces import IZCatalog
|
|
@@ -357,6 +358,11 @@ class TestPloneApiContent(unittest.TestCase):
|
|
|
357
358
|
id="test-unicode-folder",
|
|
358
359
|
container=self.portal,
|
|
359
360
|
)
|
|
361
|
+
# The indexing queue may defer catalog operations until
|
|
362
|
+
# processQueue() is called (e.g. by a catalog search or
|
|
363
|
+
# at transaction commit time). Flush explicitly so the
|
|
364
|
+
# broken indexer is invoked inside the assertRaises block.
|
|
365
|
+
processQueue()
|
|
360
366
|
|
|
361
367
|
# check that the exception is the one we raised
|
|
362
368
|
self.assertEqual(ude.exception.reason, unicode_exception_message)
|