Passed
Push — master ( 7405d0...49bb1f )
by Jordi
12:54 queued 07:30
created

  A

Complexity

Conditions 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 4
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 json
9
10
import plone
11
from bika.lims import api
12
from bika.lims import bikaMessageFactory as _
13
from bika.lims.browser import BrowserView
14
from bika.lims.browser.analyses import AnalysesView
15
from bika.lims.browser.bika_listing import BikaListingView
16
from bika.lims.browser.chart.analyses import EvolutionChart
17
from bika.lims.browser.resultsimport.autoimportlogs import AutoImportLogsView
18
from bika.lims.browser.viewlets import InstrumentQCFailuresViewlet  # noqa
19
from bika.lims.catalog.analysis_catalog import CATALOG_ANALYSIS_LISTING
20
from bika.lims.content.instrumentmaintenancetask import \
21
    InstrumentMaintenanceTaskStatuses as mstatus
22
from bika.lims.utils import get_image, get_link, t
23
from plone.app.layout.globals.interfaces import IViewView
24
from Products.CMFCore.utils import getToolByName
25
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
26
from zExceptions import Forbidden
27
from ZODB.POSException import POSKeyError
28
from zope.interface import implements
29
30
31
class InstrumentMaintenanceView(BikaListingView):
32
    """Listing view for instrument maintenance tasks
33
    """
34
35 View Code Duplication
    def __init__(self, context, request):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
36
        super(InstrumentMaintenanceView, self).__init__(context, request)
37
        self.catalog = "portal_catalog"
38
        self.contentFilter = {
39
            "portal_type": "InstrumentMaintenanceTask",
40
            "path": {
41
                "query": api.get_path(context),
42
                "depth": 1  # searching just inside the specified folder
43
            },
44
            "sort_on": "created",
45
            "sort_order": "descending",
46
        }
47
48
        self.form_id = "instrumentmaintenance"
49
        self.title = self.context.translate(_("Instrument Maintenance"))
50
51
        self.icon = "{}/{}".format(
52
            self.portal_url,
53
            "++resource++bika.lims.images/instrumentmaintenance_big.png"
54
        )
55
        self.context_actions = {
56
            _("Add"): {
57
                "url": "createObject?type_name=InstrumentMaintenanceTask",
58
                "icon": "++resource++bika.lims.images/add.png"}
59
        }
60
61
        self.allow_edit = False
62
        self.show_select_column = False
63
        self.show_workflow_action_buttons = True
64
        self.pagesize = 30
65
66
        self.columns = {
67
            'getCurrentState': {'title': ''},
68
            'Title': {'title': _('Task'),
69
                      'index': 'sortable_title'},
70
            'getType': {'title': _('Task type', 'Type'), 'sortable': True},
71
            'getDownFrom': {'title': _('Down from'), 'sortable': True},
72
            'getDownTo': {'title': _('Down to'), 'sortable': True},
73
            'getMaintainer': {'title': _('Maintainer'), 'sortable': True},
74
        }
75
76
        self.review_states = [
77
            {
78
                "id": "default",
79
                "title": _("Open"),
80
                "contentFilter": {"cancellation_state": "active"},
81
                "columns": [
82
                    "getCurrentState",
83
                    "Title",
84
                    "getType",
85
                    "getDownFrom",
86
                    "getDownTo",
87
                    "getMaintainer",
88
                ]
89
            }, {
90
                "id": "cancelled",
91
                "title": _("Cancelled"),
92
                "contentFilter": {"cancellation_state": "cancelled"},
93
                "columns": [
94
                    "getCurrentState",
95
                    "Title",
96
                    "getType",
97
                    "getDownFrom",
98
                    "getDownTo",
99
                    "getMaintainer",
100
                ]
101
            }, {
102
                "id": "all",
103
                "title": _("All"),
104
                "contentFilter": {},
105
                "columns": [
106
                    "getCurrentState",
107
                    "Title",
108
                    "getType",
109
                    "getDownFrom",
110
                    "getDownTo",
111
                    "getMaintainer"
112
                ]
113
            }
114
        ]
115
116
    def localize_date(self, date):
117
        """Return the localized date
118
        """
119
        return self.ulocalized_time(date, long_format=1)
120
121
    def folderitem(self, obj, item, index):
122
        """Augment folder listing item
123
        """
124
        url = item.get("url")
