Passed
Push — master ( 354e15...69376c )
by Jordi
04:34
created

Batch._renameAfterCreation()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nop 2
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 AccessControl import ClassSecurityInfo
22
from bika.lims import api
23
from bika.lims import bikaMessageFactory as _
24
from bika.lims import deprecated
25
from bika.lims.browser.fields.remarksfield import RemarksField
26
from bika.lims.browser.widgets import DateTimeWidget
27
from bika.lims.browser.widgets import RecordsWidget as bikaRecordsWidget
28
from bika.lims.browser.widgets import ReferenceWidget
29
from bika.lims.browser.widgets import RemarksWidget
30
from bika.lims.catalog import CATALOG_ANALYSIS_REQUEST_LISTING
31
from bika.lims.config import PROJECTNAME
32
from bika.lims.content.bikaschema import BikaFolderSchema
33
from bika.lims.interfaces import IBatch
34
from bika.lims.interfaces import ICancellable
35
from bika.lims.interfaces import IClient
36
from plone.app.folder.folder import ATFolder
37
from plone.indexer import indexer
38
from Products.Archetypes.public import DateTimeField
39
from Products.Archetypes.public import DisplayList
40
from Products.Archetypes.public import LinesField
41
from Products.Archetypes.public import MultiSelectionWidget
42
from Products.Archetypes.public import ReferenceField
43
from Products.Archetypes.public import Schema
44
from Products.Archetypes.public import StringField
45
from Products.Archetypes.public import StringWidget
46
from Products.Archetypes.public import registerType
47
from Products.Archetypes.references import HoldingReference
48
from Products.ATExtensions.ateapi import RecordsField
49
from Products.CMFCore.utils import getToolByName
50
from Products.CMFPlone.utils import safe_unicode
51
from zope.interface import implements
52
53
54
@indexer(IBatch)
55
def BatchDate(instance):
56
    return instance.Schema().getField('BatchDate').get(instance)
57
58
59
class InheritedObjectsUIField(RecordsField):
60
    """XXX bika.lims.RecordsWidget doesn't cater for multiValued fields
61
    InheritedObjectsUI is a RecordsField because we want the RecordsWidget,
62
    but the values are stored in ReferenceField 'InheritedObjects'
63
    """
64
65
    def get(self, instance, **kwargs):
66
        # Return the formatted contents of InheritedObjects field.
67
        field = instance.Schema()['InheritedObjects']
68
        value = field.get(instance)
69
        return [{'Title': x.Title(),
70
                 'ObjectID': x.id,
71
                 'Description': x.Description()} for x in value]
72
73
    def getRaw(self, instance, **kwargs):
74
        # Return the formatted contents of InheritedObjects field.
75
        field = instance.Schema()['InheritedObjects']
76
        value = field.get(instance)
77
        return [{'Title': x.Title(),
78
                 'ObjectID': x.id,
79
                 'Description': x.Description()} for x in value]
80
81
    def set(self, instance, value, **kwargs):
82
        _field = instance.Schema().getField('InheritedObjects')
83
        uids = []
84
        if value:
85
            bc = getToolByName(instance, 'bika_catalog')
86
            ids = [x['ObjectID'] for x in value]
87
            if ids:
88
                proxies = bc(id=ids)
89
                if proxies:
90
                    uids = [x.UID for x in proxies]
91
        RecordsField.set(self, instance, value)
92
        return _field.set(instance, uids)
