Passed
Push — master ( feb3de...56c573 )
by Jordi
04:25
created

ReferenceResultsView.__init__()   B

Complexity

Conditions 2

Size

Total Lines 55
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 45
dl 0
loc 55
rs 8.8
c 0
b 0
f 0
cc 2
nop 3

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
# Copyright 2018 by it's authors.
6
# Some rights reserved. See LICENSE.rst, CONTRIBUTORS.rst.
7
8
import collections
9
from datetime import datetime
10
11
from bika.lims import api
12
from bika.lims import bikaMessageFactory as _
13
from bika.lims import logger
14
from bika.lims.browser import BrowserView
15
from bika.lims.browser.analyses import AnalysesView
16
from bika.lims.browser.bika_listing import BikaListingView
17
from bika.lims.browser.chart.analyses import EvolutionChart
18
from bika.lims.utils import get_image
19
from bika.lims.utils import get_link
20
from bika.lims.utils import t
21
from plone.app.layout.globals.interfaces import IViewView
22
from plone.memoize import view
23
from Products.ATContentTypes.utils import DT2dt
24
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
25
from zope.interface import implements
26
27
28
class ViewView(BrowserView):
29
    """Reference Sample View
30
    """
31
    implements(IViewView)
32
    template = ViewPageTemplateFile("templates/referencesample_view.pt")
33
34
    def __init__(self, context, request):
35
        super(ViewView, self).__init__(context, request)
36
37
        self.icon = "{}/{}".format(
38
            self.portal_url,
39
            "++resource++bika.lims.images/referencesample_big.png")
40
41
    def __call__(self):
42
        self.results = {}  # {category_title: listofdicts}
43
        for r in self.context.getReferenceResults():
44
            service = api.get_object_by_uid(r["uid"])
45
            cat = service.getCategoryTitle()
46
            if cat not in self.results:
47
                self.results[cat] = []
48
            r["service"] = service
49
            self.results[cat].append(r)
50
        self.categories = self.results.keys()
51
        self.categories.sort()
52
        return self.template()
53
54
55
class ReferenceAnalysesViewView(BrowserView):
56
    """ View of Reference Analyses linked to the Reference Sample.
57
    """
58
59
    implements(IViewView)
60
    template = ViewPageTemplateFile(
61
        "templates/referencesample_analyses.pt")
62
63
    def __init__(self, context, request):
64
        super(ReferenceAnalysesViewView, self).__init__(context, request)
65
66
        self.title = self.context.translate(_("Reference Analyses"))
67
        self.icon = "{}/{}".format(
68
            self.portal_url,
69
            "++resource++bika.lims.images/referencesample_big.png")
70
71
    def __call__(self):
72
        return self.template()
73
74
    def get_analyses_table(self):
75
        """ Returns the table of Reference Analyses
76
        """
77
        return self.get_analyses_view().contents_table()
78
79
    def get_analyses_table_view(self):
80
        view_name = "table_referenceanalyses"
81
        view = api.get_view(
82
            view_name, context=self.context, request=self.request)
83
        # Call listing hooks
84
        view.update()
85
        view.before_render()
86
87
        # TODO Refactor QC Charts as React Components
88
        # The current QC Chart is rendered by looking at the value from a
89
        # hidden input with id "graphdata", that is rendered below the contents
90
        # listing (see referenceanalyses.pt).
91
        # The value is a json, is built by folderitem function and is returned
92
        # by self.chart.get_json(). This function is called directly by the
93
        # template on render, but the template itself does not directly render
94
        # the contents listing, but is done asyncronously.
95
        # Hence the function at this point returns an empty dictionary because
96
        # folderitems hasn't been called yet. As a result, the chart appears
97
        # empty. Here, we force folderitems function to be called in order to
98
        # ensure the graphdata is filled before the template is rendered.
99
        view.get_folderitems()
100
        return view
101
102
103
class ReferenceAnalysesView(AnalysesView):
104
    """Reference Analyses on this sample
105
    """
106
107
    def __init__(self, context, request):
108
        super(ReferenceAnalysesView, self).__init__(context, request)
109
110
        self.form_id = "{}_qcanalyses".format(api.get_uid(context))
111
        self.allow_edit = False
112
        self.show_select_column = False
113
        self.show_search = False
114
        self.omit_form = True
115
        self.show_search = False