125
        title = item.get("Title")
126
127
        item["replace"]["Title"] = get_link(url, value=title)
128
        item["getType"] = _(obj.getType()[0])
129
        item["getDownFrom"] = self.localize_date(obj.getDownFrom())
130
        item["getDownTo"] = self.localize_date(obj.getDownTo())
131
        item["getMaintainer"] = obj.getMaintainer()
132
133
        status = obj.getCurrentState()
134
        statustext = obj.getCurrentStateI18n()
135
        statusimg = ""
136
137
        if status == mstatus.CLOSED:
138
            statusimg = "instrumentmaintenance_closed.png"
139
            item["state_class"] = "state-inactive"
140
        elif status == mstatus.CANCELLED:
141
            statusimg = "instrumentmaintenance_cancelled.png"
142
            item["state_class"] = "state-cancelled"
143
        elif status == mstatus.INQUEUE:
144
            statusimg = "instrumentmaintenance_inqueue.png"
145
            item["state_class"] = "state-open"
146
        elif status == mstatus.OVERDUE:
147
            statusimg = "instrumentmaintenance_overdue.png"
148
            item["state_class"] = "state-open"
149
        elif status == mstatus.PENDING:
150
            statusimg = "instrumentmaintenance_pending.png"
151
            item["state_class"] = "state-pending"
152
153
        item["replace"]["getCurrentState"] = get_image(
154
            statusimg, title=statustext)
155
        return item
156
157
158
class InstrumentCalibrationsView(BikaListingView):
159
    """Listing view for instrument calibrations
160
    """
161
162
    def __init__(self, context, request):
163
        super(InstrumentCalibrationsView, self).__init__(context, request)
164
        self.catalog = "portal_catalog"
165
        self.contentFilter = {
166
            "portal_type": "InstrumentCalibration",
167
            "path": {
168
                "query": api.get_path(context),
169
                "depth": 1  # searching just inside the specified folder
170
            },
171
            "sort_on": "created",
172
            "sort_order": "descending",
173
        }
174
175
        self.form_id = "instrumentcalibrations"
176
        self.title = self.context.translate(_("Instrument Calibrations"))
177
        self.icon = "{}/{}".format(
178
            self.portal_url,
179
            "++resource++bika.lims.images/instrumentcalibration_big.png"
180
        )
181
        self.context_actions = {
182
            _("Add"): {
183
                "url": "createObject?type_name=InstrumentCalibration",
184
                "icon": "++resource++bika.lims.images/add.png"}
185
        }
186
187
        self.allow_edit = False
188
        self.show_select_column = False
189
        self.show_workflow_action_buttons = True
190
        self.pagesize = 30
191
192
        # instrument calibrations
193
        calibrations = self.context.getCalibrations()
194
        # current running calibrations
195
        self.active_calibrations = filter(
196
            lambda c: c.isCalibrationInProgress(), calibrations)
197
        self.latest_calibration = self.context.getLatestValidCalibration()
198
199
        self.columns = {
200
            "Title": {"title": _("Task"),
201
                      "index": "sortable_title"},
202
            "getDownFrom": {"title": _("Down from")},
203
            "getDownTo": {"title": _("Down to")},
204
            "getCalibrator": {"title": _("Calibrator")},
205
        }
206
        self.review_states = [
207
            {
208
                "id": "default",
209
                "title": _("All"),
210
                "contentFilter": {},
211
                "columns": [
212
                    "Title",
213
                    "getDownFrom",
214
                    "getDownTo",
215
                    "getCalibrator",
216
                ]
217
            }
218
        ]
219
220
    def localize_date(self, date):
221
        """Return the localized date
222
        """
223
        return self.ulocalized_time(date, long_format=1)
224
225
    def folderitem(self, obj, item, index):
226
        """Augment folder listing item
227
        """
228
        url = item.get("url")
229
        title = item.get("Title")
230
        calibrator = obj.getCalibrator()
231
232
        item["getDownFrom"] = self.localize_date(obj.getDownFrom())
233
        item["getDownTo"] = self.localize_date(obj.getDownTo())
234
        item["getCalibrator"] = ""
235
        if calibrator:
236
            props = api.get_user_properties(calibrator)
237
            name = props.get("fullname", calibrator)
238
            item["getCalibrator"] = name
239
        item["replace"]["Title"] = get_link(url, value=title)
