1
|
|
|
# -*- coding: utf-8 -*- |
2
|
|
|
# |
3
|
|
|
# This file is part of SENAITE.CORE. |
4
|
|
|
# |
5
|
|
|
# SENAITE.CORE is free software: you can redistribute it and/or modify it under |
6
|
|
|
# the terms of the GNU General Public License as published by the Free Software |
7
|
|
|
# Foundation, version 2. |
8
|
|
|
# |
9
|
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT |
10
|
|
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
11
|
|
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
12
|
|
|
# details. |
13
|
|
|
# |
14
|
|
|
# You should have received a copy of the GNU General Public License along with |
15
|
|
|
# this program; if not, write to the Free Software Foundation, Inc., 51 |
16
|
|
|
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
17
|
|
|
# |
18
|
|
|
# Copyright 2018-2021 by it's authors. |
19
|
|
|
# Some rights reserved, see README and LICENSE. |
20
|
|
|
|
21
|
|
|
from bika.lims import api |
22
|
|
|
from bika.lims.interfaces import IATWidgetVisibility |
23
|
|
|
from bika.lims.interfaces import IAnalysisRequestSecondary |
24
|
|
|
from bika.lims.interfaces import IBatch |
25
|
|
|
from bika.lims.interfaces import IClient |
26
|
|
|
from bika.lims.utils import getHiddenAttributesForClass |
27
|
|
|
from zope.interface import implements |
28
|
|
|
|
29
|
|
|
_marker = [] |
30
|
|
|
|
31
|
|
|
|
32
|
|
|
class SenaiteATWidgetVisibility(object): |
33
|
|
|
implements(IATWidgetVisibility) |
34
|
|
|
|
35
|
|
|
def __init__(self, context, sort=1000, field_names=None): |
36
|
|
|
self.context = context |
37
|
|
|
self.sort = sort |
38
|
|
|
self.field_names = field_names or list() |
39
|
|
|
|
40
|
|
|
def __call__(self, context, mode, field, default): |
41
|
|
|
state = default if default else "visible" |
42
|
|
|
if not field or field.getName() not in self.field_names: |
43
|
|
|
return state |
44
|
|
|
return self.isVisible(field, mode, default) |
45
|
|
|
|
46
|
|
|
def isVisible(self, field, mode="view", default="visible"): |
47
|
|
|
"""Returns if the field is visible in a given mode |
48
|
|
|
|
49
|
|
|
Possible returned values are: |
50
|
|
|
- hidden: Field rendered as a hidden input field |
51
|
|
|
- invisible: Field not rendered at all |
52
|
|
|
- visible: Field rendered as a label or editable field depending on the |
53
|
|
|
mode. E.g. if mode is "edit" and the value returned is "visible", |
54
|
|
|
the field will be rendered as an input. If the mode is "view", the |
55
|
|
|
field will be rendered as a span. |
56
|
|
|
""" |
57
|
|
|
raise NotImplementedError("Must be implemented by subclass") |
58
|
|
|
|
59
|
|
|
|
60
|
|
|
class ClientFieldVisibility(SenaiteATWidgetVisibility): |
61
|
|
|
"""The Client field is editable by default in ar_add. This adapter |
62
|
|
|
will force the Client field to be hidden when it should not be set |
63
|
|
|
by the user. |
64
|
|
|
""" |
65
|
|
|
def __init__(self, context): |
66
|
|
|
super(ClientFieldVisibility, self).__init__( |
67
|
|
|
context=context, sort=10, field_names=["Client"]) |
68
|
|
|
|
69
|
|
|
def isVisible(self, field, mode="view", default="visible"): |
70
|
|
|
if mode == "add": |
71
|
|
|
parent = self.context.aq_parent |
72
|
|
|
if IClient.providedBy(parent): |
73
|
|
|
# Note we return "hidden" here instead of "invisible": we want |
74
|
|
|
# the field to be auto-filled and processed on submit |
75
|
|
|
return "hidden" |
76
|
|
|
if IBatch.providedBy(parent) and parent.getClient(): |
77
|
|
|
# The Batch has a Client assigned already! |
78
|
|
|
# Note we can have Batches without a client assigned |
79
|
|
|
return "hidden" |
80
|
|
|
elif mode == "edit": |
81
|
|
|
# This is already managed by wf permission, but is **never** a good |
82
|
|
|
# idea to allow the user to change the Client from an AR (basically |
83
|
|
|
# because otherwise, we'd need to move the object from one client |
84
|
|
|
# folder to another!). |
85
|
|
|
return "invisible" |
86
|
|
|
return default |
87
|
|
|
|
88
|
|
|
|
89
|
|
|
class BatchFieldVisibility(SenaiteATWidgetVisibility): |
90
|
|
|
"""This will force the 'Batch' field to 'hidden' in ar_add when the parent |
91
|
|
|
context is a Batch and in Analysis Request view when current user is a |
92
|
|
|
client and the assigned batch does not have a client assigned. |
93
|
|
|
""" |
94
|
|
|
def __init__(self, context): |
95
|
|
|
super(BatchFieldVisibility, self).__init__( |
96
|
|
|
context=context, sort=10, field_names=["Batch"]) |
97
|
|
|
|
98
|
|
|
def isVisible(self, field, mode="view", default="visible"): |
99
|
|
|
if IBatch.providedBy(self.context.aq_parent): |
100
|
|
|
return "hidden" |
101
|
|
|
|
102
|
|
|
if mode == "edit": |
103
|
|
|
client = api.get_current_client() |
104
|
|
|
if client: |
105
|
|
|
# If current user is a client contact and the batch this Sample |
106
|
|
|
# is assigned to does not have a client assigned (e.g., the |
107
|
|
|
# batch was assigned by lab personnel), hide this field |
108
|
|
|
batch = self.context.getBatch() |
109
|
|
|
if batch and batch.getClient() != client: |
110
|
|
|
return "invisible" |
111
|
|
|
|
112
|
|
|
return default |
113
|
|
|
|
114
|
|
|
|
115
|
|
|
class PreservationFieldsVisibility(SenaiteATWidgetVisibility): |
116
|
|
|
"""Display/Hide fields related with Preservation Workflow |
117
|
|
|
""" |
118
|
|
|
def __init__(self, context): |
119
|
|
|
super(PreservationFieldsVisibility, self).__init__( |
120
|
|
|
context=context, sort=10, |
121
|
|
|
field_names=["DatePreserved", "Preserver"]) |
122
|
|
|
|
123
|
|
|
def isVisible(self, field, mode="view", default="visible"): |
124
|
|
|
if not self.context.bika_setup.getSamplePreservationEnabled(): |
125
|
|
|
return "invisible" |
126
|
|
|
return default |
127
|
|
|
|
128
|
|
|
|
129
|
|
|
class ScheduledSamplingFieldsVisibility(SenaiteATWidgetVisibility): |
130
|
|
|
"""Display/Hide fields related with ScheduledSampling Workflow |
131
|
|
|
""" |
132
|
|
|
def __init__(self, context): |
133
|
|
|
super(ScheduledSamplingFieldsVisibility, self).__init__( |
134
|
|
|
context=context, sort=10, |
135
|
|
|
field_names=["ScheduledSamplingSampler"]) |
136
|
|
|
|
137
|
|
|
def isVisible(self, field, mode="view", default="visible"): |
138
|
|
|
if not self.context.bika_setup.getScheduleSamplingEnabled(): |
139
|
|
|
return "invisible" |
140
|
|
|
return default |
141
|
|
|
|
142
|
|
|
|
143
|
|
|
class SamplingFieldsVisibility(SenaiteATWidgetVisibility): |
144
|
|
|
""" |
145
|
|
|
This will handle Handling 'DateSampled' and 'SamplingDate' fields' |
146
|
|
|
visibilities based on Sampling Workflow (SWF)status. We must check the |
147
|
|
|
attribute saved on the sample, not the bika_setup value though. See the |
148
|
|
|
internal comments how it enables/disables WidgetVisibility depending on SWF. |
149
|
|
|
""" |
150
|
|
|
def __init__(self, context): |
151
|
|
|
super(SamplingFieldsVisibility, self).__init__( |
152
|
|
|
context=context, sort=10, |
153
|
|
|
field_names=["Sampler", "DateSampled", "SamplingDate"]) |
154
|
|
|
|
155
|
|
|
def isVisible(self, field, mode="view", default="visible"): |
156
|
|
|
# If object has been already created, get SWF statues from it. |
157
|
|
|
swf_enabled = False |
158
|
|
|
if hasattr(self.context, 'getSamplingWorkflowEnabled') and \ |
159
|
|
|
self.context.getSamplingWorkflowEnabled() != '': |
160
|
|
|
swf_enabled = self.context.getSamplingWorkflowEnabled() |
161
|
|
|
else: |
162
|
|
|
swf_enabled = self.context.bika_setup.getSamplingWorkflowEnabled() |
163
|
|
|
|
164
|
|
|
if mode == "add": |
165
|
|
|
if field.getName() == "DateSampled": |
166
|
|
|
field.required = not swf_enabled |
167
|
|
|
return swf_enabled and "invisible" or "edit" |
168
|
|
|
elif field.getName() == "SamplingDate": |
169
|
|
|
field.required = swf_enabled |
170
|
|
|
return swf_enabled and "edit" or "invisible" |
171
|
|
|
elif field.getName() == "Sampler": |
172
|
|
|
return swf_enabled and "edit" or "invisible" |
173
|
|
|
|
174
|
|
|
elif not swf_enabled: |
175
|
|
|
if field.getName() != "DateSampled": |
176
|
|
|
return "invisible" |
177
|
|
|
|
178
|
|
|
return default |
179
|
|
|
|
180
|
|
|
|
181
|
|
|
class RegistryHiddenFieldsVisibility(SenaiteATWidgetVisibility): |
182
|
|
|
"""Do not display fields declared in bika.lims.hiddenattributes registry key |
183
|
|
|
""" |
184
|
|
|
def __init__(self, context): |
185
|
|
|
field_names = getHiddenAttributesForClass(context.portal_type) |
186
|
|
|
super(RegistryHiddenFieldsVisibility, self).__init__( |
187
|
|
|
context=context, sort=float("inf"), field_names=[field_names, ]) |
188
|
|
|
|
189
|
|
|
def isVisible(self, field, mode="view", default="visible"): |
190
|
|
|
return "invisible" |
191
|
|
|
|
192
|
|
|
|
193
|
|
|
class DateReceivedFieldVisibility(SenaiteATWidgetVisibility): |
194
|
|
|
"""DateReceived is editable in sample context, only if all related analyses |
195
|
|
|
are not yet submitted and if not a secondary sample. |
196
|
|
|
""" |
197
|
|
|
def __init__(self, context): |
198
|
|
|
super(DateReceivedFieldVisibility, self).__init__( |
199
|
|
|
context=context, sort=3, field_names=["DateReceived"]) |
200
|
|
|
|
201
|
|
|
def isVisible(self, field, mode="view", default="visible"): |
202
|
|
|
"""Returns whether the field is visible in a given mode |
203
|
|
|
""" |
204
|
|
|
if mode != "edit": |
205
|
|
|
return default |
206
|
|
|
|
207
|
|
|
# If this is a Secondary Analysis Request, this field is not editable |
208
|
|
|
if IAnalysisRequestSecondary.providedBy(self.context): |
209
|
|
|
return "invisible" |
210
|
|
|
|
211
|
|
|
return self.context.isOpen() and "visible" or "invisible" |
212
|
|
|
|
213
|
|
|
|
214
|
|
|
class SecondaryDateSampledFieldVisibility(SenaiteATWidgetVisibility): |
215
|
|
|
"""DateSampled is editable in sample unless secondary sample |
216
|
|
|
""" |
217
|
|
|
def __init__(self, context): |
218
|
|
|
super(SecondaryDateSampledFieldVisibility, self).__init__( |
219
|
|
|
context=context, sort=20, field_names=["DateSampled"]) |
220
|
|
|
|
221
|
|
|
def isVisible(self, field, mode="view", default="visible"): |
222
|
|
|
"""Returns whether the field is visible in a given mode |
223
|
|
|
""" |
224
|
|
|
if mode != "edit": |
225
|
|
|
return default |
226
|
|
|
|
227
|
|
|
# If this is a Secondary Analysis Request, this field is not editable |
228
|
|
|
if IAnalysisRequestSecondary.providedBy(self.context): |
229
|
|
|
return "invisible" |
230
|
|
|
|
231
|
|
|
# Delegate to SamplingFieldsVisibility adapter |
232
|
|
|
return SamplingFieldsVisibility(self.context).isVisible( |
233
|
|
|
field, mode=mode, default=default) |
234
|
|
|
|
235
|
|
|
|
236
|
|
|
class PrimaryAnalysisRequestFieldVisibility(SenaiteATWidgetVisibility): |
237
|
|
|
"""PrimarySample field is not visible unless the current Sample is a |
238
|
|
|
Secondary Sample. And even in such case, the field cannot be edited |
239
|
|
|
""" |
240
|
|
|
def __init__(self, context): |
241
|
|
|
super(PrimaryAnalysisRequestFieldVisibility, self).__init__( |
242
|
|
|
context=context, sort=3, field_names=["PrimaryAnalysisRequest"]) |
243
|
|
|
|
244
|
|
|
def isVisible(self, field, mode="view", default="visible"): |
245
|
|
|
"""Returns whether the field is visible in a given mode |
246
|
|
|
""" |
247
|
|
|
if mode == "add": |
248
|
|
|
return default |
249
|
|
|
|
250
|
|
|
if not IAnalysisRequestSecondary.providedBy(self.context): |
251
|
|
|
# If not a secondary Analysis Request, don't render the field |
252
|
|
|
return "hidden" |
253
|
|
|
|
254
|
|
|
# No mather if the mode is edit or view, display it always as readonly |
255
|
|
|
if mode == "edit": |
256
|
|
|
return "invisible" |
257
|
|
|
|
258
|
|
|
return default |
259
|
|
|
|
260
|
|
|
|
261
|
|
|
class BatchClientFieldVisibility(SenaiteATWidgetVisibility): |
262
|
|
|
"""Client field in a Batch in only editable while it is being created or |
263
|
|
|
when the Batch does not contain any sample |
264
|
|
|
""" |
265
|
|
|
def __init__(self, context): |
266
|
|
|
super(BatchClientFieldVisibility, self).__init__( |
267
|
|
|
context=context, sort=3, field_names=["Client"]) |
268
|
|
|
|
269
|
|
|
def isVisible(self, field, mode="view", default="visible"): |
270
|
|
|
"""Returns whether the field is visible in a given state |
271
|
|
|
""" |
272
|
|
|
if self.context.getClient(): |
273
|
|
|
# This batch has a client assigned already and this cannot be |
274
|
|
|
# changed to prevent inconsistencies (client contacts can access |
275
|
|
|
# to batches that belong to their same client) |
276
|
|
|
return "invisible" |
277
|
|
|
|
278
|
|
|
if mode == "edit": |
279
|
|
|
# This batch does not have a client assigned, but allow the client |
280
|
|
|
# field to be editable only if does not contain any sample |
281
|
|
|
if self.context.getAnalysisRequestsBrains(): |
282
|
|
|
return "invisible" |
283
|
|
|
|
284
|
|
|
return default |
285
|
|
|
|
286
|
|
|
|
287
|
|
|
class InternalUseFieldVisibility(SenaiteATWidgetVisibility): |
288
|
|
|
"""InternalUse field must only be visible to lab personnel |
289
|
|
|
""" |
290
|
|
|
def __init__(self, context): |
291
|
|
|
super(InternalUseFieldVisibility, self).__init__( |
292
|
|
|
context=context, sort=3, field_names=["InternalUse"]) |
293
|
|
|
|
294
|
|
|
def isVisible(self, field, mode="view", default="visible"): |
295
|
|
|
"""Returns whether the field is visible in a given mode |
296
|
|
|
""" |
297
|
|
|
return api.get_current_client() and "invisible" or default |
298
|
|
|
|