| Total Complexity | 174 | 
| Total Lines | 952 | 
| Duplicated Lines | 2.84 % | 
| Changes | 0 | ||
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like build.bika.lims.content.arimport often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
| 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 | from AccessControl import ClassSecurityInfo  | 
            ||
| 9 | import csv  | 
            ||
| 10 | from copy import deepcopy  | 
            ||
| 11 | from DateTime.DateTime import DateTime  | 
            ||
| 12 | from Products.Archetypes.event import ObjectInitializedEvent  | 
            ||
| 13 | from Products.CMFCore.WorkflowCore import WorkflowException  | 
            ||
| 14 | from bika.lims import bikaMessageFactory as _  | 
            ||
| 15 | from bika.lims.browser import ulocalized_time  | 
            ||
| 16 | from bika.lims.config import PROJECTNAME  | 
            ||
| 17 | from bika.lims.content.bikaschema import BikaSchema  | 
            ||
| 18 | from bika.lims.content.analysisrequest import schema as ar_schema  | 
            ||
| 19 | from bika.lims.content.sample import schema as sample_schema  | 
            ||
| 20 | from bika.lims.idserver import renameAfterCreation  | 
            ||
| 21 | from bika.lims.interfaces import IARImport, IClient  | 
            ||
| 22 | from bika.lims.utils import tmpID  | 
            ||
| 23 | from bika.lims.utils.analysisrequest import create_analysisrequest  | 
            ||
| 24 | from bika.lims.vocabularies import CatalogVocabulary  | 
            ||
| 25 | from bika.lims.workflow import doActionFor  | 
            ||
| 26 | from collective.progressbar.events import InitialiseProgressBar  | 
            ||
| 27 | from collective.progressbar.events import ProgressBar  | 
            ||
| 28 | from collective.progressbar.events import ProgressState  | 
            ||
| 29 | from collective.progressbar.events import UpdateProgressEvent  | 
            ||
| 30 | from Products.Archetypes import atapi  | 
            ||
| 31 | from Products.Archetypes.public import *  | 
            ||
| 32 | from plone.app.blob.field import FileField as BlobFileField  | 
            ||
| 33 | from Products.Archetypes.references import HoldingReference  | 
            ||
| 34 | from Products.Archetypes.utils import addStatusMessage  | 
            ||
| 35 | from Products.CMFCore.utils import getToolByName  | 
            ||
| 36 | from Products.CMFPlone.utils import _createObjectByType  | 
            ||
| 37 | from Products.DataGridField import CheckboxColumn  | 
            ||
| 38 | from Products.DataGridField import Column  | 
            ||
| 39 | from Products.DataGridField import DataGridField  | 
            ||
| 40 | from Products.DataGridField import DataGridWidget  | 
            ||
| 41 | from Products.DataGridField import DateColumn  | 
            ||
| 42 | from Products.DataGridField import LinesColumn  | 
            ||
| 43 | from Products.DataGridField import SelectColumn  | 
            ||
| 44 | from zope import event  | 
            ||
| 45 | from zope.event import notify  | 
            ||
| 46 | from zope.i18nmessageid import MessageFactory  | 
            ||
| 47 | from zope.interface import implements  | 
            ||
| 48 | |||
| 49 | from bika.lims.browser.widgets import ReferenceWidget as bReferenceWidget  | 
            ||
| 50 | |||
| 51 | import sys  | 
            ||
| 52 | import transaction  | 
            ||
| 53 | |||
| 54 | _p = MessageFactory(u"plone")  | 
            ||
| 55 | |||
| 56 | OriginalFile = BlobFileField(  | 
            ||
| 57 | 'OriginalFile',  | 
            ||
| 58 | widget=ComputedWidget(  | 
            ||
| 59 | visible=False  | 
            ||
| 60 | ),  | 
            ||
| 61 | )  | 
            ||
| 62 | |||
| 63 | Filename = StringField(  | 
            ||
| 64 | 'Filename',  | 
            ||
| 65 | widget=StringWidget(  | 
            ||
| 66 |         label=_('Original Filename'), | 
            ||
| 67 | visible=True  | 
            ||
| 68 | ),  | 
            ||
| 69 | )  | 
            ||
| 70 | |||
| 71 | NrSamples = StringField(  | 
            ||
| 72 | 'NrSamples',  | 
            ||
| 73 | widget=StringWidget(  | 
            ||
| 74 |         label=_('Number of samples'), | 
            ||
| 75 | visible=True  | 
            ||
| 76 | ),  | 
            ||
| 77 | )  | 
            ||
| 78 | |||
| 79 | ClientName = StringField(  | 
            ||
| 80 | 'ClientName',  | 
            ||
| 81 | searchable=True,  | 
            ||
| 82 | widget=StringWidget(  | 
            ||
| 83 |         label=_("Client Name"), | 
            ||
| 84 | ),  | 
            ||
| 85 | )  | 
            ||
| 86 | |||
| 87 | ClientID = StringField(  | 
            ||
| 88 | 'ClientID',  | 
            ||
| 89 | searchable=True,  | 
            ||
| 90 | widget=StringWidget(  | 
            ||
| 91 |         label=_('Client ID'), | 
            ||
| 92 | ),  | 
            ||
| 93 | )  | 
            ||
| 94 | |||
| 95 | ClientOrderNumber = StringField(  | 
            ||
| 96 | 'ClientOrderNumber',  | 
            ||
| 97 | searchable=True,  | 
            ||
| 98 | widget=StringWidget(  | 
            ||
| 99 |         label=_('Client Order Number'), | 
            ||
| 100 | ),  | 
            ||
| 101 | )  | 
            ||
| 102 | |||
| 103 | ClientReference = StringField(  | 
            ||
| 104 | 'ClientReference',  | 
            ||
| 105 | searchable=True,  | 
            ||
| 106 | widget=StringWidget(  | 
            ||
| 107 |         label=_('Client Reference'), | 
            ||
| 108 | ),  | 
            ||
| 109 | )  | 
            ||
| 110 | |||
| 111 | Contact = ReferenceField(  | 
            ||
| 112 | 'Contact',  | 
            ||
| 113 |     allowed_types=('Contact',), | 
            ||
| 114 | relationship='ARImportContact',  | 
            ||
| 115 | default_method='getContactUIDForUser',  | 
            ||
| 116 | referenceClass=HoldingReference,  | 
            ||
| 117 | vocabulary_display_path_bound=sys.maxint,  | 
            ||
| 118 | widget=ReferenceWidget(  | 
            ||
| 119 |         label=_('Primary Contact'), | 
            ||
| 120 | size=20,  | 
            ||
| 121 | visible=True,  | 
            ||
| 122 |         base_query={'is_active': True}, | 
            ||
| 123 | showOn=True,  | 
            ||
| 124 | popup_width='300px',  | 
            ||
| 125 |         colModel=[{'columnName': 'UID', 'hidden': True}, | 
            ||
| 126 |                   {'columnName': 'Fullname', 'width': '100', | 
            ||
| 127 |                    'label': _('Name')}], | 
            ||
| 128 | ),  | 
            ||
| 129 | )  | 
            ||
