Passed
Push — 2.x ( 5d5eda...426827 )
by Ramon
08:21
created

Instrument.getRawMethods()   A

Complexity

Conditions 1

Size

Total Lines 6
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
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
# 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-2024 by it's authors.
19
# Some rights reserved, see README and LICENSE.
20
21
from datetime import date
22
23
import six
24
from AccessControl import ClassSecurityInfo
25
from bika.lims import api
26
from bika.lims import bikaMessageFactory as _
27
from bika.lims import logger
28
from bika.lims.api.analysis import is_out_of_range
29
from bika.lims.browser.fields import UIDReferenceField
30
from bika.lims.browser.widgets import DateTimeWidget
31
from bika.lims.browser.widgets import RecordsWidget
32
from bika.lims.config import PROJECTNAME
33
from bika.lims.content.bikaschema import BikaFolderSchema
34
from bika.lims.content.bikaschema import BikaSchema
35
from bika.lims.interfaces import IDeactivable
36
from bika.lims.interfaces import IInstrument
37
from senaite.core.i18n import translate as t
38
from bika.lims.utils import to_utf8
39
from bika.lims.utils.analysis import create_reference_analysis
40
from plone.app.blob.field import FileField as BlobFileField
41
from plone.app.folder.folder import ATFolder
42
from Products.Archetypes.atapi import BooleanField
43
from Products.Archetypes.atapi import BooleanWidget
44
from Products.Archetypes.atapi import ComputedField
45
from Products.Archetypes.atapi import ComputedWidget
46
from Products.Archetypes.atapi import DateTimeField
47
from Products.Archetypes.atapi import DisplayList
48
from Products.Archetypes.atapi import FileWidget
49
from Products.Archetypes.atapi import ImageField
50
from Products.Archetypes.atapi import ImageWidget
51
from Products.Archetypes.atapi import MultiSelectionWidget
52
from Products.Archetypes.atapi import PicklistWidget
53
from Products.Archetypes.atapi import Schema
54
from Products.Archetypes.atapi import SelectionWidget
55
from Products.Archetypes.atapi import StringField
56
from Products.Archetypes.atapi import StringWidget
57
from Products.Archetypes.atapi import TextAreaWidget
58
from Products.Archetypes.atapi import TextField
59
from Products.Archetypes.atapi import registerType
60
from Products.ATContentTypes.content import schemata
61
from Products.CMFCore.utils import getToolByName
62
from Products.CMFPlone.utils import safe_unicode
63
from senaite.core.browser.fields.records import RecordsField
64
from senaite.core.browser.widgets.referencewidget import ReferenceWidget
65
from senaite.core.catalog import ANALYSIS_CATALOG
66
from senaite.core.catalog import SETUP_CATALOG
67
from senaite.core.exportimport import instruments
68
from senaite.core.p3compat import cmp
69
from zope.deprecation import deprecate
70
from zope.interface import implements
71
72
schema = BikaFolderSchema.copy() + BikaSchema.copy() + Schema((
73
74
    UIDReferenceField(
75
        "InstrumentType",
76
        allowed_types=("InstrumentType",),
77
        required=1,
78
        widget=ReferenceWidget(
79
            label=_(
80
                "label_instrument_instrumenttype",
81
                default="Instrument type"),
82
            description=_(
83
                "description_instrument_instrumenttype",
84
                default="Select the type of this instrument"),
85
            catalog=SETUP_CATALOG,
86
            query={
87
                "is_active": True,
88
                "sort_on": "sortable_title",
89
                "sort_order": "ascending"
90
            },
91
        ),
92
    ),
93
94
    UIDReferenceField(
95
        "Manufacturer",
96
        allowed_types=("Manufacturer",),
97
        required=1,
98
        widget=ReferenceWidget(
99
            label=_(
100
                "label_instrument_manufacturer",
101
                default="Manufacturer"),
102
            description=_(
103
                "description_instrument_manufacturer",
104
                default="Select the manufacturer of this instrument"),
105
            catalog=SETUP_CATALOG,
106
            query={
107
                "is_active": True,
108
                "sort_on": "sortable_title",
109
                "sort_order": "ascending"
110
            },
111
        ),
112
    ),
113
114
    UIDReferenceField(
115
        "Supplier",
116
        allowed_types=("Supplier",),
117
        required=1,
118
        widget=ReferenceWidget(
119
            label=_(
120
                "label_instrument_supplier",
121
                default="Supplier"),
122
            description=_(
123
                "description_instrument_supplier",
124
                default="Select the supplier of this instrument"),
125
            catalog=SETUP_CATALOG,
126
            query={
127
                "is_active": True,
128
                "sort_on": "sortable_title",
129
                "sort_order": "ascending"
130
            },
131
        ),
132
    ),
133
134
    StringField(
135
        'Model',
136
        widget=StringWidget(
137
            label=_("Model"),
138
            description=_("The instrument's model number"),
139
        )
140
    ),
141
142
    StringField(
143
        'SerialNo',
144
        widget=StringWidget(
145
            label=_("Serial No"),
146
            description=_("The serial number that uniquely identifies the instrument"),
147
        )
148
    ),
149
150
    # TODO Remove Instrument.Method field (functionality provided by 'Methods')
151
    UIDReferenceField(
152
        'Method',
153
        vocabulary='_getAvailableMethods',
154
        allowed_types=('Method',),
155
        required=0,
156
        widget=SelectionWidget(
157
            format='select',
158
            label=_("Method"),
159
            visible=False,
160
        ),
161
    ),
162
163
    UIDReferenceField(
164
        "Methods",
165
        vocabulary="_getAvailableMethods",
166
        allowed_types=("Method",),
167
        relationship="InstrumentMethods",
168
        required=0,
169
        multiValued=1,
170
        widget=PicklistWidget(
171
            size=10,
172
            label=_(u"label_instrument_methods",
173
                    default=u"Supported methods"),
174
            description=_(
175
                u"description_instrument_methods",
176
                default=u"Methods that are supported by this analytical "
177
                        u"instrument"
178
            )
179
        ),
180
    ),
181
182
    BooleanField(
183
        'DisposeUntilNextCalibrationTest',
184
        default=False,
185
        widget=BooleanWidget(
186
            label=_("De-activate until next calibration test"),
187
            description=_("If checked, the instrument will be unavailable until the next valid "
188
                          "calibration was performed. This checkbox will automatically be unchecked."),
189
        ),
190
    ),
191
192
    # Procedures
193
    TextField(
194
        'InlabCalibrationProcedure',
195
        schemata='Procedures',
196
        default_content_type='text/plain',
197
        allowed_content_types=('text/plain', ),
198
        default_output_type="text/plain",
199
        widget=TextAreaWidget(
200
            label=_("In-lab calibration procedure"),
201
            description=_("Instructions for in-lab regular calibration routines intended for analysts"),
202
        ),
203
    ),
204
205
    TextField(
206
        'PreventiveMaintenanceProcedure',
207
        schemata='Procedures',
208
        default_content_type='text/plain',
209
        allowed_content_types=('text/plain', ),
210
        default_output_type="text/plain",
211
        widget=TextAreaWidget(
212
            label=_("Preventive maintenance procedure"),
213
            description=_("Instructions for regular preventive and maintenance routines intended for analysts"),
214
        ),
215
    ),
216
217
    StringField(
218
        'DataInterface',
219
        vocabulary="getExportDataInterfacesList",
220
        widget=SelectionWidget(
221
            checkbox_bound=0,
222
            label=_("Data Interface"),
223
            description=_("Select an Export interface for this instrument."),
224
            format='select',
225
            default='',
226
            visible=True,
227
        ),
228
    ),
229
230
    StringField('ImportDataInterface',
231
                vocabulary="getImportDataInterfacesList",
232
                multiValued=1,
233
                widget=MultiSelectionWidget(
234
                    checkbox_bound=0,
235
                    label=_("Import Data Interface"),
236
                    description=_(
237
                        "Select an Import interface for this instrument."),
238
                    format='select',
239
                    default='',
240
                    visible=True,
241
                ),
242
                ),
243
244
    RecordsField(
245
        "ResultFilesFolder",
246
        subfields=("InterfaceName", "Folder"),
247
        subfield_labels={
248
            "InterfaceName": _("subfield_label_instrument_interface_name",
249
                               default="Interface Code"),
250
            "Folder": _("subfield_label_instrument_import_folder",
251
                        default="Folder to import the instrument results from")
252
        },
253
        subfield_sizes={
254
            "InterfaceName": 40,
255
            "Folder": 80,
256
        },
257
        subfield_maxlength={
258
            "Folder": 256,
259
        },
260
        subfield_readonly={
261
            "InterfaceName": True,
262
            "Folder": False
263
        },
264
        widget=RecordsWidget(
265
            label=_("label_instrument_result_file_folder",
266
                    default="Result files folders"),
267
            description=_("description_instrument_result_file_folder",
268
                          default="For each interface of this instrument, "
269
                                  "you can define a folder where the system "
270
                                  "should look for the results files while "
271
                                  "automatically importing results. Having a "
272
                                  "folder for each Instrument and inside that "
273
                                  "folder creating different folders for each "
274
                                  "of its Interfaces can be a good approach. "
275
                                  "You can use Interface codes to be sure "
276
                                  "that folder names are unique."),
277
            visible=True,
278
        ),
279
    ),
280
281
    RecordsField(
282
        'DataInterfaceOptions',
283
        type='interfaceoptions',
284
        subfields=('Key', 'Value'),
285
        required_subfields=('Key', 'Value'),
286
        subfield_labels={
287
            'OptionValue': _('Key'),
288
            'OptionText': _('Value'),
289
        },
290
        widget=RecordsWidget(
291
            label=_("Data Interface Options"),
292
            description=_("Use this field to pass arbitrary parameters to the export/import modules."),
293
            visible=False,
294
        ),
295
    ),
296
297
    ComputedField(
298
        'Valid',
299
        expression="'1' if context.isValid() else '0'",
300
        widget=ComputedWidget(
301
            visible=False,
302
        ),
303
    ),
304
305
    StringField(
306
        'AssetNumber',
307
        widget=StringWidget(
308
            label=_("Asset Number"),
309
            description=_("The instrument's ID in the lab's asset register"),
310
        )
311
    ),
312
313
    UIDReferenceField(
314
        "InstrumentLocation",
315
        schemata="Additional info.",
316
        allowed_types=("InstrumentLocation", ),
317
        required=0,
318
        widget=ReferenceWidget(
319
            label=_(
320
                "label_instrument_instrumentlocation",
321
                default="Location"),
322
            description=_(
323
                "description_instrument_instrumentlocation",
324
                default="Location where the instrument is installed"),
325
            catalog=SETUP_CATALOG,
326
            query={
327
                "is_active": True,
328
                "sort_on": "sortable_title",
329
                "sort_order": "ascending"
330
            },
331
        ),
332
    ),
333
334
    ImageField(
335
        'Photo',
336
        schemata='Additional info.',
337
        widget=ImageWidget(
338
            label=_("Photo image file"),
339
            description=_("Photo of the instrument"),
340
        ),
341
    ),
342
343
    DateTimeField(
344
        'InstallationDate',
345
        schemata='Additional info.',
346
        widget=DateTimeWidget(
347
            label=_("InstallationDate"),
348
            description=_("The date the instrument was installed"),
349
        )
350
    ),
351
352
    BlobFileField(
353
        'InstallationCertificate',
354
        schemata='Additional info.',
355
        widget=FileWidget(
356
            label=_("Installation Certificate"),
357
            description=_("Installation certificate upload"),
358
        )
359
    ),
360
361
))
362
363
schema.moveField('AssetNumber', before='description')
364
365
schema['description'].widget.visible = True
366
schema['description'].schemata = 'default'
367
368
def getMaintenanceTypes(context):
369
    types = [('preventive', 'Preventive'),
370
             ('repair', 'Repair'),
371
             ('enhance', 'Enhancement')]
