imio.smartweb.common 1.2.36__py3-none-any.whl → 1.2.38__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/rest/configure.zcml +10 -0
- imio/smartweb/common/rest/endpoint.py +192 -0
- {imio_smartweb_common-1.2.36.dist-info → imio_smartweb_common-1.2.38.dist-info}/METADATA +18 -1
- {imio_smartweb_common-1.2.36.dist-info → imio_smartweb_common-1.2.38.dist-info}/RECORD +11 -10
- /imio.smartweb.common-1.2.36-py3.12-nspkg.pth → /imio.smartweb.common-1.2.38-py3.12-nspkg.pth +0 -0
- {imio_smartweb_common-1.2.36.dist-info → imio_smartweb_common-1.2.38.dist-info}/WHEEL +0 -0
- {imio_smartweb_common-1.2.36.dist-info → imio_smartweb_common-1.2.38.dist-info}/licenses/LICENSE.GPL +0 -0
- {imio_smartweb_common-1.2.36.dist-info → imio_smartweb_common-1.2.38.dist-info}/licenses/LICENSE.rst +0 -0
- {imio_smartweb_common-1.2.36.dist-info → imio_smartweb_common-1.2.38.dist-info}/namespace_packages.txt +0 -0
- {imio_smartweb_common-1.2.36.dist-info → imio_smartweb_common-1.2.38.dist-info}/top_level.txt +0 -0
|
@@ -10,4 +10,14 @@
|
|
|
10
10
|
permission="zope2.View"
|
|
11
11
|
/>
|
|
12
12
|
|
|
13
|
+
<plone:service
|
|
14
|
+
name="@find"
|
|
15
|
+
method="GET"
|
|
16
|
+
accept="application/json"
|
|
17
|
+
for="zope.interface.Interface"
|
|
18
|
+
factory=".endpoint.FindEndpoint"
|
|
19
|
+
permission="zope2.View"
|
|
20
|
+
layer="imio.smartweb.common.interfaces.IImioSmartwebCommonLayer"
|
|
21
|
+
/>
|
|
22
|
+
|
|
13
23
|
</configure>
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from collections import Counter
|
|
4
|
+
from imio.smartweb.common.rest.utils import get_json
|
|
5
|
+
from plone import api
|
|
6
|
+
from plone.restapi.search.handler import SearchHandler
|
|
7
|
+
from plone.restapi.search.utils import unflatten_dotted_dict
|
|
8
|
+
from plone.restapi.services import Service
|
|
9
|
+
from Products.CMFCore.utils import getToolByName
|
|
10
|
+
from zExceptions import Unauthorized
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import logging
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger("imio.smartweb.common")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FindEndpointHandler(SearchHandler):
|
|
19
|
+
def search(self, query=None):
|
|
20
|
+
results = {"items": []}
|
|
21
|
+
portal = api.portal.get()
|
|
22
|
+
get_types_url = f"{portal.absolute_url()}/@types"
|
|
23
|
+
auth_header = self.request._orig_env.get("HTTP_AUTHORIZATION", None)
|
|
24
|
+
types = get_json(get_types_url, auth=auth_header, timeout=20)
|
|
25
|
+
if not types:
|
|
26
|
+
raise Unauthorized("No types found, you are not allowed to search")
|
|
27
|
+
self._constrain_query_by_path(query)
|
|
28
|
+
if not query.get("path"):
|
|
29
|
+
query["path"] = {"query": "/Plone"}
|
|
30
|
+
# query = self._parse_query(query)
|
|
31
|
+
if query.get("type_of_request") == "count_contents_types":
|
|
32
|
+
results = self.count_contents_types(query)
|
|
33
|
+
elif query.get("type_of_request") == "find_big_files_or_images":
|
|
34
|
+
results = self.find_big_files_or_images(query)
|
|
35
|
+
elif query.get("type_of_request") == "get_max_depth":
|
|
36
|
+
results = self.get_max_depth()
|
|
37
|
+
elif query.get("type_of_request") == "check_value_of_field":
|
|
38
|
+
results = self.check_value_of_field(
|
|
39
|
+
query.get("portal_type"),
|
|
40
|
+
query.get("field_name"),
|
|
41
|
+
query.get("expected_values"),
|
|
42
|
+
)
|
|
43
|
+
else:
|
|
44
|
+
return super().search(query)
|
|
45
|
+
return results
|
|
46
|
+
|
|
47
|
+
def count_contents_types(self, query):
|
|
48
|
+
results = {"items": []}
|
|
49
|
+
if query.get("operator") == "and":
|
|
50
|
+
query["path"]["depth"] = -1
|
|
51
|
+
lazy_resultset = self.catalog.searchResults(**query)
|
|
52
|
+
results = {
|
|
53
|
+
"items": [
|
|
54
|
+
{
|
|
55
|
+
"portal_type": query.get("portal_type"),
|
|
56
|
+
"nb_items": len(lazy_resultset),
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
else:
|
|
61
|
+
portal_types = (
|
|
62
|
+
query.get("portal_type")
|
|
63
|
+
if isinstance(query.get("portal_type"), list)
|
|
64
|
+
else [query.get("portal_type")] if query.get("portal_type") else []
|
|
65
|
+
)
|
|
66
|
+
for portal_type in portal_types:
|
|
67
|
+
new_query = query.copy()
|
|
68
|
+
new_query["portal_type"] = portal_type
|
|
69
|
+
lazy_resultset = self.catalog.searchResults(**new_query)
|
|
70
|
+
results["items"].append(
|
|
71
|
+
{"portal_type": portal_type, "nb_items": len(lazy_resultset)}
|
|
72
|
+
)
|
|
73
|
+
# fullobjects = False
|
|
74
|
+
# results = getMultiAdapter((lazy_resultset, self.request), ISerializeToJson)(
|
|
75
|
+
# fullobjects=fullobjects
|
|
76
|
+
# )
|
|
77
|
+
return results
|
|
78
|
+
|
|
79
|
+
def find_big_files_or_images(self, query):
|
|
80
|
+
SIZE_LIMIT = query.get("size", "1000000")
|
|
81
|
+
results = {"items": []}
|
|
82
|
+
lazy_resultset = self.catalog.searchResults(**query)
|
|
83
|
+
for brain in lazy_resultset:
|
|
84
|
+
obj = brain.getObject()
|
|
85
|
+
blob = getattr(obj, "file", None) or getattr(obj, "image", None)
|
|
86
|
+
if blob and hasattr(blob, "getSize"):
|
|
87
|
+
size = blob.getSize()
|
|
88
|
+
if size > int(SIZE_LIMIT):
|
|
89
|
+
results["items"].append(
|
|
90
|
+
{
|
|
91
|
+
"title": obj.title,
|
|
92
|
+
"portal_type": obj.portal_type,
|
|
93
|
+
"url": obj.absolute_url(),
|
|
94
|
+
"size": round(size / (1024 * 1024), 2),
|
|
95
|
+
}
|
|
96
|
+
)
|
|
97
|
+
return results
|
|
98
|
+
|
|
99
|
+
def get_max_depth(self):
|
|
100
|
+
portal = api.portal.get()
|
|
101
|
+
catalog = getToolByName(portal, "portal_catalog")
|
|
102
|
+
|
|
103
|
+
max_depth = 0
|
|
104
|
+
results = {"items": []}
|
|
105
|
+
|
|
106
|
+
for brain in catalog():
|
|
107
|
+
path = brain.getPath()
|
|
108
|
+
depth = len(path.strip("/").split("/"))
|
|
109
|
+
if depth > max_depth:
|
|
110
|
+
max_depth = depth
|
|
111
|
+
results["items"] = [{"path": path, "depth": depth}]
|
|
112
|
+
elif depth == max_depth:
|
|
113
|
+
results["items"].append({"path": path, "depth": depth})
|
|
114
|
+
results["max_depth"] = max_depth
|
|
115
|
+
return results
|
|
116
|
+
|
|
117
|
+
def check_value_of_field(self, portal_type, field_name, expected_values):
|
|
118
|
+
"""
|
|
119
|
+
Analyse la répartition des valeurs d'un champ dans les objets d'un content_type donné.
|
|
120
|
+
query sample : http://localhost:8085/Plone/@find?portal_type=imio.events.Event&field_name=event_type&expected_values=["activity", "event-driven"]&type_of_request=check_value_of_field
|
|
121
|
+
|
|
122
|
+
:param content_type: str, le portal_type (ex: "Event")
|
|
123
|
+
:param field_name: str, field name (ex: "event_type")
|
|
124
|
+
:param expected_values: list, list of values we want to check (ex: ["activity", "event-driven"])
|
|
125
|
+
:return: dict {valeur: {"count": n, "percent": x.xx}}
|
|
126
|
+
"""
|
|
127
|
+
brains = self.catalog(portal_type=portal_type)
|
|
128
|
+
|
|
129
|
+
total = len(brains)
|
|
130
|
+
if total == 0:
|
|
131
|
+
return {}
|
|
132
|
+
|
|
133
|
+
# Récupération et normalisation des valeurs
|
|
134
|
+
values = []
|
|
135
|
+
for brain in brains:
|
|
136
|
+
obj = brain.getObject()
|
|
137
|
+
value = getattr(obj, field_name, None)
|
|
138
|
+
if isinstance(value, (list, tuple)):
|
|
139
|
+
if not value:
|
|
140
|
+
# liste/tuple vide => on compte 1 None
|
|
141
|
+
values.append(None)
|
|
142
|
+
else:
|
|
143
|
+
mapped = [
|
|
144
|
+
None if (v in ("", "None") or v is None) else v for v in value
|
|
145
|
+
]
|
|
146
|
+
# si TOUT est None, on compte 1 None (au lieu d'en ajouter 0)
|
|
147
|
+
if all(v is None for v in mapped):
|
|
148
|
+
values.append(None)
|
|
149
|
+
else:
|
|
150
|
+
values.extend(mapped)
|
|
151
|
+
else:
|
|
152
|
+
v = None if (value in ("", "None") or value is None) else value
|
|
153
|
+
values.append(v)
|
|
154
|
+
counter = Counter(values)
|
|
155
|
+
|
|
156
|
+
# Normalisation des valeurs attendues
|
|
157
|
+
expected_values = normalize_query_param(expected_values)
|
|
158
|
+
# On garde les labels originaux pour l'affichage, mais on mappe "None" vers None réel pour le compteur
|
|
159
|
+
expected_mapping = {v: (None if v == "None" else v) for v in expected_values}
|
|
160
|
+
|
|
161
|
+
# Calcul stats
|
|
162
|
+
result = {}
|
|
163
|
+
for label, key in expected_mapping.items():
|
|
164
|
+
count = counter.get(key, 0)
|
|
165
|
+
percent = (count / total) * 100 if total > 0 else 0
|
|
166
|
+
result[label] = {"count": count, "percent": round(percent, 2)}
|
|
167
|
+
|
|
168
|
+
return result
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class FindEndpoint(Service):
|
|
172
|
+
def reply(self):
|
|
173
|
+
query = self.request.form.copy()
|
|
174
|
+
query = unflatten_dotted_dict(query)
|
|
175
|
+
return FindEndpointHandler(self.context, self.request).search(query)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def normalize_query_param(value):
|
|
179
|
+
if isinstance(value, list):
|
|
180
|
+
return value
|
|
181
|
+
|
|
182
|
+
if isinstance(value, str):
|
|
183
|
+
val = value.strip()
|
|
184
|
+
# Try to parse json list
|
|
185
|
+
if val.startswith("[") and val.endswith("]"):
|
|
186
|
+
try:
|
|
187
|
+
return json.loads(val)
|
|
188
|
+
except json.JSONDecodeError:
|
|
189
|
+
return [val]
|
|
190
|
+
else:
|
|
191
|
+
return [val]
|
|
192
|
+
return [value]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: imio.smartweb.common
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.38
|
|
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.38 (2025-09-10)
|
|
182
|
+
-------------------
|
|
183
|
+
|
|
184
|
+
- @find : Process None value in a field or ["None", "other value"] for agatha stats
|
|
185
|
+
[boulch
|
|
186
|
+
|
|
187
|
+
- Add plone.app.caching include for futur proofing override
|
|
188
|
+
[jchandelle]
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
1.2.37 (2025-09-03)
|
|
192
|
+
-------------------
|
|
193
|
+
|
|
194
|
+
- Add new @find endpoint to find content in instance
|
|
195
|
+
[boulch]
|
|
196
|
+
|
|
197
|
+
|
|
181
198
|
1.2.36 (2025-06-25)
|
|
182
199
|
-------------------
|
|
183
200
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
imio.smartweb.common-1.2.
|
|
1
|
+
imio.smartweb.common-1.2.38-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
|
|
@@ -75,7 +75,8 @@ imio/smartweb/common/profiles/testing/types/Document.xml,sha256=E3hhW_oL-WarUKcY
|
|
|
75
75
|
imio/smartweb/common/profiles/testing/types/Folder.xml,sha256=RPxIPgViAK0GGGTPKbdlw53sywyQXPhVj6B8x0tYSD8,312
|
|
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
|
-
imio/smartweb/common/rest/configure.zcml,sha256=
|
|
78
|
+
imio/smartweb/common/rest/configure.zcml,sha256=eT5gr-PcfMRxQzMME7T3nzz2Y0BVI4r9HyKHIccQC2s,575
|
|
79
|
+
imio/smartweb/common/rest/endpoint.py,sha256=6HUaGF4KgC7p0-0XSVGgUAP7WG8at1052l_zCjIkquE,7494
|
|
79
80
|
imio/smartweb/common/rest/odwb.py,sha256=zRZxowUaE76xlW_blc92vRCzToUpnzw3O5mxFXaeScU,2249
|
|
80
81
|
imio/smartweb/common/rest/search_filters.py,sha256=upZAZ6h50qiSXo0q22lUgNlcmX5elFtrIQs_jFBl1-s,3832
|
|
81
82
|
imio/smartweb/common/rest/utils.py,sha256=9zc5ge_DmVuexxN09c3U6QAITAI1s2hc0m6eZuylSxM,1269
|
|
@@ -147,10 +148,10 @@ imio/smartweb/common/viewlets/skip_to_content.py,sha256=wm22NUf8Qh5uzz8p4vkLCdFN
|
|
|
147
148
|
imio/smartweb/common/widgets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
148
149
|
imio/smartweb/common/widgets/select.py,sha256=5sV0UIgwu4AvwpgVmJeSZwUWz7OPD9VxYMowfmjcfbA,1313
|
|
149
150
|
imio/smartweb/common/widgets/templates/ajaxselect_display.pt,sha256=v8TT2Xc19er6SUGWabgNfIdrayFYIjxyLK9H_XkIG3M,1209
|
|
150
|
-
imio_smartweb_common-1.2.
|
|
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.
|
|
151
|
+
imio_smartweb_common-1.2.38.dist-info/licenses/LICENSE.GPL,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
|
|
152
|
+
imio_smartweb_common-1.2.38.dist-info/licenses/LICENSE.rst,sha256=5dd78Fdt0e-oM2ICBrMpjHnT8vEP-jhBDF7akXni6B4,655
|
|
153
|
+
imio_smartweb_common-1.2.38.dist-info/METADATA,sha256=fdTxjcsoQJD07U_hYF5HBvcyhNXJWkTHaKJfkIvycLI,19026
|
|
154
|
+
imio_smartweb_common-1.2.38.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
155
|
+
imio_smartweb_common-1.2.38.dist-info/namespace_packages.txt,sha256=Pg8AH8t9viMMW1hJbNZvTy_n2jXG2igIYUpon5RA4Js,19
|
|
156
|
+
imio_smartweb_common-1.2.38.dist-info/top_level.txt,sha256=ZktC0EGzThvMTAin9_q_41rzvvfMT2FYbP8pbhSLMSA,5
|
|
157
|
+
imio_smartweb_common-1.2.38.dist-info/RECORD,,
|
/imio.smartweb.common-1.2.36-py3.12-nspkg.pth → /imio.smartweb.common-1.2.38-py3.12-nspkg.pth
RENAMED
|
File without changes
|
|
File without changes
|
{imio_smartweb_common-1.2.36.dist-info → imio_smartweb_common-1.2.38.dist-info}/licenses/LICENSE.GPL
RENAMED
|
File without changes
|
{imio_smartweb_common-1.2.36.dist-info → imio_smartweb_common-1.2.38.dist-info}/licenses/LICENSE.rst
RENAMED
|
File without changes
|
|
File without changes
|
{imio_smartweb_common-1.2.36.dist-info → imio_smartweb_common-1.2.38.dist-info}/top_level.txt
RENAMED
|
File without changes
|