| 130 | |||
| 131 | Batch = ReferenceField(  | 
            ||
| 132 | 'Batch',  | 
            ||
| 133 |     allowed_types=('Batch',), | 
            ||
| 134 | relationship='ARImportBatch',  | 
            ||
| 135 | widget=bReferenceWidget(  | 
            ||
| 136 |         label=_('Batch'), | 
            ||
| 137 | visible=True,  | 
            ||
| 138 | catalog_name='bika_catalog',  | 
            ||
| 139 |         base_query={'review_state': 'open'}, | 
            ||
| 140 | showOn=True,  | 
            ||
| 141 | ),  | 
            ||
| 142 | )  | 
            ||
| 143 | |||
| 144 | CCContacts = DataGridField(  | 
            ||
| 145 | 'CCContacts',  | 
            ||
| 146 | allow_insert=False,  | 
            ||
| 147 | allow_delete=False,  | 
            ||
| 148 | allow_reorder=False,  | 
            ||
| 149 | allow_empty_rows=False,  | 
            ||
| 150 |     columns=('CCNamesReport', | 
            ||
| 151 | 'CCEmailsReport',  | 
            ||
| 152 | 'CCNamesInvoice',  | 
            ||
| 153 | 'CCEmailsInvoice'),  | 
            ||
| 154 |     default=[{'CCNamesReport': [], | 
            ||
| 155 | 'CCEmailsReport': [],  | 
            ||
| 156 | 'CCNamesInvoice': [],  | 
            ||
| 157 | 'CCEmailsInvoice': []  | 
            ||
| 158 | }],  | 
            ||
| 159 | widget=DataGridWidget(  | 
            ||
| 160 |         columns={ | 
            ||
| 161 |             'CCNamesReport': LinesColumn('Report CC Contacts'), | 
            ||
| 162 |             'CCEmailsReport': LinesColumn('Report CC Emails'), | 
            ||
| 163 |             'CCNamesInvoice': LinesColumn('Invoice CC Contacts'), | 
            ||
| 164 |             'CCEmailsInvoice': LinesColumn('Invoice CC Emails') | 
            ||
| 165 | }  | 
            ||
| 166 | )  | 
            ||
| 167 | )  | 
            ||
| 168 | |||
| 169 | SampleData = DataGridField(  | 
            ||
| 170 | 'SampleData',  | 
            ||
| 171 | allow_insert=True,  | 
            ||
| 172 | allow_delete=True,  | 
            ||
| 173 | allow_reorder=False,  | 
            ||
| 174 | allow_empty_rows=False,  | 
            ||
| 175 | allow_oddeven=True,  | 
            ||
| 176 |     columns=('ClientSampleID', | 
            ||
| 177 | 'SamplingDate',  | 
            ||
| 178 | 'DateSampled',  | 
            ||
| 179 | 'SamplePoint',  | 
            ||
| 180 | 'SampleMatrix',  | 
            ||
| 181 | 'SampleType', # not a schema field!  | 
            ||
| 182 | 'ContainerType', # not a schema field!  | 
            ||
| 183 | 'Analyses', # not a schema field!  | 
            ||
| 184 | 'Profiles' # not a schema field!  | 
            ||
| 185 | ),  | 
            ||
| 186 | widget=DataGridWidget(  | 
            ||
| 187 |         label=_('Samples'), | 
            ||
| 188 |         columns={ | 
            ||
| 189 |             'ClientSampleID': Column('Sample ID'), | 
            ||
| 190 |             'SamplingDate': DateColumn('Sampling Date'), | 
            ||
| 191 |             'DateSampled': DateColumn('Date Sampled'), | 
            ||
| 192 | 'SamplePoint': SelectColumn(  | 
            ||
| 193 | 'Sample Point', vocabulary='Vocabulary_SamplePoint'),  | 
            ||
| 194 | 'SampleMatrix': SelectColumn(  | 
            ||
| 195 | 'Sample Matrix', vocabulary='Vocabulary_SampleMatrix'),  | 
            ||
| 196 | 'SampleType': SelectColumn(  | 
            ||
| 197 | 'Sample Type', vocabulary='Vocabulary_SampleType'),  | 
            ||
| 198 | 'ContainerType': SelectColumn(  | 
            ||
| 199 | 'Container', vocabulary='Vocabulary_ContainerType'),  | 
            ||
| 200 |             'Analyses': LinesColumn('Analyses'), | 
            ||
| 201 |             'Profiles': LinesColumn('Profiles'), | 
            ||
| 202 | }  | 
            ||
| 203 | )  | 
            ||
| 204 | )  | 
            ||
| 205 | |||
| 206 | Errors = LinesField(  | 
            ||
| 207 | 'Errors',  | 
            ||
| 208 | widget=LinesWidget(  | 
            ||
| 209 |         label=_('Errors'), | 
            ||
| 210 | rows=10,  | 
            ||
| 211 | )  | 
            ||
| 212 | )  | 
            ||
| 213 | |||
| 214 | schema = BikaSchema.copy() + Schema((  | 
            ||
| 215 | OriginalFile,  | 
            ||
| 216 | Filename,  | 
            ||
| 217 | NrSamples,  | 
            ||
| 218 | ClientName,  | 
            ||
| 219 | ClientID,  | 
            ||
| 220 | ClientOrderNumber,  | 
            ||
| 221 | ClientReference,  | 
            ||
| 222 | Contact,  | 
            ||
| 223 | CCContacts,  | 
            ||
| 224 | Batch,  | 
            ||
| 225 | SampleData,  | 
            ||
| 226 | Errors,  | 
            ||
| 227 | ))  | 
            ||
| 228 | |||
| 229 | schema['title'].validators = ()  | 
            ||
| 230 | # Update the validation layer after change the validator in runtime  | 
            ||
| 231 | schema['title']._validationLayer()  | 
            ||
| 232 | |||
| 233 | |||
| 234 | class ARImport(BaseFolder):  | 
            ||
| 235 | security = ClassSecurityInfo()  | 
            ||
| 236 | schema = schema  | 
            ||
| 237 | displayContentsTab = False  | 
            ||
| 238 | implements(IARImport)  | 
            ||
| 239 | |||
| 240 | _at_rename_after_creation = True  | 
            ||
| 241 | |||
| 242 | def _renameAfterCreation(self, check_auto_id=False):  | 
            ||
| 243 | renameAfterCreation(self)  | 
            ||
| 244 | |||
| 245 | def guard_validate_transition(self):  | 
            ||
| 246 | """We may only attempt validation if file data has been uploaded.  | 
            ||
| 247 | """  | 
            ||
| 248 | data = self.getOriginalFile()  | 
            ||
| 249 | if data and len(data):  | 
            ||
