|
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-2019 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", "SamplingRound"]) |
|
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() is not '': |
|
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 AccountancyFieldsVisibility(SenaiteATWidgetVisibility): |
|
194
|
|
|
"""Display/Hide fields related with Accountancy (Discount, prices, invoice) |
|
195
|
|
|
""" |
|
196
|
|
|
def __init__(self, context): |
|
197
|
|
|
super(AccountancyFieldsVisibility, self).__init__( |
|
198
|
|
|
context=context, sort=3, |
|
199
|
|
|
field_names=["BulkDiscount", "MemberDiscountApplies", |
|
200
|
|
|
"InvoiceExclude", "MemberDiscount"]) |
|
201
|
|
|
|
|
202
|
|
|
def isVisible(self, field, mode="view", default="visible"): |
|
203
|
|
|
if not self.context.bika_setup.getShowPrices(): |
|
204
|
|
|
return "invisible" |
|
205
|
|
|
return default |
|
206
|
|
|
|
|
207
|
|
|
|
|
208
|
|
|
class DateReceivedFieldVisibility(SenaiteATWidgetVisibility): |
|
209
|
|
|
"""DateReceived is editable in sample context, only if all related analyses |
|
210
|
|
|
are not yet submitted and if not a secondary sample. |
|
211
|
|
|
""" |
|
212
|
|
|
def __init__(self, context): |
|
213
|
|
|
super(DateReceivedFieldVisibility, self).__init__( |
|
214
|
|
|
context=context, sort=3, field_names=["DateReceived"]) |
|
215
|
|
|
|
|
216
|
|
|
def isVisible(self, field, mode="view", default="visible"): |
|
217
|
|
|
"""Returns whether the field is visible in a given mode |
|
218
|
|
|
""" |
|
219
|
|
|
if mode != "edit": |
|
220
|
|
|
return default |
|
221
|
|
|
|
|
222
|
|
|
# If this is a Secondary Analysis Request, this field is not editable |
|
223
|
|
|
if IAnalysisRequestSecondary.providedBy(self.context): |
|
224
|
|
|
return "invisible" |
|
225
|
|
|
|
|
226
|
|
|
return self.context.isOpen() and "visible" or "invisible" |
|
227
|
|
|
|
|
228
|
|
|
|
|
229
|
|
|
class SecondaryDateSampledFieldVisibility(SenaiteATWidgetVisibility): |
|
230
|
|
|
"""DateSampled is editable in sample unless secondary sample |
|
231
|
|
|
""" |
|
232
|
|
|
def __init__(self, context): |
|
233
|
|
|
super(SecondaryDateSampledFieldVisibility, self).__init__( |
|
234
|
|
|
context=context, sort=20, field_names=["DateSampled"]) |
|
235
|
|
|
|
|
236
|
|
|
def isVisible(self, field, mode="view", default="visible"): |
|
237
|
|
|
"""Returns whether the field is visible in a given mode |
|
238
|
|
|
""" |
|
239
|
|
|
if mode != "edit": |
|
240
|
|
|
return default |
|
241
|
|
|
|
|
242
|
|
|
# If this is a Secondary Analysis Request, this field is not editable |
|
243
|
|
|
if IAnalysisRequestSecondary.providedBy(self.context): |
|
244
|
|
|
return "invisible" |
|
245
|
|
|
|
|
246
|
|
|
# Delegate to SamplingFieldsVisibility adapter |
|
247
|
|
|
return SamplingFieldsVisibility(self.context).isVisible( |
|
248
|
|
|
field, mode=mode, default=default) |
|
249
|
|
|
|
|
250
|
|
|
|
|
251
|
|
|
class PrimaryAnalysisRequestFieldVisibility(SenaiteATWidgetVisibility): |
|
252
|
|
|
"""PrimarySample field is not visible unless the current Sample is a |
|
253
|
|
|
Secondary Sample. And even in such case, the field cannot be edited |
|
254
|
|
|
""" |
|
255
|
|
|
def __init__(self, context): |
|
256
|
|
|
super(PrimaryAnalysisRequestFieldVisibility, self).__init__( |
|
257
|
|
|
context=context, sort=3, field_names=["PrimaryAnalysisRequest"]) |
|
258
|
|
|
|
|
259
|
|
|
def isVisible(self, field, mode="view", default="visible"): |
|
260
|
|
|
"""Returns whether the field is visible in a given mode |
|
261
|
|
|
""" |
|
262
|
|
|
if mode == "add": |
|
263
|
|
|
return default |
|
264
|
|
|
|
|
265
|
|
|
if not IAnalysisRequestSecondary.providedBy(self.context): |
|
266
|
|
|
# If not a secondary Analysis Request, don't render the field |
|
267
|
|
|
return "hidden" |
|
268
|
|
|
|
|
269
|
|
|
# No mather if the mode is edit or view, display it always as readonly |
|
270
|
|
|
if mode == "edit": |
|
271
|
|
|
return "invisible" |
|
272
|
|
|
|
|
273
|
|
|
return default |
|
274
|
|
|
|
|
275
|
|
|
|
|
276
|
|
|
class BatchClientFieldVisibility(SenaiteATWidgetVisibility): |
|
277
|
|
|
"""Client field in a Batch in only editable while it is being created or |
|
278
|
|
|
when the Batch does not contain any sample |
|
279
|
|
|
""" |
|
280
|
|
|
def __init__(self, context): |
|
281
|
|
|
super(BatchClientFieldVisibility, self).__init__( |
|
282
|
|
|
context=context, sort=3, field_names=["Client"]) |
|
283
|
|
|
|
|
284
|
|
|
def isVisible(self, field, mode="view", default="visible"): |
|
285
|
|
|
"""Returns whether the field is visible in a given state |
|
286
|
|
|
""" |
|
287
|
|
|
if self.context.getClient(): |
|
288
|
|
|
# This batch has a client assigned already and this cannot be |
|
289
|
|
|
# changed to prevent inconsistencies (client contacts can access |
|
290
|
|
|
# to batches that belong to their same client) |
|
291
|
|
|
return "invisible" |
|
292
|
|
|
|
|
293
|
|
|
if mode == "edit": |
|
294
|
|
|
# This batch does not have a client assigned, but allow the client |
|
295
|
|
|
# field to be editable only if does not contain any sample |
|
296
|
|
|
if self.context.getAnalysisRequestsBrains(): |
|
297
|
|
|
return "invisible" |
|
298
|
|
|
|
|
299
|
|
|
return default |
|
300
|
|
|
|
|
301
|
|
|
|
|
302
|
|
|
class InternalUseFieldVisibility(SenaiteATWidgetVisibility): |
|
303
|
|
|
"""InternalUse field must only be visible to lab personnel |
|
304
|
|
|
""" |
|
305
|
|
|
def __init__(self, context): |
|
306
|
|
|
super(InternalUseFieldVisibility, self).__init__( |
|
307
|
|
|
context=context, sort=3, field_names=["InternalUse"]) |
|
308
|
|
|
|
|
309
|
|
|
def isVisible(self, field, mode="view", default="visible"): |
|
310
|
|
|
"""Returns whether the field is visible in a given mode |
|
311
|
|
|
""" |
|
312
|
|
|
return api.get_current_client() and "invisible" or default |
|
313
|
|
|
|