Total Complexity | 58 |
Total Lines | 827 |
Duplicated Lines | 6.05 % |
Changes | 0 |
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like bika.lims.content.sample often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
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 | """Sample represents a physical sample submitted for testing |
||
9 | """ |
||
10 | from AccessControl import ClassSecurityInfo |
||
11 | from bika.lims import bikaMessageFactory as _ |
||
12 | from bika.lims.api import get_object_by_uid |
||
13 | from bika.lims.browser.fields.remarksfield import RemarksField |
||
14 | from bika.lims.browser.fields.uidreferencefield import get_backreferences |
||
15 | from bika.lims.utils import t, getUsers |
||
16 | from Products.ATExtensions.field import RecordsField |
||
17 | from bika.lims.browser.widgets.datetimewidget import DateTimeWidget |
||
18 | from bika.lims.browser.widgets import RejectionWidget |
||
19 | from bika.lims.browser.widgets import RemarksWidget |
||
20 | from bika.lims.config import PROJECTNAME |
||
21 | from bika.lims.content.bikaschema import BikaSchema |
||
22 | from bika.lims.interfaces import ISample |
||
23 | from bika.lims.permissions import SampleSample |
||
24 | from bika.lims.permissions import ScheduleSampling |
||
25 | from bika.lims.workflow.sample import guards |
||
26 | from Products.Archetypes import atapi |
||
27 | from Products.Archetypes.public import * |
||
28 | from Products.Archetypes.references import HoldingReference |
||
29 | from Products.ATContentTypes.lib.historyaware import HistoryAwareMixin |
||
30 | from Products.ATContentTypes.utils import DT2dt, dt2DT |
||
31 | from Products.CMFCore import permissions |
||
32 | from Products.CMFCore.utils import getToolByName |
||
33 | from Products.CMFPlone.utils import safe_unicode |
||
34 | from zope.interface import implements |
||
35 | |||
36 | from bika.lims.browser.fields import DateTimeField |
||
37 | from bika.lims.browser.widgets import ReferenceWidget |
||
38 | from bika.lims.browser.widgets import SelectionWidget as BikaSelectionWidget |
||
39 | |||
40 | import sys |
||
41 | from bika.lims.utils import to_unicode |
||
42 | |||
43 | schema = BikaSchema.copy() + Schema(( |
||
44 | StringField('SampleID', |
||
45 | required=1, |
||
46 | searchable=True, |
||
47 | mode="rw", |
||
48 | read_permission=permissions.View, |
||
49 | write_permission=permissions.ModifyPortalContent, |
||
50 | widget=StringWidget( |
||
51 | label=_("Sample ID"), |
||
52 | description=_("The ID assigned to the client's sample by the lab"), |
||
53 | visible={'edit': 'invisible', |
||
54 | 'view': 'invisible'}, |
||
55 | render_own_label=True, |
||
56 | ), |
||
57 | ), |
||
58 | StringField('ClientReference', |
||
59 | mode="rw", |
||
60 | read_permission=permissions.View, |
||
61 | write_permission=permissions.ModifyPortalContent, |
||
62 | widget=StringWidget( |
||
63 | label=_("Client Reference"), |
||
64 | visible={'edit': 'visible', |
||
65 | 'view': 'visible', |
||
66 | 'header_table': 'visible', |
||
67 | 'sample_registered': {'view': 'visible', 'edit': 'visible'}, |
||
68 | 'to_be_sampled': {'view': 'visible', 'edit': 'visible'}, |
||
69 | 'scheduled_sampling': {'view': 'visible', 'edit': 'visible'}, |
||
70 | 'sampled': {'view': 'visible', 'edit': 'visible'}, |
||
71 | 'to_be_preserved': {'view': 'visible', 'edit': 'visible'}, |
||
72 | 'sample_due': {'view': 'visible', 'edit': 'visible'}, |
||
73 | 'sample_received': {'view': 'visible', 'edit': 'visible'}, |
||
74 | 'expired': {'view': 'visible', 'edit': 'invisible'}, |
||
75 | 'disposed': {'view': 'visible', 'edit': 'invisible'}, |
||
76 | 'rejected': {'view': 'visible', 'edit': 'invisible'}, |
||
77 | }, |
||
78 | render_own_label=True, |
||
79 | ), |
||
80 | ), |
||
81 | StringField('ClientSampleID', |
||
82 | mode="rw", |
||
83 | read_permission=permissions.View, |
||
84 | write_permission=permissions.ModifyPortalContent, |
||
85 | widget=StringWidget( |
||
86 | label=_("Client SID"), |
||
87 | visible={'edit': 'visible', |
||
88 | 'view': 'visible', |
||
89 | 'header_table': 'visible', |
||
90 | 'sample_registered': {'view': 'visible', 'edit': 'visible'}, |
||
91 | 'to_be_sampled': {'view': 'visible', 'edit': 'visible'}, |
||
92 | 'scheduled_sampling': {'view': 'visible', 'edit': 'visible'}, |
||
93 | 'sampled': {'view': 'visible', 'edit': 'visible'}, |
||
94 | 'to_be_preserved': {'view': 'visible', 'edit': 'visible'}, |
||
95 | 'sample_due': {'view': 'visible', 'edit': 'visible'}, |
||
96 | 'sample_received': {'view': 'visible', 'edit': 'visible'}, |
||
97 | 'expired': {'view': 'visible', 'edit': 'invisible'}, |
||
98 | 'disposed': {'view': 'visible', 'edit': 'invisible'}, |
||
99 | 'rejected': {'view': 'visible', 'edit': 'invisible'}, |
||
100 | }, |
||
101 | render_own_label=True, |
||
102 | ), |
||
103 | ), |
||
104 | ReferenceField('SampleType', |
||
105 | required=1, |
||
106 | vocabulary_display_path_bound=sys.maxsize, |
||
107 | allowed_types=('SampleType',), |
||
108 | relationship='SampleSampleType', |
||
109 | referenceClass=HoldingReference, |
||
110 | mode="rw", |
||
111 | read_permission=permissions.View, |
||
112 | write_permission=permissions.ModifyPortalContent, |
||
113 | widget=ReferenceWidget( |
||
114 | label=_("Sample Type"), |
||
115 | render_own_label=True, |
||
116 | visible={'edit': 'visible', |
||
117 | 'view': 'visible', |
||
118 | 'header_table': 'visible', |
||
119 | 'sample_registered': {'view': 'visible', 'edit': 'visible'}, |
||
120 | 'to_be_sampled': {'view': 'visible', 'edit': 'invisible'}, |
||
121 | 'scheduled_sampling': {'view': 'visible', 'edit': 'visible'}, |
||
122 | 'sampled': {'view': 'visible', 'edit': 'invisible'}, |
||
123 | 'to_be_preserved': {'view': 'visible', 'edit': 'invisible'}, |
||
124 | 'sample_due': {'view': 'visible', 'edit': 'invisible'}, |
||
125 | 'sample_received': {'view': 'visible', 'edit': 'invisible'}, |
||
126 | 'expired': {'view': 'visible', 'edit': 'invisible'}, |
||
127 | 'disposed': {'view': 'visible', 'edit': 'invisible'}, |
||
128 | 'rejected': {'view': 'visible', 'edit': 'invisible'}, |
||
129 | }, |
||
130 | catalog_name='bika_setup_catalog', |
||
131 | base_query={'inactive_state': 'active'}, |
||
132 | showOn=True, |
||
133 | ), |
||
134 | ), |
||
135 | ComputedField('SampleTypeTitle', |
||
136 | expression="here.getSampleType() and here.getSampleType().Title() or ''", |
||
137 | widget=ComputedWidget( |
||
138 | visible=False, |
||
139 | ), |
||
140 | ), |
||
141 | ReferenceField('SamplePoint', |
||
142 | vocabulary_display_path_bound=sys.maxsize, |
||
143 | allowed_types=('SamplePoint',), |
||
144 | relationship = 'SampleSamplePoint', |
||
145 | referenceClass = HoldingReference, |
||
146 | mode="rw", |
||
147 | read_permission=permissions.View, |
||
148 | write_permission=permissions.ModifyPortalContent, |
||
149 | widget=ReferenceWidget( |
||
150 | label=_("Sample Point"), |
||
151 | render_own_label=True, |
||
152 | visible={'edit': 'visible', |
||
153 | 'view': 'visible', |
||
154 | 'header_table': 'visible', |
||
155 | 'sample_registered': {'view': 'visible', 'edit': 'visible'}, |
||
156 | 'to_be_sampled': {'view': 'visible', 'edit': 'invisible'}, |
||
157 | 'scheduled_sampling': {'view': 'visible', 'edit': 'invisible'}, |
||
158 | 'sampled': {'view': 'visible', 'edit': 'invisible'}, |
||
159 | 'to_be_preserved': {'view': 'visible', 'edit': 'invisible'}, |
||
160 | 'sample_due': {'view': 'visible', 'edit': 'invisible'}, |
||
161 | 'sample_received': {'view': 'visible', 'edit': 'invisible'}, |
||
162 | 'expired': {'view': 'visible', 'edit': 'invisible'}, |
||
163 | 'disposed': {'view': 'visible', 'edit': 'invisible'}, |
||
164 | 'rejected': {'view': 'visible', 'edit': 'invisible'}, |
||
165 | }, |
||
166 | catalog_name='bika_setup_catalog', |
||
167 | base_query={'inactive_state': 'active'}, |
||
168 | showOn=True, |
||
169 | ), |
||
170 | ), |
||
171 | ComputedField('SamplePointTitle', |
||
172 | expression = "here.getSamplePoint() and here.getSamplePoint().Title() or ''", |
||
173 | widget = ComputedWidget( |
||
174 | visible=False, |
||
175 | ), |
||
176 | ), |
||
177 | ReferenceField( |
||
178 | 'StorageLocation', |
||
179 | allowed_types='StorageLocation', |
||
180 | relationship='AnalysisRequestStorageLocation', |
||
181 | mode="rw", |
||
182 | read_permission=permissions.View, |
||
183 | write_permission=permissions.ModifyPortalContent, |
||
184 | widget=ReferenceWidget( |
||
185 | label=_("Storage Location"), |
||
186 | description=_("Location where sample is kept"), |
||
187 | size=20, |
||
188 | render_own_label=True, |
||
189 | visible={'edit': 'visible', |
||
190 | 'view': 'visible', |
||
191 | 'header_table': 'visible', |
||
192 | 'sample_registered': {'view': 'visible', 'edit': 'visible'}, |
||
193 | 'to_be_sampled': {'view': 'visible', 'edit': 'visible'}, |
||
194 | 'scheduled_sampling': {'view': 'visible', 'edit': 'visible'}, |
||
195 | 'sampled': {'view': 'visible', 'edit': 'visible'}, |
||
196 | 'to_be_preserved': {'view': 'visible', 'edit': 'visible'}, |
||
197 | 'sample_due': {'view': 'visible', 'edit': 'visible'}, |
||
198 | 'sample_received': {'view': 'visible', 'edit': 'visible'}, |
||
199 | 'expired': {'view': 'visible', 'edit': 'visible'}, |
||
200 | 'disposed': {'view': 'visible', 'edit': 'invisible'}, |
||
201 | 'rejected': {'view': 'visible', 'edit': 'invisible'}, |
||
202 | }, |
||
203 | catalog_name='bika_setup_catalog', |
||
204 | base_query={'inactive_state': 'active'}, |
||
205 | showOn=True, |
||
206 | ), |
||
207 | ), |
||
208 | BooleanField('SamplingWorkflowEnabled', |
||
209 | default_method='getSamplingWorkflowEnabledDefault' |
||
210 | ), |
||
211 | DateTimeField('DateSampled', |
||
212 | mode="rw", |
||
213 | read_permission=permissions.View, |
||
214 | write_permission=SampleSample, |
||
215 | widget = DateTimeWidget( |
||
216 | label=_("Date Sampled"), |
||
217 | show_time=True, |
||
218 | size=20, |
||
219 | visible={'edit': 'visible', |
||
220 | 'view': 'visible', |
||
221 | 'header_table': 'visible', |
||
222 | 'sample_registered': {'view': 'invisible', 'edit': 'invisible'}, |
||
223 | 'to_be_sampled': {'view': 'visible', 'edit': 'visible'}, |
||
224 | 'scheduled_sampling': {'view': 'visible', 'edit': 'visible'}, |
||
225 | 'sampled': {'view': 'visible', 'edit': 'invisible'}, |
||
226 | 'to_be_preserved': {'view': 'visible', 'edit': 'invisible'}, |
||
227 | 'sample_due': {'view': 'visible', 'edit': 'invisible'}, |
||
228 | 'sample_received': {'view': 'visible', 'edit': 'invisible'}, |
||
229 | 'expired': {'view': 'visible', 'edit': 'invisible'}, |
||
230 | 'disposed': {'view': 'visible', 'edit': 'invisible'}, |
||
231 | 'rejected': {'view': 'visible', 'edit': 'invisible'}, |
||
232 | }, |
||
233 | render_own_label=True, |
||
234 | ), |
||
235 | ), |
||
236 | StringField('Sampler', |
||
237 | mode="rw", |
||
238 | read_permission=permissions.View, |
||
239 | write_permission=SampleSample, |
||
240 | vocabulary='getSamplers', |
||
241 | widget=BikaSelectionWidget( |
||
242 | format='select', |
||
243 | label=_("Sampler"), |
||
244 | visible={'edit': 'visible', |
||
245 | 'view': 'visible', |
||
246 | 'header_table': 'visible', |
||
247 | 'sample_registered': {'view': 'invisible', 'edit': 'invisible'}, |
||
248 | 'to_be_sampled': {'view': 'visible', 'edit': 'visible'}, |
||
249 | 'scheduled_sampling': {'view': 'visible', 'edit': 'visible'}, |
||
250 | 'sampled': {'view': 'visible', 'edit': 'invisible'}, |
||
251 | 'to_be_preserved': {'view': 'visible', 'edit': 'invisible'}, |
||
252 | 'sample_due': {'view': 'visible', 'edit': 'invisible'}, |
||
253 | 'sample_received': {'view': 'visible', 'edit': 'invisible'}, |
||
254 | 'expired': {'view': 'visible', 'edit': 'invisible'}, |
||
255 | 'disposed': {'view': 'visible', 'edit': 'invisible'}, |
||
256 | 'rejected': {'view': 'visible', 'edit': 'visible'} |
||
257 | }, |
||
258 | render_own_label=True, |
||
259 | ), |
||
260 | ), |
||
261 | StringField('ScheduledSamplingSampler', |
||
262 | mode="rw", |
||
263 | read_permission=permissions.View, |
||
264 | write_permission=ScheduleSampling, |
||
265 | vocabulary='getSamplers', |
||
266 | widget=BikaSelectionWidget( |
||
267 | description=_("Define the sampler supposed to do the sample in " |
||
268 | "the scheduled date"), |
||
269 | format='select', |
||
270 | label=_("Sampler for scheduled sampling"), |
||
271 | visible={'edit': 'visible', |
||
272 | 'view': 'visible', |
||
273 | 'header_table': 'visible', |
||
274 | 'sample_registered': {'view': 'visible', 'edit': 'visible'}, |
||
275 | 'to_be_sampled': {'view': 'visible', 'edit': 'visible'}, |
||
276 | 'scheduled_sampling': {'view': 'visible', 'edit': 'visible'}, |
||
277 | 'sampled': {'view': 'visible', 'edit': 'invisible'}, |
||
278 | 'to_be_preserved': {'view': 'visible', 'edit': 'invisible'}, |
||
279 | 'sample_due': {'view': 'visible', 'edit': 'invisible'}, |
||
280 | 'sample_received': {'view': 'visible', 'edit': 'invisible'}, |
||
281 | 'expired': {'view': 'visible', 'edit': 'invisible'}, |
||
282 | 'disposed': {'view': 'visible', 'edit': 'invisible'}, |
||
283 | 'rejected': {'view': 'visible', 'edit': 'invisible'}, |
||
284 | }, |
||
285 | render_own_label=True, |
||
286 | ), |
||
287 | ), |
||
288 | DateTimeField('SamplingDate', |
||
289 | mode="rw", |
||
290 | read_permission=permissions.View, |
||
291 | write_permission=permissions.ModifyPortalContent, |
||
292 | widget = DateTimeWidget( |
||
293 | label=_("Expected Sampling Date"), |
||
294 | description=_("Define when the sampler has to take the samples"), |
||
295 | show_time=True, |
||
296 | visible={'edit': 'visible', |
||
297 | 'view': 'visible', |
||
298 | 'header_table': 'visible', |
||
299 | 'sample_registered': {'view': 'visible', 'edit': 'visible'}, |
||
300 | 'to_be_sampled': {'view': 'visible', 'edit': 'visible'}, |
||
301 | 'scheduled_sampling': {'view': 'visible', 'edit': 'visible'}, |
||
302 | 'sampled': {'view': 'visible', 'edit': 'invisible'}, |
||
303 | 'to_be_preserved': {'view': 'visible', 'edit': 'invisible'}, |
||
304 | 'sample_due': {'view': 'visible', 'edit': 'invisible'}, |
||
305 | 'sample_received': {'view': 'visible', 'edit': 'invisible'}, |
||
306 | 'expired': {'view': 'visible', 'edit': 'invisible'}, |
||
307 | 'disposed': {'view': 'visible', 'edit': 'invisible'}, |
||
308 | 'rejected': {'view': 'visible', 'edit': 'invisible'}, |
||
309 | }, |
||
310 | render_own_label=True, |
||
311 | ), |
||
312 | ), |
||
313 | ReferenceField('SamplingDeviation', |
||
314 | vocabulary_display_path_bound = sys.maxsize, |
||
315 | allowed_types = ('SamplingDeviation',), |
||
316 | relationship = 'SampleSamplingDeviation', |
||
317 | referenceClass = HoldingReference, |
||
318 | mode="rw", |
||
319 | read_permission=permissions.View, |
||
320 | write_permission=permissions.ModifyPortalContent, |
||
321 | widget=ReferenceWidget( |
||
322 | label=_("Sampling Deviation"), |
||
323 | render_own_label=True, |
||
324 | visible={'edit': 'visible', |
||
325 | 'view': 'visible', |
||
326 | 'header_table': 'visible', |
||
327 | 'sample_registered': {'view': 'visible', 'edit': 'visible'}, |
||
328 | 'to_be_sampled': {'view': 'visible', 'edit': 'visible'}, |
||
329 | 'scheduled_sampling': {'view': 'visible', 'edit': 'visible'}, |
||
330 | 'sampled': {'view': 'visible', 'edit': 'visible'}, |
||
331 | 'to_be_preserved': {'view': 'visible', 'edit': 'visible'}, |
||
332 | 'sample_due': {'view': 'visible', 'edit': 'visible'}, |
||
333 | 'sample_received': {'view': 'visible', 'edit': 'invisible'}, |
||
334 | 'expired': {'view': 'visible', 'edit': 'invisible'}, |
||
335 | 'disposed': {'view': 'visible', 'edit': 'invisible'}, |
||
336 | 'rejected': {'view': 'visible', 'edit': 'invisible'}, |
||
337 | }, |
||
338 | catalog_name='bika_setup_catalog', |
||
339 | base_query={'inactive_state': 'active'}, |
||
340 | showOn=True, |
||
341 | ), |
||
342 | ), |
||
343 | ReferenceField('SampleCondition', |
||
344 | vocabulary_display_path_bound = sys.maxsize, |
||
345 | allowed_types = ('SampleCondition',), |
||
346 | relationship = 'SampleSampleCondition', |
||
347 | referenceClass = HoldingReference, |
||
348 | mode="rw", |
||
349 | read_permission=permissions.View, |
||
350 | write_permission=permissions.ModifyPortalContent, |
||
351 | widget=ReferenceWidget( |
||
352 | label=_("Sample Condition"), |
||
353 | render_own_label=True, |
||
354 | visible={'edit': 'visible', |
||
355 | 'view': 'visible', |
||
356 | 'header_table': 'visible', |
||
357 | 'sample_registered': {'view': 'visible', 'edit': 'visible'}, |
||
358 | 'to_be_sampled': {'view': 'visible', 'edit': 'visible'}, |
||
359 | 'scheduled_sampling': {'view': 'visible', 'edit': 'visible'}, |
||
360 | 'sampled': {'view': 'visible', 'edit': 'visible'}, |
||
361 | 'to_be_preserved': {'view': 'visible', 'edit': 'visible'}, |
||
362 | 'sample_due': {'view': 'visible', 'edit': 'visible'}, |
||
363 | 'sample_received': {'view': 'visible', 'edit': 'invisible'}, |
||
364 | 'expired': {'view': 'visible', 'edit': 'invisible'}, |
||
365 | 'disposed': {'view': 'visible', 'edit': 'invisible'}, |
||
366 | 'rejected': {'view': 'visible', 'edit': 'invisible'}, |
||
367 | }, |
||
368 | catalog_name='bika_setup_catalog', |
||
369 | base_query={'inactive_state': 'active'}, |
||
370 | showOn=True, |
||
371 | ), |
||
372 | ), |
||
373 | StringField( |
||
374 | 'EnvironmentalConditions', |
||
375 | mode="rw", |
||
376 | read_permission=permissions.View, |
||
377 | write_permission=permissions.ModifyPortalContent, |
||
378 | widget=StringWidget( |
||
379 | label=_("Environmental Conditions"), |
||
380 | visible={'edit': 'visible', |
||
381 | 'view': 'visible', |
||
382 | 'add': 'edit', |
||
383 | 'header_table': 'prominent', |
||
384 | 'sample_registered': {'view': 'visible', 'edit': 'visible', 'add': 'edit'}, |
||
385 | 'to_be_sampled': {'view': 'visible', 'edit': 'visible'}, |
||
386 | 'scheduled_sampling': {'view': 'visible', 'edit': 'visible'}, |
||
387 | 'sampled': {'view': 'visible', 'edit': 'visible'}, |
||
388 | 'to_be_preserved': {'view': 'visible', 'edit': 'visible'}, |
||
389 | 'sample_received': {'view': 'visible', 'edit': 'visible'}, |
||
390 | 'attachment_due': {'view': 'visible', 'edit': 'visible'}, |
||
391 | 'to_be_verified': {'view': 'visible', 'edit': 'visible'}, |
||
392 | 'verified': {'view': 'visible', 'edit': 'invisible'}, |
||
393 | 'published': {'view': 'visible', 'edit': 'invisible'}, |
||
394 | 'invalid': {'view': 'visible', 'edit': 'invisible'}, |
||
395 | 'rejected': {'view': 'visible', 'edit': 'invisible'}, |
||
396 | }, |
||
397 | render_own_label=True, |
||
398 | size=20, |
||
399 | ), |
||
400 | ), |
||
401 | # Another way to obtain a transition date is using getTransitionDate |
||
402 | # function. We are using a DateTimeField/Widget here because in some |
||
403 | # cases the user may want to change the Received Date. |
||
404 | # AnalysisRequest and Sample's DateReceived fields needn't to have |
||
405 | # the same value. |
||
406 | # This field is updated in workflow_script_receive method. |
||
407 | DateTimeField('DateReceived', |
||
408 | mode="rw", |
||
409 | read_permission=permissions.View, |
||
410 | write_permission=permissions.ModifyPortalContent, |
||
411 | widget = DateTimeWidget( |
||
412 | label=_("Date Received"), |
||
413 | show_time=True, |
||
414 | datepicker_nofuture=1, |
||
415 | visible={'edit': 'visible', |
||
416 | 'view': 'visible', |
||
417 | 'header_table': 'visible', |
||
418 | 'sample_registered': {'view': 'visible', 'edit': 'invisible'}, |
||
419 | 'to_be_sampled': {'view': 'visible', 'edit': 'invisible'}, |
||
420 | 'scheduled_sampling': {'view': 'visible', 'edit': 'invisible'}, |
||
421 | 'sampled': {'view': 'visible', 'edit': 'invisible'}, |
||
422 | 'to_be_preserved': {'view': 'visible', 'edit': 'invisible'}, |
||
423 | 'sample_due': {'view': 'visible', 'edit': 'invisible'}, |
||
424 | 'sample_received': {'view': 'visible', 'edit': 'visible'}, |
||
425 | 'expired': {'view': 'visible', 'edit': 'invisible'}, |
||
426 | 'disposed': {'view': 'visible', 'edit': 'invisible'}, |
||
427 | 'rejected': {'view': 'visible', 'edit': 'invisible'}, |
||
428 | }, |
||
429 | render_own_label=True, |
||
430 | ), |
||
431 | ), |
||
432 | ComputedField('ClientUID', |
||
433 | expression = 'context.aq_parent.UID()', |
||
434 | widget = ComputedWidget( |
||
435 | visible=False, |
||
436 | ), |
||
437 | ), |
||
438 | ComputedField('SampleTypeUID', |
||
439 | expression='context.getSampleType() and \ |
||
440 | context.getSampleType().UID() or None', |
||
441 | widget=ComputedWidget( |
||
442 | visible=False, |
||
443 | ), |
||
444 | ), |
||
445 | ComputedField('SamplePointUID', |
||
446 | expression = 'context.getSamplePoint() and context.getSamplePoint().UID() or None', |
||
447 | widget = ComputedWidget( |
||
448 | visible=False, |
||
449 | ), |
||
450 | ), |
||
451 | BooleanField('Composite', |
||
452 | default = False, |
||
453 | mode="rw", |
||
454 | read_permission=permissions.View, |
||
455 | write_permission=permissions.ModifyPortalContent, |
||
456 | widget = BooleanWidget( |
||
457 | label=_("Composite"), |
||
458 | visible={'edit': 'visible', |
||
459 | 'view': 'visible', |
||
460 | 'header_table': 'visible', |
||
461 | 'sample_registered': {'view': 'visible', 'edit': 'visible'}, |
||
462 | 'to_be_sampled': {'view': 'visible', 'edit': 'visible'}, |
||
463 | 'scheduled_sampling': {'view': 'visible', 'edit': 'visible'}, |
||
464 | 'sampled': {'view': 'visible', 'edit': 'visible'}, |
||
465 | 'to_be_preserved': {'view': 'visible', 'edit': 'visible'}, |
||
466 | 'sample_due': {'view': 'visible', 'edit': 'visible'}, |
||
467 | 'sample_received': {'view': 'visible', 'edit': 'visible'}, |
||
468 | 'expired': {'view': 'visible', 'edit': 'invisible'}, |
||
469 | 'disposed': {'view': 'visible', 'edit': 'invisible'}, |
||
470 | 'rejected': {'view': 'visible', 'edit': 'invisible'}, |
||
471 | }, |
||
472 | render_own_label=True, |
||
473 | ), |
||
474 | ), |
||
475 | DateTimeField('DateExpired', |
||
476 | mode="rw", |
||
477 | read_permission=permissions.View, |
||
478 | write_permission=permissions.ModifyPortalContent, |
||
479 | widget = DateTimeWidget( |
||
480 | label=_("Date Expired"), |
||
481 | visible={'edit': 'visible', |
||
482 | 'view': 'visible', |
||
483 | 'header_table': 'visible', |
||
484 | 'sample_registered': {'view': 'invisible', 'edit': 'invisible'}, |
||
485 | 'to_be_sampled': {'view': 'invisible', 'edit': 'invisible'}, |
||
486 | 'scheduled_sampling': {'view': 'invisible', 'edit': 'invisible'}, |
||
487 | 'sampled': {'view': 'invisible', 'edit': 'invisible'}, |
||
488 | 'to_be_preserved': {'view': 'invisible', 'edit': 'invisible'}, |
||
489 | 'sample_due': {'view': 'invisible', 'edit': 'invisible'}, |
||
490 | 'sample_received': {'view': 'invisible', 'edit': 'invisible'}, |
||
491 | 'expired': {'view': 'visible', 'edit': 'invisible'}, |
||
492 | 'disposed': {'view': 'visible', 'edit': 'invisible'}, |
||
493 | 'rejected': {'view': 'invisible', 'edit': 'invisible'}, |
||
494 | }, |
||
495 | render_own_label=True, |
||
496 | ), |
||
497 | ), |
||
498 | ComputedField('DisposalDate', |
||
499 | expression = 'context.disposal_date()', |
||
500 | widget=DateTimeWidget( |
||
501 | visible={'edit': 'visible', |
||
502 | 'view': 'visible', |
||
503 | 'header_table': 'visible', |
||
504 | 'sample_registered': {'view': 'invisible', 'edit': 'invisible'}, |
||
505 | 'to_be_sampled': {'view': 'visible', 'edit': 'invisible'}, |
||
506 | 'scheduled_sampling': {'view': 'visible', 'edit': 'invisible'}, |
||
507 | 'sampled': {'view': 'visible', 'edit': 'invisible'}, |
||
508 | 'to_be_preserved': {'view': 'visible', 'edit': 'invisible'}, |
||
509 | 'sample_due': {'view': 'visible', 'edit': 'invisible'}, |
||
510 | 'sample_received': {'view': 'visible', 'edit': 'invisible'}, |
||
511 | 'expired': {'view': 'visible', 'edit': 'invisible'}, |
||
512 | 'disposed': {'view': 'invisible', 'edit': 'invisible'}, |
||
513 | 'rejected': {'view': 'invisible', 'edit': 'invisible'}, |
||
514 | }, |
||
515 | render_own_label=True, |
||
516 | ), |
||
517 | ), |
||
518 | DateTimeField('DateDisposed', |
||
519 | mode="rw", |
||
520 | read_permission=permissions.View, |
||
521 | write_permission=permissions.ModifyPortalContent, |
||
522 | widget = DateTimeWidget( |
||
523 | label=_("Date Disposed"), |
||
524 | visible={'edit': 'visible', |
||
525 | 'view': 'visible', |
||
526 | 'header_table': 'visible', |
||
527 | 'sample_registered': {'view': 'invisible', 'edit': 'invisible'}, |
||
528 | 'to_be_sampled': {'view': 'invisible', 'edit': 'invisible'}, |
||
529 | 'scheduled_sampling': {'view': 'invisible', 'edit': 'invisible'}, |
||
530 | 'sampled': {'view': 'invisible', 'edit': 'invisible'}, |
||
531 | 'to_be_preserved': {'view': 'invisible', 'edit': 'invisible'}, |
||
532 | 'sample_due': {'view': 'invisible', 'edit': 'invisible'}, |
||
533 | 'sample_received': {'view': 'invisible', 'edit': 'invisible'}, |
||
534 | 'expired': {'view': 'invisible', 'edit': 'invisible'}, |
||
535 | 'disposed': {'view': 'visible', 'edit': 'invisible'}, |
||
536 | 'rejected': {'view': 'invisible', 'edit': 'invisible'}, |
||
537 | }, |
||
538 | render_own_label=True, |
||
539 | ), |
||
540 | ), |
||
541 | BooleanField('AdHoc', |
||
542 | default=False, |
||
543 | mode="rw", |
||
544 | read_permission=permissions.View, |
||
545 | write_permission=permissions.ModifyPortalContent, |
||
546 | widget=BooleanWidget( |
||
547 | label=_("Ad-Hoc"), |
||
548 | visible={'edit': 'visible', |
||
549 | 'view': 'visible', |
||
550 | 'header_table': 'visible', |
||
551 | 'sample_registered': {'view': 'visible', 'edit': 'visible'}, |
||
552 | 'to_be_sampled': {'view': 'visible', 'edit': 'visible'}, |
||
553 | 'scheduled_sampling': {'view': 'visible', 'edit': 'visible'}, |
||
554 | 'sampled': {'view': 'visible', 'edit': 'visible'}, |
||
555 | 'to_be_preserved': {'view': 'visible', 'edit': 'visible'}, |
||
556 | 'sample_due': {'view': 'visible', 'edit': 'visible'}, |
||
557 | 'sample_received': {'view': 'visible', 'edit': 'visible'}, |
||
558 | 'expired': {'view': 'visible', 'edit': 'invisible'}, |
||
559 | 'disposed': {'view': 'visible', 'edit': 'invisible'}, |
||
560 | 'rejected': {'view': 'visible', 'edit': 'invisible'}, |
||
561 | }, |
||
562 | render_own_label=True, |
||
563 | ), |
||
564 | ), |
||
565 | RemarksField( |
||
566 | 'Remarks', |
||
567 | searchable=True, |
||
568 | widget=RemarksWidget( |
||
569 | label=_("Remarks"), |
||
570 | ), |
||
571 | ), |
||
572 | RecordsField('RejectionReasons', |
||
573 | widget = RejectionWidget( |
||
574 | label=_("Sample Rejection"), |
||
575 | description = _("Set the Sample Rejection workflow and the reasons"), |
||
576 | render_own_label=False, |
||
577 | visible={'edit': 'invisible', |
||
578 | 'view': 'visible', |
||
579 | 'add': 'edit', |
||
580 | 'secondary': 'disabled', |
||
581 | 'header_table': 'visible', |
||
582 | 'sample_registered': {'view': 'visible', 'edit': 'visible', 'add': 'edit'}, |
||
583 | 'to_be_sampled': {'view': 'visible', 'edit': 'visible'}, |
||
584 | 'sampled': {'view': 'visible', 'edit': 'visible'}, |
||
585 | 'to_be_preserved': {'view': 'visible', 'edit': 'visible'}, |
||
586 | 'sample_due': {'view': 'visible', 'edit': 'visible'}, |
||
587 | 'sample_received': {'view': 'visible', 'edit': 'visible'}, |
||
588 | 'attachment_due': {'view': 'visible', 'edit': 'visible'}, |
||
589 | 'to_be_verified': {'view': 'visible', 'edit': 'visible'}, |
||
590 | 'verified': {'view': 'visible', 'edit': 'visible'}, |
||
591 | 'published': {'view': 'visible', 'edit': 'visible'}, |
||
592 | 'invalid': {'view': 'visible', 'edit': 'visible'}, |
||
593 | 'rejected': {'view': 'visible', 'edit': 'visible'}, |
||
594 | }, |
||
595 | ), |
||
596 | ), |
||
597 | )) |
||
598 | |||
599 | |||
600 | schema['title'].required = False |
||
601 | |||
602 | |||
603 | class Sample(BaseFolder, HistoryAwareMixin): |
||
604 | implements(ISample) |
||
605 | security = ClassSecurityInfo() |
||
606 | displayContentsTab = False |
||
607 | schema = schema |
||
608 | |||
609 | _at_rename_after_creation = True |
||
610 | |||
611 | def _renameAfterCreation(self, check_auto_id=False): |
||
612 | from bika.lims.idserver import renameAfterCreation |
||
613 | renameAfterCreation(self) |
||
614 | |||
615 | def _getCatalogTool(self): |
||
616 | from bika.lims.catalog import getCatalog |
||
617 | return getCatalog(self) |
||
618 | |||
619 | def getSampleID(self): |
||
620 | """ Return the Sample ID as title """ |
||
621 | return safe_unicode(self.getId()).encode('utf-8') |
||
622 | |||
623 | def Title(self): |
||
624 | """ Return the Sample ID as title """ |
||
625 | return self.getSampleID() |
||
626 | |||
627 | def getSamplingWorkflowEnabledDefault(self): |
||
628 | return self.bika_setup.getSamplingWorkflowEnabled() |
||
629 | |||
630 | def getContactTitle(self): |
||
631 | return "" |
||
632 | |||
633 | def getClientTitle(self): |
||
634 | proxies = self.getAnalysisRequests() |
||
635 | if not proxies: |
||
636 | return "" |
||
637 | value = proxies[0].aq_parent.Title() |
||
638 | return value |
||
639 | |||
640 | def getProfilesTitle(self): |
||
641 | return "" |
||
642 | |||
643 | def getAnalysisService(self): |
||
644 | analyses = [] |
||
645 | for ar in self.getAnalysisRequests(): |
||
646 | analyses += list(ar.getAnalyses(full_objects=True)) |
||
647 | value = [] |
||
648 | for analysis in analyses: |
||
649 | val = analysis.Title() |
||
650 | if val not in value: |
||
651 | value.append(val) |
||
652 | return value |
||
653 | |||
654 | View Code Duplication | def getAnalysts(self): |
|
655 | analyses = [] |
||
656 | for ar in self.getAnalysisRequests(): |
||
657 | analyses += list(ar.getAnalyses(full_objects=True)) |
||
658 | value = [] |
||
659 | for analysis in analyses: |
||
660 | val = analysis.getAnalyst() |
||
661 | if val not in value: |
||
662 | value.append(val) |
||
663 | return value |
||
664 | |||
665 | # Forms submit Title Strings which need |
||
666 | # to be converted to objects somewhere along the way... |
||
667 | View Code Duplication | def setSampleType(self, value, **kw): |
|
668 | """ Accept Object, Title or UID, and convert SampleType title to UID |
||
669 | before saving. |
||
670 | """ |
||
671 | if hasattr(value, "portal_type") and value.portal_type == "SampleType": |
||
672 | pass |
||
673 | else: |
||
674 | bsc = getToolByName(self, 'bika_setup_catalog') |
||
675 | sampletypes = bsc(portal_type='SampleType', title=to_unicode(value)) |
||
676 | if sampletypes: |
||
677 | value = sampletypes[0].UID |
||
678 | else: |
||
679 | sampletypes = bsc(portal_type='SampleType', UID=value) |
||
680 | if sampletypes: |
||
681 | value = sampletypes[0].UID |
||
682 | else: |
||
683 | value = None |
||
684 | for ar in self.getAnalysisRequests(): |
||
685 | ar.Schema()['SampleType'].set(ar, value) |
||
686 | return self.Schema()['SampleType'].set(self, value) |
||
687 | |||
688 | # Forms submit Title Strings which need |
||
689 | # to be converted to objects somewhere along the way... |
||
690 | View Code Duplication | def setSamplePoint(self, value, **kw): |
|
691 | """ Accept Object, Title or UID, and convert SampleType title to UID |
||
692 | before saving. |
||
693 | """ |
||
694 | if hasattr(value, "portal_type") and value.portal_type == "SamplePoint": |
||
695 | pass |
||
696 | elif value: |
||
697 | bsc = getToolByName(self, 'bika_setup_catalog') |
||
698 | sampletypes = bsc(portal_type='SamplePoint', title=to_unicode(value)) |
||
699 | if sampletypes: |
||
700 | value = sampletypes[0].UID |
||
701 | else: |
||
702 | sampletypes = bsc(portal_type='SamplePoint', UID=value) |
||
703 | if sampletypes: |
||
704 | value = sampletypes[0].UID |
||
705 | else: |
||
706 | value = None |
||
707 | for ar in self.getAnalysisRequests(): |
||
708 | ar.Schema()['SamplePoint'].set(ar, value) |
||
709 | return self.Schema()['SamplePoint'].set(self, value) |
||
710 | |||
711 | def setClientReference(self, value, **kw): |
||
712 | """ Set the field on Analysis Requests. |
||
713 | """ |
||
714 | for ar in self.getAnalysisRequests(): |
||
715 | ar.Schema()['ClientReference'].set(ar, value) |
||
716 | self.Schema()['ClientReference'].set(self, value) |
||
717 | |||
718 | def setClientSampleID(self, value, **kw): |
||
719 | """ Set the field on Analysis Requests. |
||
720 | """ |
||
721 | for ar in self.getAnalysisRequests(): |
||
722 | ar.Schema()['ClientSampleID'].set(ar, value) |
||
723 | self.Schema()['ClientSampleID'].set(self, value) |
||
724 | |||
725 | def setAdHoc(self, value, **kw): |
||
726 | """ Set the field on Analysis Requests. |
||
727 | """ |
||
728 | for ar in self.getAnalysisRequests(): |
||
729 | ar.Schema()['AdHoc'].set(ar, value) |
||
730 | self.Schema()['AdHoc'].set(self, value) |
||
731 | |||
732 | def setComposite(self, value, **kw): |
||
733 | """ Set the field on Analysis Requests. |
||
734 | """ |
||
735 | for ar in self.getAnalysisRequests(): |
||
736 | ar.Schema()['Composite'].set(ar, value) |
||
737 | self.Schema()['Composite'].set(self, value) |
||
738 | |||
739 | security.declarePublic('getAnalysisRequests') |
||
740 | |||
741 | def getAnalysisRequests(self): |
||
742 | backrefs = get_backreferences(self, 'AnalysisRequestSample') |
||
743 | ars = map(get_object_by_uid, backrefs) |
||
744 | return ars |
||
745 | |||
746 | security.declarePublic('getAnalyses') |
||
747 | |||
748 | def getAnalyses(self, contentFilter=None, **kwargs): |
||
749 | """ return list of all analyses against this sample |
||
750 | """ |
||
751 | # contentFilter and kwargs are combined. They both exist for |
||
752 | # compatibility between the two signatures; kwargs has been added |
||
753 | # to be compatible with how getAnalyses() is used everywhere else. |
||
754 | cf = contentFilter if contentFilter else {} |
||
755 | cf.update(kwargs) |
||
756 | analyses = [] |
||
757 | for ar in self.getAnalysisRequests(): |
||
758 | analyses.extend(ar.getAnalyses(**cf)) |
||
759 | return analyses |
||
760 | |||
761 | def getSamplers(self): |
||
762 | return getUsers(self, ['Sampler', ]) |
||
763 | |||
764 | def disposal_date(self): |
||
765 | """ Calculate the disposal date by returning the latest |
||
766 | disposal date in this sample's partitions """ |
||
767 | |||
768 | parts = self.objectValues("SamplePartition") |
||
769 | dates = [] |
||
770 | for part in parts: |
||
771 | date = part.getDisposalDate() |
||
772 | if date: |
||
773 | dates.append(date) |
||
774 | if dates: |
||
775 | dis_date = dt2DT(max([DT2dt(date) for date in dates])) |
||
776 | else: |
||
777 | dis_date = None |
||
778 | return dis_date |
||
779 | |||
780 | def getLastARNumber(self): |
||
781 | ARs = self.getBackReferences("AnalysisRequestSample") |
||
782 | prefix = self.getSampleType().getPrefix() |
||
783 | ar_ids = sorted([AR.id for AR in ARs if AR.id.startswith(prefix)]) |
||
784 | try: |
||
785 | last_ar_number = int(ar_ids[-1].split("-R")[-1]) |
||
786 | except: |
||
787 | return 0 |
||
788 | return last_ar_number |
||
789 | |||
790 | def getSampleState(self): |
||
791 | """Returns the sample veiew_state |
||
792 | """ |
||
793 | workflow = getToolByName(self, 'portal_workflow') |
||
794 | return workflow.getInfoFor(self, 'review_state') |
||
795 | |||
796 | def getSamplePartitions(self): |
||
797 | """Returns the Sample Partitions associated to this Sample |
||
798 | """ |
||
799 | partitions = self.objectValues('SamplePartition') |
||
800 | return partitions |
||
801 | |||
802 | def getBatchUIDs(self): |
||
803 | """Returns the UIDs of the Batches to which this Sample is assigned |
||
804 | through Analysis Requests |
||
805 | """ |
||
806 | batch_uids = list() |
||
807 | for analysis_request in self.getAnalysisRequests(): |
||
808 | batch_uid = analysis_request.getBatchUID() |
||
809 | if not batch_uid or batch_uid in batch_uids: |
||
810 | continue |
||
811 | batch_uids.append(batch_uid) |
||
812 | return batch_uids |
||
813 | |||
814 | @security.public |
||
815 | def guard_to_be_preserved(self): |
||
816 | return guards.to_be_preserved(self) |
||
817 | |||
818 | @security.public |
||
819 | def guard_receive_transition(self): |
||
820 | return guards.receive(self) |
||
821 | |||
822 | @security.public |
||
823 | def guard_schedule_sampling_transition(self): |
||
824 | return guards.schedule_sampling(self) |
||
825 | |||
826 | atapi.registerType(Sample, PROJECTNAME) |
||
827 |