| 250 | return True  | 
            ||
| 251 | |||
| 252 | # TODO Workflow - ARImport - Remove  | 
            ||
| 253 | def workflow_before_validate(self):  | 
            ||
| 254 | """This function transposes values from the provided file into the  | 
            ||
| 255 | ARImport object's fields, and checks for invalid values.  | 
            ||
| 256 | |||
| 257 | If errors are found:  | 
            ||
| 258 | - Validation transition is aborted.  | 
            ||
| 259 | - Errors are stored on object and displayed to user.  | 
            ||
| 260 | |||
| 261 | """  | 
            ||
| 262 | # Re-set the errors on this ARImport each time validation is attempted.  | 
            ||
| 263 | # When errors are detected they are immediately appended to this field.  | 
            ||
| 264 | self.setErrors([])  | 
            ||
| 265 | |||
| 266 | self.validate_headers()  | 
            ||
| 267 | self.validate_samples()  | 
            ||
| 268 | |||
| 269 | if self.getErrors():  | 
            ||
| 270 |             addStatusMessage(self.REQUEST, _p('Validation errors.'), 'error') | 
            ||
| 271 | transaction.commit()  | 
            ||
| 272 | self.REQUEST.response.write(  | 
            ||
| 273 | '<script>document.location.href="%s/edit"</script>' % (  | 
            ||
| 274 | self.absolute_url()))  | 
            ||
| 275 | self.REQUEST.response.write(  | 
            ||
| 276 | '<script>document.location.href="%s/view"</script>' % (  | 
            ||
| 277 | self.absolute_url()))  | 
            ||
| 278 | |||
| 279 | def at_post_edit_script(self):  | 
            ||
| 280 | workflow = getToolByName(self, 'portal_workflow')  | 
            ||
| 281 | trans_ids = [t['id'] for t in workflow.getTransitionsFor(self)]  | 
            ||
| 282 | if 'validate' in trans_ids:  | 
            ||
| 283 | workflow.doActionFor(self, 'validate')  | 
            ||
| 284 | |||
| 285 | def workflow_script_import(self):  | 
            ||
| 286 | """Create objects from valid ARImport  | 
            ||
| 287 | """  | 
            ||
| 288 | bsc = getToolByName(self, 'bika_setup_catalog')  | 
            ||
| 289 | client = self.aq_parent  | 
            ||
| 290 | |||
| 291 |         title = _('Submitting Sample Import') | 
            ||
| 292 |         description = _('Creating and initialising objects') | 
            ||
| 293 | bar = ProgressBar(self, self.REQUEST, title, description)  | 
            ||
| 294 | notify(InitialiseProgressBar(bar))  | 
            ||
| 295 | |||
| 296 | profiles = [x.getObject() for x in bsc(portal_type='AnalysisProfile')]  | 
            ||
| 297 | |||
| 298 | gridrows = self.schema['SampleData'].get(self)  | 
            ||
| 299 | row_cnt = 0  | 
            ||
| 300 | for therow in gridrows:  | 
            ||
| 301 | row = deepcopy(therow)  | 
            ||
| 302 | row_cnt += 1  | 
            ||
| 303 | |||
| 304 | # Profiles are titles, profile keys, or UIDS: convert them to UIDs.  | 
            ||
| 305 | newprofiles = []  | 
            ||
| 306 | for title in row['Profiles']:  | 
            ||
| 307 | objects = [x for x in profiles  | 
            ||
| 308 | if title in (x.getProfileKey(), x.UID(), x.Title())]  | 
            ||
| 309 | for obj in objects:  | 
            ||
| 310 | newprofiles.append(obj.UID())  | 
            ||
| 311 | row['Profiles'] = newprofiles  | 
            ||
| 312 | |||
| 313 | # Same for analyses  | 
            ||
| 314 | newanalyses = set(self.get_row_services(row) +  | 
            ||
| 315 | self.get_row_profile_services(row))  | 
            ||
| 316 | # get batch  | 
            ||
| 317 | batch = self.schema['Batch'].get(self)  | 
            ||
| 318 | if batch:  | 
            ||
| 319 | row['Batch'] = batch.UID()  | 
            ||
| 320 | # Add AR fields from schema into this row's data  | 
            ||
| 321 | row['ClientReference'] = self.getClientReference()  | 
            ||
| 322 | row['ClientOrderNumber'] = self.getClientOrderNumber()  | 
            ||
| 323 | contact_uid =\  | 
            ||
| 324 | self.getContact().UID() if self.getContact() else None  | 
            ||
| 325 | row['Contact'] = contact_uid  | 
            ||
| 326 | # Creating analysis request from gathered data  | 
            ||
| 327 | ar = create_analysisrequest(  | 
            ||
| 328 | client,  | 
            ||
| 329 | self.REQUEST,  | 
            ||
| 330 | row,  | 
            ||
| 331 | analyses=list(newanalyses),)  | 
            ||
| 332 | |||
| 333 | # progress marker update  | 
            ||
| 334 | progress_index = float(row_cnt) / len(gridrows) * 100  | 
            ||
| 335 | progress = ProgressState(self.REQUEST, progress_index)  | 
            ||
| 336 | notify(UpdateProgressEvent(progress))  | 
            ||
| 337 | |||
| 338 | # document has been written to, and redirect() fails here  | 
            ||
| 339 | self.REQUEST.response.write(  | 
            ||
| 340 | '<script>document.location.href="%s"</script>' % (  | 
            ||
| 341 | self.absolute_url()))  | 
            ||
| 342 | |||
| 343 | def get_header_values(self):  | 
            ||
| 344 | """Scrape the "Header" values from the original input file  | 
            ||
| 345 | """  | 
            ||
| 346 | lines = self.getOriginalFile().data.splitlines()  | 
            ||
| 347 | reader = csv.reader(lines)  | 
            ||
| 348 | header_fields = header_data = []  | 
            ||
| 349 | for row in reader:  | 
            ||
| 350 | if not any(row):  | 
            ||
| 351 | continue  | 
            ||
| 352 | if row[0].strip().lower() == 'header':  | 
            ||
| 353 | header_fields = [x.strip() for x in row][1:]  | 
            ||
| 354 | continue  | 
            ||
| 355 | if row[0].strip().lower() == 'header data':  | 
            ||
| 356 | header_data = [x.strip() for x in row][1:]  | 
            ||
| 357 | break  | 
            ||
| 358 | if not (header_data or header_fields):  | 
            ||
| 359 | return None  | 
            ||
| 360 | if not (header_data and header_fields):  | 
            ||
| 361 |             self.error("File is missing header row or header data") | 
            ||
| 362 | return None  | 
            ||
| 363 | # inject us out of here  | 
            ||
| 364 | values = dict(zip(header_fields, header_data))  | 
            ||
| 365 | # blank cell from sheet will probably make it in here:  | 
            ||
| 366 | if '' in values:  | 
            ||