372
    return DisplayList(types)
373
374
375
def getCalibrationAgents(context):
376
    agents = [('analyst', 'Analyst'),
377
              ('maintainer', 'Maintainer')]
378
    return DisplayList(agents)
379
380
381
class Instrument(ATFolder):
382
    """A physical gadget of the lab
383
    """
384
    implements(IInstrument, IDeactivable)
385
    security = ClassSecurityInfo()
386
    displayContentsTab = False
387
    schema = schema
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable schema does not seem to be defined.
Loading history...
388
389
    _at_rename_after_creation = True
390
391
    def _renameAfterCreation(self, check_auto_id=False):
392
        from senaite.core.idserver import renameAfterCreation
393
        renameAfterCreation(self)
394
395
    def Title(self):
396
        return to_utf8(safe_unicode(self.title))
397
398
    def getDataInterfacesList(self, type_interface="import"):
399
        interfaces = list()
400
        if type_interface == "export":
401
            interfaces = instruments.get_instrument_export_interfaces()
402
        elif type_interface == "import":
403
            interfaces = instruments.get_instrument_import_interfaces()
404
        interfaces = map(lambda imp: (imp[0], imp[1].title), interfaces)
405
        interfaces.sort(lambda x, y: cmp(x[1].lower(), y[1].lower()))