240
241
        # calibration with the most remaining days
242
        if obj == self.latest_calibration:
243
            item["state_class"] = "state-published"
244
        # running calibrations
245
        elif obj in self.active_calibrations:
246
            item["state_class"] = "state-active"
247
        # inactive calibrations
248
        else:
249
            item["state_class"] = "state-inactive"
250
251
        return item
252
253
254
class InstrumentValidationsView(BikaListingView):
255
    """Listing view for instrument validations
256
    """
257
258
    def __init__(self, context, request):
259
        super(InstrumentValidationsView, self).__init__(context, request)
260
        self.catalog = "portal_catalog"
261
        self.contentFilter = {
262
            "portal_type": "InstrumentValidation",
263
            "path": {
264
                "query": api.get_path(context),
265
                "depth": 1  # searching just inside the specified folder
266
            },
267
            "sort_on": "created",
268
            "sort_order": "descending",
269
        }
270
271
        self.form_id = "instrumentvalidations"
272
        self.title = self.context.translate(_("Instrument Validations"))
273
        self.icon = "{}/{}".format(
274
            self.portal_url,
275
            "++resource++bika.lims.images/instrumentvalidation_big.png"
276
        )
277
        self.context_actions = {
278
            _("Add"): {
279
                "url": "createObject?type_name=InstrumentValidation",
280
                "icon": "++resource++bika.lims.images/add.png"}
281
        }
282
283
        self.allow_edit = False
284
        self.show_select_column = False
285
        self.show_workflow_action_buttons = True
286
        self.pagesize = 30
287
288
        # instrument validations
289
        validations = self.context.getValidations()
290
        # current running validations
291
        self.active_validations = filter(
292
            lambda v: v.isValidationInProgress(), validations)
293
        self.latest_validation = self.context.getLatestValidValidation()
294
295
        self.columns = {
296
            "Title": {"title": _("Task"),
297
                      "index": "sortable_title"},
298
            "getDownFrom": {"title": _("Down from")},
299
            "getDownTo": {"title": _("Down to")},
300
            "getValidator": {"title": _("Validator")},
301
        }
302
        self.review_states = [
303
            {
304
                "id": "default",
305
                "title": _("All"),
306
                "contentFilter": {},
307
                "columns": [
308
                    "Title",
309
                    "getDownFrom",
310
                    "getDownTo",
311
                    "getValidator",
312
                ]
313
            }
314
        ]
315
316
    def localize_date(self, date):
317
        """Return the localized date
318
        """
319
        return self.ulocalized_time(date, long_format=1)
320
321
    def folderitem(self, obj, item, index):
322
        """Augment folder listing item
323
        """
324
        url = item.get("url")
325
        title = item.get("Title")
326
327
        item["getDownFrom"] = self.localize_date(obj.getDownFrom())
328
        item["getDownTo"] = self.localize_date(obj.getDownTo())
329
        item["getValidator"] = obj.getValidator()
330
        item["replace"]["Title"] = get_link(url, value=title)
331
332
        # validation with the most remaining days
333
        if obj == self.latest_validation:
334
            item["state_class"] = "state-published"
335
        # running validations
336
        elif obj in self.active_validations:
337
            item["state_class"] = "state-active"
338
        # inactive validations
339
        else:
340
            item["state_class"] = "state-inactive"
341
342
        return item
343
344
345
class InstrumentScheduleView(BikaListingView):
346
    """Listing view for instrument scheduled tasks
347
    """
348
349 View Code Duplication
    def __init__(self, context, request):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
350
        super(InstrumentScheduleView, self).__init__(context, request)
351
        self.catalog = "portal_catalog"
352
        self.contentFilter = {
353
            "portal_type": "InstrumentScheduledTask",
354
            "path": {
355
                "query": api.get_path(context),
356
                "depth": 1  # searching just inside the specified folder
357
            },
358
            "sort_on": "created",
359
            "sort_order": "descending",
360
        }
361
362
        self.form_id = "instrumentschedule"
363
        self.title = self.context.translate(_("Instrument Scheduled Tasks"))
364
365
        self.icon = "{}/{}".format(
366
            self.portal_url,
367
            "++resource++bika.lims.images/instrumentschedule_big.png"
368
        )