| 367 | del (values[''])  | 
            ||
| 368 | return values  | 
            ||
| 369 | |||
| 370 | def save_header_data(self):  | 
            ||
| 371 | """Save values from the file's header row into their schema fields.  | 
            ||
| 372 | """  | 
            ||
| 373 | client = self.aq_parent  | 
            ||
| 374 | |||
| 375 | headers = self.get_header_values()  | 
            ||
| 376 | if not headers:  | 
            ||
| 377 | return False  | 
            ||
| 378 | |||
| 379 | # Plain header fields that can be set into plain schema fields:  | 
            ||
| 380 | for h, f in [  | 
            ||
| 381 |             ('File name', 'Filename'), | 
            ||
| 382 |             ('No of Samples', 'NrSamples'), | 
            ||
| 383 |             ('Client name', 'ClientName'), | 
            ||
| 384 |             ('Client ID', 'ClientID'), | 
            ||
| 385 |             ('Client Order Number', 'ClientOrderNumber'), | 
            ||
| 386 |             ('Client Reference', 'ClientReference') | 
            ||
| 387 | ]:  | 
            ||
| 388 | v = headers.get(h, None)  | 
            ||
| 389 | if v:  | 
            ||
| 390 | field = self.schema[f]  | 
            ||
| 391 | field.set(self, v)  | 
            ||
| 392 | del (headers[h])  | 
            ||
| 393 | |||
| 394 | # Primary Contact  | 
            ||
| 395 |         v = headers.get('Contact', None) | 
            ||
| 396 |         contacts = [x for x in client.objectValues('Contact')] | 
            ||
| 397 | contact = [c for c in contacts if c.Title() == v]  | 
            ||
| 398 | if contact:  | 
            ||
| 399 | self.schema['Contact'].set(self, contact)  | 
            ||
| 400 | else:  | 
            ||
| 401 |             self.error("Specified contact '%s' does not exist; using '%s'"% | 
            ||
| 402 | (v, contacts[0].Title()))  | 
            ||
| 403 | self.schema['Contact'].set(self, contacts[0])  | 
            ||
| 404 | del (headers['Contact'])  | 
            ||
| 405 | |||
| 406 | # CCContacts  | 
            ||
| 407 |         field_value = { | 
            ||
| 408 | 'CCNamesReport': '',  | 
            ||
| 409 | 'CCEmailsReport': '',  | 
            ||
| 410 | 'CCNamesInvoice': '',  | 
            ||
| 411 | 'CCEmailsInvoice': ''  | 
            ||
| 412 | }  | 
            ||
| 413 | for h, f in [  | 
            ||
| 414 | # csv header name DataGrid Column ID  | 
            ||
| 415 |             ('CC Names - Report', 'CCNamesReport'), | 
            ||
| 416 |             ('CC Emails - Report', 'CCEmailsReport'), | 
            ||
| 417 |             ('CC Names - Invoice', 'CCNamesInvoice'), | 
            ||
| 418 |             ('CC Emails - Invoice', 'CCEmailsInvoice'), | 
            ||
| 419 | ]:  | 
            ||
| 420 | if h in headers:  | 
            ||
| 421 |                 values = [x.strip() for x in headers.get(h, '').split(",")] | 
            ||
| 422 | field_value[f] = values if values else ''  | 
            ||
| 423 | del (headers[h])  | 
            ||
| 424 | self.schema['CCContacts'].set(self, [field_value])  | 
            ||
| 425 | |||
| 426 | if headers:  | 
            ||
| 427 | unexpected = ','.join(headers.keys())  | 
            ||
| 428 |             self.error("Unexpected header fields: %s" % unexpected) | 
            ||
| 429 | |||
| 430 | def get_sample_values(self):  | 
            ||
| 431 | """Read the rows specifying Samples and return a dictionary with  | 
            ||
| 432 | related data.  | 
            ||
| 433 | |||
| 434 | keys are:  | 
            ||
| 435 | headers - row with "Samples" in column 0. These headers are  | 
            ||
| 436 | used as dictionary keys in the rows below.  | 
            ||
| 437 | prices - Row with "Analysis Price" in column 0.  | 
            ||
| 438 | total_analyses - Row with "Total analyses" in colmn 0  | 
            ||
| 439 | price_totals - Row with "Total price excl Tax" in column 0  | 
            ||
| 440 | samples - All other sample rows.  | 
            ||
| 441 | |||
| 442 | """  | 
            ||
| 443 |         res = {'samples': []} | 
            ||
| 444 | lines = self.getOriginalFile().data.splitlines()  | 
            ||
| 445 | reader = csv.reader(lines)  | 
            ||
| 446 | next_rows_are_sample_rows = False  | 
            ||
| 447 | for row in reader:  | 
            ||
| 448 | if not any(row):  | 
            ||
| 449 | continue  | 
            ||
| 450 | if next_rows_are_sample_rows:  | 
            ||
| 451 | vals = [x.strip() for x in row]  | 
            ||
| 452 | if not any(vals):  | 
            ||
| 453 | continue  | 
            ||
| 454 | res['samples'].append(zip(res['headers'], vals))  | 
            ||
| 455 | elif row[0].strip().lower() == 'samples':  | 
            ||
| 456 | res['headers'] = [x.strip() for x in row]  | 
            ||
| 457 | elif row[0].strip().lower() == 'analysis price':  | 
            ||
| 458 | res['prices'] = \  | 
            ||
| 459 | zip(res['headers'], [x.strip() for x in row])  | 
            ||
| 460 | elif row[0].strip().lower() == 'total analyses':  | 
            ||
| 461 | res['total_analyses'] = \  | 
            ||
| 462 | zip(res['headers'], [x.strip() for x in row])  | 
            ||
| 463 | elif row[0].strip().lower() == 'total price excl tax':  | 
            ||
| 464 | res['price_totals'] = \  | 
            ||
| 465 | zip(res['headers'], [x.strip() for x in row])  | 
            ||
| 466 | next_rows_are_sample_rows = True  | 
            ||
| 467 | return res  | 
            ||
| 468 | |||
| 469 | def save_sample_data(self):  | 
            ||
| 470 | """Save values from the file's header row into the DataGrid columns  | 
            ||
| 471 | after doing some very basic validation  | 
            ||
| 472 | """  | 
            ||
| 473 | bsc = getToolByName(self, 'bika_setup_catalog')  | 
            ||
| 474 |         keywords = self.bika_setup_catalog.uniqueValuesFor('getKeyword') | 
            ||
| 475 | profiles = []  | 
            ||
| 476 | for p in bsc(portal_type='AnalysisProfile'):  | 
            ||
| 477 | p = p.getObject()  | 
            ||
| 478 | profiles.append(p.Title())  | 
            ||
| 479 | profiles.append(p.getProfileKey())  | 
            ||
| 480 | |||
| 481 | sample_data = self.get_sample_values()  | 
            ||
| 482 | if not sample_data:  | 
            ||