406
        interfaces.insert(0, ('', t(_('None'))))
407
        return DisplayList(interfaces)
408
409
    def getExportDataInterfacesList(self):
410
        return self.getDataInterfacesList("export")
411
412
    def getImportDataInterfacesList(self):
413
        return self.getDataInterfacesList("import")
414
415
    def getScheduleTaskTypesList(self):
416
        return getMaintenanceTypes(self)
417
418
    def getMaintenanceTypesList(self):
419
        return getMaintenanceTypes(self)
420
421
    def getCalibrationAgentsList(self):
422
        return getCalibrationAgents(self)
423
424
    def getRawMethods(self):
425
        """Returns the UIDs of the methods supported by this instrument
426
427
        :returns: Method UIDs
428
        """
429
        return self.getField("Methods").getRaw(self)
430
431
    @deprecate("Use getRawMethods instead")
432
    def getMethodUIDs(self):
433
        return self.getRawMethods()
434
435
    def _getAvailableMethods(self):
436
        """ Returns the available (active) methods.
437
            One method can be done by multiple instruments, but one
438
            instrument can only be used in one method.
439
        """
440
        bsc = getToolByName(self, 'senaite_catalog_setup')
441
        items = [(c.UID, c.Title)
442
                 for c in bsc(portal_type='Method',
443
                              is_active=True)]
