imio.smartweb.common 1.2.36__py3-none-any.whl → 1.2.37__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.
@@ -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,175 @@
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 des valeurs réelles dans les objets
134
+ values = []
135
+ for brain in brains:
136
+ obj = brain.getObject()
137
+ value = getattr(obj, field_name, None)
138
+ if isinstance(value, (list, tuple)): # cas champ multi-valué
139
+ values.extend(value)
140
+ else:
141
+ values.append(value)
142
+ counter = Counter(values)
143
+
144
+ # Calcul stats seulement pour expected_values
145
+ result = {}
146
+ expected_values = normalize_query_param(expected_values)
147
+ for val in expected_values:
148
+ count = counter.get(val, 0)
149
+ percent = (count / total) * 100 if total > 0 else 0
150
+ result[val] = {"count": count, "percent": round(percent, 2)}
151
+ return result
152
+
153
+
154
+ class FindEndpoint(Service):
155
+ def reply(self):
156
+ query = self.request.form.copy()
157
+ query = unflatten_dotted_dict(query)
158
+ return FindEndpointHandler(self.context, self.request).search(query)
159
+
160
+
161
+ def normalize_query_param(value):
162
+ if isinstance(value, list):
163
+ return value
164
+
165
+ if isinstance(value, str):
166
+ val = value.strip()
167
+ # Try to parse json list
168
+ if val.startswith("[") and val.endswith("]"):
169
+ try:
170
+ return json.loads(val)
171
+ except json.JSONDecodeError:
172
+ return [val]
173
+ else:
174
+ return [val]
175
+ return [value]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: imio.smartweb.common
3
- Version: 1.2.36
3
+ Version: 1.2.37
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,13 @@ Changelog
178
178
  =========
179
179
 
180
180
 
181
+ 1.2.37 (2025-09-03)
182
+ -------------------
183
+
184
+ - Add new @find endpoint to find content in instance
185
+ [boulch]
186
+
187
+
181
188
  1.2.36 (2025-06-25)
182
189
  -------------------
183
190
 
@@ -1,4 +1,4 @@
1
- imio.smartweb.common-1.2.36-py3.12-nspkg.pth,sha256=XZ3YhlzwpUCC8tXtelHRqxVxo3NWomIiMsUfUshrbeE,1011
1
+ imio.smartweb.common-1.2.37-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
@@ -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=8hQGCvo1EMGFe9cNUpGOcH7spKhUkglFRnRkd9IJ2eE,301
78
+ imio/smartweb/common/rest/configure.zcml,sha256=eT5gr-PcfMRxQzMME7T3nzz2Y0BVI4r9HyKHIccQC2s,575
79
+ imio/smartweb/common/rest/endpoint.py,sha256=hgQsZW8VV9kOiupnSJfSTcXEecv2iAn76KSum5ysjnY,6712
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.36.dist-info/licenses/LICENSE.GPL,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
151
- imio_smartweb_common-1.2.36.dist-info/licenses/LICENSE.rst,sha256=5dd78Fdt0e-oM2ICBrMpjHnT8vEP-jhBDF7akXni6B4,655
152
- imio_smartweb_common-1.2.36.dist-info/METADATA,sha256=5sVMJ5OumSl-oBKCj17pKY9mOwW_SXs38N_Z_etnIEQ,18706
153
- imio_smartweb_common-1.2.36.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
154
- imio_smartweb_common-1.2.36.dist-info/namespace_packages.txt,sha256=Pg8AH8t9viMMW1hJbNZvTy_n2jXG2igIYUpon5RA4Js,19
155
- imio_smartweb_common-1.2.36.dist-info/top_level.txt,sha256=ZktC0EGzThvMTAin9_q_41rzvvfMT2FYbP8pbhSLMSA,5
156
- imio_smartweb_common-1.2.36.dist-info/RECORD,,
151
+ imio_smartweb_common-1.2.37.dist-info/licenses/LICENSE.GPL,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
152
+ imio_smartweb_common-1.2.37.dist-info/licenses/LICENSE.rst,sha256=5dd78Fdt0e-oM2ICBrMpjHnT8vEP-jhBDF7akXni6B4,655
153
+ imio_smartweb_common-1.2.37.dist-info/METADATA,sha256=kCY4-SImXYTNlaTQ8SA2iHzNOjaM2B8-wSxhJz_Q-ts,18813
154
+ imio_smartweb_common-1.2.37.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
155
+ imio_smartweb_common-1.2.37.dist-info/namespace_packages.txt,sha256=Pg8AH8t9viMMW1hJbNZvTy_n2jXG2igIYUpon5RA4Js,19
156
+ imio_smartweb_common-1.2.37.dist-info/top_level.txt,sha256=ZktC0EGzThvMTAin9_q_41rzvvfMT2FYbP8pbhSLMSA,5
157
+ imio_smartweb_common-1.2.37.dist-info/RECORD,,