| 483 | return False  | 
            ||
| 484 | |||
| 485 | # columns that we expect, but do not find, are listed here.  | 
            ||
| 486 | # we report on them only once, after looping through sample rows.  | 
            ||
| 487 | missing = set()  | 
            ||
| 488 | |||
| 489 | # This contains all sample header rows that were not handled  | 
            ||
| 490 | # by this code  | 
            ||
| 491 | unexpected = set()  | 
            ||
| 492 | |||
| 493 | # Save other errors here instead of sticking them directly into  | 
            ||
| 494 | # the field, so that they show up after MISSING and before EXPECTED  | 
            ||
| 495 | errors = []  | 
            ||
| 496 | |||
| 497 | # This will be the new sample-data field value, when we are done.  | 
            ||
| 498 | grid_rows = []  | 
            ||
| 499 | |||
| 500 | row_nr = 0  | 
            ||
| 501 | for row in sample_data['samples']:  | 
            ||
| 502 | row = dict(row)  | 
            ||
| 503 | row_nr += 1  | 
            ||
| 504 | |||
| 505 | # sid is just for referring the user back to row X in their  | 
            ||
| 506 | # in put spreadsheet  | 
            ||
| 507 |             gridrow = {'sid': row['Samples']} | 
            ||
| 508 | del (row['Samples'])  | 
            ||
| 509 | |||
| 510 | # We'll use this later to verify the number against selections  | 
            ||
| 511 | if 'Total number of Analyses or Profiles' in row:  | 
            ||
| 512 | nr_an = row['Total number of Analyses or Profiles']  | 
            ||
| 513 | del (row['Total number of Analyses or Profiles'])  | 
            ||
| 514 | else:  | 
            ||
| 515 | nr_an = 0  | 
            ||
| 516 | try:  | 
            ||
| 517 | nr_an = int(nr_an)  | 
            ||
| 518 | except ValueError:  | 
            ||
| 519 | nr_an = 0  | 
            ||
| 520 | |||
| 521 | # TODO this is ignored and is probably meant to serve some purpose.  | 
            ||
| 522 | del (row['Price excl Tax'])  | 
            ||
| 523 | |||
| 524 | # ContainerType - not part of sample or AR schema  | 
            ||
| 525 | if 'ContainerType' in row:  | 
            ||
| 526 | title = row['ContainerType']  | 
            ||
| 527 | if title:  | 
            ||
| 528 |                     obj = self.lookup(('ContainerType',), | 
            ||
| 529 | Title=row['ContainerType'])  | 
            ||
| 530 | if obj:  | 
            ||
| 531 | gridrow['ContainerType'] = obj[0].UID  | 
            ||
| 532 | del (row['ContainerType'])  | 
            ||
| 533 | |||
| 534 | if 'SampleMatrix' in row:  | 
            ||
| 535 | # SampleMatrix - not part of sample or AR schema  | 
            ||
| 536 | title = row['SampleMatrix']  | 
            ||
| 537 | if title:  | 
            ||
| 538 |                     obj = self.lookup(('SampleMatrix',), | 
            ||
| 539 | Title=row['SampleMatrix'])  | 
            ||
| 540 | if obj:  | 
            ||
| 541 | gridrow['SampleMatrix'] = obj[0].UID  | 
            ||
| 542 | del (row['SampleMatrix'])  | 
            ||
| 543 | |||
| 544 | # match against sample schema  | 
            ||
| 545 | for k, v in row.items():  | 
            ||
| 546 | if k in ['Analyses', 'Profiles']:  | 
            ||
| 547 | continue  | 
            ||
| 548 | if k in sample_schema:  | 
            ||
| 549 | del (row[k])  | 
            ||
| 550 | if v:  | 
            ||
| 551 | try:  | 
            ||
| 552 | value = self.munge_field_value(  | 
            ||
| 553 | sample_schema, row_nr, k, v)  | 
            ||
| 554 | gridrow[k] = value  | 
            ||
| 555 | except ValueError as e:  | 
            ||
| 556 | errors.append(e.message)  | 
            ||
| 557 | |||
| 558 | # match against ar schema  | 
            ||
| 559 | for k, v in row.items():  | 
            ||
| 560 | if k in ['Analyses', 'Profiles']:  | 
            ||
| 561 | continue  | 
            ||
| 562 | if k in ar_schema:  | 
            ||
| 563 | del (row[k])  | 
            ||
| 564 | if v:  | 
            ||
| 565 | try:  | 
            ||
| 566 | value = self.munge_field_value(  | 
            ||
| 567 | ar_schema, row_nr, k, v)  | 
            ||
| 568 | gridrow[k] = value  | 
            ||
| 569 | except ValueError as e:  | 
            ||
| 570 | errors.append(e.message)  | 
            ||
| 571 | |||
| 572 | # Count and remove Keywords and Profiles from the list  | 
            ||
| 573 | gridrow['Analyses'] = []  | 
            ||
| 574 | for k, v in row.items():  | 
            ||
| 575 | if k in keywords:  | 
            ||
| 576 | del (row[k])  | 
            ||
| 577 |                     if str(v).strip().lower() not in ('', '0', 'false'): | 
            ||
| 578 | gridrow['Analyses'].append(k)  | 
            ||
| 579 | gridrow['Profiles'] = []  | 
            ||
| 580 | for k, v in row.items():  | 
            ||
| 581 | if k in profiles:  | 
            ||
| 582 | del (row[k])  | 
            ||
| 583 |                     if str(v).strip().lower() not in ('', '0', 'false'): | 
            ||
| 584 | gridrow['Profiles'].append(k)  | 
            ||
| 585 | if len(gridrow['Analyses']) + len(gridrow['Profiles']) != nr_an:  | 
            ||
| 586 | errors.append(  | 
            ||
| 587 | "Row %s: Number of analyses does not match provided value" %  | 
            ||
| 588 | row_nr)  | 
            ||
| 589 | |||
| 590 | grid_rows.append(gridrow)  | 
            ||
| 591 | |||
| 592 | self.setSampleData(grid_rows)  | 
            ||
| 593 | |||
| 594 | if missing:  | 
            ||
| 595 |             self.error("SAMPLES: Missing expected fields: %s" % | 
            ||
| 596 | ','.join(missing))  | 
            ||
| 597 | |||
| 598 | for thing in errors:  | 
            ||
| 599 | self.error(thing)  | 
            ||
| 600 | |||
| 601 | if unexpected:  | 
            ||
| 602 |             self.error("Unexpected header fields: %s" % | 
            ||
| 603 | ','.join(unexpected))  | 
            ||
| 604 | |||
| 605 | def get_batch_header_values(self):  | 
            ||
| 606 | """Scrape the "Batch Header" values from the original input file  | 
            ||
| 607 | """  | 
            ||
| 608 | lines = self.getOriginalFile().data.splitlines()  | 
            ||
| 609 | reader = csv.reader(lines)  | 
            ||
