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
|
|
|
import sys |
9
|
|
|
|
10
|
|
|
from AccessControl import ClassSecurityInfo |
11
|
|
|
from bika.lims import api |
12
|
|
|
from bika.lims import bikaMessageFactory as _ |
13
|
|
|
from bika.lims.browser.fields.remarksfield import RemarksField |
14
|
|
|
from bika.lims.browser.widgets import ARTemplateAnalysesWidget |
15
|
|
|
from bika.lims.browser.widgets import ARTemplatePartitionsWidget |
16
|
|
|
from bika.lims.browser.widgets import ReferenceWidget |
17
|
|
|
from bika.lims.browser.widgets import RemarksWidget |
18
|
|
|
from bika.lims.config import PROJECTNAME |
19
|
|
|
from bika.lims.content.bikaschema import BikaSchema |
20
|
|
|
from bika.lims.interfaces import IARTemplate |
21
|
|
|
from Products.Archetypes.public import BaseContent |
22
|
|
|
from Products.Archetypes.public import BooleanField |
23
|
|
|
from Products.Archetypes.public import BooleanWidget |
24
|
|
|
from Products.Archetypes.public import ComputedField |
25
|
|
|
from Products.Archetypes.public import ComputedWidget |
26
|
|
|
from Products.Archetypes.public import DisplayList |
27
|
|
|
from Products.Archetypes.public import ReferenceField |
28
|
|
|
from Products.Archetypes.public import Schema |
29
|
|
|
from Products.Archetypes.public import registerType |
30
|
|
|
from Products.Archetypes.references import HoldingReference |
31
|
|
|
from Products.ATExtensions.field.records import RecordsField |
32
|
|
|
from Products.CMFCore.utils import getToolByName |
33
|
|
|
from zope.interface import implements |
34
|
|
|
|
35
|
|
|
schema = BikaSchema.copy() + Schema(( |
36
|
|
|
ReferenceField( |
37
|
|
|
"SamplePoint", |
38
|
|
|
vocabulary_display_path_bound=sys.maxint, |
39
|
|
|
allowed_types=("SamplePoint",), |
40
|
|
|
relationship="ARTemplateSamplePoint", |
41
|
|
|
referenceClass=HoldingReference, |
42
|
|
|
accessor="getSamplePoint", |
43
|
|
|
edit_accessor="getSamplePoint", |
44
|
|
|
mutator="setSamplePoint", |
45
|
|
|
widget=ReferenceWidget( |
46
|
|
|
label=_("Sample Point"), |
47
|
|
|
description=_("Location where sample is collected"), |
48
|
|
|
visible={ |
49
|
|
|
"edit": "visible", "view": "visible", "add": "visible", |
50
|
|
|
"secondary": "invisible", |
51
|
|
|
}, |
52
|
|
|
catalog_name="bika_setup_catalog", |
53
|
|
|
base_query={"inactive_state": "active"}, |
54
|
|
|
showOn=True, |
55
|
|
|
), |
56
|
|
|
), |
57
|
|
|
ComputedField( |
58
|
|
|
"SamplePointUID", |
59
|
|
|
expression="context.Schema()['SamplePoint'].get(context) and context.Schema()['SamplePoint'].get(context).UID() or ''", |
60
|
|
|
widget=ComputedWidget( |
61
|
|
|
visible=False, |
62
|
|
|
), |
63
|
|
|
), |
64
|
|
|
ReferenceField( |
65
|
|
|
"SampleType", |
66
|
|
|
vocabulary_display_path_bound=sys.maxint, |
67
|
|
|
allowed_types=("SampleType",), |
68
|
|
|
relationship="ARTemplateSampleType", |
69
|
|
|
referenceClass=HoldingReference, |
70
|
|
|
accessor="getSampleType", |
71
|
|
|
edit_accessor="getSampleType", |
72
|
|
|
mutator="setSampleType", |
73
|
|
|
widget=ReferenceWidget( |
74
|
|
|
label=_("Sample Type"), |
75
|
|
|
description=_("Create a new sample of this type"), |
76
|
|
|
visible={ |
77
|
|
|
"edit": "visible", "view": "visible", "add": "visible", |
78
|
|
|
"secondary": "invisible" |
79
|
|
|
}, |
80
|
|
|
catalog_name="bika_setup_catalog", |
81
|
|
|
base_query={"inactive_state": "active"}, |
82
|
|
|
showOn=True, |
83
|
|
|
), |
84
|
|
|
), |
85
|
|
|
ComputedField( |
86
|
|
|
"SampleTypeUID", |
87
|
|
|
expression="context.Schema()['SampleType'].get(context) and context.Schema()['SampleType'].get(context).UID() or ''", |
88
|
|
|
widget=ComputedWidget( |
89
|
|
|
visible=False, |
90
|
|
|
), |
91
|
|
|
), |
92
|
|
|
BooleanField( |
93
|
|
|
"Composite", |
94
|
|
|
default=False, |
95
|
|
|
widget=BooleanWidget( |
96
|
|
|
label=_("Composite sample"), |
97
|
|
|
description=_("The sample is a mix of sub samples"), |
98
|
|
|
), |
99
|
|
|
), |
100
|
|
|
BooleanField( |
101
|
|
|
"SamplingRequired", |
102
|
|
|
default_method='getSamplingRequiredDefaultValue', |
103
|
|
|
widget=BooleanWidget( |
104
|
|
|
label=_("Sample collected by the laboratory"), |
105
|
|
|
description=_("Enable sampling workflow for the created sample") |
106
|
|
|
), |
107
|
|
|
), |
108
|
|
|
RemarksField( |
109
|
|
|
"Remarks", |
110
|
|
|
searchable=True, |
111
|
|
|
widget=RemarksWidget( |
112
|
|
|
label=_("Remarks"), |
113
|
|
|
), |
114
|
|
|
), |
115
|
|
|
RecordsField( |
116
|
|
|
"Partitions", |
117
|
|
|
schemata="Sample Partitions", |
118
|
|
|
required=0, |
119
|
|
|
type="artemplate_parts", |
120
|
|
|
subfields=( |
121
|
|
|
"part_id", |
122
|
|
|
"Container", |
123
|
|
|
"Preservation", |
124
|
|
|
"SampleType", |
125
|
|
|
"container_uid", |
126
|
|
|
"preservation_uid", |
127
|
|
|
"sampletype_uid" |
128
|
|
|
), |
129
|
|
|
subfield_labels={ |
130
|
|
|
"part_id": _("Partition"), |
131
|
|
|
"Container": _("Container"), |
132
|
|
|
"Preservation": _("Preservation"), |
133
|
|
|
"SampleType": _("Sample Type") |
134
|
|
|
}, |
135
|
|
|
subfield_sizes={ |
136
|
|
|
"part_id": 15, |
137
|
|
|
"Container": 35, |
138
|
|
|
"Preservation": 35, |
139
|
|
|
"SampleType": 35, |
140
|
|
|
}, |
141
|
|
|
subfield_hidden={ |
142
|
|
|
"preservation_uid": True, |
143
|
|
|
"container_uid": True, |
144
|
|
|
"sampletype_uid": True, |
145
|
|
|
}, |
146
|
|
|
default=[{ |
147
|
|
|
"part_id": "part-1", |
148
|
|
|
"Container": "", |
149
|
|
|
"Preservation": "", |
150
|
|
|
"SampleType": "", |
151
|
|
|
"container_uid": "", |
152
|
|
|
"preservation_uid": "", |
153
|
|
|
"sampletype_uid": "", |
154
|
|
|
}], |
155
|
|
|
widget=ARTemplatePartitionsWidget( |
156
|
|
|
label=_("Sample Partitions"), |
157
|
|
|
description=_( |
158
|
|
|
"Configure the sample partitions and preservations " |
159
|
|
|
"for this template. Assign analyses to the different " |
160
|
|
|
"partitions on the template's Analyses tab"), |
161
|
|
|
combogrid_options={ |
162
|
|
|
"Container": { |
163
|
|
|
"colModel": [ |
164
|
|
|
{ |
165
|
|
|
"columnName": "container_uid", |
166
|
|
|
"hidden": True}, |
167
|
|
|
{ |
168
|
|
|
"columnName": "Container", |
169
|
|
|
"width": "30", |
170
|
|
|
"label": _("Container") |
171
|
|
|
}, { |
172
|
|
|
"columnName": "Description", |
173
|
|
|
"width": "70", |
174
|
|
|
"label": _("Description") |
175
|
|
|
}], |
176
|
|
|
"url": "getcontainers", |
177
|
|
|
"showOn": True, |
178
|
|
|
"width": "550px" |
179
|
|
|
}, |
180
|
|
|
"Preservation": { |
181
|
|
|
"colModel": [ |
182
|
|
|
{ |
183
|
|
|
"columnName": "preservation_uid", |
184
|
|
|
"hidden": True |
185
|
|
|
}, { |
186
|
|
|
"columnName": "Preservation", |
187
|
|
|
"width": "30", |
188
|
|
|
"label": _("Preservation") |
189
|
|
|
}, { |
190
|
|
|
"columnName": "Description", |
191
|
|
|
"width": "70", |
192
|
|
|
"label": _("Description") |
193
|
|
|
}], |
194
|
|
|
"url": "getpreservations", |
195
|
|
|
"showOn": True, |
196
|
|
|
"width": "550px" |
197
|
|
|
}, |
198
|
|
|
"SampleType": { |
199
|
|
|
"colModel": [ |
200
|
|
|
{ |
201
|
|
|
"columnName": "sampletype_uid", |
202
|
|
|
"hidden": True |
203
|
|
|
}, { |
204
|
|
|
"columnName": "SampleType", |
205
|
|
|
"width": "30", |
206
|
|
|
"label": _("SampleType") |
207
|
|
|
}, { |
208
|
|
|
"columnName": "Description", |
209
|
|
|
"width": "70", |
210
|
|
|
"label": _("Description") |
211
|
|
|
}], |
212
|
|
|
"url": "get_sampletypes", |
213
|
|
|
"showOn": True, |
214
|
|
|
"width": "550px" |
215
|
|
|
}, |
216
|
|
|
}, |
217
|
|
|
), |
218
|
|
|
), |
219
|
|
|
|
220
|
|
|
ReferenceField( |
221
|
|
|
"AnalysisProfile", |
222
|
|
|
schemata="Analyses", |
223
|
|
|
required=0, |
224
|
|
|
multiValued=0, |
225
|
|
|
allowed_types=("AnalysisProfile",), |
226
|
|
|
relationship="ARTemplateAnalysisProfile", |
227
|
|
|
widget=ReferenceWidget( |
228
|
|
|
label=_("Analysis Profile"), |
229
|
|
|
description=_("Add analyses from the selected profile " |
230
|
|
|
"to the template"), |
231
|
|
|
visible={ |
232
|
|
|
"edit": "visible", |
233
|
|
|
"view": "visible", |
234
|
|
|
"add": "visible", |
235
|
|
|
"secondary": "invisible" |
236
|
|
|
}, |
237
|
|
|
catalog_name="bika_setup_catalog", |
238
|
|
|
base_query={"inactive_state": "active"}, |
239
|
|
|
showOn=True, |
240
|
|
|
), |
241
|
|
|
), |
242
|
|
|
RecordsField( |
243
|
|
|
"Analyses", |
244
|
|
|
schemata="Analyses", |
245
|
|
|
required=0, |
246
|
|
|
type="analyses", |
247
|
|
|
subfields=("service_uid", "partition"), |
248
|
|
|
subfield_labels={ |
249
|
|
|
"service_uid": _("Title"), |
250
|
|
|
"partition": _("Partition") |
251
|
|
|
}, |
252
|
|
|
default=[], |
253
|
|
|
widget=ARTemplateAnalysesWidget( |
254
|
|
|
label=_("Analyses"), |
255
|
|
|
description=_("Select analyses to include in this template"), |
256
|
|
|
) |
257
|
|
|
), |
258
|
|
|
# Custom settings for the assigned analysis services |
259
|
|
|
# https://jira.bikalabs.com/browse/LIMS-1324 |
260
|
|
|
# Fields: |
261
|
|
|
# - uid: Analysis Service UID |
262
|
|
|
# - hidden: True/False. Hide/Display in results reports |
263
|
|
|
RecordsField( |
264
|
|
|
"AnalysisServicesSettings", |
265
|
|
|
required=0, |
266
|
|
|
subfields=("uid", "hidden",), |
267
|
|
|
widget=ComputedWidget(visible=False), |
268
|
|
|
), |
269
|
|
|
)) |
270
|
|
|
|
271
|
|
|
schema["description"].widget.visible = True |
272
|
|
|
schema["title"].widget.visible = True |
273
|
|
|
schema["title"].validators = ("uniquefieldvalidator",) |
274
|
|
|
# Update the validation layer after change the validator in runtime |
275
|
|
|
schema["title"]._validationLayer() |
276
|
|
|
|
277
|
|
|
|
278
|
|
|
class ARTemplate(BaseContent): |
279
|
|
|
security = ClassSecurityInfo() |
280
|
|
|
schema = schema |
|
|
|
|
281
|
|
|
displayContentsTab = False |
282
|
|
|
_at_rename_after_creation = True |
283
|
|
|
implements(IARTemplate) |
284
|
|
|
|
285
|
|
|
def _renameAfterCreation(self, check_auto_id=False): |
286
|
|
|
from bika.lims.idserver import renameAfterCreation |
287
|
|
|
renameAfterCreation(self) |
288
|
|
|
|
289
|
|
|
security.declarePublic("AnalysisProfiles") |
290
|
|
|
|
291
|
|
|
def AnalysisProfiles(self, instance=None): |
292
|
|
|
instance = instance or self |
293
|
|
|
bsc = getToolByName(instance, "bika_setup_catalog") |
294
|
|
|
items = [] |
295
|
|
|
for p in bsc( |
296
|
|
|
portal_type="AnalysisProfile", |
297
|
|
|
inactive_state="active", |
298
|
|
|
sort_on="sortable_title"): |
299
|
|
|
p = p.getObject() |
300
|
|
|
title = p.Title() |
301
|
|
|
items.append((p.UID(), title)) |
302
|
|
|
items = [["", ""]] + list(items) |
303
|
|
|
return DisplayList(items) |
304
|
|
|
|
305
|
|
|
def getClientUID(self): |
306
|
|
|
"""This populates the getClientUID catalog |
307
|
|
|
If the parent is the system bika_artemplates folder, |
308
|
|
|
then that folder's UID must be returned in this index. |
309
|
|
|
""" |
310
|
|
|
return self.aq_parent.UID() |
311
|
|
|
|
312
|
|
|
def getAnalysisServiceSettings(self, uid): |
313
|
|
|
"""Returns a dictionary with the settings for the analysis service that |
314
|
|
|
match with the uid provided. |
315
|
|
|
|
316
|
|
|
If there are no settings for the analysis service and template, returns |
317
|
|
|
a dictionary with the key 'uid' |
318
|
|
|
""" |
319
|
|
|
settings = self.getAnalysisServicesSettings() |
320
|
|
|
sets = [s for s in settings if s.get("uid", "") == uid] |
321
|
|
|
return sets[0] if sets else {"uid": uid} |
322
|
|
|
|
323
|
|
View Code Duplication |
def isAnalysisServiceHidden(self, uid): |
|
|
|
|
324
|
|
|
""" Checks if the analysis service that match with the uid |
325
|
|
|
provided must be hidden in results. |
326
|
|
|
If no hidden assignment has been set for the analysis in |
327
|
|
|
this template, returns the visibility set to the analysis |
328
|
|
|
itself. |
329
|
|
|
Raise a TypeError if the uid is empty or None |
330
|
|
|
Raise a ValueError if there is no hidden assignment in this |
331
|
|
|
template or no analysis service found for this uid. |
332
|
|
|
""" |
333
|
|
|
if not uid: |
334
|
|
|
raise TypeError("None type or empty uid") |
335
|
|
|
sets = self.getAnalysisServiceSettings(uid) |
336
|
|
|
if "hidden" not in sets: |
337
|
|
|
uc = getToolByName(self, "uid_catalog") |
338
|
|
|
serv = uc(UID=uid) |
339
|
|
|
if serv and len(serv) == 1: |
340
|
|
|
return serv[0].getObject().getRawHidden() |
341
|
|
|
else: |
342
|
|
|
raise ValueError("%s is not valid" % uid) |
343
|
|
|
return sets.get("hidden", False) |
344
|
|
|
|
345
|
|
|
def remove_service(self, service): |
346
|
|
|
"""Removes the service passed in from the services offered by the |
347
|
|
|
current Template. If the Analysis Service passed in is not assigned to |
348
|
|
|
this Analysis Template, returns False. |
349
|
|
|
:param service: the service to be removed from this AR Template |
350
|
|
|
:type service: AnalysisService |
351
|
|
|
:return: True if the AnalysisService has been removed successfully |
352
|
|
|
""" |
353
|
|
|
uid = api.get_uid(service) |
354
|
|
|
|
355
|
|
|
# Remove the service from the referenced services |
356
|
|
|
services = self.getAnalyses() |
357
|
|
|
num_services = len(services) |
358
|
|
|
services = [item for item in services |
359
|
|
|
if item.get("service_uid", "") != uid] |
360
|
|
|
removed = len(services) < num_services |
361
|
|
|
self.setAnalyses(services) |
362
|
|
|
|
363
|
|
|
# Remove the service from the settings map |
364
|
|
|
settings = self.getAnalysisServicesSettings() |
365
|
|
|
settings = [item for item in settings if item.get('uid', '') != uid] |
366
|
|
|
self.setAnalysisServicesSettings(settings) |
367
|
|
|
|
368
|
|
|
return removed |
369
|
|
|
|
370
|
|
|
def getSamplingRequiredDefaultValue(self): |
371
|
|
|
"""Returns the default value for field SamplingRequired, that is the |
372
|
|
|
value for setting SamplingWorkflowEnabled from setup |
373
|
|
|
""" |
374
|
|
|
return self.bika_setup.getSamplingWorkflowEnabled() |
375
|
|
|
|
376
|
|
|
registerType(ARTemplate, PROJECTNAME) |
377
|
|
|
|