def addReferenceAnalysis(self, service_uid, reference_type): """ add an analysis to the sample """ rc = getToolByName(self, REFERENCE_CATALOG) service = rc.lookupObject(service_uid) analysis = _createObjectByType("ReferenceAnalysis", self, tmpID()) analysis.unmarkCreationFlag() calculation = service.getCalculation() interim_fields = calculation and calculation.getInterimFields() or [] renameAfterCreation(analysis) # maxtime = service.getMaxTimeAllowed() and service.getMaxTimeAllowed() \ # or {'days':0, 'hours':0, 'minutes':0} # starttime = DateTime() # max_days = float(maxtime.get('days', 0)) + \ # ( # (float(maxtime.get('hours', 0)) * 3600 + \ # float(maxtime.get('minutes', 0)) * 60) # / 86400 # ) # duetime = starttime + max_days analysis.setReferenceType(reference_type) analysis.setService(service_uid) analysis.setInterimFields(interim_fields) return analysis.UID()
def create_service(self, src_uid, dst_title, dst_keyword): folder = self.context.bika_setup.bika_analysisservices dst_service = _createObjectByType("AnalysisService", folder, tmpID()) # manually set keyword and title dst_service.setKeyword(dst_keyword) dst_service.setTitle(dst_title) dst_service.unmarkCreationFlag() _id = renameAfterCreation(dst_service) dst_service = folder[_id] return dst_service
def test_instrument_calibration(self): # Getting all instruments instrument_names = self.portal.bika_setup.bika_instruments.keys() # Setting calibration dates for instrument_name in instrument_names: # Getting each instrument instrument = self.portal.bika_setup.bika_instruments[ instrument_name] today = date.today() # Getting last valid calibration lastcal = instrument.getLatestValidCalibration() if not lastcal: # Creating a new calibration cal_obj = _createObjectByType("InstrumentCalibration", instrument, tmpID()) cal_obj.edit(title='test', DownFrom=today.strftime("%Y/%m/%d"), DownTo=today.strftime("%Y/%m/%d"), Instrument=instrument) cal_obj.unmarkCreationFlag() renameAfterCreation(cal_obj) else: # Updating last calibration lastcal.setDownTo(today) lastcal.setDownFrom(today) # Testing calibration state for instrument_name in instrument_names: instrument = self.portal.bika_setup.bika_instruments[ instrument_name] self.assertTrue(instrument.isCalibrationInProgress()) for instrument_name in instrument_names: instrument = self.portal.bika_setup.bika_instruments[ instrument_name] anotherday = '2014/11/27' lastcal = instrument.getLatestValidCalibration() lastcal.setDownTo(anotherday) lastcal.setDownFrom(anotherday) for instrument_name in instrument_names: instrument = self.portal.bika_setup.bika_instruments[ instrument_name] self.assertFalse(instrument.isCalibrationInProgress())
def test_instrument_calibration(self): # Getting all instruments instrument_names = self.portal.bika_setup.bika_instruments.keys() # Setting calibration dates for instrument_name in instrument_names: # Getting each instrument instrument = self.portal.bika_setup.bika_instruments[instrument_name] today = date.today() # Getting last valid calibration lastcal = instrument.getLatestValidCalibration() if not lastcal: # Creating a new calibration cal_obj = _createObjectByType("InstrumentCalibration", instrument, tmpID()) cal_obj.edit( title='test', DownFrom=today.strftime("%Y/%m/%d"), DownTo=today.strftime("%Y/%m/%d"), Instrument=instrument ) cal_obj.unmarkCreationFlag() renameAfterCreation(cal_obj) else: # Updating last calibration lastcal.setDownTo(today) lastcal.setDownFrom(today) # Testing calibration state for instrument_name in instrument_names: instrument = self.portal.bika_setup.bika_instruments[instrument_name] self.assertTrue(instrument.isCalibrationInProgress()) for instrument_name in instrument_names: instrument = self.portal.bika_setup.bika_instruments[instrument_name] anotherday = '2014/11/27' lastcal = instrument.getLatestValidCalibration() lastcal.setDownTo(anotherday) lastcal.setDownFrom(anotherday) for instrument_name in instrument_names: instrument = self.portal.bika_setup.bika_instruments[instrument_name] self.assertFalse(instrument.isCalibrationInProgress())
def _renameAfterCreation(self, check_auto_id=False): from lims.idserver import renameAfterCreation renameAfterCreation(self)
def workflow_script_reject(self): """Copy real analyses to RejectAnalysis, with link to real create a new worksheet, with the original analyses, and new duplicates and references to match the rejected worksheet. """ if skip(self, "reject"): return utils = getToolByName(self, 'plone_utils') workflow = self.portal_workflow def copy_src_fields_to_dst(src, dst): # These will be ignored when copying field values between analyses ignore_fields = ['UID', 'id', 'title', 'allowDiscussion', 'subject', 'description', 'location', 'contributors', 'creators', 'effectiveDate', 'expirationDate', 'language', 'rights', 'creation_date', 'modification_date', 'Layout', # ws 'Analyses', # ws ] fields = src.Schema().fields() for field in fields: fieldname = field.getName() if fieldname in ignore_fields: continue getter = getattr(src, 'get'+fieldname, src.Schema().getField(fieldname).getAccessor(src)) setter = getattr(dst, 'set'+fieldname, dst.Schema().getField(fieldname).getMutator(dst)) if getter is None or setter is None: # ComputedField continue setter(getter()) analysis_positions = {} for item in self.getLayout(): analysis_positions[item['analysis_uid']] = item['position'] old_layout = [] new_layout = [] # New worksheet worksheets = self.aq_parent new_ws = _createObjectByType('Worksheet', worksheets, tmpID()) new_ws.unmarkCreationFlag() new_ws_id = renameAfterCreation(new_ws) copy_src_fields_to_dst(self, new_ws) new_ws.edit( Number = new_ws_id, Remarks = self.getRemarks() ) # Objects are being created inside other contexts, but we want their # workflow handlers to be aware of which worksheet this is occurring in. # We save the worksheet in request['context_uid']. # We reset it again below.... be very sure that this is set to the # UID of the containing worksheet before invoking any transitions on # analyses. self.REQUEST['context_uid'] = new_ws.UID() # loop all analyses analyses = self.getAnalyses() new_ws_analyses = [] old_ws_analyses = [] for analysis in analyses: # Skip published or verified analyses review_state = workflow.getInfoFor(analysis, 'review_state', '') if review_state in ['published', 'verified', 'retracted']: old_ws_analyses.append(analysis.UID()) old_layout.append({'position': position, 'type':'a', 'analysis_uid':analysis.UID(), 'container_uid':analysis.aq_parent.UID()}) continue # Normal analyses: # - Create matching RejectAnalysis inside old WS # - Link analysis to new WS in same position # - Copy all field values # - Clear analysis result, and set Retested flag if analysis.portal_type == 'Analysis': reject = _createObjectByType('RejectAnalysis', self, tmpID()) reject.unmarkCreationFlag() reject_id = renameAfterCreation(reject) copy_src_fields_to_dst(analysis, reject) reject.setAnalysis(analysis) reject.reindexObject() analysis.edit( Result = None, Retested = True, ) analysis.reindexObject() position = analysis_positions[analysis.UID()] old_ws_analyses.append(reject.UID()) old_layout.append({'position': position, 'type':'r', 'analysis_uid':reject.UID(), 'container_uid':self.UID()}) new_ws_analyses.append(analysis.UID()) new_layout.append({'position': position, 'type':'a', 'analysis_uid':analysis.UID(), 'container_uid':analysis.aq_parent.UID()}) # Reference analyses # - Create a new reference analysis in the new worksheet # - Transition the original analysis to 'rejected' state if analysis.portal_type == 'ReferenceAnalysis': service_uid = analysis.getService().UID() reference = analysis.aq_parent reference_type = analysis.getReferenceType() new_analysis_uid = reference.addReferenceAnalysis(service_uid, reference_type) position = analysis_positions[analysis.UID()] old_ws_analyses.append(analysis.UID()) old_layout.append({'position': position, 'type':reference_type, 'analysis_uid':analysis.UID(), 'container_uid':reference.UID()}) new_ws_analyses.append(new_analysis_uid) new_layout.append({'position': position, 'type':reference_type, 'analysis_uid':new_analysis_uid, 'container_uid':reference.UID()}) workflow.doActionFor(analysis, 'reject') new_reference = reference.uid_catalog(UID=new_analysis_uid)[0].getObject() workflow.doActionFor(new_reference, 'assign') analysis.reindexObject() # Duplicate analyses # - Create a new duplicate inside the new worksheet # - Transition the original analysis to 'rejected' state if analysis.portal_type == 'DuplicateAnalysis': src_analysis = analysis.getAnalysis() ar = src_analysis.aq_parent service = src_analysis.getService() duplicate_id = new_ws.generateUniqueId('DuplicateAnalysis') new_duplicate = _createObjectByType('DuplicateAnalysis', new_ws, duplicate_id) new_duplicate.unmarkCreationFlag() copy_src_fields_to_dst(analysis, new_duplicate) workflow.doActionFor(new_duplicate, 'assign') new_duplicate.reindexObject() position = analysis_positions[analysis.UID()] old_ws_analyses.append(analysis.UID()) old_layout.append({'position': position, 'type':'d', 'analysis_uid':analysis.UID(), 'container_uid':self.UID()}) new_ws_analyses.append(new_duplicate.UID()) new_layout.append({'position': position, 'type':'d', 'analysis_uid':new_duplicate.UID(), 'container_uid':new_ws.UID()}) workflow.doActionFor(analysis, 'reject') analysis.reindexObject() new_ws.setAnalyses(new_ws_analyses) new_ws.setLayout(new_layout) new_ws.replaces_rejected_worksheet = self.UID() for analysis in new_ws.getAnalyses(): review_state = workflow.getInfoFor(analysis, 'review_state', '') if review_state == 'to_be_verified': changeWorkflowState(analysis, "bika_analysis_workflow", "sample_received") self.REQUEST['context_uid'] = self.UID() self.setLayout(old_layout) self.setAnalyses(old_ws_analyses) self.replaced_by = new_ws.UID()
def _renameAfterCreation(self, check_auto_id=False): renameAfterCreation(self)
def create(self, context, request): """/@@API/create: Create new object. Required parameters: - obj_type = portal_type of new object. - obj_path = path of new object, from plone site root. - Not required for obj_type=AnalysisRequest Optionally: - obj_id = ID of new object. All other parameters in the request are matched against the object's Schema. If a matching field is found in the schema, then the value is taken from the request and sent to the field's mutator. Reference fields may have their target value(s) specified with a delimited string query syntax, containing the portal_catalog search: <FieldName>=index1:value1|index2:value2 eg to set the Client of a batch: ...@@API/update?obj_path=<path>... ...&Client=title:<client_title>&... And, to set a multi-valued reference, these both work: ...@@API/update?obj_path=<path>... ...&InheritedObjects:list=title:AR1... ...&InheritedObjects:list=title:AR2... ...@@API/update?obj_path=<path>... ...&InheritedObjects[]=title:AR1... ...&InheritedObjects[]=title:AR2... The Analysis_Specification parameter is special, it mimics the format of the python dictionaries, and only service Keyword can be used to reference services. Even if the keyword is not actively required, it must be supplied: <service_keyword>:min:max:error tolerance The function returns a dictionary as a json string: { runtime: Function running time. error: true or string(message) if error. false if no error. success: true or string(message) if success. false if no success. } >>> portal = layer['portal'] >>> portal_url = portal.absolute_url() >>> from plone.app.testing import SITE_OWNER_NAME >>> from plone.app.testing import SITE_OWNER_PASSWORD Simple AR creation, no obj_path parameter is required: >>> browser = layer['getBrowser'](portal, loggedIn=True, username=SITE_OWNER_NAME, password=SITE_OWNER_PASSWORD) >>> browser.open(portal_url+"/@@API/create", "&".join([ ... "obj_type=AnalysisRequest", ... "Client=portal_type:Client|id:client-1", ... "SampleType=portal_type:SampleType|title:Apple Pulp", ... "Contact=portal_type:Contact|getFullname:Rita Mohale", ... "Services:list=portal_type:AnalysisService|title:Calcium", ... "Services:list=portal_type:AnalysisService|title:Copper", ... "Services:list=portal_type:AnalysisService|title:Magnesium", ... "SamplingDate=2013-09-29", ... "Specification=portal_type:AnalysisSpec|title:Apple Pulp", ... ])) >>> browser.contents '{..."success": true...}' If some parameters are specified and are not located as existing fields or properties of the created instance, the create should fail: >>> browser = layer['getBrowser'](portal, loggedIn=True, username=SITE_OWNER_NAME, password=SITE_OWNER_PASSWORD) >>> browser.open(portal_url+"/@@API/create?", "&".join([ ... "obj_type=Batch", ... "obj_path=/batches", ... "title=Test", ... "Thing=Fish" ... ])) >>> browser.contents '{...The following request fields were not used: ...Thing...}' Now we test that the AR create also fails if some fields are spelled wrong >>> browser = layer['getBrowser'](portal, loggedIn=True, username=SITE_OWNER_NAME, password=SITE_OWNER_PASSWORD) >>> browser.open(portal_url+"/@@API/create", "&".join([ ... "obj_type=AnalysisRequest", ... "thing=Fish", ... "Client=portal_type:Client|id:client-1", ... "SampleType=portal_type:SampleType|title:Apple Pulp", ... "Contact=portal_type:Contact|getFullname:Rita Mohale", ... "Services:list=portal_type:AnalysisService|title:Calcium", ... "Services:list=portal_type:AnalysisService|title:Copper", ... "Services:list=portal_type:AnalysisService|title:Magnesium", ... "SamplingDate=2013-09-29" ... ])) >>> browser.contents '{...The following request fields were not used: ...thing...}' """ savepoint = transaction.savepoint() self.context = context self.request = request self.unused = [x for x in self.request.form.keys()] self.used("form.submitted") self.used("__ac_name") self.used("__ac_password") # always require obj_type self.require("obj_type") obj_type = self.request['obj_type'] self.used("obj_type") # AnalysisRequest shortcut: creates Sample, Partition, AR, Analyses. if obj_type == "AnalysisRequest": try: return self._create_ar(context, request) except: savepoint.rollback() raise # Other object types require explicit path as their parent self.require("obj_path") obj_path = self.request['obj_path'] if not obj_path.startswith("/"): obj_path = "/" + obj_path self.used("obj_path") site_path = request['PATH_INFO'].replace("/@@API/create", "") parent = context.restrictedTraverse(str(site_path + obj_path)) # normal permissions still apply for this user if not getSecurityManager().checkPermission(AccessJSONAPI, parent): msg = "You don't have the '{0}' permission on {1}".format( AccessJSONAPI, parent.absolute_url()) raise Unauthorized(msg) obj_id = request.get("obj_id", "") _renameAfterCreation = False if not obj_id: _renameAfterCreation = True obj_id = tmpID() self.used(obj_id) ret = { "url": router.url_for("create", force_external=True), "success": True, "error": False, } try: obj = _createObjectByType(obj_type, parent, obj_id) obj.unmarkCreationFlag() if _renameAfterCreation: renameAfterCreation(obj) ret['obj_id'] = obj.getId() used_fields = set_fields_from_request(obj, request) for field in used_fields: self.used(field) obj.reindexObject() obj.aq_parent.reindexObject() event.notify(ObjectInitializedEvent(obj)) obj.at_post_create_script() except: savepoint.rollback() raise if self.unused: raise BadRequest("The following request fields were not used: %s. Request aborted." % self.unused) return ret
def cloneAR(self, ar): newar = _createObjectByType("AnalysisRequest", ar.aq_parent, tmpID()) newar.title = ar.title newar.description = ar.description newar.setContact(ar.getContact()) newar.setCCContact(ar.getCCContact()) newar.setCCEmails(ar.getCCEmails()) newar.setBatch(ar.getBatch()) newar.setTemplate(ar.getTemplate()) newar.setProfile(ar.getProfile()) newar.setSamplingDate(ar.getSamplingDate()) newar.setSampleType(ar.getSampleType()) newar.setSamplePoint(ar.getSamplePoint()) newar.setStorageLocation(ar.getStorageLocation()) newar.setSamplingDeviation(ar.getSamplingDeviation()) newar.setPriority(ar.getPriority()) newar.setSampleCondition(ar.getSampleCondition()) newar.setSample(ar.getSample()) newar.setClientOrderNumber(ar.getClientOrderNumber()) newar.setClientReference(ar.getClientReference()) newar.setClientSampleID(ar.getClientSampleID()) newar.setDefaultContainerType(ar.getDefaultContainerType()) newar.setAdHoc(ar.getAdHoc()) newar.setComposite(ar.getComposite()) newar.setReportDryMatter(ar.getReportDryMatter()) newar.setInvoiceExclude(ar.getInvoiceExclude()) newar.setAttachment(ar.getAttachment()) newar.setInvoice(ar.getInvoice()) newar.setDateReceived(ar.getDateReceived()) newar.setMemberDiscount(ar.getMemberDiscount()) # Set the results for each AR analysis ans = ar.getAnalyses(full_objects=True) for an in ans: nan = _createObjectByType("Analysis", newar, an.getKeyword()) nan.setService(an.getService()) nan.setCalculation(an.getCalculation()) nan.setInterimFields(an.getInterimFields()) nan.setResult(an.getResult()) nan.setResultDM(an.getResultDM()) nan.setRetested = False, nan.setMaxTimeAllowed(an.getMaxTimeAllowed()) nan.setDueDate(an.getDueDate()) nan.setDuration(an.getDuration()) nan.setReportDryMatter(an.getReportDryMatter()) nan.setAnalyst(an.getAnalyst()) nan.setInstrument(an.getInstrument()) nan.setSamplePartition(an.getSamplePartition()) nan.unmarkCreationFlag() notify(ObjectInitializedEvent(nan)) changeWorkflowState(nan, 'bika_analysis_workflow', 'to_be_verified') nan.reindexObject() newar.reindexObject() newar.aq_parent.reindexObject() renameAfterCreation(newar) newar.setRequestID(newar.getId()) if hasattr(ar, 'setChildAnalysisRequest'): ar.setChildAnalysisRequest(newar) newar.setParentAnalysisRequest(ar) return newar
def publishFromHTML(self, aruid, results_html): # The AR can be published only and only if allowed uc = getToolByName(self.context, 'uid_catalog') ars = uc(UID=aruid) if not ars or len(ars) != 1: return [] ar = ars[0].getObject(); wf = getToolByName(ar, 'portal_workflow') allowed_states = ['verified', 'published'] # Publish/Republish allowed? if wf.getInfoFor(ar, 'review_state') not in allowed_states: # Pre-publish allowed? if not ar.getAnalyses(review_state=allowed_states): return [] # HTML written to debug file # debug_mode = App.config.getConfiguration().debug_mode "Commented by Yasir" debug_mode = True #" Added by Yasir" if debug_mode: tmp_fn = tempfile.mktemp(suffix=".html") logger.debug("Writing HTML for %s to %s" % (ar.Title(), tmp_fn)) open(tmp_fn, "wb").write(results_html) # Create the pdf report (will always be attached to the AR) # we must supply the file ourself so that createPdf leaves it alone. # This version replaces 'attachment' links; probably not required, # so it's repeated below, without these localise_images. # cleanup, results_html_for_pdf = self.localise_images(results_html) # pdf_fn = tempfile.mktemp(suffix=".pdf") # pdf_report = createPdf(htmlreport=results_html_for_pdf, outfile=pdf_fn) # for fn in cleanup: # os.remove(fn) # Create the pdf report (will always be attached to the AR) # we must supply the file ourself so that createPdf leaves it alone. pdf_fn = tempfile.mktemp(suffix=".pdf") pdf_report = createPdf(htmlreport=results_html, outfile=pdf_fn) # PDF written to debug file if debug_mode: logger.debug("Writing PDF for %s to %s" % (ar.Title(), pdf_fn)) else: os.remove(pdf_fn) recipients = [] contact = ar.getContact() lab = ar.bika_setup.laboratory if pdf_report: if contact: recipients = [{ 'UID': contact.UID(), 'Username': to_utf8(contact.getUsername()), 'Fullname': to_utf8(contact.getFullname()), 'EmailAddress': to_utf8(contact.getEmailAddress()), 'PublicationModes': contact.getPublicationPreference() }] reportid = ar.generateUniqueId('ARReport') report = _createObjectByType("ARReport", ar, reportid) report.edit( AnalysisRequest=ar.UID(), Pdf=pdf_report, Html=results_html, Recipients=recipients ) report.unmarkCreationFlag() renameAfterCreation(report) # Set status to prepublished/published/republished status = wf.getInfoFor(ar, 'review_state') transitions = {'verified': 'publish', 'published' : 'republish'} transition = transitions.get(status, 'prepublish') try: wf.doActionFor(ar, transition) except WorkflowException: pass # compose and send email. # The managers of the departments for which the current AR has # at least one AS must receive always the pdf report by email. # https://github.com/bikalabs/Bika-LIMS/issues/1028 mime_msg = MIMEMultipart('related') mime_msg['Subject'] = self.get_mail_subject(ar)[0] mime_msg['From'] = formataddr( (encode_header(lab.getName()), lab.getEmailAddress())) mime_msg.preamble = 'This is a multi-part MIME message.' msg_txt = MIMEText(results_html, _subtype='html') mime_msg.attach(msg_txt) to = [] mngrs = ar.getResponsible() for mngrid in mngrs['ids']: name = mngrs['dict'][mngrid].get('name', '') email = mngrs['dict'][mngrid].get('email', '') if (email != ''): to.append(formataddr((encode_header(name), email))) if len(to) > 0: # Send the email to the managers mime_msg['To'] = ','.join(to) attachPdf(mime_msg, pdf_report, pdf_fn) try: host = getToolByName(ar, 'MailHost') host.send(mime_msg.as_string(), immediate=True) except SMTPServerDisconnected as msg: logger.warn("SMTPServerDisconnected: %s." % msg) except SMTPRecipientsRefused as msg: raise WorkflowException(str(msg)) # Send report to recipients recips = self.get_recipients(ar) for recip in recips: if 'email' not in recip.get('pubpref', []) \ or not recip.get('email', ''): continue title = encode_header(recip.get('title', '')) email = recip.get('email') formatted = formataddr((title, email)) # Create the new mime_msg object, cause the previous one # has the pdf already attached mime_msg = MIMEMultipart('related') mime_msg['Subject'] = self.get_mail_subject(ar)[0] mime_msg['From'] = formataddr( (encode_header(lab.getName()), lab.getEmailAddress())) mime_msg.preamble = 'This is a multi-part MIME message.' msg_txt = MIMEText(results_html, _subtype='html') mime_msg.attach(msg_txt) mime_msg['To'] = formatted # Attach the pdf to the email if requested if pdf_report and 'pdf' in recip.get('pubpref'): attachPdf(mime_msg, pdf_report, pdf_fn) # For now, I will simply ignore mail send under test. if hasattr(self.portal, 'robotframework'): continue msg_string = mime_msg.as_string() # content of outgoing email written to debug file if debug_mode: tmp_fn = tempfile.mktemp(suffix=".email") logger.debug("Writing MIME message for %s to %s" % (ar.Title(), tmp_fn)) open(tmp_fn, "wb").write(msg_string) try: host = getToolByName(ar, 'MailHost') host.send(msg_string, immediate=True) except SMTPServerDisconnected as msg: logger.warn("SMTPServerDisconnected: %s." % msg) except SMTPRecipientsRefused as msg: raise WorkflowException(str(msg)) ar.setDatePublished(DateTime()) return [ar]
def process(self): self._parser.parse() parsed = self._parser.resume() self._errors = self._parser.errors self._warns = self._parser.warns self._logs = self._parser.logs self._priorizedsearchcriteria = '' if parsed == False: return False # Allowed analysis states allowed_ar_states_msg = [t(_(s)) for s in self.getAllowedARStates()] allowed_an_states_msg = [t(_(s)) for s in self.getAllowedAnalysisStates()] self.log("Allowed Analysis Request states: ${allowed_states}", mapping={'allowed_states': ', '.join(allowed_ar_states_msg)}) self.log("Allowed analysis states: ${allowed_states}", mapping={'allowed_states': ', '.join(allowed_an_states_msg)}) # Exclude non existing ACODEs acodes = [] ancount = 0 arprocessed = [] instprocessed = [] importedars = {} importedinsts = {} rawacodes = self._parser.getAnalysisKeywords() exclude = self.getKeywordsToBeExcluded() for acode in rawacodes: if acode in exclude: continue service = self.bsc(getKeyword=acode) if not service: self.warn('Service keyword ${analysis_keyword} not found', mapping={"analysis_keyword": acode}) else: acodes.append(acode) if len(acodes) == 0: self.err("Service keywords: no matches found") searchcriteria = self.getIdSearchCriteria(); #self.log(_("Search criterias: %s") % (', '.join(searchcriteria))) for objid, results in self._parser.getRawResults().iteritems(): # Allowed more than one result for the same sample and analysis. # Needed for calibration tests for result in results: analyses = self._getZODBAnalyses(objid) inst = None if len(analyses) == 0 and self.instrument_uid: # No registered analyses found, but maybe we need to # create them first if an instruemnt id has been set in insts = self.bsc(portal_type='Instrument', UID=self.instrument_uid) if len(insts) == 0: # No instrument found self.err("No Analysis Request with '${allowed_ar_states}' " "states found, And no QC analyses found for ${object_id}", mapping={"allowed_ar_states": ', '.join(allowed_ar_states_msg), "object_id": objid}) self.err("Instrument not found") continue inst = insts[0].getObject() # Create a new ReferenceAnalysis and link it to the Instrument # Here we have an objid (i.e. R01200012) and # a dict with results (the key is the AS keyword). # How can we create a ReferenceAnalysis if we don't know # which ReferenceSample we might use? # Ok. The objid HAS to be the ReferenceSample code. refsample = self.bc(portal_type='ReferenceSample', id=objid) if refsample and len(refsample) == 1: refsample = refsample[0].getObject() elif refsample and len(refsample) > 1: # More than one reference sample found! self.err( "More than one reference sample found for '${object_id}'", mapping={"object_id": objid}) continue else: # No reference sample found! self.err("No Reference Sample found for ${object_id}", mapping={"object_id": objid}) continue # For each acode, create a ReferenceAnalysis and attach it # to the Reference Sample service_uids = [] reference_type = 'b' if refsample.getBlank() == True else 'c' services = self.bsc(portal_type='AnalysisService') service_uids = [service.UID for service in services \ if service.getObject().getKeyword() in result.keys()] analyses = inst.addReferences(refsample, service_uids) elif len(analyses) == 0: # No analyses found self.err("No Analysis Request with '${allowed_ar_states}' " "states neither QC analyses found for ${object_id}", mapping={ "allowed_ar_states":', '.join(allowed_ar_states_msg), "object_id": objid}) continue # Look for timestamp capturedate = result.get('DateTime',{}).get('DateTime',None) if capturedate: del result['DateTime'] for acode, values in result.iteritems(): if acode not in acodes: # Analysis keyword doesn't exist continue ans = [analysis for analysis in analyses \ if analysis.getKeyword() == acode] if len(ans) > 1: self.err("More than one analyses found for ${object_id} and ${analysis_keyword)", mapping={"object_id": objid, "analysis_keyword": acode}) continue elif len(ans) == 0: self.err("No analyses found for ${object_id} and ${analysis_keyword)", mapping={"object_id": objid, "analysis_keyword": acode}) continue analysis = ans[0] if capturedate: values['DateTime'] = capturedate processed = self._process_analysis(objid, analysis, values) if processed: ancount += 1 if inst: # Calibration Test (import to Instrument) instprocessed.append(inst.UID()) importedinst = inst.title in importedinsts.keys() \ and importedinsts[inst.title] or [] if acode not in importedinst: importedinst.append(acode) importedinsts[inst.title] = importedinst else: ar = analysis.portal_type == 'Analysis' and analysis.aq_parent or None if ar and ar.UID: # Set AR imported info arprocessed.append(ar.UID()) importedar = ar.getRequestID() in importedars.keys() \ and importedars[ar.getRequestID()] or [] if acode not in importedar: importedar.append(acode) importedars[ar.getRequestID()] = importedar # Create the AttachmentType for mime type if not exists attuid = None attachmentType = self.bsc(portal_type="AttachmentType", title=self._parser.getAttachmentFileType()) if len(attachmentType) == 0: try: folder = self.context.bika_setup.bika_attachmenttypes obj = _createObjectByType("AttachmentType", folder, tmpID()) obj.edit(title=self._parser.getAttachmentFileType(), description="Autogenerated file type") obj.unmarkCreationFlag() renameAfterCreation(obj) attuid = obj.UID() except: attuid = None self.err( "Unable to create the Attachment Type ${mime_type}", mapping={ "mime_type": self._parser.getFileMimeType()}) else: attuid = attachmentType[0].UID if attuid is not None: try: # Attach the file to the Analysis wss = analysis.getBackReferences('WorksheetAnalysis') if wss and len(wss) > 0: #TODO: Mirar si es pot evitar utilitzar el WS i utilitzar directament l'Anàlisi (útil en cas de CalibrationTest) ws = wss[0] attachment = _createObjectByType("Attachment", ws, tmpID()) attachment.edit( AttachmentFile=self._parser.getInputFile(), AttachmentType=attuid, AttachmentKeys='Results, Automatic import') attachment.reindexObject() others = analysis.getAttachment() attachments = [] for other in others: if other.getAttachmentFile().filename != attachment.getAttachmentFile().filename: attachments.append(other.UID()) attachments.append(attachment.UID()) analysis.setAttachment(attachments) except: # self.err(_("Unable to attach results file '${file_name}' to AR ${request_id}", # mapping={"file_name": self._parser.getInputFile().filename, # "request_id": ar.getRequestID()})) pass # Calculate analysis dependencies for aruid in arprocessed: ar = self.bc(portal_type='AnalysisRequest', UID=aruid) ar = ar[0].getObject() analyses = ar.getAnalyses() for analysis in analyses: analysis = analysis.getObject() if analysis.calculateResult(True, True): self.log( "${request_id} calculated result for '${analysis_keyword}': '${analysis_result}'", mapping={"request_id": ar.getRequestID(), "analysis_keyword": analysis.getKeyword(), "analysis_result": str(analysis.getResult())} ) # Not sure if there's any reason why ReferenceAnalyses have not # defined the method calculateResult... # Needs investigation. #for instuid in instprocessed: # inst = self.bsc(portal_type='Instrument',UID=instuid)[0].getObject() # analyses = inst.getAnalyses() # for analysis in analyses: # if (analysis.calculateResult(True, True)): # self.log(_("%s calculated result for '%s': '%s'") % # (inst.title, analysis.getKeyword(), str(analysis.getResult()))) for arid, acodes in importedars.iteritems(): acodesmsg = ["Analysis %s" % acod for acod in acodes] self.log("${request_id}: ${analysis_keywords} imported sucessfully", mapping={"request_id": arid, "analysis_keywords": acodesmsg}) for instid, acodes in importedinsts.iteritems(): acodesmsg = ["Analysis %s" % acod for acod in acodes] msg = "%s: %s %s" % (instid, ", ".join(acodesmsg), "imported sucessfully") self.log(msg) if self.instrument_uid: self.log( "Import finished successfully: ${nr_updated_ars} ARs, " "${nr_updated_instruments} Instruments and ${nr_updated_results} " "results updated", mapping={"nr_updated_ars": str(len(importedars)), "nr_updated_instruments": str(len(importedinsts)), "nr_updated_results": str(ancount)}) else: self.log( "Import finished successfully: ${nr_updated_ars} ARs and " "${nr_updated_results} results updated", mapping={"nr_updated_ars": str(len(importedars)), "nr_updated_results": str(ancount)})
def publishFromHTML(self, aruid, results_html): # The AR can be published only and only if allowed uc = getToolByName(self.context, 'uid_catalog') ars = uc(UID=aruid) if not ars or len(ars) != 1: return [] ar = ars[0].getObject() wf = getToolByName(ar, 'portal_workflow') allowed_states = ['verified', 'published'] # Publish/Republish allowed? if wf.getInfoFor(ar, 'review_state') not in allowed_states: # Pre-publish allowed? if not ar.getAnalyses(review_state=allowed_states): return [] # HTML written to debug file # debug_mode = App.config.getConfiguration().debug_mode "Commented by Yasir" debug_mode = True #" Added by Yasir" if debug_mode: tmp_fn = tempfile.mktemp(suffix=".html") logger.debug("Writing HTML for %s to %s" % (ar.Title(), tmp_fn)) open(tmp_fn, "wb").write(results_html) # Create the pdf report (will always be attached to the AR) # we must supply the file ourself so that createPdf leaves it alone. # This version replaces 'attachment' links; probably not required, # so it's repeated below, without these localise_images. # cleanup, results_html_for_pdf = self.localise_images(results_html) # pdf_fn = tempfile.mktemp(suffix=".pdf") # pdf_report = createPdf(htmlreport=results_html_for_pdf, outfile=pdf_fn) # for fn in cleanup: # os.remove(fn) # Create the pdf report (will always be attached to the AR) # we must supply the file ourself so that createPdf leaves it alone. pdf_fn = tempfile.mktemp(suffix=".pdf") pdf_report = createPdf(htmlreport=results_html, outfile=pdf_fn) # PDF written to debug file if debug_mode: logger.debug("Writing PDF for %s to %s" % (ar.Title(), pdf_fn)) else: os.remove(pdf_fn) recipients = [] contact = ar.getContact() lab = ar.bika_setup.laboratory if pdf_report: if contact: recipients = [{ 'UID': contact.UID(), 'Username': to_utf8(contact.getUsername()), 'Fullname': to_utf8(contact.getFullname()), 'EmailAddress': to_utf8(contact.getEmailAddress()), 'PublicationModes': contact.getPublicationPreference() }] reportid = ar.generateUniqueId('ARReport') report = _createObjectByType("ARReport", ar, reportid) report.edit(AnalysisRequest=ar.UID(), Pdf=pdf_report, Html=results_html, Recipients=recipients) report.unmarkCreationFlag() renameAfterCreation(report) # Set status to prepublished/published/republished status = wf.getInfoFor(ar, 'review_state') transitions = {'verified': 'publish', 'published': 'republish'} transition = transitions.get(status, 'prepublish') try: wf.doActionFor(ar, transition) except WorkflowException: pass # compose and send email. # The managers of the departments for which the current AR has # at least one AS must receive always the pdf report by email. # https://github.com/bikalabs/Bika-LIMS/issues/1028 mime_msg = MIMEMultipart('related') mime_msg['Subject'] = self.get_mail_subject(ar)[0] mime_msg['From'] = formataddr( (encode_header(lab.getName()), lab.getEmailAddress())) mime_msg.preamble = 'This is a multi-part MIME message.' msg_txt = MIMEText(results_html, _subtype='html') mime_msg.attach(msg_txt) to = [] mngrs = ar.getResponsible() for mngrid in mngrs['ids']: name = mngrs['dict'][mngrid].get('name', '') email = mngrs['dict'][mngrid].get('email', '') if (email != ''): to.append(formataddr((encode_header(name), email))) if len(to) > 0: # Send the email to the managers mime_msg['To'] = ','.join(to) attachPdf(mime_msg, pdf_report, pdf_fn) try: host = getToolByName(ar, 'MailHost') host.send(mime_msg.as_string(), immediate=True) except SMTPServerDisconnected as msg: logger.warn("SMTPServerDisconnected: %s." % msg) except SMTPRecipientsRefused as msg: raise WorkflowException(str(msg)) # Send report to recipients recips = self.get_recipients(ar) for recip in recips: if 'email' not in recip.get('pubpref', []) \ or not recip.get('email', ''): continue title = encode_header(recip.get('title', '')) email = recip.get('email') formatted = formataddr((title, email)) # Create the new mime_msg object, cause the previous one # has the pdf already attached mime_msg = MIMEMultipart('related') mime_msg['Subject'] = self.get_mail_subject(ar)[0] mime_msg['From'] = formataddr( (encode_header(lab.getName()), lab.getEmailAddress())) mime_msg.preamble = 'This is a multi-part MIME message.' msg_txt = MIMEText(results_html, _subtype='html') mime_msg.attach(msg_txt) mime_msg['To'] = formatted # Attach the pdf to the email if requested if pdf_report and 'pdf' in recip.get('pubpref'): attachPdf(mime_msg, pdf_report, pdf_fn) # For now, I will simply ignore mail send under test. if hasattr(self.portal, 'robotframework'): continue msg_string = mime_msg.as_string() # content of outgoing email written to debug file if debug_mode: tmp_fn = tempfile.mktemp(suffix=".email") logger.debug("Writing MIME message for %s to %s" % (ar.Title(), tmp_fn)) open(tmp_fn, "wb").write(msg_string) try: host = getToolByName(ar, 'MailHost') host.send(msg_string, immediate=True) except SMTPServerDisconnected as msg: logger.warn("SMTPServerDisconnected: %s." % msg) except SMTPRecipientsRefused as msg: raise WorkflowException(str(msg)) ar.setDatePublished(DateTime()) return [ar]
def process(self): self._parser.parse() parsed = self._parser.resume() self._errors = self._parser.errors self._warns = self._parser.warns self._logs = self._parser.logs self._priorizedsearchcriteria = '' if parsed == False: return False # Allowed analysis states allowed_ar_states_msg = [t(_(s)) for s in self.getAllowedARStates()] allowed_an_states_msg = [ t(_(s)) for s in self.getAllowedAnalysisStates() ] self.log("Allowed Analysis Request states: ${allowed_states}", mapping={'allowed_states': ', '.join(allowed_ar_states_msg)}) self.log("Allowed analysis states: ${allowed_states}", mapping={'allowed_states': ', '.join(allowed_an_states_msg)}) # Exclude non existing ACODEs acodes = [] ancount = 0 arprocessed = [] instprocessed = [] importedars = {} importedinsts = {} rawacodes = self._parser.getAnalysisKeywords() exclude = self.getKeywordsToBeExcluded() for acode in rawacodes: if acode in exclude: continue service = self.bsc(getKeyword=acode) if not service: self.warn('Service keyword ${analysis_keyword} not found', mapping={"analysis_keyword": acode}) else: acodes.append(acode) if len(acodes) == 0: self.err("Service keywords: no matches found") searchcriteria = self.getIdSearchCriteria() #self.log(_("Search criterias: %s") % (', '.join(searchcriteria))) for objid, results in self._parser.getRawResults().iteritems(): # Allowed more than one result for the same sample and analysis. # Needed for calibration tests for result in results: analyses = self._getZODBAnalyses(objid) inst = None if len(analyses) == 0 and self.instrument_uid: # No registered analyses found, but maybe we need to # create them first if an instruemnt id has been set in insts = self.bsc(portal_type='Instrument', UID=self.instrument_uid) if len(insts) == 0: # No instrument found self.err( "No Analysis Request with '${allowed_ar_states}' " "states found, And no QC analyses found for ${object_id}", mapping={ "allowed_ar_states": ', '.join(allowed_ar_states_msg), "object_id": objid }) self.err("Instrument not found") continue inst = insts[0].getObject() # Create a new ReferenceAnalysis and link it to the Instrument # Here we have an objid (i.e. R01200012) and # a dict with results (the key is the AS keyword). # How can we create a ReferenceAnalysis if we don't know # which ReferenceSample we might use? # Ok. The objid HAS to be the ReferenceSample code. refsample = self.bc(portal_type='ReferenceSample', id=objid) if refsample and len(refsample) == 1: refsample = refsample[0].getObject() elif refsample and len(refsample) > 1: # More than one reference sample found! self.err( "More than one reference sample found for '${object_id}'", mapping={"object_id": objid}) continue else: # No reference sample found! self.err("No Reference Sample found for ${object_id}", mapping={"object_id": objid}) continue # For each acode, create a ReferenceAnalysis and attach it # to the Reference Sample service_uids = [] reference_type = 'b' if refsample.getBlank( ) == True else 'c' services = self.bsc(portal_type='AnalysisService') service_uids = [service.UID for service in services \ if service.getObject().getKeyword() in result.keys()] analyses = inst.addReferences(refsample, service_uids) elif len(analyses) == 0: # No analyses found self.err( "No Analysis Request with '${allowed_ar_states}' " "states neither QC analyses found for ${object_id}", mapping={ "allowed_ar_states": ', '.join(allowed_ar_states_msg), "object_id": objid }) continue # Look for timestamp capturedate = result.get('DateTime', {}).get('DateTime', None) if capturedate: del result['DateTime'] for acode, values in result.iteritems(): if acode not in acodes: # Analysis keyword doesn't exist continue ans = [analysis for analysis in analyses \ if analysis.getKeyword() == acode] if len(ans) > 1: self.err( "More than one analyses found for ${object_id} and ${analysis_keyword)", mapping={ "object_id": objid, "analysis_keyword": acode }) continue elif len(ans) == 0: self.err( "No analyses found for ${object_id} and ${analysis_keyword)", mapping={ "object_id": objid, "analysis_keyword": acode }) continue analysis = ans[0] if capturedate: values['DateTime'] = capturedate processed = self._process_analysis(objid, analysis, values) if processed: ancount += 1 if inst: # Calibration Test (import to Instrument) instprocessed.append(inst.UID()) importedinst = inst.title in importedinsts.keys() \ and importedinsts[inst.title] or [] if acode not in importedinst: importedinst.append(acode) importedinsts[inst.title] = importedinst else: ar = analysis.portal_type == 'Analysis' and analysis.aq_parent or None if ar and ar.UID: # Set AR imported info arprocessed.append(ar.UID()) importedar = ar.getRequestID() in importedars.keys() \ and importedars[ar.getRequestID()] or [] if acode not in importedar: importedar.append(acode) importedars[ar.getRequestID()] = importedar # Create the AttachmentType for mime type if not exists attuid = None attachmentType = self.bsc( portal_type="AttachmentType", title=self._parser.getAttachmentFileType()) if len(attachmentType) == 0: try: folder = self.context.bika_setup.bika_attachmenttypes obj = _createObjectByType( "AttachmentType", folder, tmpID()) obj.edit( title=self._parser.getAttachmentFileType(), description="Autogenerated file type") obj.unmarkCreationFlag() renameAfterCreation(obj) attuid = obj.UID() except: attuid = None self.err( "Unable to create the Attachment Type ${mime_type}", mapping={ "mime_type": self._parser.getFileMimeType() }) else: attuid = attachmentType[0].UID if attuid is not None: try: # Attach the file to the Analysis wss = analysis.getBackReferences( 'WorksheetAnalysis') if wss and len(wss) > 0: #TODO: Mirar si es pot evitar utilitzar el WS i utilitzar directament l'Anàlisi (útil en cas de CalibrationTest) ws = wss[0] attachment = _createObjectByType( "Attachment", ws, tmpID()) attachment.edit( AttachmentFile=self._parser. getInputFile(), AttachmentType=attuid, AttachmentKeys= 'Results, Automatic import') attachment.reindexObject() others = analysis.getAttachment() attachments = [] for other in others: if other.getAttachmentFile( ).filename != attachment.getAttachmentFile( ).filename: attachments.append(other.UID()) attachments.append(attachment.UID()) analysis.setAttachment(attachments) except: # self.err(_("Unable to attach results file '${file_name}' to AR ${request_id}", # mapping={"file_name": self._parser.getInputFile().filename, # "request_id": ar.getRequestID()})) pass # Calculate analysis dependencies for aruid in arprocessed: ar = self.bc(portal_type='AnalysisRequest', UID=aruid) ar = ar[0].getObject() analyses = ar.getAnalyses() for analysis in analyses: analysis = analysis.getObject() if analysis.calculateResult(True, True): self.log( "${request_id} calculated result for '${analysis_keyword}': '${analysis_result}'", mapping={ "request_id": ar.getRequestID(), "analysis_keyword": analysis.getKeyword(), "analysis_result": str(analysis.getResult()) }) # Not sure if there's any reason why ReferenceAnalyses have not # defined the method calculateResult... # Needs investigation. #for instuid in instprocessed: # inst = self.bsc(portal_type='Instrument',UID=instuid)[0].getObject() # analyses = inst.getAnalyses() # for analysis in analyses: # if (analysis.calculateResult(True, True)): # self.log(_("%s calculated result for '%s': '%s'") % # (inst.title, analysis.getKeyword(), str(analysis.getResult()))) for arid, acodes in importedars.iteritems(): acodesmsg = ["Analysis %s" % acod for acod in acodes] self.log( "${request_id}: ${analysis_keywords} imported sucessfully", mapping={ "request_id": arid, "analysis_keywords": acodesmsg }) for instid, acodes in importedinsts.iteritems(): acodesmsg = ["Analysis %s" % acod for acod in acodes] msg = "%s: %s %s" % (instid, ", ".join(acodesmsg), "imported sucessfully") self.log(msg) if self.instrument_uid: self.log( "Import finished successfully: ${nr_updated_ars} ARs, " "${nr_updated_instruments} Instruments and ${nr_updated_results} " "results updated", mapping={ "nr_updated_ars": str(len(importedars)), "nr_updated_instruments": str(len(importedinsts)), "nr_updated_results": str(ancount) }) else: self.log( "Import finished successfully: ${nr_updated_ars} ARs and " "${nr_updated_results} results updated", mapping={ "nr_updated_ars": str(len(importedars)), "nr_updated_results": str(ancount) })