plone.api 2.5.2__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.2 → plone_api-3.0.0}/CHANGES.md +55 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/PKG-INFO +64 -7
- {plone_api-2.5.2 → plone_api-3.0.0}/docs/portal.md +103 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/pyproject.toml +12 -11
- {plone_api-2.5.2 → plone_api-3.0.0}/setup.py +7 -11
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/addon.py +18 -21
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/content.py +0 -1
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/env.py +0 -1
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/portal.py +0 -1
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/relation.py +0 -1
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/tests/doctests/portal.md +103 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/tests/test_addon.py +0 -1
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/tests/test_content.py +6 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/tests/test_doctests.py +0 -1
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/tests/test_portal.py +0 -1
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/tests/test_user.py +3 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/user.py +24 -4
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone.api.egg-info/PKG-INFO +64 -7
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone.api.egg-info/SOURCES.txt +0 -2
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone.api.egg-info/requires.txt +1 -1
- {plone_api-2.5.2 → plone_api-3.0.0}/tox.ini +85 -108
- plone_api-2.5.2/src/plone/__init__.py +0 -1
- plone_api-2.5.2/src/plone.api.egg-info/namespace_packages.txt +0 -1
- {plone_api-2.5.2 → plone_api-3.0.0}/CONTRIBUTING.md +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/LICENSE +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/MANIFEST.in +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/README.md +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/docs/about.md +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/docs/addon.md +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/docs/content.md +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/docs/contribute.md +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/docs/env.md +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/docs/group.md +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/docs/index.md +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/docs/relation.md +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/docs/user.md +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/setup.cfg +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/__init__.py +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/configure.zcml +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/exc.py +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/group.py +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/profiles/testfixture/metadata.xml +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/profiles/testfixture/types/Dexterity_Folder.xml +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/profiles/testfixture/types/Dexterity_Item.xml +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/profiles/testfixture/types.xml +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/testing.zcml +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/tests/Dexterity_Folder.xml +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/tests/Dexterity_Item.xml +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/tests/__init__.py +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/tests/base.py +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/tests/doctests/about.md +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/tests/doctests/addon.md +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/tests/doctests/content.md +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/tests/doctests/contribute.md +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/tests/doctests/env.md +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/tests/doctests/group.md +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/tests/doctests/relation.md +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/tests/doctests/user.md +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/tests/test_env.py +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/tests/test_group.py +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/tests/test_relation.py +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/tests/test_validation.py +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone/api/validation.py +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone.api.egg-info/dependency_links.txt +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone.api.egg-info/not-zip-safe +0 -0
- {plone_api-2.5.2 → plone_api-3.0.0}/src/plone.api.egg-info/top_level.txt +0 -0
|
@@ -9,6 +9,61 @@
|
|
|
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
|
+
|
|
60
|
+
## 2.5.3 (2025-09-10)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
### Bug fixes:
|
|
64
|
+
|
|
65
|
+
- Drop `pkg_resources` usage @gforcada #4126
|
|
66
|
+
|
|
12
67
|
## 2.5.2 (2025-06-05)
|
|
13
68
|
|
|
14
69
|
|
|
@@ -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,61 @@ 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
|
+
|
|
185
|
+
## 2.5.3 (2025-09-10)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
### Bug fixes:
|
|
189
|
+
|
|
190
|
+
- Drop `pkg_resources` usage @gforcada #4126
|
|
191
|
+
|
|
135
192
|
## 2.5.2 (2025-06-05)
|
|
136
193
|
|
|
137
194
|
|
|
@@ -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.2"
|
|
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={
|
|
@@ -2,22 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from functools import lru_cache
|
|
5
|
+
from importlib.metadata import distribution
|
|
6
|
+
from importlib.metadata import PackageNotFoundError
|
|
5
7
|
from plone.api import portal
|
|
6
8
|
from plone.api.exc import InvalidParameterError
|
|
7
9
|
from plone.api.validation import required_parameters
|
|
10
|
+
from plone.base.interfaces import INonInstallable
|
|
11
|
+
from plone.base.utils import get_installer
|
|
8
12
|
from Products.CMFPlone.controlpanel.browser.quickinstaller import InstallerView
|
|
9
|
-
from Products.CMFPlone.interfaces import INonInstallable
|
|
10
|
-
from Products.CMFPlone.utils import get_installer
|
|
11
13
|
from Products.GenericSetup import EXTENSION
|
|
12
|
-
from typing import Dict
|
|
13
|
-
from typing import List
|
|
14
|
-
from typing import Tuple
|
|
15
14
|
from zope.component import getAllUtilitiesRegisteredFor
|
|
16
15
|
from zope.globalrequest import getRequest
|
|
17
16
|
|
|
18
17
|
import logging
|
|
19
|
-
import pkg_resources
|
|
20
|
-
|
|
21
18
|
|
|
22
19
|
logger = logging.getLogger("plone.api.addon")
|
|
23
20
|
|
|
@@ -38,8 +35,8 @@ __all__ = [
|
|
|
38
35
|
class NonInstallableAddons:
|
|
39
36
|
"""Set of add-ons not available for installation."""
|
|
40
37
|
|
|
41
|
-
profiles:
|
|
42
|
-
products:
|
|
38
|
+
profiles: list[str]
|
|
39
|
+
products: list[str]
|
|
43
40
|
|
|
44
41
|
|
|
45
42
|
@dataclass
|
|
@@ -51,14 +48,14 @@ class AddonInformation:
|
|
|
51
48
|
title: str
|
|
52
49
|
description: str
|
|
53
50
|
|
|
54
|
-
upgrade_profiles:
|
|
55
|
-
other_profiles:
|
|
56
|
-
install_profile:
|
|
57
|
-
uninstall_profile:
|
|
51
|
+
upgrade_profiles: dict
|
|
52
|
+
other_profiles: list[list]
|
|
53
|
+
install_profile: dict
|
|
54
|
+
uninstall_profile: dict
|
|
58
55
|
profile_type: str
|
|
59
|
-
upgrade_info:
|
|
56
|
+
upgrade_info: dict
|
|
60
57
|
valid: bool
|
|
61
|
-
flags:
|
|
58
|
+
flags: list[str]
|
|
62
59
|
|
|
63
60
|
def __repr__(self) -> str:
|
|
64
61
|
"""Return a string representation of this object."""
|
|
@@ -97,7 +94,7 @@ def _get_non_installable_addons() -> NonInstallableAddons:
|
|
|
97
94
|
|
|
98
95
|
|
|
99
96
|
@lru_cache(maxsize=1)
|
|
100
|
-
def _cached_addons() ->
|
|
97
|
+
def _cached_addons() -> tuple[tuple[str, AddonInformation]]:
|
|
101
98
|
"""Return information about add-ons in this installation.
|
|
102
99
|
|
|
103
100
|
:returns: Tuple of tuples with add-on id and AddonInformation.
|
|
@@ -197,7 +194,7 @@ def _update_addon_info(
|
|
|
197
194
|
return addon
|
|
198
195
|
|
|
199
196
|
|
|
200
|
-
def _get_addons() ->
|
|
197
|
+
def _get_addons() -> list[AddonInformation]:
|
|
201
198
|
"""Return an updated list of add-on information.
|
|
202
199
|
|
|
203
200
|
:returns: List of AddonInformation.
|
|
@@ -211,7 +208,7 @@ def _get_addons() -> List[AddonInformation]:
|
|
|
211
208
|
return result
|
|
212
209
|
|
|
213
210
|
|
|
214
|
-
def get_addons(limit: str = "") ->
|
|
211
|
+
def get_addons(limit: str = "") -> list[AddonInformation]:
|
|
215
212
|
"""List add-ons in this Plone site.
|
|
216
213
|
|
|
217
214
|
:param limit: Limit list of add-ons.
|
|
@@ -238,7 +235,7 @@ def get_addons(limit: str = "") -> List[AddonInformation]:
|
|
|
238
235
|
return addons
|
|
239
236
|
|
|
240
237
|
|
|
241
|
-
def get_addon_ids(limit: str = "") ->
|
|
238
|
+
def get_addon_ids(limit: str = "") -> list[str]:
|
|
242
239
|
"""List add-ons ids in this Plone site.
|
|
243
240
|
|
|
244
241
|
:param limit: Limit list of add-ons.
|
|
@@ -258,9 +255,9 @@ def get_addon_ids(limit: str = "") -> List[str]:
|
|
|
258
255
|
def get_version(addon: str) -> str:
|
|
259
256
|
"""Return the version of the product (package)."""
|
|
260
257
|
try:
|
|
261
|
-
dist =
|
|
258
|
+
dist = distribution(addon)
|
|
262
259
|
return dist.version
|
|
263
|
-
except
|
|
260
|
+
except PackageNotFoundError:
|
|
264
261
|
if "." in addon:
|
|
265
262
|
return ""
|
|
266
263
|
return get_version(f"Products.{addon}")
|
|
@@ -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
|
|