116
117
        self.contentFilter = {
118
            "portal_type": "ReferenceAnalysis",
119
            "path": {
120
                "query": "/".join(self.context.getPhysicalPath()),
121
                "level": 0}
122
        }
123
124
        self.columns = collections.OrderedDict((
125
            ("id", {
126
                "title": _("ID"),
127
                "sortable": False,
128
                "toggle": False}),
129
            ("getReferenceAnalysesGroupID", {
130
                "title": _("QC Sample ID"),
131
                "sortable": False,
132
                "toggle": True}),
133
            ("Category", {
134
                "title": _("Category"),
135
                "sortable": False,
136
                "toggle": True}),
137
            ("Service", {
138
                "title": _("Service"),
139
                "sortable": False,
140
                "toggle": True}),
141
            ("Worksheet", {
142
                "title": _("Worksheet"),
143
                "sortable": False,
144
                "toggle": True}),
145
            ("Method", {
146
                "title": _("Method"),
147
                "sortable": False,
148
                "toggle": True}),
149
            ("Instrument", {
150
                "title": _("Instrument"),
151
                "sortable": False,
152
                "toggle": True}),
153
            ("Result", {
154
                "title": _("Result"),
155
                "sortable": False,
156
                "toggle": True}),
157
            ("CaptureDate", {
158
                "title": _("Captured"),
159
                "sortable": False,
160
                "toggle": True}),
161
            ("Uncertainty", {
162
                "title": _("+-"),
163
                "sortable": False,
164
                "toggle": True}),
165
            ("DueDate", {
166
                "title": _("Due Date"),
167
                "sortable": False,
168
                "toggle": True}),
169
            ("retested", {
170
                "title": _("Retested"),
171
                "sortable": False,
172
                "type": "boolean",
173
                "toggle": True}),
174
            ("state_title", {
175
                "title": _("State"),
176
                "sortable": False,
177
                "toggle": True}),
178
        ))
179
180
        self.review_states = [
181
            {
182
                "id": "default",
183
                "title": _("All"),
184
                "contentFilter": {},
185
                "transitions": [],
186
                "columns": self.columns.keys()
187
            },
188
        ]
189
        self.chart = EvolutionChart()
190
191
    def is_analysis_edition_allowed(self, analysis_brain):
192
        """see AnalysesView.is_analysis_edition_allowed
193
        """
194
        return False
195
196
    def folderitem(self, obj, item, index):
197
        """Service triggered each time an item is iterated in folderitems.
198
199
        The use of this service prevents the extra-loops in child objects.
200
201
        :obj: the instance of the class to be foldered
202
        :item: dict containing the properties of the object to be used by
203
            the template
204
        :index: current index of the item
205
        """
206
        item = super(ReferenceAnalysesView, self).folderitem(obj, item, index)
207
208
        if not item:
209
            return None
210
        item["Category"] = obj.getCategoryTitle
211
        ref_analysis = api.get_object(obj)
212
        ws = ref_analysis.getWorksheet()
213
        if not ws:
214
            logger.warn(
215
                "No Worksheet found for ReferenceAnalysis {}"
216
                .format(obj.getId))
217
        else:
218
            item["Worksheet"] = ws.Title()
219
            anchor = "<a href='%s'>%s</a>" % (ws.absolute_url(), ws.Title())
220
            item["replace"]["Worksheet"] = anchor
221
222
        # Add the analysis to the QC Chart
223
        self.chart.add_analysis(obj)
224
        return item
225
226
227
class ReferenceResultsView(BikaListingView):
228
    """Listing of all reference results
229
    """
230
231
    def __init__(self, context, request):
232
        super(ReferenceResultsView, self).__init__(context, request)
233
234
        self.catalog = "bika_setup_catalog"
235
        self.contentFilter = {
236
            "portal_type": "AnalysisService",
237
            "UID": self.get_reference_results().keys(),
238
            "is_active": True,
239
            "sort_on": "sortable_title",
240
            "sort_order": "ascending",
241
        }
242
        self.context_actions = {}
243
        self.title = self.context.translate(_("Reference Values"))
244
        self.icon = "{}/{}".format(
245
            self.portal_url,
246
            "/++resource++bika.lims.images/referencesample_big.png"
247
        )
248
249
        self.show_select_row = False
250
        self.show_workflow_action_buttons = False
251
        self.show_select_column = False
252
        self.pagesize = 999999
