Passed
Push — master ( f42093...226b52 )
by Ramon
04:24
created

AnalysisSpec.contextual_title()   A

Complexity

Conditions 2

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 2
nop 1
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.browser.fields import UIDReferenceField
25
from bika.lims.browser.widgets import AnalysisSpecificationWidget
26
from bika.lims.config import PROJECTNAME
27
from bika.lims.content.bikaschema import BikaSchema
28
from bika.lims.content.clientawaremixin import ClientAwareMixin
29
from bika.lims.interfaces import IAnalysisSpec, IDeactivable
30
from Products.Archetypes import atapi
31
from Products.Archetypes.public import BaseFolder
32
from Products.Archetypes.public import ComputedField
33
from Products.Archetypes.public import ComputedWidget
34
from Products.Archetypes.public import ReferenceWidget
35
from Products.Archetypes.public import Schema
36
from Products.Archetypes.utils import DisplayList
37
from Products.ATContentTypes.lib.historyaware import HistoryAwareMixin
38
from Products.ATExtensions.field.records import RecordsField
39
from Products.CMFCore.utils import getToolByName
40
from Products.CMFPlone.utils import safe_unicode
41
from zope.i18n import translate
42
from zope.interface import implements
43
44
from bika.lims.interfaces import IClient
45
46
schema = Schema((
47
48
    UIDReferenceField(
49
        'SampleType',
50
        vocabulary="getSampleTypes",
51
        allowed_types=('SampleType',),
52
        widget=ReferenceWidget(
53
            checkbox_bound=0,
54
            label=_("Sample Type"),
55
        ),
56
    ),
57
58
    ComputedField(
59
        'SampleTypeTitle',
60
        expression="context.getSampleType().Title() if context.getSampleType() else ''",
61
        widget=ComputedWidget(
62
            visible=False,
63
        ),
64
    ),
65
66
    ComputedField(
67
        'SampleTypeUID',
68
        expression="context.getSampleType().UID() if context.getSampleType() else ''",
69
        widget=ComputedWidget(
70
            visible=False,
71
        ),
72
    ),
73
)) + BikaSchema.copy() + Schema((
74
75
    RecordsField(
76
        'ResultsRange',
77
        # schemata = 'Specifications',
78
        required=1,
79
        type='resultsrange',
80
        subfields=(
81
            'keyword',
82
            'min_operator',
83
            'min',
84
            'max_operator',
85
            'max',
86
            'warn_min',
87
            'warn_max',
88
            'hidemin',
89
            'hidemax',
90
            'rangecomment'
91
        ),
92
        required_subfields=('keyword',),
93
        subfield_validators={
94
            'min': 'analysisspecs_validator',
95
            'max': 'analysisspecs_validator',
96
        },
97
        subfield_labels={
98
            'keyword': _('Analysis Service'),
99
            'min_operator': _('Min operator'),
100
            'min': _('Min'),
101
            'max_operator': _('Max operator'),
102
            'max': _('Max'),
103
            'warn_min': _('Min warn'),
104
            'warn_max': _('Max warn'),
105
            'hidemin': _('< Min'),
106
            'hidemax': _('> Max'),
107
            'rangecomment': _('Range Comment'),
108
        },
109
        widget=AnalysisSpecificationWidget(
110
            checkbox_bound=0,
111
            label=_("Specifications"),
112
            description=_(
113
                "'Min' and 'Max' values indicate a valid results range. Any "
114
                "result outside this results range will raise an alert. 'Min "
115
                "warn' and 'Max warn' values indicate a shoulder range. Any "
116
                "result outside the results range but within the shoulder "
117
                "range will raise a less severe alert. If the result is out of "
118
                "range, the value set for '< Min' or '< Max' will be displayed "
119
                "in lists and results reports instead of the real result.")
120
        ),
121
    ),
122
123
    ComputedField(
124
        'ClientUID',
125
        expression="context.aq_parent.UID()",
126
        widget=ComputedWidget(
127
            visible=False,
128
        ),
129
    ),
130
))
131
132
schema['description'].widget.visible = True
133
schema['title'].required = True
134
135
136
class AnalysisSpec(BaseFolder, HistoryAwareMixin, ClientAwareMixin):
137
    """Analysis Specification
138
    """
139
    implements(IAnalysisSpec, IDeactivable)
140
    security = ClassSecurityInfo()
