imio.smartweb.common 1.2.38__py3-none-any.whl → 1.2.40__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/profiles/default/metadata.xml +1 -1
- imio/smartweb/common/profiles/default/registry/tinymce.xml +3 -12
- imio/smartweb/common/rest/endpoint.py +0 -3
- imio/smartweb/common/tests/test_rest_endpoint.py +238 -0
- imio/smartweb/common/tests/test_rest_utils.py +116 -0
- imio/smartweb/common/tests/test_utils.py +16 -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/utils.py +13 -1
- {imio_smartweb_common-1.2.38.dist-info → imio_smartweb_common-1.2.40.dist-info}/METADATA +16 -1
- {imio_smartweb_common-1.2.38.dist-info → imio_smartweb_common-1.2.40.dist-info}/RECORD +17 -14
- /imio.smartweb.common-1.2.38-py3.12-nspkg.pth → /imio.smartweb.common-1.2.40-py3.13-nspkg.pth +0 -0
- {imio_smartweb_common-1.2.38.dist-info → imio_smartweb_common-1.2.40.dist-info}/WHEEL +0 -0
- {imio_smartweb_common-1.2.38.dist-info → imio_smartweb_common-1.2.40.dist-info}/licenses/LICENSE.GPL +0 -0
- {imio_smartweb_common-1.2.38.dist-info → imio_smartweb_common-1.2.40.dist-info}/licenses/LICENSE.rst +0 -0
- {imio_smartweb_common-1.2.38.dist-info → imio_smartweb_common-1.2.40.dist-info}/namespace_packages.txt +0 -0
- {imio_smartweb_common-1.2.38.dist-info → imio_smartweb_common-1.2.40.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)
|
|
@@ -164,7 +162,6 @@ class FindEndpointHandler(SearchHandler):
|
|
|
164
162
|
count = counter.get(key, 0)
|
|
165
163
|
percent = (count / total) * 100 if total > 0 else 0
|
|
166
164
|
result[label] = {"count": count, "percent": round(percent, 2)}
|
|
167
|
-
|
|
168
165
|
return result
|
|
169
166
|
|
|
170
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)
|
|
@@ -102,6 +102,22 @@ class TestUtils(unittest.TestCase):
|
|
|
102
102
|
self.assertEqual(obj.geolocation.latitude, 1)
|
|
103
103
|
self.assertEqual(obj.geolocation.longitude, 2)
|
|
104
104
|
|
|
105
|
+
def test_geocode_object_geocoder_unavailable(self):
|
|
106
|
+
obj = GeolocatedObject()
|
|
107
|
+
obj.street = "Test Street"
|
|
108
|
+
obj.number = "1"
|
|
109
|
+
obj.complement = ""
|
|
110
|
+
obj.zipcode = "12345"
|
|
111
|
+
obj.city = "Testville"
|
|
112
|
+
obj.country = "be"
|
|
113
|
+
with patch("geopy.geocoders.Nominatim") as mock_nominatim, patch(
|
|
114
|
+
"geopy.exc.GeocoderUnavailable", new=geopy.exc.GeocoderUnavailable
|
|
115
|
+
):
|
|
116
|
+
instance = mock_nominatim.return_value
|
|
117
|
+
instance.geocode.side_effect = geopy.exc.GeocoderUnavailable
|
|
118
|
+
result = geocode_object(obj)
|
|
119
|
+
self.assertFalse(result)
|
|
120
|
+
|
|
105
121
|
def test_get_uncroppable_scales_infos(self):
|
|
106
122
|
folder = api.content.create(
|
|
107
123
|
container=self.portal,
|
|
@@ -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>
|
imio/smartweb/common/utils.py
CHANGED
|
@@ -14,6 +14,7 @@ from plone.namedfile.field import NamedBlobImage
|
|
|
14
14
|
from plone.namedfile.interfaces import IAvailableSizes
|
|
15
15
|
from urllib.parse import urlparse
|
|
16
16
|
from zope.component import getUtility
|
|
17
|
+
from zope.globalrequest import getRequest
|
|
17
18
|
from zope.i18n import translate
|
|
18
19
|
from zope.schema import getFields
|
|
19
20
|
from zope.schema.interfaces import IVocabularyFactory
|
|
@@ -77,7 +78,18 @@ def geocode_object(obj):
|
|
|
77
78
|
if not address:
|
|
78
79
|
return
|
|
79
80
|
geolocator = geopy.geocoders.Nominatim(user_agent="contact@imio.be", timeout=3)
|
|
80
|
-
location =
|
|
81
|
+
location = None
|
|
82
|
+
try:
|
|
83
|
+
location = geolocator.geocode(address)
|
|
84
|
+
except geopy.exc.GeocoderUnavailable:
|
|
85
|
+
api.portal.show_message(
|
|
86
|
+
_(
|
|
87
|
+
"Error: Geolocation service is unavailable. Your content is not geocoded."
|
|
88
|
+
),
|
|
89
|
+
request=getRequest(),
|
|
90
|
+
type="warning",
|
|
91
|
+
)
|
|
92
|
+
return False
|
|
81
93
|
if location:
|
|
82
94
|
obj.geolocation = Geolocation(
|
|
83
95
|
latitude=location.latitude, longitude=location.longitude
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: imio.smartweb.common
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.40
|
|
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,21 @@ Changelog
|
|
|
178
178
|
=========
|
|
179
179
|
|
|
180
180
|
|
|
181
|
+
1.2.40 (2025-11-05)
|
|
182
|
+
-------------------
|
|
183
|
+
|
|
184
|
+
- Catch Exception on geocoding to avoid blocking content creation
|
|
185
|
+
if geopy service is down
|
|
186
|
+
[remdub]
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
1.2.39 (2025-11-03)
|
|
190
|
+
-------------------
|
|
191
|
+
|
|
192
|
+
- Migration to Plone 6.1.3
|
|
193
|
+
[boulch]
|
|
194
|
+
|
|
195
|
+
|
|
181
196
|
1.2.38 (2025-09-10)
|
|
182
197
|
-------------------
|
|
183
198
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
imio.smartweb.common-1.2.
|
|
1
|
+
imio.smartweb.common-1.2.40-py3.13-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
|
|
@@ -20,7 +20,7 @@ imio/smartweb/common/subscribers.zcml,sha256=8v2lqNNVAHlrh2G3jtjVgr3Asrw8WJLS9jS
|
|
|
20
20
|
imio/smartweb/common/testing.py,sha256=GKlYMHJUSxZFVX0o_R_c_Au1VYlkd-GjykQqfONHv7I,1985
|
|
21
21
|
imio/smartweb/common/testing.zcml,sha256=7N-ALtklyWeLSzZmlxN2Tp-aZe_3An--w_6BGl1991E,172
|
|
22
22
|
imio/smartweb/common/utility_overrides.zcml,sha256=Nn1q1FwQUyktB-EJM2qzPfsc-zUmHxX0Gj9OQTlYKQM,413
|
|
23
|
-
imio/smartweb/common/utils.py,sha256=
|
|
23
|
+
imio/smartweb/common/utils.py,sha256=Or5oV0l33wuoc9-Y4WDeVjXzGgluFZVyQWBoc_PRUjo,8195
|
|
24
24
|
imio/smartweb/common/vocabularies.py,sha256=L4H73GQxLbIRZOpn_fHBU4-J-gY6BeiYjDyU90jV0U8,4809
|
|
25
25
|
imio/smartweb/common/vocabularies.zcml,sha256=hnsIJYrof0k4gCj1wAJQtekXznvfFHIQXu-58eqXevk,1306
|
|
26
26
|
imio/smartweb/common/widgets.zcml,sha256=GQ0aJblx44OJpj0HGXiyQJnFeSaJlOTMd1RAZn_1Qgk,375
|
|
@@ -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,12 +97,14 @@ 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
|
|
103
105
|
imio/smartweb/common/tests/test_taxonomy.py,sha256=z4WawTa_hIsW7kVqQSlGBDWSnkeHAa2CPYFkfjZ0oRg,2600
|
|
104
106
|
imio/smartweb/common/tests/test_text.py,sha256=Ll6TYnYxEBtpYe3T0yTOVT9wXWJaWGWW23eUV2jK92A,1570
|
|
105
|
-
imio/smartweb/common/tests/test_utils.py,sha256=
|
|
107
|
+
imio/smartweb/common/tests/test_utils.py,sha256=7cYVfHCxsty6U1UgW1Knzpi1yfzb8IG4TLVDQ2AZ6c8,9946
|
|
106
108
|
imio/smartweb/common/tests/test_viewlets.py,sha256=NVEZWEmYo4K_-81Q6w-JTx2nT_3NBcJCl3946MAUnhU,1738
|
|
107
109
|
imio/smartweb/common/tests/test_vocabularies.py,sha256=C-78USTvOHljuMSLkNb9onSRxLEJmBQLpktetAO74RE,848
|
|
108
110
|
imio/smartweb/common/tests/test_vocabulary.py,sha256=4E5i5znt_hLhnnRay1iBaOZRmQyFJs8-OMdPm4iSGSg,1638
|
|
@@ -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.40.dist-info/licenses/LICENSE.GPL,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
|
|
155
|
+
imio_smartweb_common-1.2.40.dist-info/licenses/LICENSE.rst,sha256=5dd78Fdt0e-oM2ICBrMpjHnT8vEP-jhBDF7akXni6B4,655
|
|
156
|
+
imio_smartweb_common-1.2.40.dist-info/METADATA,sha256=bbUZwn_XP16TqLQeimrjRCkdjBeW7sHPRhmdgfXH-k0,19254
|
|
157
|
+
imio_smartweb_common-1.2.40.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
158
|
+
imio_smartweb_common-1.2.40.dist-info/namespace_packages.txt,sha256=Pg8AH8t9viMMW1hJbNZvTy_n2jXG2igIYUpon5RA4Js,19
|
|
159
|
+
imio_smartweb_common-1.2.40.dist-info/top_level.txt,sha256=ZktC0EGzThvMTAin9_q_41rzvvfMT2FYbP8pbhSLMSA,5
|
|
160
|
+
imio_smartweb_common-1.2.40.dist-info/RECORD,,
|
/imio.smartweb.common-1.2.38-py3.12-nspkg.pth → /imio.smartweb.common-1.2.40-py3.13-nspkg.pth
RENAMED
|
File without changes
|
|
File without changes
|
{imio_smartweb_common-1.2.38.dist-info → imio_smartweb_common-1.2.40.dist-info}/licenses/LICENSE.GPL
RENAMED
|
File without changes
|
{imio_smartweb_common-1.2.38.dist-info → imio_smartweb_common-1.2.40.dist-info}/licenses/LICENSE.rst
RENAMED
|
File without changes
|
|
File without changes
|
{imio_smartweb_common-1.2.38.dist-info → imio_smartweb_common-1.2.40.dist-info}/top_level.txt
RENAMED
|
File without changes
|