444
        items.sort(lambda x, y: cmp(x[1], y[1]))
445
        items.insert(0, ('', t(_('None'))))
446
        return DisplayList(items)
447
448
    def getMaintenanceTasks(self):
449
        return self.objectValues('InstrumentMaintenanceTask')
450
451
    def getCalibrations(self):
452
        """ Return all calibration objects related with the instrument
453
        """
454
        return self.objectValues('InstrumentCalibration')
455
456
    def getCertifications(self):
457
        """ Returns the certifications of the instrument. Both internal
458
            and external certifitions
459
        """
460
        return self.objectValues('InstrumentCertification')
461
462
    def getValidCertifications(self):
463
        """ Returns the certifications fully valid
464
        """
465
        certs = []
466
        today = date.today()
467
        for c in self.getCertifications():
468
            validfrom = c.getValidFrom() if c else None
469
            validto = c.getValidTo() if validfrom else None
470
            if not validfrom or not validto:
471
                continue
472
            validfrom = validfrom.asdatetime().date()
473
            validto = validto.asdatetime().date()
474
            if (today >= validfrom and today <= validto):
475
                certs.append(c)
476
        return certs
477
478
    def isValid(self):
479
        """ Returns if the current instrument is not out for verification, calibration,
480
        out-of-date regards to its certificates and if the latest QC succeed
481
        """
482
        return self.isOutOfDate() is False \
483
            and self.isQCValid() is True \
484
            and self.getDisposeUntilNextCalibrationTest() is False \
485
            and self.isValidationInProgress() is False \
486
            and self.isCalibrationInProgress() is False
487
488
    def isQCValid(self):
489
        """ Returns True if the results of the last batch of QC Analyses
490
        performed against this instrument was within the valid range.
491
492
        For a given Reference Sample, more than one Reference Analyses assigned
493
        to this same instrument can be performed and the Results Capture Date
494
        might slightly differ amongst them. Thus, this function gets the latest
495
        QC Analysis performed, looks for siblings (through RefAnalysisGroupID)
496
        and if the results for all them are valid, then returns True. If there
497
        is one single Reference Analysis from the group with an out-of-range
498
        result, the function returns False
499
        """
500
        query = {
501
            "portal_type": "ReferenceAnalysis",
502
            "getInstrumentUID": self.UID(),
503
            "sort_on": "getResultCaptureDate",
504
            "sort_order": "descending",
505
            # Do not rely on DateIndex!
506
            # It has a resolution of 1 second, which might give us the wrong
507
            # analysis here (see workaround below)
508
            # "sort_limit": 1
509
        }
