|
1
|
|
|
# -*- coding: utf-8 -*- |
|
2
|
|
|
# |
|
3
|
|
|
# This file is part of SENAITE.CORE |
|
4
|
|
|
# |
|
5
|
|
|
# Copyright 2018 by it's authors. |
|
6
|
|
|
# Some rights reserved. See LICENSE.rst, CONTRIBUTORS.rst. |
|
7
|
|
|
|
|
8
|
|
|
from datetime import datetime |
|
9
|
|
|
|
|
10
|
|
|
from BTrees.OOBTree import OOBTree |
|
11
|
|
|
from DateTime import DateTime |
|
12
|
|
|
from Products.Five.browser import BrowserView |
|
13
|
|
|
from plone import protect |
|
14
|
|
|
from plone.memoize.volatile import cache |
|
15
|
|
|
from zope.annotation.interfaces import IAnnotations |
|
16
|
|
|
from zope.i18n.locales import locales |
|
17
|
|
|
from zope.interface import implements |
|
18
|
|
|
from zope.publisher.interfaces import IPublishTraverse |
|
19
|
|
|
|
|
20
|
|
|
from bika.lims import api |
|
21
|
|
|
from bika.lims import logger |
|
22
|
|
|
from bika.lims.utils import cache_key |
|
23
|
|
|
from bika.lims.utils import returns_json |
|
24
|
|
|
|
|
25
|
|
|
|
|
26
|
|
|
class BaseManageAddView(BrowserView): |
|
27
|
|
|
""" |
|
28
|
|
|
Base view to create a visibility and order manager for an Add View. |
|
29
|
|
|
""" |
|
30
|
|
|
template = None |
|
31
|
|
|
|
|
32
|
|
|
def __init__(self, context, request): |
|
33
|
|
|
BrowserView.__init__(self, context, request) |
|
34
|
|
|
self.context = context |
|
35
|
|
|
self.request = request |
|
36
|
|
|
self.tmp_obj = None |
|
37
|
|
|
self.CONFIGURATION_STORAGE = None |
|
38
|
|
|
self.SKIP_FIELD_ON_COPY = [] |
|
39
|
|
|
|
|
40
|
|
|
def __call__(self): |
|
41
|
|
|
protect.CheckAuthenticator(self.request.form) |
|
42
|
|
|
form = self.request.form |
|
43
|
|
|
if form.get("submitted", False) and form.get("save", False): |
|
44
|
|
|
order = form.get("order") |
|
45
|
|
|
self.set_field_order(order) |
|
46
|
|
|
visibility = form.get("visibility") |
|
47
|
|
|
self.set_field_visibility(visibility) |
|
48
|
|
|
if form.get("submitted", False) and form.get("reset", False): |
|
49
|
|
|
self.flush() |
|
50
|
|
|
return self.template() |
|
51
|
|
|
|
|
52
|
|
|
def get_obj(self): |
|
53
|
|
|
""" |
|
54
|
|
|
This method should return a temporary object. This object should be |
|
55
|
|
|
of the same type as the one we are creating. |
|
56
|
|
|
:return: ATObjectType |
|
57
|
|
|
""" |
|
58
|
|
|
raise NotImplementedError("get_obj is not implemented.") |
|
59
|
|
|
|
|
60
|
|
|
def get_annotation(self): |
|
61
|
|
|
bika_setup = api.get_bika_setup() |
|
62
|
|
|
return IAnnotations(bika_setup) |
|
63
|
|
|
|
|
64
|
|
|
@property |
|
65
|
|
|
def storage(self): |
|
66
|
|
|
annotation = self.get_annotation() |
|
67
|
|
|
if annotation.get(self.CONFIGURATION_STORAGE) is None: |
|
68
|
|
|
annotation[self.CONFIGURATION_STORAGE] = OOBTree() |
|
69
|
|
|
return annotation[self.CONFIGURATION_STORAGE] |
|
70
|
|
|
|
|
71
|
|
|
def flush(self): |
|
72
|
|
|
annotation = self.get_annotation() |
|
73
|
|
|
if annotation.get(self.CONFIGURATION_STORAGE) is not None: |
|
74
|
|
|
del annotation[self.CONFIGURATION_STORAGE] |
|
75
|
|
|
|
|
76
|
|
|
def set_field_order(self, order): |
|
77
|
|
|
self.storage.update({"order": order}) |
|
78
|
|
|
|
|
79
|
|
|
def get_field_order(self): |
|
80
|
|
|
order = self.storage.get("order") |
|
81
|
|
|
if order is None: |
|
82
|
|
|
return map(lambda f: f.getName(), self.get_fields()) |
|
83
|
|
|
return order |
|
84
|
|
|
|
|
85
|
|
|
def set_field_visibility(self, visibility): |
|
86
|
|
|
self.storage.update({"visibility": visibility}) |
|
87
|
|
|
|
|
88
|
|
|
def get_field_visibility(self): |
|
89
|
|
|
return self.storage.get("visibility") |
|
90
|
|
|
|
|
91
|
|
|
def is_field_visible(self, field): |
|
92
|
|
|
if field.required: |
|
93
|
|
|
return True |
|
94
|
|
|
visibility = self.get_field_visibility() |
|
95
|
|
|
if visibility is None: |
|
96
|
|
|
return True |
|
97
|
|
|
return visibility.get(field.getName(), True) |
|
98
|
|
|
|
|
99
|
|
|
def get_field(self, name): |
|
100
|
|
|
"""Get a field of the object by name |
|
101
|
|
|
""" |
|
102
|
|
|
obj = self.get_obj() |
|
103
|
|
|
return obj.getField(name) |
|
104
|
|
|
|
|
105
|
|
|
def get_fields(self): |
|
106
|
|
|
"""Return all Object fields |
|
107
|
|
|
""" |
|
108
|
|
|
obj = self.get_obj() |
|
109
|
|
|
return obj.Schema().fields() |
|
110
|
|
|
|
|
111
|
|
|
def get_sorted_fields(self): |
|
112
|
|
|
"""Return the sorted fields |
|
113
|
|
|
""" |
|
114
|
|
|
inf = float("inf") |
|
115
|
|
|
order = self.get_field_order() |
|
116
|
|
|
|
|
117
|
|
|
def field_cmp(field1, field2): |
|
118
|
|
|
_n1 = field1.getName() |
|
119
|
|
|
_n2 = field2.getName() |
|
120
|
|
|
_i1 = _n1 in order and order.index(_n1) + 1 or inf |
|
121
|
|
|
_i2 = _n2 in order and order.index(_n2) + 1 or inf |
|
122
|
|
|
return cmp(_i1, _i2) |
|
123
|
|
|
|
|
124
|
|
|
return sorted(self.get_fields(), cmp=field_cmp) |
|
125
|
|
|
|
|
126
|
|
|
def get_fields_with_visibility(self, visibility="edit", mode="add"): |
|
127
|
|
|
"""Return the fields with visibility |
|
128
|
|
|
""" |
|
129
|
|
|
fields = self.get_sorted_fields() |
|
130
|
|
|
|
|
131
|
|
|
out = [] |
|
132
|
|
|
|
|
133
|
|
|
for field in fields: |
|
134
|
|
|
v = field.widget.isVisible( |
|
135
|
|
|
self.context, mode, default='invisible', field=field) |
|
136
|
|
|
|
|
137
|
|
|
if self.is_field_visible(field) is False: |
|
138
|
|
|
v = "hidden" |
|
139
|
|
|
|
|
140
|
|
|
visibility_guard = True |
|
141
|
|
|
# visibility_guard is a widget field defined in the schema in order |
|
142
|
|
|
# to know the visibility of the widget when the field is related to |
|
143
|
|
|
# a dynamically changing content such as workflows. For instance |
|
144
|
|
|
# those fields related to the workflow will be displayed only if |
|
145
|
|
|
# the workflow is enabled, otherwise they should not be shown. |
|
146
|
|
|
if 'visibility_guard' in dir(field.widget): |
|
147
|
|
|
visibility_guard = eval(field.widget.visibility_guard) |
|
148
|
|
|
if v == visibility and visibility_guard: |
|
149
|
|
|
out.append(field) |
|
150
|
|
|
|
|
151
|
|
|
return out |
|
152
|
|
|
|
|
153
|
|
|
|
|
154
|
|
|
class BaseAddView(BrowserView): |
|
155
|
|
|
""" |
|
156
|
|
|
Base Object Add view |
|
157
|
|
|
|
|
158
|
|
|
This class offers the necessary methods to create a Add view. |
|
159
|
|
|
""" |
|
160
|
|
|
template = None |
|
161
|
|
|
|
|
162
|
|
|
def __init__(self, context, request): |
|
163
|
|
|
BrowserView.__init__(self, context, request) |
|
164
|
|
|
self.request = request |
|
165
|
|
|
self.context = context |
|
166
|
|
|
self.fieldvalues = {} |
|
167
|
|
|
self.tmp_obj = None |
|
168
|
|
|
self.icon = None |
|
169
|
|
|
self.portal = None |
|
170
|
|
|
self.portal_url = None |
|
171
|
|
|
self.bika_setup = None |
|
172
|
|
|
self.came_from = None |
|
173
|
|
|
self.obj_count = None |
|
174
|
|
|
self.specifications = None |
|
175
|
|
|
self.ShowPrices = None |
|
176
|
|
|
self.SKIP_FIELD_ON_COPY = [] |
|
177
|
|
|
|
|
178
|
|
|
def __call__(self): |
|
179
|
|
|
self.portal = api.get_portal() |
|
180
|
|
|
self.portal_url = self.portal.absolute_url() |
|
181
|
|
|
self.bika_setup = api.get_bika_setup() |
|
182
|
|
|
self.request.set('disable_plone.rightcolumn', 1) |
|
183
|
|
|
self.came_from = "add" |
|
184
|
|
|
self.tmp_obj = self.get_obj() |
|
185
|
|
|
self.obj_count = self.get_obj_count() |
|
186
|
|
|
self.ShowPrices = self.bika_setup.getShowPrices() |
|
187
|
|
|
|
|
188
|
|
|
def get_view_url(self): |
|
189
|
|
|
"""Return the current view url including request parameters |
|
190
|
|
|
""" |
|
191
|
|
|
request = self.request |
|
192
|
|
|
url = request.getURL() |
|
193
|
|
|
qs = request.getHeader("query_string") |
|
194
|
|
|
if not qs: |
|
195
|
|
|
return url |
|
196
|
|
|
return "{}?{}".format(url, qs) |
|
197
|
|
|
|
|
198
|
|
|
def get_object_by_uid(self, uid): |
|
199
|
|
|
"""Get the object by UID |
|
200
|
|
|
""" |
|
201
|
|
|
logger.debug("get_object_by_uid::UID={}".format(uid)) |
|
202
|
|
|
obj = api.get_object_by_uid(uid, None) |
|
203
|
|
|
if obj is None: |
|
204
|
|
|
logger.warn("!! No object found for UID #{} !!") |
|
205
|
|
|
return obj |
|
206
|
|
|
|
|
207
|
|
|
def get_currency(self): |
|
208
|
|
|
"""Returns the configured currency |
|
209
|
|
|
""" |
|
210
|
|
|
bika_setup = api.get_bika_setup() |
|
211
|
|
|
currency = bika_setup.getCurrency() |
|
212
|
|
|
currencies = locales.getLocale('en').numbers.currencies |
|
213
|
|
|
return currencies[currency] |
|
214
|
|
|
|
|
215
|
|
|
def get_obj_count(self): |
|
216
|
|
|
"""Return the obj_count request parameter |
|
217
|
|
|
""" |
|
218
|
|
|
try: |
|
219
|
|
|
obj_count = int(self.request.form.get("obj_count", 1)) |
|
220
|
|
|
except (TypeError, ValueError): |
|
221
|
|
|
obj_count = 1 |
|
222
|
|
|
return obj_count |
|
223
|
|
|
|
|
224
|
|
|
def get_obj(self): |
|
225
|
|
|
"""Create a temporary Object to fetch the fields from |
|
226
|
|
|
""" |
|
227
|
|
|
raise NotImplementedError("get_obj is not implemented.") |
|
228
|
|
|
|
|
229
|
|
|
def get_obj_schema(self): |
|
230
|
|
|
"""Return the Object schema |
|
231
|
|
|
""" |
|
232
|
|
|
obj = self.get_obj() |
|
233
|
|
|
return obj.Schema() |
|
234
|
|
|
|
|
235
|
|
|
def get_obj_fields(self): |
|
236
|
|
|
"""Return the Object schema fields (including extendend fields) |
|
237
|
|
|
""" |
|
238
|
|
|
schema = self.get_obj_schema() |
|
239
|
|
|
return schema.fields() |
|
240
|
|
|
|
|
241
|
|
|
def get_fieldname(self, field, objnum): |
|
242
|
|
|
"""Generate a new fieldname with a '-<objnum>' suffix |
|
243
|
|
|
""" |
|
244
|
|
|
name = field.getName() |
|
245
|
|
|
# ensure we have only *one* suffix |
|
246
|
|
|
base_name = name.split("-")[0] |
|
247
|
|
|
suffix = "-{}".format(objnum) |
|
248
|
|
|
return "{}{}".format(base_name, suffix) |
|
249
|
|
|
|
|
250
|
|
|
def get_input_widget(self, fieldname, objnum=0, **kw): |
|
251
|
|
|
"""Get the field widget of the Object in column <objnum> |
|
252
|
|
|
|
|
253
|
|
|
:param fieldname: The base fieldname |
|
254
|
|
|
:type fieldname: string |
|
255
|
|
|
""" |
|
256
|
|
|
|
|
257
|
|
|
# temporary Object Context |
|
258
|
|
|
context = self.get_obj() |
|
259
|
|
|
# request = self.request |
|
260
|
|
|
schema = context.Schema() |
|
261
|
|
|
|
|
262
|
|
|
# get original field in the schema from the base_fieldname |
|
263
|
|
|
base_fieldname = fieldname.split("-")[0] |
|
264
|
|
|
field = context.getField(base_fieldname) |
|
265
|
|
|
|
|
266
|
|
|
# fieldname with -<objnum> suffix |
|
267
|
|
|
new_fieldname = self.get_fieldname(field, objnum) |
|
268
|
|
|
new_field = field.copy(name=new_fieldname) |
|
269
|
|
|
|
|
270
|
|
|
# get the default value for this field |
|
271
|
|
|
fieldvalues = self.fieldvalues |
|
272
|
|
|
field_value = fieldvalues.get(new_fieldname) |
|
273
|
|
|
# request_value = request.form.get(new_fieldname) |
|
274
|
|
|
# value = request_value or field_value |
|
275
|
|
|
value = field_value |
|
276
|
|
|
|
|
277
|
|
|
def getAccessor(instance): |
|
278
|
|
|
def accessor(**kw): |
|
279
|
|
|
return value |
|
280
|
|
|
return accessor |
|
281
|
|
|
|
|
282
|
|
|
# inject the new context for the widget renderer |
|
283
|
|
|
# see: Products.Archetypes.Renderer.render |
|
284
|
|
|
kw["here"] = context |
|
285
|
|
|
kw["context"] = context |
|
286
|
|
|
kw["fieldName"] = new_fieldname |
|
287
|
|
|
|
|
288
|
|
|
# make the field available with this name |
|
289
|
|
|
# XXX: This is actually a hack to make the widget |
|
290
|
|
|
# available in the template |
|
291
|
|
|
schema._fields[new_fieldname] = new_field |
|
292
|
|
|
new_field.getAccessor = getAccessor |
|
293
|
|
|
|
|
294
|
|
|
# set the default value |
|
295
|
|
|
form = dict() |
|
296
|
|
|
form[new_fieldname] = value |
|
297
|
|
|
self.request.form.update(form) |
|
298
|
|
|
|
|
299
|
|
|
logger.info( |
|
300
|
|
|
"get_input_widget: fieldname={} objnum={} -> " |
|
301
|
|
|
"new_fieldname={} value={}" |
|
302
|
|
|
.format(fieldname, objnum, new_fieldname, value)) |
|
303
|
|
|
widget = context.widget(new_fieldname, **kw) |
|
304
|
|
|
return widget |
|
305
|
|
|
|
|
306
|
|
|
def get_copy_from(self): |
|
307
|
|
|
"""Returns a mapping of UID index -> Object |
|
308
|
|
|
""" |
|
309
|
|
|
# Create a mapping of source Objects for copy |
|
310
|
|
|
copy_from = self.request.form.get("copy_from", "").split(",") |
|
311
|
|
|
# clean out empty strings |
|
312
|
|
|
copy_from_uids = filter(lambda x: x, copy_from) |
|
313
|
|
|
out = dict().fromkeys(range(len(copy_from_uids))) |
|
314
|
|
|
for n, uid in enumerate(copy_from_uids): |
|
315
|
|
|
obj = self.get_object_by_uid(uid) |
|
316
|
|
|
if obj is None: |
|
317
|
|
|
continue |
|
318
|
|
|
out[n] = obj |
|
319
|
|
|
logger.info("get_copy_from: uids={}".format(copy_from_uids)) |
|
320
|
|
|
return out |
|
321
|
|
|
|
|
322
|
|
|
def get_default_value(self, field, context): |
|
323
|
|
|
"""Get the default value of the field |
|
324
|
|
|
""" |
|
325
|
|
|
name = field.getName() |
|
326
|
|
|
default = field.getDefault(context) |
|
327
|
|
|
if name == "Batch": |
|
328
|
|
|
batch = self.get_batch() |
|
329
|
|
|
if batch is not None: |
|
330
|
|
|
default = batch |
|
331
|
|
|
if name == "Client": |
|
332
|
|
|
client = self.get_client() |
|
333
|
|
|
if client is not None: |
|
334
|
|
|
default = client |
|
335
|
|
|
if name == "Contact": |
|
336
|
|
|
contact = self.get_default_contact() |
|
337
|
|
|
if contact is not None: |
|
338
|
|
|
default = contact |
|
339
|
|
|
logger.info( |
|
340
|
|
|
"get_default_value: context={} field={} value={}" |
|
341
|
|
|
.format(context, name, default)) |
|
342
|
|
|
return default |
|
343
|
|
|
|
|
344
|
|
|
def get_field_value(self, field, context): |
|
345
|
|
|
"""Get the stored value of the field |
|
346
|
|
|
""" |
|
347
|
|
|
name = field.getName() |
|
348
|
|
|
value = context.getField(name).get(context) |
|
349
|
|
|
logger.info( |
|
350
|
|
|
"get_field_value: context={} field={} value={}" |
|
351
|
|
|
.format(context, name, value)) |
|
352
|
|
|
return value |
|
353
|
|
|
|
|
354
|
|
|
def get_client(self): |
|
355
|
|
|
"""Returns the Client |
|
356
|
|
|
""" |
|
357
|
|
|
context = self.context |
|
358
|
|
|
parent = api.get_parent(context) |
|
359
|
|
|
if context.portal_type == "Client": |
|
360
|
|
|
return context |
|
361
|
|
|
elif parent.portal_type == "Client": |
|
362
|
|
|
return parent |
|
363
|
|
|
elif context.portal_type == "Batch": |
|
364
|
|
|
return context.getClient() |
|
365
|
|
|
elif parent.portal_type == "Batch": |
|
366
|
|
|
return context.getClient() |
|
367
|
|
|
return None |
|
368
|
|
|
|
|
369
|
|
|
def get_batch(self): |
|
370
|
|
|
"""Returns the Batch |
|
371
|
|
|
""" |
|
372
|
|
|
context = self.context |
|
373
|
|
|
parent = api.get_parent(context) |
|
374
|
|
|
if context.portal_type == "Batch": |
|
375
|
|
|
return context |
|
376
|
|
|
elif parent.portal_type == "Batch": |
|
377
|
|
|
return parent |
|
378
|
|
|
return None |
|
379
|
|
|
|
|
380
|
|
|
def generate_fieldvalues(self, count=1): |
|
381
|
|
|
"""Returns a mapping of '<fieldname>-<count>' to the default value |
|
382
|
|
|
of the field or the field value of the source Object (copy from) |
|
383
|
|
|
""" |
|
384
|
|
|
raise NotImplementedError("generate_fieldvalues is not implemented.") |
|
385
|
|
|
|
|
386
|
|
|
def is_field_visible(self, field): |
|
387
|
|
|
"""Check if the field is visible |
|
388
|
|
|
""" |
|
389
|
|
|
context = self.context |
|
390
|
|
|
fieldname = field.getName() |
|
391
|
|
|
|
|
392
|
|
|
# hide the Client field on client and batch contexts |
|
393
|
|
|
if fieldname == "Client" and context.portal_type in ("Client", ): |
|
394
|
|
|
return False |
|
395
|
|
|
|
|
396
|
|
|
# hide the Batch field on batch contexts |
|
397
|
|
|
if fieldname == "Batch" and context.portal_type in ("Batch", ): |
|
398
|
|
|
return False |
|
399
|
|
|
|
|
400
|
|
|
return True |
|
401
|
|
|
|
|
402
|
|
|
def get_fields_with_visibility(self, visibility, mode="add"): |
|
403
|
|
|
"""Return the Object fields with the current visibility. |
|
404
|
|
|
|
|
405
|
|
|
It is recommended to get those values from an AddManagerView |
|
406
|
|
|
""" |
|
407
|
|
|
raise NotImplementedError( |
|
408
|
|
|
"get_fields_with_visibility is not implemented.") |
|
409
|
|
|
|
|
410
|
|
|
|
|
411
|
|
|
class BaseAjaxAddView(BaseAddView): |
|
412
|
|
|
"""Ajax helpers for an Object Add form |
|
413
|
|
|
""" |
|
414
|
|
|
implements(IPublishTraverse) |
|
415
|
|
|
|
|
416
|
|
|
def __init__(self, context, request): |
|
417
|
|
|
BaseAddView.__init__(self, context, request) |
|
418
|
|
|
self.traverse_subpath = [] |
|
419
|
|
|
# Errors are aggregated here, and returned together to the browser |
|
420
|
|
|
self.errors = {} |
|
421
|
|
|
|
|
422
|
|
|
def publishTraverse(self, request, name): |
|
423
|
|
|
""" get's called before __call__ for each path name |
|
424
|
|
|
""" |
|
425
|
|
|
self.traverse_subpath.append(name) |
|
426
|
|
|
return self |
|
427
|
|
|
|
|
428
|
|
|
@returns_json |
|
429
|
|
|
def __call__(self): |
|
430
|
|
|
"""Dispatch the path to a method and return JSON. |
|
431
|
|
|
""" |
|
432
|
|
|
protect.CheckAuthenticator(self.request.form) |
|
433
|
|
|
protect.PostOnly(self.request.form) |
|
434
|
|
|
|
|
435
|
|
|
if len(self.traverse_subpath) != 1: |
|
436
|
|
|
return self.error("Not found", status=404) |
|
437
|
|
|
func_name = "ajax_{}".format(self.traverse_subpath[0]) |
|
438
|
|
|
func = getattr(self, func_name, None) |
|
439
|
|
|
if func is None: |
|
440
|
|
|
return self.error("Invalid function", status=400) |
|
441
|
|
|
return func() |
|
442
|
|
|
|
|
443
|
|
|
def error(self, message, status=500, **kw): |
|
444
|
|
|
"""Set a JSON error object and a status to the response |
|
445
|
|
|
""" |
|
446
|
|
|
self.request.response.setStatus(status) |
|
447
|
|
|
result = {"success": False, "errors": message} |
|
448
|
|
|
result.update(kw) |
|
449
|
|
|
return result |
|
450
|
|
|
|
|
451
|
|
|
def to_iso_date(self, dt): |
|
452
|
|
|
"""Return the ISO representation of a date object |
|
453
|
|
|
""" |
|
454
|
|
|
if dt is None: |
|
455
|
|
|
return "" |
|
456
|
|
|
if isinstance(dt, DateTime): |
|
457
|
|
|
return dt.ISO8601() |
|
458
|
|
|
if isinstance(dt, datetime): |
|
459
|
|
|
return dt.isoformat() |
|
460
|
|
|
raise TypeError("{} is neither an instance of DateTime nor datetime" |
|
461
|
|
|
.format(repr(dt))) |
|
462
|
|
|
|
|
463
|
|
|
def get_records(self): |
|
464
|
|
|
"""Returns a list of Sample records |
|
465
|
|
|
|
|
466
|
|
|
All fields coming from `request.form` have a number prefix, |
|
467
|
|
|
e.g. `Contact-0`. |
|
468
|
|
|
All fields with the same suffix number are grouped together in a |
|
469
|
|
|
record. |
|
470
|
|
|
Each record represents the data for one column in the Sample Add |
|
471
|
|
|
form and contains a mapping of the fieldName (w/o prefix) -> value. |
|
472
|
|
|
|
|
473
|
|
|
Example: |
|
474
|
|
|
[{"Contact": "Rita Mohale", ...}, {Contact: "Neil Standard"} ...] |
|
475
|
|
|
""" |
|
476
|
|
|
form = self.request.form |
|
477
|
|
|
obj_count = self.get_obj_count() |
|
478
|
|
|
|
|
479
|
|
|
records = [] |
|
480
|
|
|
# Group belonging Object fields together |
|
481
|
|
|
for objenum in range(obj_count): |
|
482
|
|
|
record = {} |
|
483
|
|
|
s1 = "-{}".format(objenum) |
|
484
|
|
|
keys = filter(lambda key: s1 in key, form.keys()) |
|
485
|
|
|
for key in keys: |
|
486
|
|
|
new_key = key.replace(s1, "") |
|
487
|
|
|
value = form.get(key) |
|
488
|
|
|
record[new_key] = value |
|
489
|
|
|
records.append(record) |
|
490
|
|
|
return records |
|
491
|
|
|
|
|
492
|
|
|
def get_objs_from_record(self, record, key): |
|
493
|
|
|
"""Returns a mapping of UID -> object |
|
494
|
|
|
""" |
|
495
|
|
|
uids = self.get_uids_from_record(record, key) |
|
496
|
|
|
objs = map(self.get_object_by_uid, uids) |
|
497
|
|
|
return dict(zip(uids, objs)) |
|
498
|
|
|
|
|
499
|
|
|
def get_uids_from_record(self, record, key): |
|
500
|
|
|
"""Returns a list of parsed UIDs from a single form field identified |
|
501
|
|
|
by the given key. |
|
502
|
|
|
|
|
503
|
|
|
A form field ending with `_uid` can contain an empty value, a |
|
504
|
|
|
single UID or multiple UIDs separated by a comma. |
|
505
|
|
|
|
|
506
|
|
|
This method parses the UID value and returns a list of non-empty UIDs. |
|
507
|
|
|
""" |
|
508
|
|
|
value = record.get(key, None) |
|
509
|
|
|
if value is None: |
|
510
|
|
|
return [] |
|
511
|
|
|
if isinstance(value, basestring): |
|
512
|
|
|
value = value.split(",") |
|
513
|
|
|
return filter(lambda uid: uid, value) |
|
514
|
|
|
|
|
515
|
|
|
@cache(cache_key) |
|
516
|
|
|
def get_base_info(self, obj): |
|
517
|
|
|
"""Returns the base info of an object |
|
518
|
|
|
""" |
|
519
|
|
|
if obj is None: |
|
520
|
|
|
return {} |
|
521
|
|
|
|
|
522
|
|
|
info = { |
|
523
|
|
|
"id": obj.getId(), |
|
524
|
|
|
"uid": obj.UID(), |
|
525
|
|
|
"title": obj.Title(), |
|
526
|
|
|
"description": obj.Description(), |
|
527
|
|
|
"url": obj.absolute_url(), |
|
528
|
|
|
} |
|
529
|
|
|
|
|
530
|
|
|
return info |
|
531
|
|
|
|
|
532
|
|
|
@cache(cache_key) |
|
533
|
|
|
def get_container_info(self, obj): |
|
534
|
|
|
"""Returns the info for a Container |
|
535
|
|
|
""" |
|
536
|
|
|
info = self.get_base_info(obj) |
|
537
|
|
|
info.update({}) |
|
538
|
|
|
return info |
|
539
|
|
|
|
|
540
|
|
|
@cache(cache_key) |
|
541
|
|
|
def get_preservation_info(self, obj): |
|
542
|
|
|
"""Returns the info for a Preservation |
|
543
|
|
|
""" |
|
544
|
|
|
info = self.get_base_info(obj) |
|
545
|
|
|
info.update({}) |
|
546
|
|
|
return info |
|
547
|
|
|
|
|
548
|
|
|
def ajax_get_global_settings(self): |
|
549
|
|
|
"""Returns the global Bika settings |
|
550
|
|
|
""" |
|
551
|
|
|
bika_setup = api.get_bika_setup() |
|
552
|
|
|
settings = { |
|
553
|
|
|
"show_prices": bika_setup.getShowPrices(), |
|
554
|
|
|
} |
|
555
|
|
|
return settings |
|
556
|
|
|
|
|
557
|
|
|
def ajax_recalculate_records(self): |
|
558
|
|
|
"""Recalculate all Object records and dependencies |
|
559
|
|
|
""" |
|
560
|
|
|
raise NotImplementedError( |
|
561
|
|
|
"ajax_recalculate_records is not implemented.") |
|
562
|
|
|
|
|
563
|
|
|
def ajax_submit(self): |
|
564
|
|
|
"""Submit & create the Object |
|
565
|
|
|
""" |
|
566
|
|
|
raise NotImplementedError( |
|
567
|
|
|
"ajax_recalculate_records is not implemented.") |
|
568
|
|
|
|