ims.fieldupdater 3.0.1__py3-none-any.whl → 3.0.3__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.
- ims/fieldupdater/__init__.py +4 -4
- ims/fieldupdater/browser/__init__.py +1 -1
- ims/fieldupdater/browser/configure.zcml +15 -15
- ims/fieldupdater/browser/mass.pt +365 -265
- ims/fieldupdater/browser/mass.py +369 -362
- ims/fieldupdater/browser/schema.pt +66 -66
- ims/fieldupdater/configure.zcml +20 -20
- ims/fieldupdater/profiles/default/controlpanel.xml +10 -10
- ims/fieldupdater/profiles/default/metadata.xml +3 -3
- ims/fieldupdater/profiles/uninstall/controlpanel.xml +4 -4
- ims/fieldupdater/testing.py +53 -53
- {ims.fieldupdater-3.0.1.dist-info → ims_fieldupdater-3.0.3.dist-info}/METADATA +1 -1
- ims_fieldupdater-3.0.3.dist-info/RECORD +15 -0
- {ims.fieldupdater-3.0.1.dist-info → ims_fieldupdater-3.0.3.dist-info}/WHEEL +1 -1
- ims.fieldupdater-3.0.1.dist-info/RECORD +0 -15
- {ims.fieldupdater-3.0.1.dist-info → ims_fieldupdater-3.0.3.dist-info}/entry_points.txt +0 -0
ims/fieldupdater/browser/mass.py
CHANGED
|
@@ -1,362 +1,369 @@
|
|
|
1
|
-
import datetime
|
|
2
|
-
|
|
3
|
-
import plone.api as api
|
|
4
|
-
from
|
|
5
|
-
from
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from plone.
|
|
9
|
-
from
|
|
10
|
-
from
|
|
11
|
-
from z3c.form.interfaces import
|
|
12
|
-
from zope.component import getMultiAdapter, getUtilitiesFor, queryUtility
|
|
13
|
-
from zope.event import notify
|
|
14
|
-
from zope.lifecycleevent import ObjectModifiedEvent
|
|
15
|
-
from zope.schema.interfaces import WrongType
|
|
16
|
-
|
|
17
|
-
from .. import _
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def get_behav(name):
|
|
21
|
-
behav = queryUtility(IBehavior, name=name)
|
|
22
|
-
if behav is not None:
|
|
23
|
-
return behav.interface
|
|
24
|
-
else:
|
|
25
|
-
return resolveDottedName(name)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class MassEditForm(BrowserView):
|
|
29
|
-
template = ViewPageTemplateFile("mass.pt")
|
|
30
|
-
unsupported = None
|
|
31
|
-
|
|
32
|
-
def __call__(self):
|
|
33
|
-
if not self.request.form.get("form.button.Merge", "") and not self.request.form.get("form.button.Delete", ""):
|
|
34
|
-
return self.template({})
|
|
35
|
-
|
|
36
|
-
schema = self.request.get("schema", None)
|
|
37
|
-
field = self.request.get("field", None)
|
|
38
|
-
fkey = self.request.get("fkey", None)
|
|
39
|
-
match = self.request.get("match", None)
|
|
40
|
-
replacement = self.request.get("replacement_marker", None)
|
|
41
|
-
|
|
42
|
-
if self.request.form.get("form.button.Merge", ""):
|
|
43
|
-
if replacement:
|
|
44
|
-
try:
|
|
45
|
-
self.replace_term(schema, field, fkey, match)
|
|
46
|
-
except Exception as e:
|
|
47
|
-
api.portal.show_message(
|
|
48
|
-
message=f"Failed to validate: {e.__repr__()}", request=self.request, type="error"
|
|
49
|
-
)
|
|
50
|
-
else:
|
|
51
|
-
api.portal.show_message(message="Please enter a replacement value.", request=self.request)
|
|
52
|
-
elif self.request.form.get("form.button.Delete", ""):
|
|
53
|
-
try:
|
|
54
|
-
self.delete_term(schema, field, fkey, match)
|
|
55
|
-
except Exception as e:
|
|
56
|
-
api.portal.show_message(
|
|
57
|
-
message=f"Failed to validate: {e.__repr__()}", request=self.request, type="error"
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
return self.template()
|
|
61
|
-
|
|
62
|
-
def get_schemas(self):
|
|
63
|
-
"""
|
|
64
|
-
Our source of schemas is the object_provides KeywordIndex catalog in the catalog as well as Dexterity
|
|
65
|
-
Behavior schema cache.
|
|
66
|
-
:return: dotted name of interfaces
|
|
67
|
-
"""
|
|
68
|
-
behaviors = tuple([behav[1].interface.__identifier__ for behav in getUtilitiesFor(IBehavior)])
|
|
69
|
-
catalog = api.portal.get_tool("portal_catalog")
|
|
70
|
-
interfaces = sorted(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
"""
|
|
93
|
-
|
|
94
|
-
:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
interface
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
""
|
|
127
|
-
|
|
128
|
-
:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
elif field_value and isinstance(field_value,
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
1
|
+
import datetime
|
|
2
|
+
|
|
3
|
+
import plone.api as api
|
|
4
|
+
from collective.z3cform.datagridfield.row import DictRow
|
|
5
|
+
from DateTime import DateTime
|
|
6
|
+
from plone.behavior.interfaces import IBehavior
|
|
7
|
+
from plone.dexterity.events import EditFinishedEvent
|
|
8
|
+
from plone.dexterity.utils import resolveDottedName
|
|
9
|
+
from Products.Five import BrowserView
|
|
10
|
+
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
|
|
11
|
+
from z3c.form.interfaces import NO_VALUE, IDataConverter, IFieldWidget
|
|
12
|
+
from zope.component import getMultiAdapter, getUtilitiesFor, queryUtility
|
|
13
|
+
from zope.event import notify
|
|
14
|
+
from zope.lifecycleevent import ObjectModifiedEvent
|
|
15
|
+
from zope.schema.interfaces import WrongType
|
|
16
|
+
|
|
17
|
+
from .. import _
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_behav(name):
|
|
21
|
+
behav = queryUtility(IBehavior, name=name)
|
|
22
|
+
if behav is not None:
|
|
23
|
+
return behav.interface
|
|
24
|
+
else:
|
|
25
|
+
return resolveDottedName(name)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class MassEditForm(BrowserView):
|
|
29
|
+
template = ViewPageTemplateFile("mass.pt")
|
|
30
|
+
unsupported = None
|
|
31
|
+
|
|
32
|
+
def __call__(self):
|
|
33
|
+
if not self.request.form.get("form.button.Merge", "") and not self.request.form.get("form.button.Delete", ""):
|
|
34
|
+
return self.template({})
|
|
35
|
+
|
|
36
|
+
schema = self.request.get("schema", None)
|
|
37
|
+
field = self.request.get("field", None)
|
|
38
|
+
fkey = self.request.get("fkey", None)
|
|
39
|
+
match = self.request.get("match", None)
|
|
40
|
+
replacement = self.request.get("replacement_marker", None)
|
|
41
|
+
|
|
42
|
+
if self.request.form.get("form.button.Merge", ""):
|
|
43
|
+
if replacement:
|
|
44
|
+
try:
|
|
45
|
+
self.replace_term(schema, field, fkey, match)
|
|
46
|
+
except Exception as e:
|
|
47
|
+
api.portal.show_message(
|
|
48
|
+
message=f"Failed to validate: {e.__repr__()}", request=self.request, type="error"
|
|
49
|
+
)
|
|
50
|
+
else:
|
|
51
|
+
api.portal.show_message(message="Please enter a replacement value.", request=self.request)
|
|
52
|
+
elif self.request.form.get("form.button.Delete", ""):
|
|
53
|
+
try:
|
|
54
|
+
self.delete_term(schema, field, fkey, match)
|
|
55
|
+
except Exception as e:
|
|
56
|
+
api.portal.show_message(
|
|
57
|
+
message=f"Failed to validate: {e.__repr__()}", request=self.request, type="error"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
return self.template()
|
|
61
|
+
|
|
62
|
+
def get_schemas(self):
|
|
63
|
+
"""
|
|
64
|
+
Our source of schemas is the object_provides KeywordIndex catalog in the catalog as well as Dexterity
|
|
65
|
+
Behavior schema cache.
|
|
66
|
+
:return: dotted name of interfaces
|
|
67
|
+
"""
|
|
68
|
+
behaviors = tuple([behav[1].interface.__identifier__ for behav in getUtilitiesFor(IBehavior)])
|
|
69
|
+
catalog = api.portal.get_tool("portal_catalog")
|
|
70
|
+
interfaces = sorted(
|
|
71
|
+
set(catalog.uniqueValuesFor("object_provides") + behaviors), key=lambda term: term.split(".")[-1]
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
for interface in interfaces:
|
|
75
|
+
try:
|
|
76
|
+
if get_behav(interface).names():
|
|
77
|
+
yield {
|
|
78
|
+
"id": interface,
|
|
79
|
+
"title": interface.split(".")[-1],
|
|
80
|
+
}
|
|
81
|
+
except ModuleNotFoundError:
|
|
82
|
+
api.portal.show_message(
|
|
83
|
+
f"Failed to find interface: {interface}. This is registered as a behavior or noted as a provided interface in the catalog, but the class does not exist.",
|
|
84
|
+
type="error",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
def schema_matches(self, schema):
|
|
88
|
+
"""
|
|
89
|
+
Get a count of matches
|
|
90
|
+
:param schema: dotted name interface
|
|
91
|
+
:return: int
|
|
92
|
+
"""
|
|
93
|
+
catalog = api.portal.get_tool("portal_catalog")
|
|
94
|
+
if schema in catalog.uniqueValuesFor("object_provides"):
|
|
95
|
+
return len(catalog(object_provides=schema))
|
|
96
|
+
else:
|
|
97
|
+
return "unknown (cannot get a subset for behavior interfaces)"
|
|
98
|
+
|
|
99
|
+
def get_fields(self):
|
|
100
|
+
"""
|
|
101
|
+
Get all fields for a schema
|
|
102
|
+
:return: fields
|
|
103
|
+
"""
|
|
104
|
+
schema = self.request.get("schema", None)
|
|
105
|
+
if not schema:
|
|
106
|
+
return
|
|
107
|
+
interface = get_behav(schema)
|
|
108
|
+
for name in interface.names():
|
|
109
|
+
if interface[name] and hasattr(interface[name], "title"):
|
|
110
|
+
yield {"id": name, "title": f"{interface[name].title} [{name}]"}
|
|
111
|
+
|
|
112
|
+
def is_dg(self):
|
|
113
|
+
"""
|
|
114
|
+
DataGridField (collective.z3cform.datagridfield) support. Lists with dicts
|
|
115
|
+
:return: bool
|
|
116
|
+
"""
|
|
117
|
+
schema = self.request.get("schema", None)
|
|
118
|
+
field = self.request.get("field", None)
|
|
119
|
+
if not field or not schema:
|
|
120
|
+
return
|
|
121
|
+
interface = get_behav(schema)
|
|
122
|
+
return hasattr(interface[field], "value_type") and isinstance(interface[field].value_type, DictRow)
|
|
123
|
+
|
|
124
|
+
def get_dgschema(self):
|
|
125
|
+
"""Look up the schema used for the dg field"""
|
|
126
|
+
schema = self.request.get("schema", None)
|
|
127
|
+
field = self.request.get("field", None)
|
|
128
|
+
if not field or not schema:
|
|
129
|
+
return
|
|
130
|
+
interface = get_behav(schema)
|
|
131
|
+
return interface[field].value_type.schema
|
|
132
|
+
|
|
133
|
+
def get_dgkeys(self):
|
|
134
|
+
"""
|
|
135
|
+
Get DataGridField schema keys
|
|
136
|
+
:return:
|
|
137
|
+
"""
|
|
138
|
+
dg_schema = self.get_dgschema()
|
|
139
|
+
for dg_field in dg_schema.names():
|
|
140
|
+
yield {
|
|
141
|
+
"id": dg_field,
|
|
142
|
+
"title": dg_schema[dg_field].title,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
def get_values(self): # noqa: C901
|
|
146
|
+
"""
|
|
147
|
+
Find all the current values for objects that provide this schema
|
|
148
|
+
|
|
149
|
+
:return: values
|
|
150
|
+
"""
|
|
151
|
+
self.unsupported = None
|
|
152
|
+
schema = self.request.get("schema", None)
|
|
153
|
+
field = self.request.get("field", None)
|
|
154
|
+
fkey = self.request.get("fkey", None)
|
|
155
|
+
|
|
156
|
+
if (self.is_dg() and not (schema and field and fkey)) or (not self.is_dg() and not (schema and field)):
|
|
157
|
+
return
|
|
158
|
+
values = set()
|
|
159
|
+
catalog = api.portal.get_tool("portal_catalog")
|
|
160
|
+
query = catalog(object_provides=schema) if schema in catalog.uniqueValuesFor("object_provides") else catalog()
|
|
161
|
+
|
|
162
|
+
for brain in query:
|
|
163
|
+
obj = brain.getObject()
|
|
164
|
+
field_value = getattr(obj, field, None)
|
|
165
|
+
if not field_value:
|
|
166
|
+
continue
|
|
167
|
+
if field_value and isinstance(field_value, str):
|
|
168
|
+
values.add(field_value)
|
|
169
|
+
elif field_value and isinstance(field_value, tuple | list):
|
|
170
|
+
for item_value in field_value:
|
|
171
|
+
if fkey: # dg
|
|
172
|
+
if item_value[fkey] and isinstance(item_value[fkey], str):
|
|
173
|
+
values.add(item_value[fkey])
|
|
174
|
+
else: # regular list or tuple
|
|
175
|
+
if item_value and isinstance(item_value, str):
|
|
176
|
+
values.add(item_value)
|
|
177
|
+
elif field_value and isinstance(field_value, datetime.datetime | datetime.date):
|
|
178
|
+
values.add(field_value)
|
|
179
|
+
else:
|
|
180
|
+
self.unsupported = field_value.__class__.__name__
|
|
181
|
+
return sorted(values)
|
|
182
|
+
|
|
183
|
+
def results(self):
|
|
184
|
+
"""
|
|
185
|
+
Find all of the content that matches the current selection
|
|
186
|
+
|
|
187
|
+
:return: brains
|
|
188
|
+
"""
|
|
189
|
+
schema = self.request.get("schema", None)
|
|
190
|
+
field = self.request.get("field", None)
|
|
191
|
+
fkey = self.request.get("fkey", None)
|
|
192
|
+
match = self.request.get("match", None)
|
|
193
|
+
if not schema or not field:
|
|
194
|
+
return
|
|
195
|
+
if self.is_dg() and not fkey:
|
|
196
|
+
return
|
|
197
|
+
if not match:
|
|
198
|
+
return
|
|
199
|
+
|
|
200
|
+
catalog = api.portal.get_tool("portal_catalog")
|
|
201
|
+
_results = []
|
|
202
|
+
query = catalog(object_provides=schema) if schema in catalog.uniqueValuesFor("object_provides") else catalog()
|
|
203
|
+
for brain in query:
|
|
204
|
+
obj = brain.getObject()
|
|
205
|
+
field_value = getattr(obj, field, None)
|
|
206
|
+
checks = [
|
|
207
|
+
field_value == match and isinstance(field_value, str), # str
|
|
208
|
+
isinstance(field_value, tuple | list) and match in field_value, # iterator
|
|
209
|
+
fkey and match in [item_value[fkey] for item_value in field_value], # dg
|
|
210
|
+
isinstance(field_value, datetime.date) and DateTime(match).asdatetime().date() == field_value,
|
|
211
|
+
isinstance(field_value, datetime.date) and DateTime(match).asdatetime() == field_value,
|
|
212
|
+
]
|
|
213
|
+
if any(checks):
|
|
214
|
+
_results.append(brain)
|
|
215
|
+
return _results
|
|
216
|
+
|
|
217
|
+
def replace_term(self, schema, field, fkey, match):
|
|
218
|
+
"""
|
|
219
|
+
Replace a field term. For multi valued fields it will replace just the matching part of it. For single
|
|
220
|
+
valued fields it replaces the whole field, where it matches. We get the widget from the actual schema field
|
|
221
|
+
so we can use it to extract and parse the appropriate value too (!)
|
|
222
|
+
|
|
223
|
+
:param schema: dotted name schema (str)
|
|
224
|
+
:param field: field
|
|
225
|
+
:param fkey: boolean, representing DateGridField
|
|
226
|
+
:param match: the value being matched
|
|
227
|
+
:return: None
|
|
228
|
+
"""
|
|
229
|
+
widget = self.replacement_widget
|
|
230
|
+
replacement = widget.extract()
|
|
231
|
+
if replacement is not NO_VALUE:
|
|
232
|
+
try:
|
|
233
|
+
replacement = IDataConverter(widget).toFieldValue(replacement)
|
|
234
|
+
except WrongType:
|
|
235
|
+
# for some reason some things that should come in as unicode are coming in as strings
|
|
236
|
+
replacement = IDataConverter(widget).toFieldValue(IDataConverter(widget).toWidgetValue(replacement))
|
|
237
|
+
if not replacement or replacement is NO_VALUE:
|
|
238
|
+
api.portal.show_message(message=_("No replacement value given"), request=self.request, type="error")
|
|
239
|
+
return
|
|
240
|
+
|
|
241
|
+
results = self.results()
|
|
242
|
+
for brain in results:
|
|
243
|
+
self.set_value_by_type(brain, schema, field, fkey, match, replacement)
|
|
244
|
+
api.portal.show_message(
|
|
245
|
+
message=_(f"Replaced term in {len(results)} records"), request=self.request, type="info"
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
def set_value_by_type(self, brain, schema, field, fkey, match, replacement):
|
|
249
|
+
"""Set value based on field value type"""
|
|
250
|
+
obj = brain.getObject()
|
|
251
|
+
|
|
252
|
+
field_value = getattr(obj, field, None)
|
|
253
|
+
if isinstance(field_value, str | datetime.date | datetime.datetime):
|
|
254
|
+
self.set_value(obj, schema, field, replacement)
|
|
255
|
+
elif isinstance(field_value, tuple | list):
|
|
256
|
+
if fkey:
|
|
257
|
+
for item_value in field_value:
|
|
258
|
+
if item_value[fkey] == match:
|
|
259
|
+
item_value[fkey] = replacement
|
|
260
|
+
self.set_value(obj, schema, field, field_value)
|
|
261
|
+
else:
|
|
262
|
+
if replacement in field_value:
|
|
263
|
+
field_value = [item_value for item_value in field_value if item_value != match]
|
|
264
|
+
else:
|
|
265
|
+
field_value = [(item_value == match and replacement) or item_value for item_value in field_value]
|
|
266
|
+
self.set_value(obj, schema, field, field_value)
|
|
267
|
+
|
|
268
|
+
def delete_term(self, schema, field, fkey, match):
|
|
269
|
+
"""
|
|
270
|
+
Delete this field. For single value fields, this is set to None - it must still pass validation so
|
|
271
|
+
required fields should fail.
|
|
272
|
+
:param schema: dotted name schema (str)
|
|
273
|
+
:param field: field
|
|
274
|
+
:param fkey: boolean, representing DateGridField
|
|
275
|
+
:param match: the value being matched
|
|
276
|
+
:return: None
|
|
277
|
+
"""
|
|
278
|
+
|
|
279
|
+
for brain in self.results():
|
|
280
|
+
obj = brain.getObject()
|
|
281
|
+
field_value = getattr(obj, field, None)
|
|
282
|
+
|
|
283
|
+
if isinstance(field_value, tuple | list):
|
|
284
|
+
if fkey:
|
|
285
|
+
for item_value in field_value:
|
|
286
|
+
if item_value[fkey] == match:
|
|
287
|
+
item_value[fkey] = None
|
|
288
|
+
# if this was the only value in the row, delete the row
|
|
289
|
+
field_value = [
|
|
290
|
+
item_value for item_value in field_value if [i for i in list(item_value.values()) if i]
|
|
291
|
+
]
|
|
292
|
+
else:
|
|
293
|
+
field_value = [item_value for item_value in field_value if item_value != match]
|
|
294
|
+
self.set_value(obj, schema, field, field_value)
|
|
295
|
+
else:
|
|
296
|
+
self.set_value(obj, schema, field, None)
|
|
297
|
+
api.portal.show_message(
|
|
298
|
+
message=_(f"Removed term in {len(self.results())} records"), request=self.request, type="info"
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
def set_value(self, obj, dottedname, field, field_value, attempts=0):
|
|
302
|
+
"""
|
|
303
|
+
Set the value for an object's field. Values must be validated as defined in the schema. This can be called
|
|
304
|
+
recursively as a sort of hack where zope.schema gets finicky between unicode and str.
|
|
305
|
+
:param obj: the object being modified
|
|
306
|
+
:param dottedname: non-resolved schema interface
|
|
307
|
+
:param field: field being changed
|
|
308
|
+
:param field_value: the new value of the field. In this case, for multi values, it is the entire value
|
|
309
|
+
:param attempts: recursive attempts made
|
|
310
|
+
:return:
|
|
311
|
+
"""
|
|
312
|
+
attempt_limit = 1 # some of the more common validation problems are unicode where it expects ascii
|
|
313
|
+
# or vice versa. Try once
|
|
314
|
+
schema = get_behav(dottedname)
|
|
315
|
+
bound = schema[field].bind(obj)
|
|
316
|
+
try:
|
|
317
|
+
bound.validate(field_value)
|
|
318
|
+
except WrongType as e:
|
|
319
|
+
if attempts < attempt_limit:
|
|
320
|
+
attempts += 1
|
|
321
|
+
if isinstance(field_value, str):
|
|
322
|
+
field_value = str(field_value)
|
|
323
|
+
return self.set_value(obj, dottedname, field, field_value, attempts)
|
|
324
|
+
else:
|
|
325
|
+
api.portal.show_message(
|
|
326
|
+
message=f"Failed to validate: {e.__repr__()}", request=self.request, type="error"
|
|
327
|
+
)
|
|
328
|
+
else:
|
|
329
|
+
setattr(obj, field, field_value)
|
|
330
|
+
notify(ObjectModifiedEvent(obj))
|
|
331
|
+
notify(EditFinishedEvent(obj))
|
|
332
|
+
obj.reindexObject()
|
|
333
|
+
|
|
334
|
+
@property
|
|
335
|
+
def replacement_widget(self):
|
|
336
|
+
"""
|
|
337
|
+
Get a widget for use in getting the replacement value. If the widget has a value_type, assume we want to
|
|
338
|
+
render just that part. For instance, if it's a list we are only replacing one value in it so a multi value
|
|
339
|
+
widget wouldn't make sense. So for schema.List(value_type=Choice()) we would render that Choice
|
|
340
|
+
:return: widget
|
|
341
|
+
"""
|
|
342
|
+
if self.is_dg():
|
|
343
|
+
fkey = self.request["fkey"]
|
|
344
|
+
field = self.get_dgschema()[fkey]
|
|
345
|
+
else:
|
|
346
|
+
schema = self.request.get("schema", None)
|
|
347
|
+
schema = get_behav(schema)
|
|
348
|
+
field = self.request.get("field", None)
|
|
349
|
+
field = schema[field]
|
|
350
|
+
if hasattr(field, "value_type"):
|
|
351
|
+
field = field.value_type
|
|
352
|
+
|
|
353
|
+
widget = getMultiAdapter((field, self.request), IFieldWidget)
|
|
354
|
+
widget.name = "replacement"
|
|
355
|
+
widget.update()
|
|
356
|
+
return widget
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
class SchemaFinderForm(BrowserView):
|
|
360
|
+
def get_types(self):
|
|
361
|
+
catalog = api.portal.get_tool("portal_catalog")
|
|
362
|
+
return sorted(catalog.uniqueValuesFor("portal_type"))
|
|
363
|
+
|
|
364
|
+
def schemas(self):
|
|
365
|
+
content_type = self.request["content_type"]
|
|
366
|
+
type_info = api.portal.get_tool("portal_types").getTypeInfo(content_type)
|
|
367
|
+
yield {"id": type_info.schema, "title": type_info.schema.split(".")[-1]}
|
|
368
|
+
for behav in type_info.behaviors:
|
|
369
|
+
yield {"id": behav, "title": behav.split(".")[-1]}
|