510
        brains = api.search(query, ANALYSIS_CATALOG)
511
512
        if len(brains) == 0:
513
            # There are no Reference Analyses assigned to this instrument yet
514
            return True
515
516
        # NOTE: The `DateIndex` used for sorting has a resolution of 1 second,
517
        # which might return the wrong result. Therefore, we sort manually
518
        brains = sorted(
519
            brains, key=lambda b: b.getResultCaptureDate, reverse=1)
520
521
        # Look for siblings. These are the QC Analyses that were created
522
        # together with this last ReferenceAnalysis and for the same Reference
523
        # Sample. If they were added through "Add Reference Analyses" in a
524
        # Worksheet, they typically appear in the same slot.
525
        group_id = brains[0].getReferenceAnalysesGroupID
526
        query = {
527
            "portal_type": "ReferenceAnalysis",
528
            "getInstrumentUID": self.UID(),
529
            "getReferenceAnalysesGroupID": group_id,
530
            "sort_on": "getResultCaptureDate",
531
            "sort_order": "descending",
532
        }
533
        brains = api.search(query, ANALYSIS_CATALOG)
534
        for brain in brains:
535
            analysis = api.get_object(brain)
536
            results_range = analysis.getResultsRange()
537
            if not results_range:
538
                continue
539
            # Is out of range?
540
            out_of_range = is_out_of_range(analysis)[0]
541
            if out_of_range:
542
                return False
543
544
        # By default, in range
545
        return True
546
547
    def isOutOfDate(self):
548
        """ Returns if the current instrument is out-of-date regards to
549
            its certifications
550
        """
551
        certification = self.getLatestValidCertification()
552
        if certification:
553
            return not certification.isValid()
554
        return True
555
556
    def isValidationInProgress(self):
557
        """ Returns if the current instrument is under validation progress
558
        """
559
        validation = self.getLatestValidValidation()
560
        if validation:
561
            return validation.isValidationInProgress()
562
        return False
563
564
    def isCalibrationInProgress(self):
565
        """ Returns if the current instrument is under calibration progress
566
        """
567
        calibration = self.getLatestValidCalibration()
568
        if calibration is not None:
569
            return calibration.isCalibrationInProgress()
570
        return False
571
572
    def getCertificateExpireDate(self):
573
        """ Returns the current instrument's data expiration certificate
574
        """
575
        certification = self.getLatestValidCertification()
576
        if certification:
577
            return certification.getValidTo()
578
        return None
579
580
    def getWeeksToExpire(self):
581
        """ Returns the amount of weeks and days untils it's certification expire
582
        """
583
        certification = self.getLatestValidCertification()
584
        if certification:
585
            return certification.getWeeksAndDaysToExpire()
586
        return 0, 0
587
588
    def getLatestValidCertification(self):
589
        """Returns the certification with the most remaining days until expiration.
590
           If no certification was found, it returns None.
591
        """
592
593
        # 1. get all certifications
594
        certifications = self.getCertifications()
595
596
        # 2. filter out certifications, which are invalid
597
        valid_certifications = filter(lambda x: x.isValid(), certifications)
598
599
        # 3. sort by the remaining days to expire, e.g. [10, 7, 6, 1]
600
        def sort_func(x, y):
601
            return cmp(x.getDaysToExpire(), y.getDaysToExpire())
602
        sorted_certifications = sorted(valid_certifications, cmp=sort_func, reverse=True)
603
604
        # 4. return the certification with the most remaining days
605
        if len(sorted_certifications) > 0:
606
            return sorted_certifications[0]
607
        return None
608
609
    def getLatestValidValidation(self):
610
        """Returns the validation with the most remaining days in validation.
611
           If no validation was found, it returns None.
612
        """
613
        # 1. get all validations
614
        validations = self.getValidations()
615
616
        # 2. filter out validations, which are not in progress
617
        active_validations = filter(lambda x: x.isValidationInProgress(), validations)
618
619
        # 3. sort by the remaining days in validation, e.g. [10, 7, 6, 1]
620
        def sort_func(x, y):
621
            return cmp(x.getRemainingDaysInValidation(), y.getRemainingDaysInValidation())
622
        sorted_validations = sorted(active_validations, cmp=sort_func, reverse=True)
