Passed
Pull Request — 2.x (#1952)
by Ramon
05:52
created

AnalysisSpecificationWidget.process_form()   C

Complexity

Conditions 9

Size

Total Lines 73
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 43
dl 0
loc 73
rs 6.5146
c 0
b 0
f 0
cc 9
nop 6

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
import collections
22
23
from AccessControl import ClassSecurityInfo
24
from bika.lims import api
25
from bika.lims import bikaMessageFactory as _
26
from bika.lims.api.security import check_permission
27
from bika.lims.browser.bika_listing import BikaListingView
28
from bika.lims.config import MAX_OPERATORS
29
from bika.lims.config import MIN_OPERATORS
30
from bika.lims.permissions import FieldEditSpecification
31
from bika.lims.utils import dicts_to_dict
32
from bika.lims.utils import get_image
33
from bika.lims.utils import get_link
34
from bika.lims.utils import to_choices
35
from plone.memoize import view
36
from Products.Archetypes.Registry import registerWidget
37
from Products.Archetypes.Widget import TypesWidget
38
39
40
class AnalysisSpecificationView(BikaListingView):
41
    """Listing table to display Analysis Specifications
42
    """
43
44
    def __init__(self, context, request):
45
        super(AnalysisSpecificationView, self).__init__(context, request)
46
47
        self.catalog = "senaite_catalog_setup"
48
        self.contentFilter = {
49
            "portal_type": "AnalysisService",
50
            "is_active": True,
51
            "sort_on": "sortable_title",
52
            "sort_order": "ascending",
53
        }
54
        self.context_actions = {}
55
56
        self.show_column_toggles = False
57
        self.show_select_column = True
58
        self.show_select_all_checkbox = False
59
        self.pagesize = 999999
60
        self.allow_edit = True
61
        self.show_search = True
62
        self.omit_form = True
63
        self.fetch_transitions_on_select = False
64
65
        # Categories
66
        if self.show_categories_enabled():
67
            self.categories = []
68
            self.show_categories = True
69
            self.expand_all_categories = False
70
71
        # Operator Choices
72
        self.min_operator_choices = to_choices(MIN_OPERATORS)
73
        self.max_operator_choices = to_choices(MAX_OPERATORS)
74
75
        self.columns = collections.OrderedDict((
76
            ("Title", {
77
                "title": _("Service"),
78
                "index": "sortable_title",
79
                "sortable": False}),
80
            ("Keyword", {
81
                "title": _("Keyword"),
82
                "sortable": False}),
83
            ("Methods", {
84
                "title": _("Methods"),
85
                "sortable": False}),
86
            ("Unit", {
87
                "title": _("Unit"),
88
                "sortable": False}),
89
            ("warn_min", {
90
                "title": _("Min warn"),
91
                "sortable": False}),
92
            ("min_operator", {
93
                "title": _("Min operator"),
94
                "type": "choices",
95
                "sortable": False}),
96
            ("min", {
97
                "title": _("Min"),
98
                "sortable": False}),
99
            ("max_operator", {
100
                "title": _("Max operator"),
101
                "type": "choices",
102
                "sortable": False}),
103
            ("max", {
104
                "title": _("Max"),
105
                "sortable": False}),
106
            ("warn_max", {
107
                "title": _("Max warn"),
108
                "sortable": False}),
109
            ("hidemin", {
110
                "title": _("< Min"),
111
                "sortable": False}),
112
            ("hidemax", {
113
                "title": _("> Max"),
114
                "sortable": False}),
115
            ("rangecomment", {
116
                "title": _("Range comment"),
117
                "sortable": False,
118
                "type": "remarks",
119
                "toggle": False}),
120
        ))
121
122
        self.review_states = [
123
            {
124
                "id": "default",
125
                "title": _("All"),
126
                "contentFilter": {},
127
                "transitions": [{"id": "disallow-all-possible-transitions"}],
128
                "columns": self.columns.keys(),
129
            },
130
        ]
131
132
    def update(self):
133
        """Update hook
134
        """
135
        super(AnalysisSpecificationView, self).update()
136
        self.allow_edit = self.is_edit_allowed()
137
        results_range = self.context.getResultsRange()
138
        self.specification = dicts_to_dict(results_range, "keyword")
139
        self.dynamic_spec = self.context.getDynamicAnalysisSpec()
140
141
    @view.memoize
142
    def is_edit_allowed(self):
143
        """Check if edit is allowed
144
        """
145
        return check_permission(FieldEditSpecification, self.context)
146
147
    @view.memoize
148
    def show_categories_enabled(self):
149
        """Check in the setup if categories are enabled
150
        """
151
        return self.context.bika_setup.getCategoriseAnalysisServices()
152
153
    def get_editable_columns(self):
154
        """Return editable fields
155
        """
156
        columns = ["min", "max", "warn_min", "warn_max", "hidemin", "hidemax",
157
                   "rangecomment", "min_operator", "max_operator"]
158
        return columns
159
160
    def get_required_columns(self):
161
        """Return required editable fields
162
        """
163
        columns = []
164
        return columns
165
166
    @view.memoize
167
    def get_dynamic_analysisspecs(self):
168
        if not self.dynamic_spec:
169
            return {}
170
        return self.dynamic_spec.get_by_keyword()
171
172
    def folderitems(self):
173
        items = super(AnalysisSpecificationView, self).folderitems()
174
        self.categories.sort()
175
        return items
176
177
    def folderitem(self, obj, item, index):
178
        """Service triggered each time an item is iterated in folderitems.
179
180
        The use of this service prevents the extra-loops in child objects.
181
182
        :obj: the instance of the class to be foldered
183
        :item: dict containing the properties of the object to be used by
184
            the template
185
        :index: current index of the item
186
        """
187
        # ensure we have an object and not a brain
188
        obj = api.get_object(obj)
189
        url = api.get_url(obj)
190
        title = api.get_title(obj)
191
        keyword = obj.getKeyword()
192
193
        # dynamic analysisspecs
194
        dspecs = self.get_dynamic_analysisspecs()
195
        dspec = dspecs.get(keyword)
196
        # show the dynamic specification icon next to the Keyword
197
        if dspec:
198
            item["before"]["Keyword"] = get_image(
199
                "dynamic_analysisspec.png",
200
                title=_("Found Dynamic Analysis Specification for '{}' in '{}'"
201
                        .format(keyword, self.dynamic_spec.Title())))
202
203
        # get the category
204
        if self.show_categories_enabled():
205
            category = obj.getCategoryTitle()
206
            if category not in self.categories:
207
                self.categories.append(category)
208
            item["category"] = category
209
210
        item["Title"] = title
211
        item["replace"]["Title"] = get_link(url, value=title)
212
        item["choices"]["min_operator"] = self.min_operator_choices
213
        item["choices"]["max_operator"] = self.max_operator_choices
214
        item["allow_edit"] = self.get_editable_columns()
215
        item["required"] = self.get_required_columns()
216
217
        spec = self.specification.get(keyword, {})
218
219
        item["selected"] = spec and True or False
220
        item["min"] = spec.get("min", "")
221
        item["max"] = spec.get("max", "")
222
        item["warn_min"] = spec.get("warn_min", "")
223
        item["warn_max"] = spec.get("warn_max", "")
224
        item["hidemin"] = spec.get("hidemin", "")
225
        item["hidemax"] = spec.get("hidemax", "")
226
        item["rangecomment"] = spec.get("rangecomment", "")
227
228
        # min/max operators
229
        max_op = spec.get("max_operator", "leq")
230
        min_op = spec.get("min_operator", "geq")
231
        if self.allow_edit:
232
            item["max_operator"] = max_op
233
            item["min_operator"] = min_op
234
        else:
235
            # Render display values instead of the raw values
236
            item["max_operator"] = MAX_OPERATORS.getValue(max_op)
237
            item["min_operator"] = MIN_OPERATORS.getValue(min_op)
238
239
        # Add methods
240
        methods = obj.getMethods()
241
        if methods:
242
            links = map(
243
                lambda m: get_link(
244
                    m.absolute_url(), value=m.Title(), css_class="link"),
245
                methods)
246
            item["replace"]["Methods"] = ", ".join(links)
247
        else:
248
            item["methods"] = ""
249
250
        # Icons
251
        after_icons = ""
252
        if obj.getAccredited():
253
            after_icons += get_image(
254
                "accredited.png", title=_("Accredited"))
255
        if obj.getAttachmentRequired():
256
            after_icons += get_image(
257
                "attach_reqd.png", title=_("Attachment required"))
258
        if after_icons:
259
            item["after"]["Title"] = after_icons
260
261
        return item
262
263
264
class AnalysisSpecificationWidget(TypesWidget):
265
    """Analysis Specification Widget
266
    """
267
    _properties = TypesWidget._properties.copy()
268
    _properties.update({
269
        "macro": "bika_widgets/analysisspecificationwidget",
270
    })
271
272
    security = ClassSecurityInfo()
273
274
    security.declarePublic("process_form")
275
276
    def process_form(self, instance, field, form, empty_marker=None,
277
                     emptyReturnsMarker=False):
278
        """Return a list of dictionaries fit for AnalysisSpecsResultsField
279
           consumption.
280
281
        If neither hidemin nor hidemax are specified, only services which have
282
        float()able entries in result,min and max field will be included. If
283
        hidemin and/or hidemax specified, results might contain empty min
284
        and/or max fields.
285
        """
286
        values = []
287
288
        # selected services
289
        service_uids = form.get("uids", [])
290
291
        # return immediately if now services were selected
292
        if not service_uids:
293
            return values, {}
294
295
        # dynamic analysis specification
296
        dynamic_spec = {}
297
        if instance.getDynamicAnalysisSpec():
298
            dynamic_spec = instance.getDynamicAnalysisSpec().get_by_keyword()
299
300
        for uid in service_uids:
301
            s_min = self._get_spec_value(form, uid, "min")
302
            s_max = self._get_spec_value(form, uid, "max")
303
304
            if not s_min and not s_max:
305
                service = api.get_object_by_uid(uid)
306
                keyword = service.getKeyword()
307
                if not dynamic_spec.get(keyword):
308
                    # If user has not set value neither for min nor max, omit
309
                    # this record. Otherwise, since 'min' and 'max' are defined
310
                    # as mandatory subfields, the following message will appear
311
                    # after submission: "Specifications is required, please
312
                    # correct."
313
                    continue
314
                s_min = 0
315
                s_max = 0
316
317
            min_operator = self._get_spec_value(
318
                form, uid, "min_operator", check_floatable=False)
319
            max_operator = self._get_spec_value(
320
                form, uid, "max_operator", check_floatable=False)
321
322
            service = api.get_object_by_uid(uid)
323
            subfield_values = {
324
                "keyword": service.getKeyword(),
325
                "uid": uid,
326
                "min_operator": min_operator,
327
                "min": s_min,
328
                "max_operator": max_operator,
329
                "max": s_max,
330
                "warn_min": self._get_spec_value(form, uid, "warn_min"),
331
                "warn_max": self._get_spec_value(form, uid, "warn_max"),
332
                "hidemin": self._get_spec_value(form, uid, "hidemin"),
333
                "hidemax": self._get_spec_value(form, uid, "hidemax"),
334
                "rangecomment": self._get_spec_value(form, uid, "rangecomment",
335
                                                     check_floatable=False)
336
            }
337
338
            # Include values from other subfields that might be added
339
            # by other add-ons independently via SchemaModifier
340
            for subfield in field.subfields:
341
                if subfield not in subfield_values.keys():
342
                    subfield_values.update({
343
                        subfield: self._get_spec_value(form, uid, subfield)
344
                    })
345
346
            values.append(subfield_values)
347
348
        return values, {}
349
350 View Code Duplication
    def _get_spec_value(self, form, uid, key, check_floatable=True,
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
351
                        default=''):
352
        """Returns the value assigned to the passed in key for the analysis
353
        service uid from the passed in form.
354
355
        If check_floatable is true, will return the passed in default if the
356
        obtained value is not floatable
357
358
        :param form: form being submitted
359
        :param uid: uid of the Analysis Service the specification relates
360
        :param key: id of the specs param to get (e.g. 'min')
361
        :param check_floatable: check if the value is floatable
362
        :param default: fallback value that will be returned by default
363
        :type default: str, None
364
        """
365
        if not form or not uid:
366
            return default
367
        values = form.get(key, None)
368
        if not values or len(values) == 0:
369
            return default
370
        value = values[0].get(uid, default)
371
        if not check_floatable:
372
            return value
373
        return api.is_floatable(value) and value or default
374
375
    security.declarePublic("AnalysisSpecificationResults")
376
377
    def AnalysisSpecificationResults(self, field, allow_edit=False):
378
        """Render Analyses Specifications Table
379
        """
380
        instance = getattr(self, "instance", field.aq_parent)
381
        table = api.get_view("table_analysis_specifications",
382
                             context=instance,
383
                             request=self.REQUEST)
384
385
        # Call listing hooks
386
        table.update()
387
        table.before_render()
388
389
        if allow_edit is False:
390
            # This is a hack to notify read-only mode to the view
391
            table.allow_edit = allow_edit
392
            return table.contents_table_view()
393
        return table.ajax_contents_table()
394
395
396
registerWidget(AnalysisSpecificationWidget,
397
               title="Analysis Specification Results",
398
               description=("Analysis Specification Results"))
399