93
94
95
schema = BikaFolderSchema.copy() + Schema((
96
97
    StringField(
98
        'BatchID',
99
        required=False,
100
        validators=('uniquefieldvalidator',),
101
        widget=StringWidget(
102
            # XXX This field can never hold a user value, because it is
103
            #     invisible (see custom getBatchID getter method)
104
            # => we should remove that field
105
            visible=False,
106
            label=_("Batch ID"),
107
        )
108
    ),
109
110
    ReferenceField(
111
        'Client',
112
        required=0,
113
        allowed_types=('Client',),
114
        relationship='BatchClient',
115
        widget=ReferenceWidget(
116
            label=_("Client"),
117
            size=30,
118
            visible=True,
119
            base_query={'review_state': 'active'},
120
            showOn=True,
121
            colModel=[
122
                {'columnName': 'UID', 'hidden': True},
123
                {'columnName': 'Title', 'width': '60', 'label': _('Title')},
124
                {'columnName': 'ClientID', 'width': '20', 'label': _('Client ID')}
125
            ],
126
        ),
127
    ),
128
129
    StringField(
130
        'ClientBatchID',
131
        required=0,
132
        widget=StringWidget(
133
            label=_("Client Batch ID")
134
        )
135
    ),
136
137
    DateTimeField(
138
        'BatchDate',
139
        required=False,
140
        widget=DateTimeWidget(
141
            label=_('Date'),
142
        ),
143
    ),
144
145
    LinesField(
146
        'BatchLabels',
147
        vocabulary="BatchLabelVocabulary",
148
        accessor="getLabelNames",
149
        widget=MultiSelectionWidget(
150
            label=_("Batch Labels"),
151
            format="checkbox",
152
        )
153
    ),
154
155
    RemarksField(
156
        'Remarks',
157
        searchable=True,
158
        widget=RemarksWidget(
159
            label=_('Remarks'),
160
        )
161
    ),
162
163
    ReferenceField(
164
        'InheritedObjects',
165
        required=0,
166
        multiValued=True,
167
        allowed_types=('AnalysisRequest'),  # batches are expanded on save
168
        referenceClass=HoldingReference,
169
        relationship='BatchInheritedObjects',
170
        widget=ReferenceWidget(
171
            visible=False,
172
        ),
173
    ),
174
175
    InheritedObjectsUIField(
176
        'InheritedObjectsUI',
177
        required=False,
178
        type='InheritedObjects',
179
        subfields=('Title', 'ObjectID', 'Description'),
180
        subfield_sizes={
181
            'Title': 25,
182
            'ObjectID': 25,
183
            'Description': 50,
184
        },
185
186
        subfield_labels={
187
            'Title': _('Title'),
188
            'ObjectID': _('Object ID'),
189
            'Description': _('Description')
190
        },
191
192
        widget=bikaRecordsWidget(
193
            label=_("Inherit From"),
194
            description=_(
195
                "Include all samples belonging to the selected objects."),
196
            innerJoin="<br/>",
197
            combogrid_options={
198
                'Title': {
199
                    'colModel': [
200
                        {'columnName': 'Title', 'width': '25',
201
                         'label': _('Title'), 'align': 'left'},
202
                        {'columnName': 'ObjectID', 'width': '25',
203
                         'label': _('Object ID'), 'align': 'left'},
204
                        {'columnName': 'Description', 'width': '50',
205
                         'label': _('Description'), 'align': 'left'},
206
                        {'columnName': 'UID', 'hidden': True},
207
                    ],
208
                    'url': 'getAnalysisContainers',
209
                    'showOn': False,
210
                    'width': '600px'
211
                },
212
                'ObjectID': {
213
                    'colModel': [
214
                        {'columnName': 'Title', 'width': '25',
215
                         'label': _('Title'), 'align': 'left'},
216
                        {'columnName': 'ObjectID', 'width': '25',
217
                         'label': _('Object ID'), 'align': 'left'},
218
                        {'columnName': 'Description', 'width': '50',
219
                         'label': _('Description'), 'align': 'left'},
220
                        {'columnName': 'UID', 'hidden': True},
221
                    ],
222
                    'url': 'getAnalysisContainers',
223
                    'showOn': False,
224
                    'width': '600px'
225
                },
226
            },
227
        ),
228
    ),
229
))
230
231
# Remove implicit `uniquefieldvalidator` coming from `BikaFolderSchema`
232
schema['title'].validators = ()
233
schema['title'].widget.description = _("If no value is entered, the Batch ID"
234
                                       " will be auto-generated.")
235
schema['title'].required = False
236
schema['title'].widget.visible = True
237
schema['title'].widget.description = _("If no Title value is entered,"
238
                                       " the Batch ID will be used.")
239
schema['description'].required = False
240
schema['description'].widget.visible = True
241
242
schema.moveField('ClientBatchID', before='description')
243
schema.moveField('BatchID', before='description')
244
schema.moveField('title', before='description')
245
schema.moveField('Client', after='title')
246
247
248
class Batch(ATFolder):
249
    """A Batch combines multiple ARs into a logical unit
250
    """
251
    implements(IBatch, ICancellable)
252
253
    schema = schema
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable schema does not seem to be defined.
Loading history...
254
    displayContentsTab = False
255
    security = ClassSecurityInfo()
256
    _at_rename_after_creation = True
