|
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 AccessControl import ClassSecurityInfo |
|
9
|
|
|
from bika.lims import api |
|
10
|
|
|
from bika.lims import bikaMessageFactory as _ |
|
11
|
|
|
from bika.lims.browser.fields import UIDReferenceField |
|
12
|
|
|
from bika.lims.config import PROJECTNAME |
|
13
|
|
|
from bika.lims.content.bikaschema import BikaSchema |
|
14
|
|
|
from bika.lims.interfaces import IDeactivable |
|
15
|
|
|
from bika.lims.interfaces import IMethod |
|
16
|
|
|
from bika.lims.utils import t |
|
17
|
|
|
from plone.app.blob.field import FileField as BlobFileField |
|
18
|
|
|
from Products.Archetypes.public import BaseFolder |
|
19
|
|
|
from Products.Archetypes.public import BooleanField |
|
20
|
|
|
from Products.Archetypes.public import BooleanWidget |
|
21
|
|
|
from Products.Archetypes.public import ComputedField |
|
22
|
|
|
from Products.Archetypes.public import FileWidget |
|
23
|
|
|
from Products.Archetypes.public import LinesField |
|
24
|
|
|
from Products.Archetypes.public import MultiSelectionWidget |
|
25
|
|
|
from Products.Archetypes.public import Schema |
|
26
|
|
|
from Products.Archetypes.public import SelectionWidget |
|
27
|
|
|
from Products.Archetypes.public import StringField |
|
28
|
|
|
from Products.Archetypes.public import StringWidget |
|
29
|
|
|
from Products.Archetypes.public import TextAreaWidget |
|
30
|
|
|
from Products.Archetypes.public import TextField |
|
31
|
|
|
from Products.Archetypes.public import registerType |
|
32
|
|
|
from Products.Archetypes.utils import DisplayList |
|
33
|
|
|
from Products.CMFCore.utils import getToolByName |
|
34
|
|
|
from zope.interface import implements |
|
35
|
|
|
|
|
36
|
|
|
|
|
37
|
|
|
schema = BikaSchema.copy() + Schema(( |
|
38
|
|
|
|
|
39
|
|
|
# Method ID should be unique, specified on MethodSchemaModifier |
|
40
|
|
|
StringField( |
|
41
|
|
|
"MethodID", |
|
42
|
|
|
searchable=1, |
|
43
|
|
|
required=0, |
|
44
|
|
|
validators=("uniquefieldvalidator",), |
|
45
|
|
|
widget=StringWidget( |
|
46
|
|
|
visible={"view": "visible", "edit": "visible"}, |
|
47
|
|
|
label=_("Method ID"), |
|
48
|
|
|
description=_("Define an identifier code for the method. " |
|
49
|
|
|
"It must be unique."), |
|
50
|
|
|
), |
|
51
|
|
|
), |
|
52
|
|
|
|
|
53
|
|
|
TextField( |
|
54
|
|
|
"Instructions", |
|
55
|
|
|
default_content_type="text/plain", |
|
56
|
|
|
allowed_content_types=("text/plain", ), |
|
57
|
|
|
default_output_type="text/plain", |
|
58
|
|
|
widget=TextAreaWidget( |
|
59
|
|
|
label=_("Instructions"), |
|
60
|
|
|
description=_("Technical description and instructions " |
|
61
|
|
|
"intended for analysts"), |
|
62
|
|
|
), |
|
63
|
|
|
), |
|
64
|
|
|
|
|
65
|
|
|
BlobFileField( |
|
66
|
|
|
"MethodDocument", # XXX Multiple Method documents please |
|
67
|
|
|
widget=FileWidget( |
|
68
|
|
|
label=_("Method Document"), |
|
69
|
|
|
description=_("Load documents describing the method here"), |
|
70
|
|
|
) |
|
71
|
|
|
), |
|
72
|
|
|
|
|
73
|
|
|
# The instruments linked to this method. Don't use this |
|
74
|
|
|
# method, use getInstrumentUIDs() or getInstruments() instead |
|
75
|
|
|
LinesField( |
|
76
|
|
|
"_Instruments", |
|
77
|
|
|
vocabulary="getInstrumentsDisplayList", |
|
78
|
|
|
widget=MultiSelectionWidget( |
|
79
|
|
|
modes=("edit"), |
|
80
|
|
|
label=_("Instruments"), |
|
81
|
|
|
description=_( |
|
82
|
|
|
"The selected instruments have support for this method. " |
|
83
|
|
|
"Use the Instrument edit view to assign " |
|
84
|
|
|
"the method to a specific instrument"), |
|
85
|
|
|
), |
|
86
|
|
|
), |
|
87
|
|
|
|
|
88
|
|
|
# All the instruments available in the system. Don't use this |
|
89
|
|
|
# method to retrieve the instruments linked to this method, use |
|
90
|
|
|
# getInstruments() or getInstrumentUIDs() instead. |
|
91
|
|
|
LinesField( |
|
92
|
|
|
"_AvailableInstruments", |
|
93
|
|
|
vocabulary="_getAvailableInstrumentsDisplayList", |
|
94
|
|
|
widget=MultiSelectionWidget( |
|
95
|
|
|
modes=("edit"), |
|
96
|
|
|
) |
|
97
|
|
|
), |
|
98
|
|
|
|
|
99
|
|
|
# If no instrument selected, always True. Otherwise, the user will |
|
100
|
|
|
# be able to set or unset the value. The behavior for this field |
|
101
|
|
|
# is controlled with javascript. |
|
102
|
|
|
BooleanField( |
|
103
|
|
|
"ManualEntryOfResults", |
|
104
|
|
|
default=False, |
|
105
|
|
|
widget=BooleanWidget( |
|
106
|
|
|
label=_("Manual entry of results"), |
|
107
|
|
|
description=_("The results for the Analysis Services that use " |
|
108
|
|
|
"this method can be set manually"), |
|
109
|
|
|
modes=("edit"), |
|
110
|
|
|
) |
|
111
|
|
|
), |
|
112
|
|
|
|
|
113
|
|
|
# Only shown in readonly view. Not in edit view |
|
114
|
|
|
ComputedField( |
|
115
|
|
|
"ManualEntryOfResultsViewField", |
|
116
|
|
|
expression="context.isManualEntryOfResults()", |
|
117
|
|
|
widget=BooleanWidget( |
|
118
|
|
|
label=_("Manual entry of results"), |
|
119
|
|
|
description=_("The results for the Analysis Services that use " |
|
120
|
|
|
"this method can be set manually"), |
|
121
|
|
|
modes=("view"), |
|
122
|
|
|
), |
|
123
|
|
|
), |
|
124
|
|
|
|
|
125
|
|
|
# Calculations associated to this method. The analyses services |
|
126
|
|
|
# with this method assigned will use the calculation selected here. |
|
127
|
|
|
UIDReferenceField( |
|
128
|
|
|
"Calculation", |
|
129
|
|
|
vocabulary="_getCalculations", |
|
130
|
|
|
allowed_types=("Calculation",), |
|
131
|
|
|
accessor="getCalculationUID", |
|
132
|
|
|
widget=SelectionWidget( |
|
133
|
|
|
visible={"edit": "visible", "view": "visible"}, |
|
134
|
|
|
format="select", |
|
135
|
|
|
checkbox_bound=0, |
|
136
|
|
|
label=_("Calculation"), |
|
137
|
|
|
description=_( |
|
138
|
|
|
"If required, select a calculation for the The analysis " |
|
139
|
|
|
"services linked to this method. Calculations can be " |
|
140
|
|
|
"configured under the calculations item in the LIMS set-up"), |
|
141
|
|
|
catalog_name="bika_setup_catalog", |
|
142
|
|
|
base_query={"is_active": True}, |
|
143
|
|
|
) |
|
144
|
|
|
), |
|
145
|
|
|
BooleanField( |
|
146
|
|
|
"Accredited", |
|
147
|
|
|
schemata="default", |
|
148
|
|
|
default=True, |
|
149
|
|
|
widget=BooleanWidget( |
|
150
|
|
|
label=_("Accredited"), |
|
151
|
|
|
description=_("Check if the method has been accredited")) |
|
152
|
|
|
), |
|
153
|
|
|
)) |
|
154
|
|
|
|
|
155
|
|
|
schema["description"].schemata = "default" |
|
156
|
|
|
schema["description"].widget.visible = True |
|
157
|
|
|
schema["description"].widget.label = _("Description") |
|
158
|
|
|
schema["description"].widget.description = _( |
|
159
|
|
|
"Describes the method in layman terms. " |
|
160
|
|
|
"This information is made available to lab clients") |
|
161
|
|
|
|
|
162
|
|
|
|
|
163
|
|
|
class Method(BaseFolder): |
|
164
|
|
|
"""Method content |
|
165
|
|
|
""" |
|
166
|
|
|
implements(IMethod, IDeactivable) |
|
167
|
|
|
|
|
168
|
|
|
security = ClassSecurityInfo() |
|
169
|
|
|
displayContentsTab = False |
|
170
|
|
|
schema = schema |
|
|
|
|
|
|
171
|
|
|
_at_rename_after_creation = True |
|
172
|
|
|
|
|
173
|
|
|
def _renameAfterCreation(self, check_auto_id=False): |
|
174
|
|
|
from bika.lims.idserver import renameAfterCreation |
|
175
|
|
|
renameAfterCreation(self) |
|
176
|
|
|
|
|
177
|
|
|
@security.public |
|
178
|
|
|
def getCalculation(self): |
|
179
|
|
|
"""Returns the assigned calculation |
|
180
|
|
|
|
|
181
|
|
|
:returns: Calculation object |
|
182
|
|
|
""" |
|
183
|
|
|
return self.getField("Calculation").get(self) |
|
184
|
|
|
|
|
185
|
|
|
@security.public |
|
186
|
|
|
def getCalculationUID(self): |
|
187
|
|
|
"""Returns the UID of the assigned calculation |
|
188
|
|
|
|
|
189
|
|
|
NOTE: This is the default accessor of the `Calculation` schema field |
|
190
|
|
|
and needed for the selection widget to render the selected value |
|
191
|
|
|
properly in _view_ mode. |
|
192
|
|
|
|
|
193
|
|
|
:returns: Calculation UID |
|
194
|
|
|
""" |
|
195
|
|
|
calculation = self.getCalculation() |
|
196
|
|
|
if not calculation: |
|
197
|
|
|
return None |
|
198
|
|
|
return api.get_uid(calculation) |
|
199
|
|
|
|
|
200
|
|
|
def isManualEntryOfResults(self): |
|
201
|
|
|
"""Indicates if manual entry of results is allowed. |
|
202
|
|
|
|
|
203
|
|
|
If no instrument is selected for this method, returns True. Otherwise, |
|
204
|
|
|
returns False by default, but its value can be modified using the |
|
205
|
|
|
ManualEntryOfResults Boolean Field |
|
206
|
|
|
""" |
|
207
|
|
|
instruments = self.getInstruments() |
|
208
|
|
|
return len(instruments) == 0 or self.getManualEntryOfResults() |
|
209
|
|
|
|
|
210
|
|
View Code Duplication |
def _getCalculations(self): |
|
|
|
|
|
|
211
|
|
|
"""Available Calculations registered in Setup |
|
212
|
|
|
""" |
|
213
|
|
|
bsc = getToolByName(self, "bika_setup_catalog") |
|
214
|
|
|
items = [(c.UID, c.Title) |
|
215
|
|
|
for c in bsc(portal_type="Calculation", |
|
216
|
|
|
is_active=True)] |
|
217
|
|
|
items.sort(lambda x, y: cmp(x[1], y[1])) |
|
218
|
|
|
items.insert(0, ("", t(_("None")))) |
|
219
|
|
|
return DisplayList(items) |
|
220
|
|
|
|
|
221
|
|
|
def getInstruments(self): |
|
222
|
|
|
"""Instruments capable to perform this method |
|
223
|
|
|
""" |
|
224
|
|
|
return self.getBackReferences("InstrumentMethods") |
|
225
|
|
|
|
|
226
|
|
|
def getInstrumentUIDs(self): |
|
227
|
|
|
"""UIDs of the instruments capable to perform this method |
|
228
|
|
|
""" |
|
229
|
|
|
return map(api.get_uid, self.getInstruments()) |
|
230
|
|
|
|
|
231
|
|
|
def getInstrumentsDisplayList(self): |
|
232
|
|
|
"""Instruments capable to perform this method |
|
233
|
|
|
""" |
|
234
|
|
|
items = [(i.UID(), i.Title()) for i in self.getInstruments()] |
|
235
|
|
|
return DisplayList(list(items)) |
|
236
|
|
|
|
|
237
|
|
View Code Duplication |
def _getAvailableInstrumentsDisplayList(self): |
|
|
|
|
|
|
238
|
|
|
"""Available instruments registered in the system |
|
239
|
|
|
|
|
240
|
|
|
Only instruments with state=active will be fetched |
|
241
|
|
|
""" |
|
242
|
|
|
bsc = getToolByName(self, "bika_setup_catalog") |
|
243
|
|
|
items = [(i.UID, i.Title) |
|
244
|
|
|
for i in bsc(portal_type="Instrument", |
|
245
|
|
|
is_active=True)] |
|
246
|
|
|
items.sort(lambda x, y: cmp(x[1], y[1])) |
|
247
|
|
|
return DisplayList(list(items)) |
|
248
|
|
|
|
|
249
|
|
|
|
|
250
|
|
|
registerType(Method, PROJECTNAME) |
|
251
|
|
|
|