| 610 | batch_headers = batch_data = []  | 
            ||
| 611 | for row in reader:  | 
            ||
| 612 | if not any(row):  | 
            ||
| 613 | continue  | 
            ||
| 614 | if row[0].strip().lower() == 'batch header':  | 
            ||
| 615 | batch_headers = [x.strip() for x in row][1:]  | 
            ||
| 616 | continue  | 
            ||
| 617 | if row[0].strip().lower() == 'batch data':  | 
            ||
| 618 | batch_data = [x.strip() for x in row][1:]  | 
            ||
| 619 | break  | 
            ||
| 620 | if not (batch_data or batch_headers):  | 
            ||
| 621 | return None  | 
            ||
| 622 | if not (batch_data and batch_headers):  | 
            ||
| 623 |             self.error("Missing batch headers or data") | 
            ||
| 624 | return None  | 
            ||
| 625 | # Inject us out of here  | 
            ||
| 626 | values = dict(zip(batch_headers, batch_data))  | 
            ||
| 627 | return values  | 
            ||
| 628 | |||
| 629 | def create_or_reference_batch(self):  | 
            ||
| 630 | """Save reference to batch, if existing batch specified  | 
            ||
| 631 | Create new batch, if possible with specified values  | 
            ||
| 632 | """  | 
            ||
| 633 | client = self.aq_parent  | 
            ||
| 634 | batch_headers = self.get_batch_header_values()  | 
            ||
| 635 | if not batch_headers:  | 
            ||
| 636 | return False  | 
            ||
| 637 | # if the Batch's Title is specified and exists, no further  | 
            ||
| 638 | # action is required. We will just set the Batch field to  | 
            ||
| 639 | # use the existing object.  | 
            ||
| 640 |         batch_title = batch_headers.get('title', False) | 
            ||
| 641 | if batch_title:  | 
            ||
| 642 |             existing_batch = [x for x in client.objectValues('Batch') | 
            ||
| 643 | if x.title == batch_title]  | 
            ||
| 644 | if existing_batch:  | 
            ||
| 645 | self.setBatch(existing_batch[0])  | 
            ||
| 646 | return existing_batch[0]  | 
            ||
| 647 | # If the batch title is specified but does not exist,  | 
            ||
| 648 | # we will attempt to create the bach now.  | 
            ||
| 649 | if 'title' in batch_headers:  | 
            ||
| 650 | if 'id' in batch_headers:  | 
            ||
| 651 | del (batch_headers['id'])  | 
            ||
| 652 | if '' in batch_headers:  | 
            ||
| 653 | del (batch_headers[''])  | 
            ||
| 654 |             batch = _createObjectByType('Batch', client, tmpID()) | 
            ||
| 655 | batch.processForm()  | 
            ||
| 656 | batch.edit(**batch_headers)  | 
            ||
| 657 | self.setBatch(batch)  | 
            ||
| 658 | |||
| 659 | def munge_field_value(self, schema, row_nr, fieldname, value):  | 
            ||
| 660 | """Convert a spreadsheet value into a field value that fits in  | 
            ||
| 661 | the corresponding schema field.  | 
            ||
| 662 | - boolean: All values are true except '', 'false', or '0'.  | 
            ||
| 663 | - reference: The title of an object in field.allowed_types;  | 
            ||
| 664 | returns a UID or list of UIDs  | 
            ||
| 665 | - datetime: returns a string value from ulocalized_time  | 
            ||
| 666 | |||
| 667 | Tho this is only used during "Saving" of csv data into schema fields,  | 
            ||
| 668 | it will flag 'validation' errors, as this is the only chance we will  | 
            ||
| 669 | get to complain about these field values.  | 
            ||
| 670 | |||
| 671 | """  | 
            ||
| 672 | field = schema[fieldname]  | 
            ||
| 673 | if field.type == 'boolean':  | 
            ||
| 674 | value = str(value).strip().lower()  | 
            ||
| 675 | value = '' if value in ['0', 'no', 'false', 'none'] else '1'  | 
            ||
| 676 | return value  | 
            ||
| 677 | View Code Duplication | if field.type == 'reference':  | 
            |
| 678 | value = str(value).strip()  | 
            ||
| 679 | brains = self.lookup(field.allowed_types, Title=value)  | 
            ||
| 680 | if not brains:  | 
            ||
| 681 | brains = self.lookup(field.allowed_types, UID=value)  | 
            ||
| 682 | if not brains:  | 
            ||
| 683 |                 raise ValueError('Row %s: value is invalid (%s=%s)' % ( | 
            ||
| 684 | row_nr, fieldname, value))  | 
            ||
| 685 | if field.multiValued:  | 
            ||
| 686 | return [b.UID for b in brains] if brains else []  | 
            ||
| 687 | else:  | 
            ||
| 688 | return brains[0].UID if brains else None  | 
            ||
| 689 | if field.type == 'datetime':  | 
            ||
| 690 | try:  | 
            ||
| 691 | value = DateTime(value)  | 
            ||
| 692 | return ulocalized_time(  | 
            ||
| 693 | value, long_format=True, time_only=False, context=self)  | 
            ||
| 694 | except:  | 
            ||
| 695 |                 raise ValueError('Row %s: value is invalid (%s=%s)' % ( | 
            ||
| 696 | row_nr, fieldname, value))  | 
            ||
| 697 | return str(value)  | 
            ||
| 698 | |||
| 699 | def validate_headers(self):  | 
            ||
| 700 | """Validate headers fields from schema  | 
            ||
| 701 | """  | 
            ||
| 702 | |||
| 703 | pc = getToolByName(self, 'portal_catalog')  | 
            ||
| 704 | pu = getToolByName(self, "plone_utils")  | 
            ||
| 705 | |||
| 706 | client = self.aq_parent  | 
            ||
| 707 | |||
| 708 | # Verify Client Name  | 
            ||
| 709 | if self.getClientName() != client.Title():  | 
            ||
| 710 |             self.error("%s: value is invalid (%s)." % ( | 
            ||
| 711 | 'Client name', self.getClientName()))  | 
            ||
| 712 | |||
| 713 | # Verify Client ID  | 
            ||
| 714 | if self.getClientID() != client.getClientID():  | 
            ||
| 715 |             self.error("%s: value is invalid (%s)." % ( | 
            ||
| 716 | 'Client ID', self.getClientID()))  | 
            ||
| 717 | |||
| 718 | existing_arimports = pc(portal_type='ARImport',  | 
            ||
| 719 | review_state=['valid', 'imported'])  | 
            ||
| 720 | # Verify Client Order Number  | 
            ||
| 721 | for arimport in existing_arimports:  | 
            ||
| 722 | if arimport.UID == self.UID() \  | 
            ||
| 723 | or not arimport.getClientOrderNumber():  | 
            ||
| 724 | continue  | 
            ||
| 725 | arimport = arimport.getObject()  | 
            ||