253
        self.show_search = False
254
255
        # Categories
256
        if self.show_categories_enabled():
257
            self.categories = []
258
            self.show_categories = True
259
            self.expand_all_categories = True
260
            self.category_index = "getCategoryTitle"
261
262
        self.columns = collections.OrderedDict((
263
            ("Title", {
264
                "title": _("Analysis Service"),
265
                "sortable": False}),
266
            ("result", {
267
                "title": _("Expected Result"),
268
                "sortable": False}),
269
            ("error", {
270
                "title": _("Permitted Error %"),
271
                "sortable": False}),
272
            ("min", {
273
                "title": _("Min"),
274
                "sortable": False}),
275
            ("max", {
276
                "title": _("Max"),
277
                "sortable": False}),
278
        ))
279
280
        self.review_states = [
281
            {
282
                "id": "default",
283
                "title": _("All"),
284
                "contentFilter": {},
285
                "columns": self.columns.keys()
286
            }
287
        ]
288
289
    def update(self):
290
        """Update hook
291
        """
292
        super(ReferenceResultsView, self).update()
293
        self.categories.sort()
294
295
    @view.memoize
296
    def show_categories_enabled(self):
297
        """Check in the setup if categories are enabled
298
        """
299
        return self.context.bika_setup.getCategoriseAnalysisServices()
300
301
    @view.memoize
302
    def get_reference_results(self):
303
        """Return a mapping of Analysis Service -> Reference Results
304
        """
305
        referenceresults = self.context.getReferenceResults()
306
        return dict(map(lambda rr: (rr.get("uid"), rr), referenceresults))
307
308
    def folderitem(self, obj, item, index):
309
        """Service triggered each time an item is iterated in folderitems.
310
311
        The use of this service prevents the extra-loops in child objects.
312
313
        :obj: the instance of the class to be foldered
314
        :item: dict containing the properties of the object to be used by
315
            the template
316
        :index: current index of the item
317
        """
318
319
        uid = api.get_uid(obj)
320
        url = api.get_url(obj)
321
        title = api.get_title(obj)
322
323
        # get the category
324
        if self.show_categories_enabled():
325
            category = obj.getCategoryTitle()
326
            if category not in self.categories:
327
                self.categories.append(category)
328
            item["category"] = category
329
330
        ref_results = self.get_reference_results()
331
        ref_result = ref_results.get(uid)
332
333
        item["Title"] = title
334
        item["replace"]["Title"] = get_link(url, value=title)
335
        item["result"] = ref_result.get("result")
336
        item["min"] = ref_result.get("min")
337
        item["max"] = ref_result.get("max")
338
339
        # Icons
340
        after_icons = ""
341
        if obj.getAccredited():
342
            after_icons += get_image(
343
                "accredited.png", title=_("Accredited"))
344
        if obj.getAttachmentOption() == "r":
345
            after_icons += get_image(
346
                "attach_reqd.png", title=_("Attachment required"))
347
        if obj.getAttachmentOption() == "n":
348
            after_icons += get_image(
349
                "attach_no.png", title=_("Attachment not permitted"))
350
        if after_icons:
351
            item["after"]["Title"] = after_icons
352
353
        return item
354
355
356
class ReferenceSamplesView(BikaListingView):
357
    """Main reference samples folder view
358
    """
359
360
    def __init__(self, context, request):
361
        super(ReferenceSamplesView, self).__init__(context, request)
362
363
        self.catalog = "bika_catalog"
364
365
        self.contentFilter = {
366
            "portal_type": "ReferenceSample",
367
            "sort_on": "sortable_title",
368
            "sort_order": "ascending",
369
            "path": {"query": ["/"], "level": 0},
370
        }
371
372
        self.context_actions = {}
373
        self.title = self.context.translate(_("Reference Samples"))
374
        self.icon = "{}/{}".format(
375
            self.portal_url,
376
            "++resource++bika.lims.images/referencesample_big.png")
377
378
        self.show_select_column = True
379
        self.pagesize = 25