369
        self.context_actions = {
370
            _("Add"): {
371
                "url": "createObject?type_name=InstrumentScheduledTask",
372
                "icon": "++resource++bika.lims.images/add.png"}
373
        }
374
375
        self.allow_edit = False
376
        self.show_select_column = False
377
        self.show_workflow_action_buttons = True
378
        self.pagesize = 30
379
380
        self.columns = {
381
            "Title": {"title": _("Scheduled task"),
382
                      "index": "sortable_title"},
383
            "getType": {"title": _("Task type", "Type")},
384
            "getCriteria": {"title": _("Criteria")},
385
            "creator": {"title": _("Created by")},
386
            "created": {"title": _("Created")},
387
        }
388
389
        self.review_states = [
390
            {
391
                "id": "default",
392
                "title": _("Active"),
393
                "contentFilter": {"inactive_state": "active"},
394
                "transitions": [{"id": "deactivate"}, ],
395
                "columns": [
396
                    "Title",
397
                    "getType",
398
                    "getCriteria",
399
                    "creator",
400
                    "created",
401
                ]
402
            }, {
403
                "id": "inactive",
404
                "title": _("Dormant"),
405
                "contentFilter": {"inactive_state": "inactive"},
406
                "transitions": [{"id": "activate"}, ],
407
                "columns": [
408
                    "Title",
409
                    "getType",
410
                    "getCriteria",
411
                    "creator",
412
                    "created"
413
                ]
414
            }, {
415
                "id": "all",
416
                "title": _("All"),
417
                "contentFilter": {},
418
                "columns": [
419
                    "Title",
420
                    "getType",
421
                    "getCriteria",
422
                    "creator",
423
                    "created",
424
                ]
425
            }
426
        ]
427
428
    def localize_date(self, date):
429
        """Return the localized date
430
        """
431
        return self.ulocalized_time(date, long_format=1)
432
433
    def folderitem(self, obj, item, index):
434
        """Augment folder listing item
435
        """
436
        url = item.get("url")
437
        title = item.get("Title")
438
        creator = obj.Creator()
439
440
        item["replace"]["Title"] = get_link(url, value=title)
441
        item["created"] = self.localize_date(obj.created())
442
        item["getType"] = _(obj.getType()[0])
443
        item["creator"] = ""
444
        if creator:
445
            props = api.get_user_properties(creator)
446
            name = props.get("fullname", creator)
447
            item["creator"] = name
448
449
        return item
450
451
452
class InstrumentReferenceAnalysesViewView(BrowserView):
453
    """View of Reference Analyses linked to the Instrument.
454
455
    Only shows the Reference Analyses (Control and Blanks), the rest of regular
456
    and duplicate analyses linked to this instrument are not displayed.
457
458
    The Reference Analyses from an Instrument can be from Worksheets (QC
459
    analysis performed regularly for any Analysis Request) or attached directly
460
    to the instrument, without being linked to any Worksheet).
461
462
    In this case, the Reference Analyses are created automatically by the
463
    instrument import tool.
464
    """
465
466
    implements(IViewView)
467
    template = ViewPageTemplateFile(
468
        "templates/instrument_referenceanalyses.pt")
469
470
    def __init__(self, context, request):
471
        super(InstrumentReferenceAnalysesViewView, self).__init__(
472
            context, request)
473
474
        self.title = self.context.translate(_("Internal Calibration Tests"))
475
        self.icon = "{}/{}".format(
476
            self.portal_url,
477
            "++resource++bika.lims.images/referencesample_big.png"
478
        )
479
        self._analysesview = None
480
481
    def __call__(self):
482
        return self.template()
483
484
    def get_analyses_table_view(self):
485
        view_name = "table_instrument_referenceanalyses"
486
        view = api.get_view(
487
            view_name, context=self.context, request=self.request)
488
        # Call listing hooks
489
        view.update()
490
        view.before_render()
491
492
        # TODO Refactor QC Charts as React Components
493
        # The current QC Chart is rendered by looking at the value from a hidden
494
        # input with id "graphdata", that is rendered below the contents listing
495
        # (see instrument_referenceanalyses.pt).
496
        # The value is a json, is built by folderitem function and is returned
497
        # by self.chart.get_json(). This function is called directly by the
498
        # template on render, but the template itself does not directly render
499
        # the contents listing, but is done asyncronously.
500
        # Hence the function at this point returns an empty dictionary because