141
    schema = schema
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable schema does not seem to be defined.
Loading history...
142
    displayContentsTab = False
143
144
    _at_rename_after_creation = True
145
146
    def _renameAfterCreation(self, check_auto_id=False):
147
        from bika.lims.idserver import renameAfterCreation
148
        renameAfterCreation(self)
149
150
    def Title(self):
151
        """ Return the title if possible, else return the Sample type.
152
        Fall back on the instance's ID if there's no sample type or title.
153
        """
154
        title = ''
155
        if self.title:
156
            title = self.title
157
        else:
158
            sampletype = self.getSampleType()
159
            if sampletype:
160
                title = sampletype.Title()
161
        return safe_unicode(title).encode('utf-8')
162
163
    def contextual_title(self):
164
        parent = self.aq_parent
165
        if parent == self.bika_setup.bika_analysisspecs:
166
            return self.title + " (" + translate(_("Lab")) + ")"
167
        else:
168
            return self.title + " (" + translate(_("Client")) + ")"
169
170
    security.declarePublic('getResultsRangeDict')
171
172
    def getResultsRangeDict(self):
173
        """Return a dictionary with the specification fields for each
174
           service. The keys of the dictionary are the keywords of each
175
           analysis service. Each service contains a dictionary in which
176
           each key is the name of the spec field:
177
           specs['keyword'] = {'min': value,
178
                               'max': value,
179
                               'warnmin': value,
180
                               ... }
181
        """
182
        specs = {}
183
        subfields = self.Schema()['ResultsRange'].subfields
184
        for spec in self.getResultsRange():
185
            keyword = spec['keyword']
186
            specs[keyword] = {}
187
            for key in subfields:
188
                if key not in ['uid', 'keyword']:
189
                    specs[keyword][key] = spec.get(key, '')
190
        return specs
191
192
    security.declarePublic('getRemainingSampleTypes')
193
194
    def getSampleTypes(self, active_only=True):
195
        """Return all sampletypes
196
        """
197
        catalog = api.get_tool("bika_setup_catalog")
198
        query = {
199
            "portal_type": "SampleType",
200
            # N.B. The `sortable_title` index sorts case sensitive. Since there
201
            #      is no sort key for sample types, it makes more sense to sort
202
            #      them alphabetically in the selection
203
            "sort_on": "title",
204
            "sort_order": "ascending"
205
        }
206
        results = catalog(query)
207
        if active_only:
208
            results = filter(api.is_active, results)
209
        sampletypes = map(
210
            lambda brain: (brain.UID, brain.Title), results)
211
        return DisplayList(sampletypes)
212
213
214
atapi.registerType(AnalysisSpec, PROJECTNAME)
215
216
217
class ResultsRangeDict(dict):
218
219
    def __init__(self, *arg, **kw):
220
        super(ResultsRangeDict, self).__init__(*arg, **kw)
221
        self["min"] = self.min
222
        self["max"] = self.max
223
        self["error"] = self.error
224
        self["warn_min"] = self.warn_min
225
        self["warn_max"] = self.warn_max
226
        self["min_operator"] = self.min_operator
227
        self["max_operator"] = self.max_operator
228
229
    @property
230
    def min(self):
231
        return self.get("min", '')
232
233
    @property
234
    def max(self):
235
        return self.get("max", '')
236
237
    @property
238
    def error(self):
239
        return self.get("error", '')
240
241
    @property
242
    def warn_min(self):
243
        return self.get("warn_min", self.min)
244
245
    @property
246
    def warn_max(self):
247
        return self.get('warn_max', self.max)
248
249
    @property
250
    def min_operator(self):
251
        return self.get('min_operator', 'geq')
252
253
    @property
254
    def max_operator(self):
255
        return self.get('max_operator', 'leq')
256
257
    @min.setter
258
    def min(self, value):
259
        self["min"] = value
260
261
    @max.setter
262
    def max(self, value):
263
        self["max"] = value
264
265
    @warn_min.setter
266
    def warn_min(self, value):
267
        self['warn_min'] = value
268
269
    @warn_max.setter
270
    def warn_max(self, value):
271
        self['warn_max'] = value
272
273
    @min_operator.setter
274
    def min_operator(self, value):
275
        self['min_operator'] = value
276
277
    @max_operator.setter
278
    def max_operator(self, value):
279
        self['max_operator'] = value
280