380
381
        self.columns = collections.OrderedDict((
382
            ("ID", {
383
                "title": _("ID"),
384
                "index": "id"}),
385
            ("Title", {
386
                "title": _("Title"),
387
                "index": "sortable_title",
388
                "toggle": True}),
389
            ("Supplier", {
390
                "title": _("Supplier"),
391
                "toggle": True,
392
                "attr": "aq_parent.Title",
393
                "replace_url": "aq_parent.absolute_url"}),
394
            ("Manufacturer", {
395
                "title": _("Manufacturer"),
396
                "toggle": True,
397
                "attr": "getManufacturer.Title",
398
                "replace_url": "getManufacturer.absolute_url"}),
399
            ("Definition", {
400
                "title": _("Reference Definition"),
401
                "toggle": True,
402
                "attr": "getReferenceDefinition.Title",
403
                "replace_url": "getReferenceDefinition.absolute_url"}),
404
            ("DateSampled", {
405
                "title": _("Date Sampled"),
406
                "index": "getDateSampled",
407
                "toggle": True}),
408
            ("DateReceived", {
409
                "title": _("Date Received"),
410
                "index": "getDateReceived",
411
                "toggle": True}),
412
            ("DateOpened", {
413
                "title": _("Date Opened"),
414
                "toggle": True}),
415
            ("ExpiryDate", {
416
                "title": _("Expiry Date"),
417
                "index": "getExpiryDate",
418
                "toggle": True}),
419
            ("state_title", {
420
                "title": _("State"),
421
                "toggle": True}),
422
        ))
423
424
        self.review_states = [
425
            {
426
                "id": "default",
427
                "title": _("Current"),
428
                "contentFilter": {"review_state": "current"},
429
                "columns": self.columns.keys(),
430
            }, {
431
                "id": "expired",
432
                "title": _("Expired"),
433
                "contentFilter": {"review_state": "expired"},
434
                "columns": self.columns.keys(),
435
            }, {
436
                "id": "disposed",
437
                "title": _("Disposed"),
438
                "contentFilter": {"review_state": "disposed"},
439
                "columns": self.columns.keys(),
440
            }, {
441
                "id": "all",
442
                "title": _("All"),
443
                "contentFilter": {},
444
                "columns": self.columns.keys(),
445
            }
446
        ]
447
448
    def before_render(self):
449
        """Before template render hook
450
        """
451
        super(ReferenceSamplesView, self).before_render()
452
        # Don't allow any context actions on the Methods folder
453
        self.request.set("disable_border", 1)
454
455
    def folderitem(self, obj, item, index):
456
        """Applies new properties to the item (Client) that is currently being
457
        rendered as a row in the list
458
459
        :param obj: client to be rendered as a row in the list
460
        :param item: dict representation of the client, suitable for the list
461
        :param index: current position of the item within the list
462
        :type obj: ATContentType/DexterityContentType
463
        :type item: dict
464
        :type index: int
465
        :return: the dict representation of the item
466
        :rtype: dict
467
        """
468
469
        # XXX Refactor expiration to a proper place
470
        # ---------------------------- 8< -------------------------------------
471
        if item.get("review_state", "current") == "current":
472
            # Check expiry date
473
            exdate = obj.getExpiryDate()
474
            if exdate:
475
                expirydate = DT2dt(exdate).replace(tzinfo=None)
476
                if (datetime.today() > expirydate):
477
                    # Trigger expiration
478
                    self.workflow.doActionFor(obj, "expire")
479
                    item["review_state"] = "expired"
480
                    item["obj"] = obj
481
482
        if self.contentFilter.get('review_state', '') \
483
           and item.get('review_state', '') == 'expired':
484
            # This item must be omitted from the list
485
            return None
486
        # ---------------------------- >8 -------------------------------------
487
488
        url = api.get_url(obj)
489
        id = api.get_id(obj)
490
491
        item["ID"] = id
492
        item["replace"]["ID"] = get_link(url, value=id)
493
        item["DateSampled"] = self.ulocalized_time(
494
            obj.getDateSampled(), long_format=True)
495
        item["DateReceived"] = self.ulocalized_time(obj.getDateReceived())
496
        item["DateOpened"] = self.ulocalized_time(obj.getDateOpened())
497
        item["ExpiryDate"] = self.ulocalized_time(obj.getExpiryDate())
498
499
        # Icons
500
        after_icons = ''
501
        if obj.getBlank():
502
            after_icons += get_image(
503
                "blank.png", title=t(_("Blank")))
504
        if obj.getHazardous():
505
            after_icons += get_image(
506
                "hazardous.png", title=t(_("Hazardous")))
507
        if after_icons:
508
            item["after"]["ID"] = after_icons
509
510
        return item
511