501
        # folderitems hasn't been called yet. As a result, the chart appears
502
        # empty. Here, we force folderitems function to be called in order to
503
        # ensure the graphdata is filled before the template is rendered.
504
        view.get_folderitems()
505
        return view
506
507
508
class InstrumentReferenceAnalysesView(AnalysesView):
509
    """View for the table of Reference Analyses linked to the Instrument.
510
511
    Only shows the Reference Analyses (Control and Blanks), the rest of regular
512
    and duplicate analyses linked to this instrument are not displayed.
513
    """
514
515
    def __init__(self, context, request, **kwargs):
516
        AnalysesView.__init__(self, context, request, **kwargs)
517
518
        self.form_id = "{}_qcanalyses".format(api.get_uid(context))
519
        self.allow_edit = False
520
        self.show_select_column = False
521
        self.show_search = False
522
        self.omit_form = True
523
524
        self.catalog = CATALOG_ANALYSIS_LISTING
525
526
        self.contentFilter = {
527
            "portal_type": "ReferenceAnalysis",
528
            "getInstrumentUID": api.get_uid(self.context),
529
            "sort_on": "getResultCaptureDate",
530
            "sort_order": "reverse"
531
        }
532
        self.columns["getReferenceAnalysesGroupID"] = {
533
            "title": _("QC Sample ID"),
534
            "sortable": False
535
        }
536
        self.columns["Partition"] = {
537
            "title": _("Reference Sample"),
538
            "sortable": False
539
        }
540
        self.columns["Retractions"] = {
541
            "title": "",
542
            "sortable": False
543
        }
544
545
        self.review_states[0]["columns"] = [
546
            "Service",
547
            "getReferenceAnalysesGroupID",
548
            "Partition",
549
            "Result",
550
            "Uncertainty",
551
            "CaptureDate",
552
            "Retractions"
553
        ]
554
        self.review_states[0]["transitions"] = [{}]
555
        self.chart = EvolutionChart()
556
557
    def isItemAllowed(self, obj):
558
        allowed = super(InstrumentReferenceAnalysesView,
559
                        self).isItemAllowed(obj)
560
        return allowed or obj.getResult != ""
561
562
    def folderitem(self, obj, item, index):
563
        item = super(InstrumentReferenceAnalysesView,
564
                     self).folderitem(obj, item, index)
565
        analysis = api.get_object(obj)
566
567
        # Partition is used to group/toggle QC Analyses
568
        sample = analysis.getSample()
569
        item["replace"]["Partition"] = get_link(api.get_url(sample),
570
                                                api.get_id(sample))
571
572
        # Get retractions field
573
        item["Retractions"] = ""
574
        report = analysis.getRetractedAnalysesPdfReport()
575
        if report:
576
            url = api.get_url(analysis)
577
            href = "{}/at_download/RetractedAnalysesPdfReport".format(url)
578
            attrs = {"class": "pdf", "target": "_blank"}
579
            title = _("Retractions")
580
            link = get_link(href, title, **attrs)
581
            item["Retractions"] = title
582
            item["replace"]["Retractions"] = link
583
584
        # Add the analysis to the QC Chart
585
        self.chart.add_analysis(analysis)
586
587
        return item
588
589
590
class InstrumentCertificationsView(BikaListingView):
591
    """Listing view for instrument certifications
592
    """
593
594
    def __init__(self, context, request, **kwargs):
595
        BikaListingView.__init__(self, context, request, **kwargs)
596
        self.catalog = "portal_catalog"
597
        self.contentFilter = {
598
            "portal_type": "InstrumentCertification",
599
            "path": {
600
                "query": api.get_path(context),
601
                "depth": 1  # searching just inside the specified folder
602
            },
603
            "sort_on": "created",
604
            "sort_order": "descending",
605
        }
606
607
        self.form_id = "instrumentcertifications"
608
        self.title = self.context.translate(_("Calibration Certificates"))
609
        self.icon = "{}/{}".format(
610
            self.portal_url,
611
            "++resource++bika.lims.images/instrumentcertification_big.png"
612
        )
613
        self.context_actions = {
614
            _("Add"): {
615
                "url": "createObject?type_name=InstrumentCertification",
616
                "icon": "++resource++bika.lims.images/add.png"
617
            }
618
        }
619
620
        self.allow_edit = False
621
        self.show_select_column = False
