Passed
Push — master ( b81f15...92201a )
by Jordi
04:59
created

AnalysisSpecificationView.update()   A

Complexity

Conditions 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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