imio.smartweb.common 1.2.37__py3-none-any.whl → 1.2.39__py3-none-any.whl
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.
- imio/smartweb/common/caching_overrides.zcml +1 -0
- imio/smartweb/common/profiles/default/metadata.xml +1 -1
- imio/smartweb/common/profiles/default/registry/tinymce.xml +3 -12
- imio/smartweb/common/rest/endpoint.py +25 -11
- imio/smartweb/common/tests/test_rest_endpoint.py +238 -0
- imio/smartweb/common/tests/test_rest_utils.py +116 -0
- imio/smartweb/common/upgrades/configure.zcml +19 -0
- imio/smartweb/common/upgrades/profiles/1033_to_1034/registry/tinymce.xml +70 -0
- {imio_smartweb_common-1.2.37.dist-info → imio_smartweb_common-1.2.39.dist-info}/METADATA +18 -1
- {imio_smartweb_common-1.2.37.dist-info → imio_smartweb_common-1.2.39.dist-info}/RECORD +16 -13
- /imio.smartweb.common-1.2.37-py3.12-nspkg.pth → /imio.smartweb.common-1.2.39-py3.12-nspkg.pth +0 -0
- {imio_smartweb_common-1.2.37.dist-info → imio_smartweb_common-1.2.39.dist-info}/WHEEL +0 -0
- {imio_smartweb_common-1.2.37.dist-info → imio_smartweb_common-1.2.39.dist-info}/licenses/LICENSE.GPL +0 -0
- {imio_smartweb_common-1.2.37.dist-info → imio_smartweb_common-1.2.39.dist-info}/licenses/LICENSE.rst +0 -0
- {imio_smartweb_common-1.2.37.dist-info → imio_smartweb_common-1.2.39.dist-info}/namespace_packages.txt +0 -0
- {imio_smartweb_common-1.2.37.dist-info → imio_smartweb_common-1.2.39.dist-info}/top_level.txt +0 -0
|
@@ -53,11 +53,8 @@
|
|
|
53
53
|
<element>code</element>
|
|
54
54
|
<element>charmap</element>
|
|
55
55
|
<element>fullscreen</element>
|
|
56
|
-
<element>hr</element>
|
|
57
56
|
<element>lists</element>
|
|
58
57
|
<element>nonbreaking</element>
|
|
59
|
-
<element>noneditable</element>
|
|
60
|
-
<element>paste</element>
|
|
61
58
|
<element>searchreplace</element>
|
|
62
59
|
<element>table</element>
|
|
63
60
|
<element>visualblocks</element>
|
|
@@ -70,13 +67,7 @@
|
|
|
70
67
|
<record interface="plone.base.interfaces.controlpanel.ITinyMCESchema"
|
|
71
68
|
name="plone.menubar"
|
|
72
69
|
>
|
|
73
|
-
<value purge="true">
|
|
74
|
-
<element>edit</element>
|
|
75
|
-
<element>format</element>
|
|
76
|
-
<element>insert</element>
|
|
77
|
-
<element>table</element>
|
|
78
|
-
<element>view</element>
|
|
79
|
-
</value>
|
|
70
|
+
<value purge="true">edit format insert table view</value>
|
|
80
71
|
</record>
|
|
81
72
|
|
|
82
73
|
<!-- See possible values in https://www.tiny.cloud/docs/tinymce/latest/available-menu-items/ -->
|
|
@@ -106,8 +97,8 @@
|
|
|
106
97
|
"items": ""
|
|
107
98
|
},
|
|
108
99
|
"insert": {
|
|
109
|
-
|
|
110
|
-
|
|
100
|
+
"title": "Insert",
|
|
101
|
+
"items": "hr"
|
|
111
102
|
},
|
|
112
103
|
"table": {
|
|
113
104
|
"title": "Table",
|
|
@@ -25,8 +25,6 @@ class FindEndpointHandler(SearchHandler):
|
|
|
25
25
|
if not types:
|
|
26
26
|
raise Unauthorized("No types found, you are not allowed to search")
|
|
27
27
|
self._constrain_query_by_path(query)
|
|
28
|
-
if not query.get("path"):
|
|
29
|
-
query["path"] = {"query": "/Plone"}
|
|
30
28
|
# query = self._parse_query(query)
|
|
31
29
|
if query.get("type_of_request") == "count_contents_types":
|
|
32
30
|
results = self.count_contents_types(query)
|
|
@@ -130,24 +128,40 @@ class FindEndpointHandler(SearchHandler):
|
|
|
130
128
|
if total == 0:
|
|
131
129
|
return {}
|
|
132
130
|
|
|
133
|
-
# Récupération des valeurs
|
|
131
|
+
# Récupération et normalisation des valeurs
|
|
134
132
|
values = []
|
|
135
133
|
for brain in brains:
|
|
136
134
|
obj = brain.getObject()
|
|
137
135
|
value = getattr(obj, field_name, None)
|
|
138
|
-
if isinstance(value, (list, tuple)):
|
|
139
|
-
|
|
136
|
+
if isinstance(value, (list, tuple)):
|
|
137
|
+
if not value:
|
|
138
|
+
# liste/tuple vide => on compte 1 None
|
|
139
|
+
values.append(None)
|
|
140
|
+
else:
|
|
141
|
+
mapped = [
|
|
142
|
+
None if (v in ("", "None") or v is None) else v for v in value
|
|
143
|
+
]
|
|
144
|
+
# si TOUT est None, on compte 1 None (au lieu d'en ajouter 0)
|
|
145
|
+
if all(v is None for v in mapped):
|
|
146
|
+
values.append(None)
|
|
147
|
+
else:
|
|
148
|
+
values.extend(mapped)
|
|
140
149
|
else:
|
|
141
|
-
|
|
150
|
+
v = None if (value in ("", "None") or value is None) else value
|
|
151
|
+
values.append(v)
|
|
142
152
|
counter = Counter(values)
|
|
143
153
|
|
|
144
|
-
#
|
|
145
|
-
result = {}
|
|
154
|
+
# Normalisation des valeurs attendues
|
|
146
155
|
expected_values = normalize_query_param(expected_values)
|
|
147
|
-
|
|
148
|
-
|
|
156
|
+
# On garde les labels originaux pour l'affichage, mais on mappe "None" vers None réel pour le compteur
|
|
157
|
+
expected_mapping = {v: (None if v == "None" else v) for v in expected_values}
|
|
158
|
+
|
|
159
|
+
# Calcul stats
|
|
160
|
+
result = {}
|
|
161
|
+
for label, key in expected_mapping.items():
|
|
162
|
+
count = counter.get(key, 0)
|
|
149
163
|
percent = (count / total) * 100 if total > 0 else 0
|
|
150
|
-
result[
|
|
164
|
+
result[label] = {"count": count, "percent": round(percent, 2)}
|
|
151
165
|
return result
|
|
152
166
|
|
|
153
167
|
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from imio.smartweb.common.rest.endpoint import FindEndpointHandler
|
|
3
|
+
from imio.smartweb.common.testing import IMIO_SMARTWEB_COMMON_INTEGRATION_TESTING
|
|
4
|
+
from plone import api
|
|
5
|
+
from plone.app.testing import setRoles
|
|
6
|
+
from plone.app.testing import TEST_USER_ID
|
|
7
|
+
from plone.namedfile.file import NamedBlobFile, NamedBlobImage
|
|
8
|
+
from unittest.mock import patch
|
|
9
|
+
from zExceptions import Unauthorized
|
|
10
|
+
|
|
11
|
+
import unittest
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestRestEndpoint(unittest.TestCase):
|
|
15
|
+
layer = IMIO_SMARTWEB_COMMON_INTEGRATION_TESTING
|
|
16
|
+
|
|
17
|
+
def setUp(self):
|
|
18
|
+
self.request = self.layer["request"]
|
|
19
|
+
self.portal = self.layer["portal"]
|
|
20
|
+
|
|
21
|
+
setRoles(self.portal, TEST_USER_ID, ["Manager"])
|
|
22
|
+
# Simule un header d'auth présent dans la requête
|
|
23
|
+
self.request._orig_env = {"HTTP_AUTHORIZATION": "Bearer TEST"}
|
|
24
|
+
self.folder = api.content.create(
|
|
25
|
+
container=self.portal,
|
|
26
|
+
type="Folder",
|
|
27
|
+
title="Folder",
|
|
28
|
+
)
|
|
29
|
+
self.doc1 = api.content.create(
|
|
30
|
+
container=self.folder, type="Document", title="Doc 1"
|
|
31
|
+
)
|
|
32
|
+
self.doc2 = api.content.create(
|
|
33
|
+
container=self.folder, type="Document", title="Doc 2"
|
|
34
|
+
)
|
|
35
|
+
self.page = api.content.create(
|
|
36
|
+
container=self.folder, type="Document", title="Doc 3"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def test_no_types(self):
|
|
40
|
+
query = {
|
|
41
|
+
"type_of_request": "count_contents_types",
|
|
42
|
+
"portal_type": "Document",
|
|
43
|
+
"path": {"query": self.portal.absolute_url_path()},
|
|
44
|
+
"operator": "and",
|
|
45
|
+
}
|
|
46
|
+
handler = FindEndpointHandler(self.portal, self.request)
|
|
47
|
+
self.assertRaises(Unauthorized, handler.search, query)
|
|
48
|
+
|
|
49
|
+
# ------------------------------
|
|
50
|
+
# count_contents_types
|
|
51
|
+
# ------------------------------
|
|
52
|
+
@patch("imio.smartweb.common.rest.endpoint.get_json")
|
|
53
|
+
def test_count_contents_types_operator_and(self, mjson):
|
|
54
|
+
mjson.return_value = [
|
|
55
|
+
{
|
|
56
|
+
"@id": f"https://{self.portal.absolute_url()}/@types/Document",
|
|
57
|
+
"addable": "false",
|
|
58
|
+
"id": "Document",
|
|
59
|
+
"immediately_addable": "false",
|
|
60
|
+
"title": "Document",
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
handler = FindEndpointHandler(self.portal, self.request)
|
|
64
|
+
query = {
|
|
65
|
+
"type_of_request": "count_contents_types",
|
|
66
|
+
"portal_type": "Document",
|
|
67
|
+
"path": {"query": self.portal.absolute_url_path()},
|
|
68
|
+
"operator": "and",
|
|
69
|
+
}
|
|
70
|
+
res = handler.search(query)
|
|
71
|
+
self.assertEqual(res, {"items": [{"portal_type": "Document", "nb_items": 3}]})
|
|
72
|
+
|
|
73
|
+
# Test without path in query.
|
|
74
|
+
# Path is automaticaly set.
|
|
75
|
+
query = {
|
|
76
|
+
"type_of_request": "count_contents_types",
|
|
77
|
+
"portal_type": "Document",
|
|
78
|
+
"operator": "and",
|
|
79
|
+
}
|
|
80
|
+
res = handler.search(query)
|
|
81
|
+
self.assertEqual(res, {"items": [{"portal_type": "Document", "nb_items": 3}]})
|
|
82
|
+
|
|
83
|
+
@patch("imio.smartweb.common.rest.endpoint.get_json")
|
|
84
|
+
def test_count_contents_types_operator_or_multiple_types(self, mjson):
|
|
85
|
+
mjson.return_value = [
|
|
86
|
+
{
|
|
87
|
+
"@id": f"https://{self.portal.absolute_url()}/@types/Document",
|
|
88
|
+
"addable": "false",
|
|
89
|
+
"id": "Document",
|
|
90
|
+
"immediately_addable": "false",
|
|
91
|
+
"title": "Document",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"@id": f"https://{self.portal.absolute_url()}/@types/Folder",
|
|
95
|
+
"addable": "false",
|
|
96
|
+
"id": "Folder",
|
|
97
|
+
"immediately_addable": "false",
|
|
98
|
+
"title": "Folder",
|
|
99
|
+
},
|
|
100
|
+
]
|
|
101
|
+
handler = FindEndpointHandler(self.portal, self.request)
|
|
102
|
+
query = {
|
|
103
|
+
"type_of_request": "count_contents_types",
|
|
104
|
+
"portal_type": ["Document", "Folder"],
|
|
105
|
+
"path": {"query": self.portal.absolute_url_path()},
|
|
106
|
+
"operator": "and",
|
|
107
|
+
}
|
|
108
|
+
res = handler.search(query)
|
|
109
|
+
self.assertEqual(
|
|
110
|
+
res, {"items": [{"portal_type": ["Document", "Folder"], "nb_items": 4}]}
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
@patch("imio.smartweb.common.rest.endpoint.get_json")
|
|
114
|
+
def test_get_max_depth(self, mjson):
|
|
115
|
+
mjson.return_value = [
|
|
116
|
+
{
|
|
117
|
+
"@id": f"https://{self.portal.absolute_url()}/@types/Document",
|
|
118
|
+
"addable": "false",
|
|
119
|
+
"id": "Document",
|
|
120
|
+
"immediately_addable": "false",
|
|
121
|
+
"title": "Document",
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
handler = FindEndpointHandler(self.portal, self.request)
|
|
125
|
+
res = handler.search({"type_of_request": "get_max_depth"})
|
|
126
|
+
self.assertEqual(res["max_depth"], 3)
|
|
127
|
+
|
|
128
|
+
paths = {i["path"] for i in res["items"]}
|
|
129
|
+
self.assertEqual(
|
|
130
|
+
paths, {"/plone/folder/doc-2", "/plone/folder/doc-3", "/plone/folder/doc-1"}
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
folder2 = api.content.create(
|
|
134
|
+
container=self.folder,
|
|
135
|
+
type="Folder",
|
|
136
|
+
title="Folder 2",
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
api.content.create(
|
|
140
|
+
container=folder2,
|
|
141
|
+
type="Document",
|
|
142
|
+
title="Kamoulox",
|
|
143
|
+
)
|
|
144
|
+
handler = FindEndpointHandler(self.portal, self.request)
|
|
145
|
+
res = handler.search({"type_of_request": "get_max_depth"})
|
|
146
|
+
self.assertEqual(res["max_depth"], 4)
|
|
147
|
+
|
|
148
|
+
paths = {i["path"] for i in res["items"]}
|
|
149
|
+
self.assertEqual(paths, {"/plone/folder/folder-2/kamoulox"})
|
|
150
|
+
|
|
151
|
+
@patch("imio.smartweb.common.rest.endpoint.get_json")
|
|
152
|
+
def test_check_value_of_field_counts_and_percents(self, mjson):
|
|
153
|
+
mjson.return_value = [
|
|
154
|
+
{
|
|
155
|
+
"@id": f"https://{self.portal.absolute_url()}/@types/Document",
|
|
156
|
+
"addable": "false",
|
|
157
|
+
"id": "Document",
|
|
158
|
+
"immediately_addable": "false",
|
|
159
|
+
"title": "Document",
|
|
160
|
+
}
|
|
161
|
+
]
|
|
162
|
+
api.content.create(
|
|
163
|
+
container=self.folder,
|
|
164
|
+
type="Document",
|
|
165
|
+
title="Doc 2",
|
|
166
|
+
)
|
|
167
|
+
handler = FindEndpointHandler(self.portal, self.request)
|
|
168
|
+
q = {
|
|
169
|
+
"type_of_request": "check_value_of_field",
|
|
170
|
+
"portal_type": "Document",
|
|
171
|
+
"field_name": "title",
|
|
172
|
+
"expected_values": ["Doc 1", "Doc 2", "Doc 3"],
|
|
173
|
+
}
|
|
174
|
+
res = handler.search(q)
|
|
175
|
+
# Number
|
|
176
|
+
self.assertEqual(res["Doc 1"]["count"], 1)
|
|
177
|
+
self.assertEqual(res["Doc 2"]["count"], 2)
|
|
178
|
+
self.assertEqual(res["Doc 3"]["count"], 1)
|
|
179
|
+
|
|
180
|
+
# Pourcentages
|
|
181
|
+
self.assertEqual(res["Doc 1"]["percent"], round(1 / 4 * 100, 2)) # 25%
|
|
182
|
+
self.assertEqual(res["Doc 2"]["percent"], round(2 / 4 * 100, 2)) # 50%
|
|
183
|
+
self.assertEqual(res["Doc 3"]["percent"], round(1 / 4 * 100, 2)) # 25%
|
|
184
|
+
|
|
185
|
+
@patch("imio.smartweb.common.rest.endpoint.get_json")
|
|
186
|
+
def test_find_big_files_or_images(self, mjson):
|
|
187
|
+
mjson.return_value = [
|
|
188
|
+
{
|
|
189
|
+
"@id": f"https://{self.portal.absolute_url()}/@types/Image",
|
|
190
|
+
"addable": "false",
|
|
191
|
+
"id": "Image",
|
|
192
|
+
"immediately_addable": "false",
|
|
193
|
+
"title": "Image",
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
"@id": f"https://{self.portal.absolute_url()}/@types/File",
|
|
197
|
+
"addable": "false",
|
|
198
|
+
"id": "File",
|
|
199
|
+
"immediately_addable": "false",
|
|
200
|
+
"title": "File",
|
|
201
|
+
},
|
|
202
|
+
]
|
|
203
|
+
api.content.create(
|
|
204
|
+
container=self.folder,
|
|
205
|
+
type="File",
|
|
206
|
+
id="bigfile",
|
|
207
|
+
title="Big file",
|
|
208
|
+
file=NamedBlobFile(data=b"x" * 2500000, filename="big.pdf"), # 2,5 Mo
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
api.content.create(
|
|
212
|
+
container=self.folder,
|
|
213
|
+
type="Image",
|
|
214
|
+
id="smallimage",
|
|
215
|
+
title="Small image",
|
|
216
|
+
image=NamedBlobImage(data=b"x" * 100000, filename="small.jpg"), # 0,1 Mo
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
api.content.create(
|
|
220
|
+
container=self.folder,
|
|
221
|
+
type="Image",
|
|
222
|
+
id="bigimage",
|
|
223
|
+
title="Big image",
|
|
224
|
+
image=NamedBlobImage(data=b"x" * 3100000, filename="big.jpg"), # 3,1 Mo
|
|
225
|
+
)
|
|
226
|
+
handler = FindEndpointHandler(self.portal, self.request)
|
|
227
|
+
q = {
|
|
228
|
+
"type_of_request": "find_big_files_or_images",
|
|
229
|
+
"portal_type": ["Image", "File"],
|
|
230
|
+
"size": 1000000,
|
|
231
|
+
}
|
|
232
|
+
res = handler.search(q)
|
|
233
|
+
# On attend seulement les gros
|
|
234
|
+
titles = [it["title"] for it in res["items"]]
|
|
235
|
+
self.assertEqual(set(titles), {"Big file", "Big image"})
|
|
236
|
+
# Vérifie calcul Mo arrondi (2_500_000 bytes ≈ 2.38 Mo)
|
|
237
|
+
big_entry = next(i for i in res["items"] if i["title"] == "Big file")
|
|
238
|
+
self.assertAlmostEqual(big_entry["size"], round(2_500_000 / (1024 * 1024), 2))
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from imio.smartweb.common.testing import IMIO_SMARTWEB_COMMON_ACCEPTANCE_TESTING
|
|
4
|
+
from imio.smartweb.common.rest.utils import batch_results
|
|
5
|
+
from imio.smartweb.common.rest.utils import get_json
|
|
6
|
+
from imio.smartweb.common.rest.utils import hash_md5
|
|
7
|
+
from unittest.mock import patch
|
|
8
|
+
from unittest.mock import Mock
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import requests
|
|
12
|
+
import unittest
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestRestUtils(unittest.TestCase):
|
|
16
|
+
layer = IMIO_SMARTWEB_COMMON_ACCEPTANCE_TESTING
|
|
17
|
+
|
|
18
|
+
def setUp(self):
|
|
19
|
+
self.request = self.layer["request"]
|
|
20
|
+
self.portal = self.layer["portal"]
|
|
21
|
+
self.portal_url = self.portal.absolute_url()
|
|
22
|
+
self.url = "https://api.kamoulox.test/endpoint"
|
|
23
|
+
|
|
24
|
+
@patch("imio.smartweb.common.rest.utils.requests.get")
|
|
25
|
+
def test_get_json_success(self, mock_get):
|
|
26
|
+
payload = {"ok": True, "items": [1, 2]}
|
|
27
|
+
mock_get.return_value = Mock(status_code=200, text=json.dumps(payload))
|
|
28
|
+
result = get_json(self.url)
|
|
29
|
+
self.assertEqual(result, payload)
|
|
30
|
+
mock_get.assert_called_once()
|
|
31
|
+
(called_url,) = mock_get.call_args[0]
|
|
32
|
+
self.assertEqual(called_url, self.url)
|
|
33
|
+
kwargs = mock_get.call_args.kwargs
|
|
34
|
+
self.assertEqual(kwargs["timeout"], 5)
|
|
35
|
+
self.assertEqual(kwargs["headers"]["Accept"], "application/json")
|
|
36
|
+
self.assertNotIn("Authorization", kwargs["headers"])
|
|
37
|
+
|
|
38
|
+
@patch("imio.smartweb.common.rest.utils.requests.get")
|
|
39
|
+
def test_get_json_non_200_returns_none(self, mock_get):
|
|
40
|
+
mock_get.return_value = Mock(status_code=404, text="Not found")
|
|
41
|
+
result = get_json(self.url)
|
|
42
|
+
self.assertIsNone(result)
|
|
43
|
+
|
|
44
|
+
@patch("imio.smartweb.common.rest.utils.logger")
|
|
45
|
+
@patch("imio.smartweb.common.rest.utils.requests.get")
|
|
46
|
+
def test_get_json_timeout_returns_none_and_logs(self, mock_get, mock_logger):
|
|
47
|
+
mock_get.side_effect = requests.exceptions.Timeout()
|
|
48
|
+
result = get_json(self.url)
|
|
49
|
+
self.assertIsNone(result)
|
|
50
|
+
mock_logger.warning.assert_called_once()
|
|
51
|
+
# check if url is in log
|
|
52
|
+
self.assertIn(self.url, mock_logger.warning.call_args[0][0])
|
|
53
|
+
|
|
54
|
+
@patch("imio.smartweb.common.rest.utils.requests.get")
|
|
55
|
+
def test_get_json_other_exception_returns_none(self, mock_get):
|
|
56
|
+
mock_get.side_effect = RuntimeError("kamoulox")
|
|
57
|
+
result = get_json(self.url)
|
|
58
|
+
self.assertIsNone(result)
|
|
59
|
+
|
|
60
|
+
@patch("imio.smartweb.common.rest.utils.requests.get")
|
|
61
|
+
def test_get_json_sets_auth_header_when_provided(self, mock_get):
|
|
62
|
+
mock_get.return_value = Mock(status_code=200, text="{}")
|
|
63
|
+
auth = "Bearer TOKEN123"
|
|
64
|
+
_ = get_json(self.url, auth=auth, timeout=10)
|
|
65
|
+
mock_get.assert_called_once()
|
|
66
|
+
kwargs = mock_get.call_args.kwargs
|
|
67
|
+
self.assertEqual(kwargs["timeout"], 10)
|
|
68
|
+
self.assertEqual(kwargs["headers"]["Authorization"], auth)
|
|
69
|
+
self.assertEqual(kwargs["headers"]["Accept"], "application/json")
|
|
70
|
+
|
|
71
|
+
@patch("imio.smartweb.common.rest.utils.requests.get")
|
|
72
|
+
def test_get_json_custom_timeout_is_used(self, mock_get):
|
|
73
|
+
mock_get.return_value = Mock(status_code=200, text="{}")
|
|
74
|
+
_ = get_json(self.url, timeout=2.5)
|
|
75
|
+
self.assertEqual(mock_get.call_args.kwargs["timeout"], 2.5)
|
|
76
|
+
|
|
77
|
+
def test_batch_results_exact_division(self):
|
|
78
|
+
data = [1, 2, 3, 4]
|
|
79
|
+
result = batch_results(data, 2)
|
|
80
|
+
self.assertEqual(result, [[1, 2], [3, 4]])
|
|
81
|
+
|
|
82
|
+
def test_batch_results_not_exact(self):
|
|
83
|
+
data = [1, 2, 3, 4, 5]
|
|
84
|
+
result = batch_results(data, 2)
|
|
85
|
+
self.assertEqual(result, [[1, 2], [3, 4], [5]])
|
|
86
|
+
|
|
87
|
+
def test_batch_results_batch_size_larger_than_list(self):
|
|
88
|
+
data = [1, 2, 3]
|
|
89
|
+
result = batch_results(data, 10)
|
|
90
|
+
self.assertEqual(result, [[1, 2, 3]])
|
|
91
|
+
|
|
92
|
+
def test_batch_results_empty_iterable(self):
|
|
93
|
+
data = []
|
|
94
|
+
result = batch_results(data, 3)
|
|
95
|
+
self.assertEqual(result, [])
|
|
96
|
+
|
|
97
|
+
def test_batch_results_with_generator(self):
|
|
98
|
+
gen = (i for i in range(5))
|
|
99
|
+
result = batch_results(gen, 2)
|
|
100
|
+
self.assertEqual(result, [[0, 1], [2, 3], [4]])
|
|
101
|
+
|
|
102
|
+
def test_hash_md5_basic_string(self):
|
|
103
|
+
result = hash_md5("hello")
|
|
104
|
+
self.assertEqual(result, "5d41402abc4b2a76b9719d911017c592")
|
|
105
|
+
|
|
106
|
+
def test_hash_md5_empty_string(self):
|
|
107
|
+
result = hash_md5("")
|
|
108
|
+
self.assertEqual(result, "d41d8cd98f00b204e9800998ecf8427e")
|
|
109
|
+
|
|
110
|
+
def test_hash_md5_unicode_string(self):
|
|
111
|
+
result = hash_md5("éèà")
|
|
112
|
+
# tu peux vérifier avec hashlib directement
|
|
113
|
+
import hashlib
|
|
114
|
+
|
|
115
|
+
expected = hashlib.md5("éèà".encode()).hexdigest()
|
|
116
|
+
self.assertEqual(result, expected)
|
|
@@ -165,6 +165,14 @@
|
|
|
165
165
|
directory="profiles/1032_to_1033"
|
|
166
166
|
/>
|
|
167
167
|
|
|
168
|
+
<genericsetup:registerProfile
|
|
169
|
+
name="upgrade_1033_to_1034"
|
|
170
|
+
title="Upgrade common from 1033 to 1034"
|
|
171
|
+
description="Update configuration from TinyMCE 7.7.0 to be TinyMCE 7.9.1"
|
|
172
|
+
provides="Products.GenericSetup.interfaces.EXTENSION"
|
|
173
|
+
directory="profiles/1033_to_1034"
|
|
174
|
+
/>
|
|
175
|
+
|
|
168
176
|
<genericsetup:upgradeStep
|
|
169
177
|
title="Configure first official release"
|
|
170
178
|
description="Run needed registry step"
|
|
@@ -515,4 +523,15 @@
|
|
|
515
523
|
/>
|
|
516
524
|
</genericsetup:upgradeSteps>
|
|
517
525
|
|
|
526
|
+
<genericsetup:upgradeSteps
|
|
527
|
+
profile="imio.smartweb.common:default"
|
|
528
|
+
source="1033"
|
|
529
|
+
destination="1034"
|
|
530
|
+
>
|
|
531
|
+
<genericsetup:upgradeDepends
|
|
532
|
+
title="Update configuration from TinyMCE 7.7.0 to be TinyMCE 7.9.1"
|
|
533
|
+
import_profile="imio.smartweb.common.upgrades:upgrade_1033_to_1034"
|
|
534
|
+
/>
|
|
535
|
+
</genericsetup:upgradeSteps>
|
|
536
|
+
|
|
518
537
|
</configure>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<registry>
|
|
3
|
+
|
|
4
|
+
<record interface="plone.base.interfaces.controlpanel.ITinyMCESchema"
|
|
5
|
+
name="plone.plugins"
|
|
6
|
+
>
|
|
7
|
+
<value purge="true">
|
|
8
|
+
<element>code</element>
|
|
9
|
+
<element>charmap</element>
|
|
10
|
+
<element>fullscreen</element>
|
|
11
|
+
<element>lists</element>
|
|
12
|
+
<element>nonbreaking</element>
|
|
13
|
+
<element>searchreplace</element>
|
|
14
|
+
<element>table</element>
|
|
15
|
+
<element>visualblocks</element>
|
|
16
|
+
<element>visualchars</element>
|
|
17
|
+
<element>wordcount</element>
|
|
18
|
+
</value>
|
|
19
|
+
</record>
|
|
20
|
+
|
|
21
|
+
<!-- See possible values in https://www.tiny.cloud/docs/tinymce/latest/menus-configuration-options/#menubar -->
|
|
22
|
+
<record interface="plone.base.interfaces.controlpanel.ITinyMCESchema"
|
|
23
|
+
name="plone.menubar">
|
|
24
|
+
<value purge="true">edit format insert table view</value>
|
|
25
|
+
</record>
|
|
26
|
+
|
|
27
|
+
<record interface="plone.base.interfaces.controlpanel.ITinyMCESchema"
|
|
28
|
+
name="plone.menu">
|
|
29
|
+
<value purge="true">
|
|
30
|
+
{
|
|
31
|
+
"file": {
|
|
32
|
+
"title": "File",
|
|
33
|
+
"items": ""
|
|
34
|
+
},
|
|
35
|
+
"tools": {
|
|
36
|
+
"title": "Tools",
|
|
37
|
+
"items": ""
|
|
38
|
+
},
|
|
39
|
+
"edit": {
|
|
40
|
+
"title": "Edit",
|
|
41
|
+
"items": "undo redo | cut copy paste | searchreplace selectall"
|
|
42
|
+
},
|
|
43
|
+
"format": {
|
|
44
|
+
"title": "Format",
|
|
45
|
+
"items": ""
|
|
46
|
+
},
|
|
47
|
+
"insert": {
|
|
48
|
+
"title": "Insert",
|
|
49
|
+
"items": "hr"
|
|
50
|
+
},
|
|
51
|
+
"table": {
|
|
52
|
+
"title": "Table",
|
|
53
|
+
"items": "inserttable deletetable | cell row column"
|
|
54
|
+
},
|
|
55
|
+
"view": {
|
|
56
|
+
"title": "View",
|
|
57
|
+
"items": "visualblocks preview fullscreen"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
</value>
|
|
61
|
+
</record>
|
|
62
|
+
|
|
63
|
+
<!-- See possible values in https://www.tiny.cloud/docs/tinymce/latest/available-toolbar-buttons/ -->
|
|
64
|
+
<record interface="plone.base.interfaces.controlpanel.ITinyMCESchema"
|
|
65
|
+
name="plone.toolbar"
|
|
66
|
+
>
|
|
67
|
+
<value>undo redo | styleselect | bold italic superscript | bullist numlist | nonbreaking | plonelink unlink | fullscreen</value>
|
|
68
|
+
</record>
|
|
69
|
+
|
|
70
|
+
</registry>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: imio.smartweb.common
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.39
|
|
4
4
|
Summary: Common utilities, vocabularies, taxonomies for imio.smartweb & co products
|
|
5
5
|
Home-page: https://github.com/imio/imio.smartweb.common
|
|
6
6
|
Author: iMio
|
|
@@ -178,6 +178,23 @@ Changelog
|
|
|
178
178
|
=========
|
|
179
179
|
|
|
180
180
|
|
|
181
|
+
1.2.39 (2025-11-03)
|
|
182
|
+
-------------------
|
|
183
|
+
|
|
184
|
+
- Migration to Plone 6.1.3
|
|
185
|
+
[boulch]
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
1.2.38 (2025-09-10)
|
|
189
|
+
-------------------
|
|
190
|
+
|
|
191
|
+
- @find : Process None value in a field or ["None", "other value"] for agatha stats
|
|
192
|
+
[boulch
|
|
193
|
+
|
|
194
|
+
- Add plone.app.caching include for futur proofing override
|
|
195
|
+
[jchandelle]
|
|
196
|
+
|
|
197
|
+
|
|
181
198
|
1.2.37 (2025-09-03)
|
|
182
199
|
-------------------
|
|
183
200
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
imio.smartweb.common-1.2.
|
|
1
|
+
imio.smartweb.common-1.2.39-py3.12-nspkg.pth,sha256=XZ3YhlzwpUCC8tXtelHRqxVxo3NWomIiMsUfUshrbeE,1011
|
|
2
2
|
imio/smartweb/common/__init__.py,sha256=Na9XBfEQUMrm2c5jbqQgwWeg40ih0aXVG1vT8NeAjMQ,2709
|
|
3
3
|
imio/smartweb/common/adapters.py,sha256=dG4MALuHPQI6lTeNvs5p4vVOxHoBBYaWN6g8J96KxzQ,1507
|
|
4
4
|
imio/smartweb/common/adapters.zcml,sha256=VO3luZDtRL9FIIEghxPrqpx8paZF3m6MGEy-8kF1sOQ,437
|
|
5
5
|
imio/smartweb/common/caching.py,sha256=aN7gX9rT63fnmUuZnx82Mbu3ghjJUeOiLvLEp9OiFm4,895
|
|
6
|
-
imio/smartweb/common/caching_overrides.zcml,sha256=
|
|
6
|
+
imio/smartweb/common/caching_overrides.zcml,sha256=rpyKruOfuhAWfhtDVS9X54ibmNTZ9FmueH5Jz8aBf0w,570
|
|
7
7
|
imio/smartweb/common/config.py,sha256=imgOo0_BBIbXGFNvBnhPReLOSY4JqIOH_QCAl0j0XGI,435
|
|
8
8
|
imio/smartweb/common/configure.zcml,sha256=Ypqlo27RCC_w48QaQ0xucQRDjIBs9VpdtoJSjionK6Y,1128
|
|
9
9
|
imio/smartweb/common/contact_utils.py,sha256=Pr-Nhz9MRnfirZ7RbmF5JZw9fX4gWa0dWkvJlQBiDKY,11341
|
|
@@ -63,10 +63,10 @@ imio/smartweb/common/faceted/widget.py,sha256=EMvFyEcS9t6lJAgz4zIkUnGAPd1fQcrv4A
|
|
|
63
63
|
imio/smartweb/common/profiles/default/actions.xml,sha256=q5ajChGAPlPtpN_H80voDgg4C8Z-yDcIghUJIgFYtRg,1193
|
|
64
64
|
imio/smartweb/common/profiles/default/browserlayer.xml,sha256=Up2dVOgFtzE43eLMNJY1xXvdUbQRSEpNxnySqSncFqQ,185
|
|
65
65
|
imio/smartweb/common/profiles/default/catalog.xml,sha256=hB2Cu64TPp29EKICqbfixwa3AFmrDJVC784KeKTMX90,576
|
|
66
|
-
imio/smartweb/common/profiles/default/metadata.xml,sha256
|
|
66
|
+
imio/smartweb/common/profiles/default/metadata.xml,sha256=-sNjkCsLXvI_7uF69oQlt2DClwKxecy0vDVeN5oFQ-w,432
|
|
67
67
|
imio/smartweb/common/profiles/default/rolemap.xml,sha256=XCdOazRleiwTAAZt3lzTsOw3ZtIwoPDqA8s4zXACvkc,1595
|
|
68
68
|
imio/smartweb/common/profiles/default/registry/registry.xml,sha256=GXo92ntNu5GPhFOvDzde5ES7z6pgzrvK1aJtH-ElAHg,44314
|
|
69
|
-
imio/smartweb/common/profiles/default/registry/tinymce.xml,sha256=
|
|
69
|
+
imio/smartweb/common/profiles/default/registry/tinymce.xml,sha256=rHtD373Y3-3MYHv6xCo3HVhbGPijwfM8mDNghEwBVk8,4912
|
|
70
70
|
imio/smartweb/common/profiles/testing/catalog.xml,sha256=CzEXLuHkT5NH-lRzTSDIR_3y_U7t2D7oBDm1oAJIoP4,214
|
|
71
71
|
imio/smartweb/common/profiles/testing/metadata.xml,sha256=hPZnje-9ffhOiQfVamGoE_lOf9ISjBBf2ynQlg-Hv20,229
|
|
72
72
|
imio/smartweb/common/profiles/testing/registry.xml,sha256=8lTQSGT_bnP2Z0y-86ztgpdQY8yDOLoXgqTC7fXiD2Y,1010
|
|
@@ -76,7 +76,7 @@ imio/smartweb/common/profiles/testing/types/Folder.xml,sha256=RPxIPgViAK0GGGTPKb
|
|
|
76
76
|
imio/smartweb/common/profiles/uninstall/browserlayer.xml,sha256=glaCGlITwc36-1Fi2z0VCbLcoQ5GddkAhuW-SGYeZy8,130
|
|
77
77
|
imio/smartweb/common/rest/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
78
78
|
imio/smartweb/common/rest/configure.zcml,sha256=eT5gr-PcfMRxQzMME7T3nzz2Y0BVI4r9HyKHIccQC2s,575
|
|
79
|
-
imio/smartweb/common/rest/endpoint.py,sha256=
|
|
79
|
+
imio/smartweb/common/rest/endpoint.py,sha256=QDxyYuhI7ipB-JkFbMsl2OS7Vk-Uc39NwTJ41ZKZVDc,7411
|
|
80
80
|
imio/smartweb/common/rest/odwb.py,sha256=zRZxowUaE76xlW_blc92vRCzToUpnzw3O5mxFXaeScU,2249
|
|
81
81
|
imio/smartweb/common/rest/search_filters.py,sha256=upZAZ6h50qiSXo0q22lUgNlcmX5elFtrIQs_jFBl1-s,3832
|
|
82
82
|
imio/smartweb/common/rest/utils.py,sha256=9zc5ge_DmVuexxN09c3U6QAITAI1s2hc0m6eZuylSxM,1269
|
|
@@ -97,6 +97,8 @@ imio/smartweb/common/tests/test_local_roles.py,sha256=4cwVdtrkeecp3HRM26db6AJHJ7
|
|
|
97
97
|
imio/smartweb/common/tests/test_permissions.py,sha256=LCz7Ixpd1IrzJeTV6Ov5GXjWNVz7h5UbpS9-DD4SjmE,1578
|
|
98
98
|
imio/smartweb/common/tests/test_privacy.py,sha256=ObnquDYgd29P9hxnpF6VxuOtyVgxvW8HQnf8ZBDGnXk,2953
|
|
99
99
|
imio/smartweb/common/tests/test_rest.py,sha256=cXbiQjXXHCaXBMCCVk_PjnjG6Z4A_EfVUEpYVfhHDBM,5303
|
|
100
|
+
imio/smartweb/common/tests/test_rest_endpoint.py,sha256=iwoPp5tSjyi9oXyPH7jmobe11Q8IM6TI8Y9CUy2ztiQ,8779
|
|
101
|
+
imio/smartweb/common/tests/test_rest_utils.py,sha256=-YpTtRerWUZQBZGEvWfJoDqmFSu5YDmnP44YfydJkL4,4598
|
|
100
102
|
imio/smartweb/common/tests/test_robot.py,sha256=0Qdn5A8lXoVxQ9zE9IIhekSqfJJF003KiYqLVuDrros,955
|
|
101
103
|
imio/smartweb/common/tests/test_setup.py,sha256=TxoQZrESFOgGRBN9uO8FzEOx57G550YICBd-NzaGHvY,2102
|
|
102
104
|
imio/smartweb/common/tests/test_subscribers.py,sha256=AiJ5LTqGP2JKGJFYNeCCAMJL53DWO41iYQ9VYiA7DIA,1699
|
|
@@ -112,7 +114,7 @@ imio/smartweb/common/tests/resources/image_1400x800.png,sha256=Iy3bWGH4NBlgYC5Bo
|
|
|
112
114
|
imio/smartweb/common/tests/resources/image_1800x700.png,sha256=aQcMisSFQO78c6QX4mO7qIfDb9SDRmfzTuPjxMJ_0HM,32643
|
|
113
115
|
imio/smartweb/common/tests/robot/test_example.robot,sha256=3ClbWps1Pfr6-RHT4FmmzGWGmtJrNnDW53bJGP69Nz4,2011
|
|
114
116
|
imio/smartweb/common/upgrades/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
115
|
-
imio/smartweb/common/upgrades/configure.zcml,sha256=
|
|
117
|
+
imio/smartweb/common/upgrades/configure.zcml,sha256=jaV3j9lpj6GYVhgRIMDhBwAyaqGGy929u5Xvh81N800,17424
|
|
116
118
|
imio/smartweb/common/upgrades/upgrades.py,sha256=NObmvdWPUrfgaPJhTB_tIZsmLawxg5jbui-TRLhS2NE,5474
|
|
117
119
|
imio/smartweb/common/upgrades/profiles/1007_to_1008/rolemap.xml,sha256=dKUuBq-2pO4dGtHshBisISSTBP8aWfbwTGHj_pJvY5A,1346
|
|
118
120
|
imio/smartweb/common/upgrades/profiles/1008_to_1009/actions.xml,sha256=D41gp1PDVINobhCM8L11IMXABYatjmoUuW-EQkPT804,721
|
|
@@ -136,6 +138,7 @@ imio/smartweb/common/upgrades/profiles/1029_to_1030/registry.xml,sha256=cApJaG7d
|
|
|
136
138
|
imio/smartweb/common/upgrades/profiles/1030_to_1031/registry/tinymce.xml,sha256=EYBpIVuUnQyfxntiLmXoTLiRHBzMlwzfFXmTqwMNrn0,5813
|
|
137
139
|
imio/smartweb/common/upgrades/profiles/1031_to_1032/registry/tinymce.xml,sha256=3IOzZra7FIfxDO8DDIXwygL8z92f-cUCYEXq2GsVetg,2419
|
|
138
140
|
imio/smartweb/common/upgrades/profiles/1032_to_1033/registry/tinymce.xml,sha256=V-8TeadC9Wk4aEBPOKOKd_48iSeyQEYlYXMLP2ILJ10,2421
|
|
141
|
+
imio/smartweb/common/upgrades/profiles/1033_to_1034/registry/tinymce.xml,sha256=vxpLtDuSApvFQJ5t7hI6_5cWtZ78BoycJgy1emPkhr8,2013
|
|
139
142
|
imio/smartweb/common/viewlets/__init__.py,sha256=iwhKnzeBJLKxpRVjvzwiRE63_zNpIBfaKLITauVph-0,24
|
|
140
143
|
imio/smartweb/common/viewlets/analytics.pt,sha256=J6e8fWmusLWMKXg3cJbNYV0LPS9ONz2yJ921vMvrrzE,33
|
|
141
144
|
imio/smartweb/common/viewlets/colophon.pt,sha256=qaEUV2N5aZpAEcztLzK_R2eINBod1AByWxi11B1HYa0,1278
|
|
@@ -148,10 +151,10 @@ imio/smartweb/common/viewlets/skip_to_content.py,sha256=wm22NUf8Qh5uzz8p4vkLCdFN
|
|
|
148
151
|
imio/smartweb/common/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
149
152
|
imio/smartweb/common/widgets/select.py,sha256=5sV0UIgwu4AvwpgVmJeSZwUWz7OPD9VxYMowfmjcfbA,1313
|
|
150
153
|
imio/smartweb/common/widgets/templates/ajaxselect_display.pt,sha256=v8TT2Xc19er6SUGWabgNfIdrayFYIjxyLK9H_XkIG3M,1209
|
|
151
|
-
imio_smartweb_common-1.2.
|
|
152
|
-
imio_smartweb_common-1.2.
|
|
153
|
-
imio_smartweb_common-1.2.
|
|
154
|
-
imio_smartweb_common-1.2.
|
|
155
|
-
imio_smartweb_common-1.2.
|
|
156
|
-
imio_smartweb_common-1.2.
|
|
157
|
-
imio_smartweb_common-1.2.
|
|
154
|
+
imio_smartweb_common-1.2.39.dist-info/licenses/LICENSE.GPL,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
|
|
155
|
+
imio_smartweb_common-1.2.39.dist-info/licenses/LICENSE.rst,sha256=5dd78Fdt0e-oM2ICBrMpjHnT8vEP-jhBDF7akXni6B4,655
|
|
156
|
+
imio_smartweb_common-1.2.39.dist-info/METADATA,sha256=VP43Afd73OoLbCjrSZTXMnjbKZC00dqvwS00IJUwDO4,19107
|
|
157
|
+
imio_smartweb_common-1.2.39.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
158
|
+
imio_smartweb_common-1.2.39.dist-info/namespace_packages.txt,sha256=Pg8AH8t9viMMW1hJbNZvTy_n2jXG2igIYUpon5RA4Js,19
|
|
159
|
+
imio_smartweb_common-1.2.39.dist-info/top_level.txt,sha256=ZktC0EGzThvMTAin9_q_41rzvvfMT2FYbP8pbhSLMSA,5
|
|
160
|
+
imio_smartweb_common-1.2.39.dist-info/RECORD,,
|
/imio.smartweb.common-1.2.37-py3.12-nspkg.pth → /imio.smartweb.common-1.2.39-py3.12-nspkg.pth
RENAMED
|
File without changes
|
|
File without changes
|
{imio_smartweb_common-1.2.37.dist-info → imio_smartweb_common-1.2.39.dist-info}/licenses/LICENSE.GPL
RENAMED
|
File without changes
|
{imio_smartweb_common-1.2.37.dist-info → imio_smartweb_common-1.2.39.dist-info}/licenses/LICENSE.rst
RENAMED
|
File without changes
|
|
File without changes
|
{imio_smartweb_common-1.2.37.dist-info → imio_smartweb_common-1.2.39.dist-info}/top_level.txt
RENAMED
|
File without changes
|