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
|
|
|
|