257
258
    def _renameAfterCreation(self, check_auto_id=False):
259
        from bika.lims.idserver import renameAfterCreation
260
        renameAfterCreation(self)
261
262
    def Title(self):
263
        """Return the Batch ID if title is not defined
264
        """
265
        titlefield = self.Schema().getField('title')
266
        if titlefield.widget.visible:
267
            return safe_unicode(self.title).encode('utf-8')
268
        else:
269
            return safe_unicode(self.id).encode('utf-8')
270
271
    @deprecated("This method will be removed in senaite.core 1.2.0")
272
    def _getCatalogTool(self):
273
        from bika.lims.catalog import getCatalog
274
        return getCatalog(self)
275
276
    def getClient(self):
277
        """Retrieves the Client for which the current Batch is attached to
278
           Tries to retrieve the Client from the Schema property, but if not
279
           found, searches for linked ARs and retrieve the Client from the
280
           first one. If the Batch has no client, returns None.
281
        """
282
        client = self.Schema().getField('Client').get(self)
283
        if client:
284
            return client
285
        client = self.aq_parent
286
        if IClient.providedBy(client):
287
            return client
288
289
    def getClientTitle(self):
290
        client = self.getClient()
291
        if client:
292
            return client.Title()
293
        return ""
294
295
    def getClientUID(self):
296
        """This index is required on batches so that batch listings can be
297
        filtered by client
298
        """
299
        client = self.getClient()
300
        if client:
301
            return client.UID()
302
303
    def getContactTitle(self):
304
        return ""
305
306
    def getProfilesTitle(self):
307
        return ""
308
309
    def getAnalysisService(self):
310
        analyses = set()
311
        for ar in self.getAnalysisRequests():
312
            for an in ar.getAnalyses():
313
                analyses.add(an)
314
        value = []
315
        for analysis in analyses:
316
            val = analysis.Title
317
            if val not in value:
318
                value.append(val)
319
        return list(value)
320
321 View Code Duplication
    def getAnalysts(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
322
        analyses = []
323
        for ar in self.getAnalysisRequests():
324
            analyses += list(ar.getAnalyses(full_objects=True))
325
        value = []
326
        for analysis in analyses:
327
            val = analysis.getAnalyst()
328
            if val not in value:
329
                value.append(val)
330
        return value
331
332
    security.declarePublic('getBatchID')
333
334
    @deprecated("Please use getId instead")
335
    def getBatchID(self):
336
        # NOTE This method is a custom getter of the invisible field "BatchID".
337
        #      Therefore, it is unlikely that it returns anything else than `getId`.
338
        if self.BatchID:
339
            return self.BatchID
340
        if self.checkCreationFlag():
341
            return self.BatchID
342
        return self.getId()
343
344
    def BatchLabelVocabulary(self):
345
        """Return all batch labels as a display list
346
        """
347
        bsc = getToolByName(self, 'bika_setup_catalog')
348
        ret = []
349
        for p in bsc(portal_type='BatchLabel',
350
                     is_active=True,
351
                     sort_on='sortable_title'):
352
            ret.append((p.UID, p.Title))
353
        return DisplayList(ret)
354
355
    def getAnalysisRequestsBrains(self, **kwargs):
356
        """Return all the Analysis Requests brains linked to the Batch
357
        kargs are passed directly to the catalog.
358
        """
359
        kwargs['getBatchUID'] = self.UID()
360
        catalog = getToolByName(self, CATALOG_ANALYSIS_REQUEST_LISTING)
361
        brains = catalog(kwargs)
362
        return brains
363
364
    def getAnalysisRequests(self, **kwargs):
365
        """Return all the Analysis Requests objects linked to the Batch kargs
366
        are passed directly to the catalog.
367
        """
368
        brains = self.getAnalysisRequestsBrains(**kwargs)
369
        return [b.getObject() for b in brains]
370
371
    def isOpen(self):
372
        """Returns true if the Batch is in 'open' state
373
        """
374
        return api.get_workflow_status_of(self) not in ["cancelled", "closed"]
375
376
    def getLabelNames(self):
377
        uc = getToolByName(self, 'uid_catalog')
378
        uids = [uid for uid in self.Schema().getField('BatchLabels').get(self)]
379
        labels = [label.getObject().title for label in uc(UID=uids)]
380
        return labels
381
382
383
registerType(Batch, PROJECTNAME)
384