| 726 | |||
| 727 | if arimport.getClientOrderNumber() == self.getClientOrderNumber():  | 
            ||
| 728 |                 self.error('%s: already used by existing ARImport.' % | 
            ||
| 729 | 'ClientOrderNumber')  | 
            ||
| 730 | break  | 
            ||
| 731 | |||
| 732 | # Verify Client Reference  | 
            ||
| 733 | for arimport in existing_arimports:  | 
            ||
| 734 | if arimport.UID == self.UID() \  | 
            ||
| 735 | or not arimport.getClientReference():  | 
            ||
| 736 | continue  | 
            ||
| 737 | arimport = arimport.getObject()  | 
            ||
| 738 | if arimport.getClientReference() == self.getClientReference():  | 
            ||
| 739 |                 self.error('%s: already used by existing ARImport.' % | 
            ||
| 740 | 'ClientReference')  | 
            ||
| 741 | break  | 
            ||
| 742 | |||
| 743 | # getCCContacts has no value if object is not complete (eg during test)  | 
            ||
| 744 | if self.getCCContacts():  | 
            ||
| 745 | cc_contacts = self.getCCContacts()[0]  | 
            ||
| 746 |             contacts = [x for x in client.objectValues('Contact')] | 
            ||
| 747 | contact_names = [c.Title() for c in contacts]  | 
            ||
| 748 | # validate Contact existence in this Client  | 
            ||
| 749 | for k in ['CCNamesReport', 'CCNamesInvoice']:  | 
            ||
| 750 | for val in cc_contacts[k]:  | 
            ||
| 751 | if val and val not in contact_names:  | 
            ||
| 752 |                         self.error('%s: value is invalid (%s)' % (k, val)) | 
            ||
| 753 | else:  | 
            ||
| 754 |             cc_contacts = {'CCNamesReport': [], | 
            ||
| 755 | 'CCEmailsReport': [],  | 
            ||
| 756 | 'CCNamesInvoice': [],  | 
            ||
| 757 | 'CCEmailsInvoice': []  | 
            ||
| 758 | }  | 
            ||
| 759 | # validate Contact existence in this Client  | 
            ||
| 760 | for k in ['CCEmailsReport', 'CCEmailsInvoice']:  | 
            ||
| 761 | for val in cc_contacts.get(k, []):  | 
            ||
| 762 | if val and not pu.validateSingleNormalizedEmailAddress(val):  | 
            ||
| 763 |                         self.error('%s: value is invalid (%s)' % (k, val)) | 
            ||
| 764 | |||
| 765 | def validate_samples(self):  | 
            ||
| 766 | """Scan through the SampleData values and make sure  | 
            ||
| 767 | that each one is correct  | 
            ||
| 768 | """  | 
            ||
| 769 | |||
| 770 | bsc = getToolByName(self, 'bika_setup_catalog')  | 
            ||
| 771 |         keywords = bsc.uniqueValuesFor('getKeyword') | 
            ||
| 772 | profiles = []  | 
            ||
| 773 | for p in bsc(portal_type='AnalysisProfile'):  | 
            ||
| 774 | p = p.getObject()  | 
            ||
| 775 | profiles.append(p.Title())  | 
            ||
| 776 | profiles.append(p.getProfileKey())  | 
            ||
| 777 | |||
| 778 | row_nr = 0  | 
            ||
| 779 | for gridrow in self.getSampleData():  | 
            ||
| 780 | row_nr += 1  | 
            ||
| 781 | |||
| 782 | # validate against sample and ar schemas  | 
            ||
| 783 | for k, v in gridrow.items():  | 
            ||
| 784 | if k in ['Analysis', 'Profiles']:  | 
            ||
| 785 | break  | 
            ||
| 786 | if k in sample_schema:  | 
            ||
| 787 | try:  | 
            ||
| 788 | self.validate_against_schema(  | 
            ||
| 789 | sample_schema, row_nr, k, v)  | 
            ||
| 790 | continue  | 
            ||
| 791 | except ValueError as e:  | 
            ||
| 792 | self.error(e.message)  | 
            ||
| 793 | break  | 
            ||
| 794 | if k in ar_schema:  | 
            ||
| 795 | try:  | 
            ||
| 796 | self.validate_against_schema(  | 
            ||
| 797 | ar_schema, row_nr, k, v)  | 
            ||
| 798 | except ValueError as e:  | 
            ||
| 799 | self.error(e.message)  | 
            ||
| 800 | |||
| 801 | an_cnt = 0  | 
            ||
| 802 | for v in gridrow['Analyses']:  | 
            ||
| 803 | if v and v not in keywords:  | 
            ||
| 804 |                     self.error("Row %s: value is invalid (%s=%s)" % | 
            ||
| 805 |                                ('Analysis keyword', row_nr, v)) | 
            ||
| 806 | else:  | 
            ||
| 807 | an_cnt += 1  | 
            ||
| 808 | for v in gridrow['Profiles']:  | 
            ||
| 809 | if v and v not in profiles:  | 
            ||
| 810 |                     self.error("Row %s: value is invalid (%s=%s)" % | 
            ||
| 811 |                                ('Profile Title', row_nr, v)) | 
            ||
| 812 | else:  | 
            ||
| 813 | an_cnt += 1  | 
            ||
| 814 | if not an_cnt:  | 
            ||
| 815 |                 self.error("Row %s: No valid analyses or profiles" % row_nr) | 
            ||
| 816 | |||
| 817 | def validate_against_schema(self, schema, row_nr, fieldname, value):  | 
            ||
| 818 | """  | 
            ||
| 819 | """  | 
            ||
| 820 | field = schema[fieldname]  | 
            ||
| 821 | if field.type == 'boolean':  | 
            ||
| 822 | value = str(value).strip().lower()  | 
            ||
| 823 | return value  | 
            ||
| 824 | View Code Duplication | if field.type == 'reference':  | 
            |
| 825 | value = str(value).strip()  | 
            ||
| 826 | if field.required and not value:  | 
            ||
| 827 |                 raise ValueError("Row %s: %s field requires a value" % ( | 
            ||
| 828 | row_nr, fieldname))  | 
            ||
| 829 | if not value:  | 
            ||
| 830 | return value  | 
            ||
| 831 | brains = self.lookup(field.allowed_types, UID=value)  | 
            ||
| 832 | if not brains:  | 
            ||
| 833 |                 raise ValueError("Row %s: value is invalid (%s=%s)" % ( | 
            ||
| 834 | row_nr, fieldname, value))  | 
            ||
| 835 | if field.multiValued:  | 
            ||
| 836 | return [b.UID for b in brains] if brains else []  | 
            ||
| 837 | else:  | 
            ||
| 838 | return brains[0].UID if brains else None  | 
            ||
| 839 | if field.type == 'datetime':  | 
            ||
| 840 | try:  | 
            ||
| 841 | ulocalized_time(DateTime(value), long_format=True,  | 
            ||
| 842 | time_only=False, context=self)  | 
            ||