622
        self.show_workflow_action_buttons = True
623
        self.pagesize = 30
624
625
        # latest valid certificate UIDs
626
        self.valid_certificates = self.context.getValidCertifications()
627
        self.latest_certificate = self.context.getLatestValidCertification()
628
629
        self.columns = {
630
            "Title": {"title": _("Cert. Num"), "index": "sortable_title"},
631
            "getAgency": {"title": _("Agency"), "sortable": False},
632
            "getDate": {"title": _("Date"), "sortable": False},
633
            "getValidFrom": {"title": _("Valid from"), "sortable": False},
634
            "getValidTo": {"title": _("Valid to"), "sortable": False},
635
            "getDocument": {"title": _("Document"), "sortable": False},
636
        }
637
638
        self.review_states = [
639
            {
640
                "id": "default",
641
                "title": _("All"),
642
                "contentFilter": {},
643
                "columns": [
644
                    "Title",
645
                    "getAgency",
646
                    "getDate",
647
                    "getValidFrom",
648
                    "getValidTo",
649
                    "getDocument",
650
                ],
651
                "transitions": []
652
            }
653
        ]
654
655
    def get_document(self, certificate):
656
        """Return the document of the given document
657
        """
658
        try:
659
            return certificate.getDocument()
660
        except POSKeyError:  # POSKeyError: "No blob file"
661
            # XXX When does this happen?
662
            return None
663
664
    def localize_date(self, date):
665
        """Return the localized date
666
        """
667
        return self.ulocalized_time(date, long_format=0)
668
669
    def folderitem(self, obj, item, index):
670
        """Augment folder listing item with additional data
671
        """
672
        url = item.get("url")
673
        title = item.get("Title")
674
675
        item["replace"]["Title"] = get_link(url, value=title)
676
        item["getDate"] = self.localize_date(obj.getDate())
677
        item["getValidFrom"] = self.localize_date(obj.getValidFrom())
678
        item["getValidTo"] = self.localize_date(obj.getValidTo())
679
680
        if obj.getInternal() is True:
681
            item["replace"]["getAgency"] = ""
682
            item["state_class"] = "%s %s" % \
683
                (item["state_class"], "internalcertificate")
684
685
        item["getDocument"] = ""
686
        item["replace"]["getDocument"] = ""
687
        doc = self.get_document(obj)
688
        if doc and doc.get_size() > 0:
689
            filename = doc.filename
690
            download_url = "{}/at_download/Document".format(url)
691
            anchor = get_link(download_url, filename)
692
            item["getDocument"] = filename
693
            item["replace"]["getDocument"] = anchor
694
695
        # Latest valid certificate
696
        if obj == self.latest_certificate:
697
            item["state_class"] = "state-published"
698
        # Valid certificate
699
        elif obj in self.valid_certificates:
700
            item["state_class"] = "state-valid state-published"
701
        # Invalid certificates
702
        else:
703
            img = get_image("exclamation.png", title=t(_("Out of date")))
704
            item["replace"]["getValidTo"] = "%s %s" % (item["getValidTo"], img)
705
            item["state_class"] = "state-invalid"
706
707
        return item
708
709
710
class InstrumentAutoImportLogsView(AutoImportLogsView):
711
    """Logs of Auto-Imports of this instrument.
712
    """
713
714
    def __init__(self, context, request, **kwargs):
715
        AutoImportLogsView.__init__(self, context, request, **kwargs)
716
        del self.columns["Instrument"]
717
        self.review_states[0]["columns"].remove("Instrument")
718
        self.contentFilter = {
719
            "portal_type": "AutoImportLog",
720
            "path": {
721
                "query": api.get_path(context),
722
                "depth": 1  # searching just inside the specified folder
723
            },
724
            "sort_on": "created",
725
            "sort_order": "descending",
726
        }
727
728
        self.title = self.context.translate(
729
            _("Auto Import Logs of %s" % self.context.Title()))
730
        self.icon = "{}/{}".format(
731
            self.portal_url,
732
            "++resource++bika.lims.images/instrumentcertification_big.png"
733
        )
734
        self.context_actions = {}
735
736
        self.allow_edit = False
737
        self.show_select_column = False
738
        self.show_workflow_action_buttons = True
739
        self.pagesize = 30
740
741
742
class InstrumentMultifileView(BikaListingView):
743
    """Listing view for instrument multi files
744
    """