623
624
        # 4. return the validation with the most remaining days
625
        if len(sorted_validations) > 0:
626
            return sorted_validations[0]
627
        return None
628
629
    def getLatestValidCalibration(self):
630
        """Returns the calibration with the most remaining days in calibration.
631
           If no calibration was found, it returns None.
632
        """
633
        # 1. get all calibrations
634
        calibrations = self.getCalibrations()
635
636
        # 2. filter out calibrations, which are not in progress
637
        active_calibrations = filter(lambda x: x.isCalibrationInProgress(), calibrations)
638
639
        # 3. sort by the remaining days in calibration, e.g. [10, 7, 6, 1]
640
        def sort_func(x, y):
641
            return cmp(x.getRemainingDaysInCalibration(), y.getRemainingDaysInCalibration())
642
        sorted_calibrations = sorted(active_calibrations, cmp=sort_func, reverse=True)
643
644
        # 4. return the calibration with the most remaining days
645
        if len(sorted_calibrations) > 0:
646
            return sorted_calibrations[0]
647
        return None
648
649
    def getValidations(self):
650
        """ Return all the validations objects related with the instrument
651
        """
652
        return self.objectValues('InstrumentValidation')
653
654
    def getDocuments(self):
655
        """ Return all the multifile objects related with the instrument
656
        """
657
        return self.objectValues('Multifile')
658
659
    def getSchedule(self):
660
        return self.objectValues('InstrumentScheduledTask')
661
662
    def addReferences(self, reference, service_uids):
663
        """ Add reference analyses to reference
664
        """
665
        # TODO Workflow - Analyses. Assignment of refanalysis to Instrument
666
        addedanalyses = []
667
        bsc = getToolByName(self, 'senaite_catalog_setup')
668
        postfix = 1
669
        for refa in reference.getReferenceAnalyses():
670
            grid = refa.getReferenceAnalysesGroupID()
671
            try:
672
                cand = int(grid.split('-')[2])
673
                if cand >= postfix:
674
                    postfix = cand + 1
675
            except Exception:
676
                pass
677
        postfix = str(postfix).zfill(int(3))
678
        refgid = 'I%s-%s' % (reference.id, postfix)
679
        for service_uid in service_uids:
680
            # services with dependents don't belong in references
681
            service = bsc(portal_type='AnalysisService', UID=service_uid)[0].getObject()
682
            calc = service.getCalculation()
683
            if calc and calc.getDependentServices():
684
                continue
685
686
            # Create the reference analysis
687
            values = {"ReferenceAnalysesGroupID": refgid, "Instrument": self}
688
            analysis = create_reference_analysis(reference, service, **values)
689
            addedanalyses.append(analysis)
690
691
        # Set DisposeUntilNextCalibrationTest to False
692
        if (len(addedanalyses) > 0):
693
            self.getField('DisposeUntilNextCalibrationTest').set(self, False)
694
695
        return addedanalyses
696
697
    def setImportDataInterface(self, values):
698
        """ Return the current list of import data interfaces
699
        """
700
        exims = self.getImportDataInterfacesList()
701
        new_values = [value for value in values if value in exims]
702
        if len(new_values) < len(values):
703
            logger.warn("Some Interfaces weren't added...")
704
        self.Schema().getField('ImportDataInterface').set(self, new_values)
705
706
    def displayValue(self, vocab, value, widget):
707
        """Overwrite the Script (Python) `displayValue.py` located at
708
           `Products.Archetypes.skins.archetypes` to handle the references
709
           of our Picklist Widget (Methods) gracefully.
710
           This method gets called by the `picklist.pt` template like this:
711
712
           display python:context.displayValue(vocab, value, widget);"
713
        """
714
        # Taken from the Script (Python)
715
        t = self.restrictedTraverse('@@at_utils').translate
716
717
        # ensure we have strings, otherwise the `getValue` method of
718
        # Products.Archetypes.utils will raise a TypeError
719
        def to_string(v):
720
            if isinstance(v, six.string_types):
721
                return v
722
            return api.get_title(v)
723
724
        if isinstance(value, (list, tuple)):
725
            value = map(to_string, value)
726
727
        return t(vocab, value, widget)
728
729
730
schemata.finalizeATCTSchema(schema, folderish=True, moveDiscussion=False)
731
732
registerType(Instrument, PROJECTNAME)
733