| 843 | except:  | 
            ||
| 844 |                 raise ValueError('Row %s: value is invalid (%s=%s)' % ( | 
            ||
| 845 | row_nr, fieldname, value))  | 
            ||
| 846 | return value  | 
            ||
| 847 | |||
| 848 | def lookup(self, allowed_types, **kwargs):  | 
            ||
| 849 | """Lookup an object of type (allowed_types). kwargs is sent  | 
            ||
| 850 | directly to the catalog.  | 
            ||
| 851 | """  | 
            ||
| 852 | at = getToolByName(self, 'archetype_tool')  | 
            ||
| 853 | for portal_type in allowed_types:  | 
            ||
| 854 | catalog = at.catalog_map.get(portal_type, [None])[0]  | 
            ||
| 855 | catalog = getToolByName(self, catalog)  | 
            ||
| 856 | kwargs['portal_type'] = portal_type  | 
            ||
| 857 | brains = catalog(**kwargs)  | 
            ||
| 858 | if brains:  | 
            ||
| 859 | return brains  | 
            ||
| 860 | |||
| 861 | def get_row_services(self, row):  | 
            ||
| 862 | """Return a list of services which are referenced in Analyses.  | 
            ||
| 863 | values may be UID, Title or Keyword.  | 
            ||
| 864 | """  | 
            ||
| 865 | bsc = getToolByName(self, 'bika_setup_catalog')  | 
            ||
| 866 | services = set()  | 
            ||
| 867 |         for val in row.get('Analyses', []): | 
            ||
| 868 | brains = bsc(portal_type='AnalysisService', getKeyword=val)  | 
            ||
| 869 | if not brains:  | 
            ||
| 870 | brains = bsc(portal_type='AnalysisService', title=val)  | 
            ||
| 871 | if not brains:  | 
            ||
| 872 | brains = bsc(portal_type='AnalysisService', UID=val)  | 
            ||
| 873 | if brains:  | 
            ||
| 874 | services.add(brains[0].UID)  | 
            ||
| 875 | else:  | 
            ||
| 876 |                 self.error("Invalid analysis specified: %s" % val) | 
            ||
| 877 | return list(services)  | 
            ||
| 878 | |||
| 879 | def get_row_profile_services(self, row):  | 
            ||
| 880 | """Return a list of services which are referenced in profiles  | 
            ||
| 881 | values may be UID, Title or ProfileKey.  | 
            ||
| 882 | """  | 
            ||
| 883 | bsc = getToolByName(self, 'bika_setup_catalog')  | 
            ||
| 884 | services = set()  | 
            ||
| 885 | profiles = [x.getObject() for x in bsc(portal_type='AnalysisProfile')]  | 
            ||
| 886 |         for val in row.get('Profiles', []): | 
            ||
| 887 | objects = [x for x in profiles  | 
            ||
| 888 | if val in (x.getProfileKey(), x.UID(), x.Title())]  | 
            ||
| 889 | if objects:  | 
            ||
| 890 | for service in objects[0].getService():  | 
            ||
| 891 | services.add(service.UID())  | 
            ||
| 892 | else:  | 
            ||
| 893 |                 self.error("Invalid profile specified: %s" % val) | 
            ||
| 894 | return list(services)  | 
            ||
| 895 | |||
| 896 | def get_row_container(self, row):  | 
            ||
| 897 | """Return a sample container  | 
            ||
| 898 | """  | 
            ||
| 899 | bsc = getToolByName(self, 'bika_setup_catalog')  | 
            ||
| 900 |         val = row.get('Container', False) | 
            ||
| 901 | if val:  | 
            ||
| 902 | brains = bsc(portal_type='Container', UID=row['Container'])  | 
            ||
| 903 | if brains:  | 
            ||
| 904 | brains[0].getObject()  | 
            ||
| 905 | brains = bsc(portal_type='ContainerType', UID=row['Container'])  | 
            ||
| 906 | if brains:  | 
            ||
| 907 | # XXX Cheating. The calculation of capacity vs. volume is not done.  | 
            ||
| 908 | return brains[0].getObject()  | 
            ||
| 909 | return None  | 
            ||
| 910 | |||
| 911 | def get_row_profiles(self, row):  | 
            ||
| 912 | bsc = getToolByName(self, 'bika_setup_catalog')  | 
            ||
| 913 | profiles = []  | 
            ||
| 914 |         for profile_title in row.get('Profiles', []): | 
            ||
| 915 | profile = bsc(portal_type='AnalysisProfile', title=profile_title)  | 
            ||
| 916 | profiles.append(profile)  | 
            ||
| 917 | return profiles  | 
            ||
| 918 | |||
| 919 | def Vocabulary_SamplePoint(self):  | 
            ||
| 920 | vocabulary = CatalogVocabulary(self)  | 
            ||
| 921 | vocabulary.catalog = 'bika_setup_catalog'  | 
            ||
| 922 | folders = [self.bika_setup.bika_samplepoints]  | 
            ||
| 923 | if IClient.providedBy(self.aq_parent):  | 
            ||
| 924 | folders.append(self.aq_parent)  | 
            ||
| 925 | return vocabulary(allow_blank=True, portal_type='SamplePoint')  | 
            ||
| 926 | |||
| 927 | def Vocabulary_SampleMatrix(self):  | 
            ||
| 928 | vocabulary = CatalogVocabulary(self)  | 
            ||
| 929 | vocabulary.catalog = 'bika_setup_catalog'  | 
            ||
| 930 | return vocabulary(allow_blank=True, portal_type='SampleMatrix')  | 
            ||
| 931 | |||
| 932 | def Vocabulary_SampleType(self):  | 
            ||
| 933 | vocabulary = CatalogVocabulary(self)  | 
            ||
| 934 | vocabulary.catalog = 'bika_setup_catalog'  | 
            ||
| 935 | folders = [self.bika_setup.bika_sampletypes]  | 
            ||
| 936 | if IClient.providedBy(self.aq_parent):  | 
            ||
| 937 | folders.append(self.aq_parent)  | 
            ||
| 938 | return vocabulary(allow_blank=True, portal_type='SampleType')  | 
            ||
| 939 | |||
| 940 | def Vocabulary_ContainerType(self):  | 
            ||
| 941 | vocabulary = CatalogVocabulary(self)  | 
            ||
| 942 | vocabulary.catalog = 'bika_setup_catalog'  | 
            ||
| 943 | return vocabulary(allow_blank=True, portal_type='ContainerType')  | 
            ||
| 944 | |||
| 945 | def error(self, msg):  | 
            ||
| 946 | errors = list(self.getErrors())  | 
            ||
| 947 | errors.append(msg)  | 
            ||
| 948 | self.setErrors(errors)  | 
            ||
| 949 | |||
| 950 | |||
| 951 | atapi.registerType(ARImport, PROJECTNAME)  | 
            ||
| 952 |