745
746
    def __init__(self, context, request):
747
        super(InstrumentMultifileView, self).__init__(context, request)
748
749
        self.catalog = "bika_setup_catalog"
750
        self.contentFilter = {
751
            "portal_type": "Multifile",
752
            "path": {
753
                "query": api.get_path(context),
754
                "depth": 1  # searching just inside the specified folder
755
            },
756
            "sort_on": "created",
757
            "sort_order": "descending",
758
        }
759
760
        self.form_id = "instrumentfiles"
761
        self.title = self.context.translate(_("Instrument Files"))
762
        self.icon = "{}/{}".format(
763
            self.portal_url,
764
            "++resource++bika.lims.images/instrumentcertification_big.png"
765
        )
766
        self.context_actions = {
767
            _("Add"): {
768
                "url": "createObject?type_name=Multifile",
769
                "icon": "++resource++bika.lims.images/add.png"
770
            }
771
        }
772
773
        self.allow_edit = False
774
        self.show_select_column = False
775
        self.show_workflow_action_buttons = True
776
        self.pagesize = 30
777
778
        self.columns = {
779
            "DocumentID": {"title": _("Document ID"),
780
                           "index": "sortable_title"},
781
            "DocumentVersion": {"title": _("Document Version"),
782
                                "index": "sortable_title"},
783
            "DocumentLocation": {"title": _("Document Location"),
784
                                 "index": "sortable_title"},
785
            "DocumentType": {"title": _("Document Type"),
786
                             "index": "sortable_title"},
787
            "FileDownload": {"title": _("File")}
788
        }
789
790
        self.review_states = [
791
            {
792
                "id": "default",
793
                "title": _("All"),
794
                "contentFilter": {},
795
                "columns": [
796
                    "DocumentID",
797
                    "DocumentVersion",
798
                    "DocumentLocation",
799
                    "DocumentType",
800
                    "FileDownload"
801
                ]
802
            },
803
        ]
804
805
    def get_file(self, obj):
806
        """Return the file of the given object
807
        """
808
        try:
809
            return obj.getFile()
810
        except POSKeyError:  # POSKeyError: "No blob file"
811
            # XXX When does this happen?
812
            return None
813
814
    def folderitem(self, obj, item, index):
815
        """Augment folder listing item with additional data
816
        """
817
        url = item.get("url")
818
        title = item.get("DocumentID")
819
820
        item["replace"]["DocumentID"] = get_link(url, title)
821
822
        item["FileDownload"] = ""
823
        item["replace"]["FileDownload"] = ""
824
        file = self.get_file(obj)
825
        if file and file.get_size() > 0:
826
            filename = file.filename
827
            download_url = "{}/at_download/File".format(url)
828
            anchor = get_link(download_url, filename)
829
            item["FileDownload"] = filename
830
            item["replace"]["FileDownload"] = anchor
831
832
        item["DocumentVersion"] = obj.getDocumentVersion()
833
        item["DocumentLocation"] = obj.getDocumentLocation()
834
        item["DocumentType"] = obj.getDocumentType()
835
836
        return item
837
838
839
class ajaxGetInstrumentMethods(BrowserView):
840
    """ Returns the method assigned to the defined instrument.
841
        uid: unique identifier of the instrument
842
    """
843
    # Modified to return multiple methods after enabling multiple method
844
    # for intruments.
845
    def __call__(self):
846
        out = {
847
            "title": None,
848
            "instrument": None,
849
            "methods": [],
850
        }
851
        try:
852
            plone.protect.CheckAuthenticator(self.request)
853
        except Forbidden:
854
            return json.dumps(out)
855
        bsc = getToolByName(self, "bika_setup_catalog")
856
        results = bsc(portal_type="Instrument",
857
                      UID=self.request.get("uid", "0"))
858
        instrument = results[0] if results and len(results) == 1 else None
859
        if instrument:
860
            instrument_obj = instrument.getObject()
861
            out["title"] = instrument_obj.Title()
862
            out["instrument"] = instrument.UID
863
            # Handle multiple Methods per instrument
864
            methods = instrument_obj.getMethods()
865
            for method in methods:
866
                out["methods"].append({
867
                    "uid": method.UID(),
868
                    "title": method.Title(),
869
                })
870
        return json.dumps(out)
871