def getFormattedResult(self, specs=None, decimalmark='.', sciformat=1): """Formatted result: 1. If the result is not floatable, return it without being formatted 2. If the analysis specs has hidemin or hidemax enabled and the result is out of range, render result as '<min' or '>max' 3. If the result is floatable, render it to the correct precision specs param is optional. A dictionary as follows: {'min': <min_val>, 'max': <max_val>, 'error': <error>, 'hidemin': <hidemin_val>, 'hidemax': <hidemax_val>} :param sciformat: 1. The sci notation has to be formatted as aE^+b 2. The sci notation has to be formatted as a·10^b 3. As 2, but with super html entity for exp 4. The sci notation has to be formatted as a·10^b 5. As 4, but with super html entity for exp By default 1 """ result = self.getResult() service = self.getService() # 1. If the result is not floatable, return it without being formatted try: result = float(result) except: return result # 2. If the analysis specs has enabled hidemin or hidemax and the # result is out of range, render result as '<min' or '>max' belowmin = False abovemax = False if not specs: uid = self.getServiceUID() specs = self.aq_parent.getResultsRangeDict().get(uid, {}) hidemin = specs.get('hidemin', '') hidemax = specs.get('hidemax', '') try: belowmin = hidemin and result < float(hidemin) or False except: belowmin = False pass try: abovemax = hidemax and result > float(hidemax) or False except: abovemax = False pass # 2.1. If result is below min and hidemin enabled, return '<min' if belowmin: return formatDecimalMark('< %s' % hidemin, decimalmark) # 2.2. If result is above max and hidemax enabled, return '>max' if abovemax: return formatDecimalMark('> %s' % hidemax, decimalmark) # 3. If the result is floatable, render it to the correct precision return formatDecimalMark(format_numeric_result(self, result, sciformat), decimalmark)
def folderitems(self): rc = getToolByName(self.context, REFERENCE_CATALOG) bsc = getToolByName(self.context, 'bika_setup_catalog') workflow = getToolByName(self.context, 'portal_workflow') mtool = getToolByName(self.context, 'portal_membership') checkPermission = mtool.checkPermission if not self.allow_edit: can_edit_analyses = False else: if self.contentFilter.get('getPointOfCapture', '') == 'field': can_edit_analyses = checkPermission(EditFieldResults, self.context) else: can_edit_analyses = checkPermission(EditResults, self.context) self.allow_edit = can_edit_analyses self.show_select_column = self.allow_edit context_active = isActive(self.context) self.categories = [] items = super(AnalysesView, self).folderitems(full_objects = True) # manually skim retracted analyses from the list new_items = [] for i,item in enumerate(items): # self.contentsMethod may return brains or objects. if not ('obj' in items[i]): continue obj = hasattr(items[i]['obj'], 'getObject') and \ items[i]['obj'].getObject() or \ items[i]['obj'] if workflow.getInfoFor(obj, 'review_state') == 'retracted' \ and not checkPermission(ViewRetractedAnalyses, self.context): continue new_items.append(item) items = new_items methods = self.get_methods_vocabulary() self.interim_fields = {} self.interim_columns = {} self.specs = {} show_methodinstr_columns = False for i, item in enumerate(items): # self.contentsMethod may return brains or objects. obj = hasattr(items[i]['obj'], 'getObject') and \ items[i]['obj'].getObject() or \ items[i]['obj'] if workflow.getInfoFor(obj, 'review_state') == 'retracted' \ and not checkPermission(ViewRetractedAnalyses, self.context): continue result = obj.getResult() service = obj.getService() calculation = service.getCalculation() unit = service.getUnit() keyword = service.getKeyword() if self.show_categories: cat = obj.getService().getCategoryTitle() items[i]['category'] = cat if cat not in self.categories: self.categories.append(cat) # Check for InterimFields attribute on our object, interim_fields = hasattr(obj, 'getInterimFields') \ and obj.getInterimFields() or [] # kick some pretty display values in. for x in range(len(interim_fields)): interim_fields[x]['formatted_value'] = \ format_numeric_result(obj, interim_fields[x]['value']) self.interim_fields[obj.UID()] = interim_fields items[i]['service_uid'] = service.UID() items[i]['Service'] = service.Title() items[i]['Keyword'] = keyword items[i]['Unit'] = format_supsub(unit) if unit else '' items[i]['Result'] = '' items[i]['formatted_result'] = '' items[i]['interim_fields'] = interim_fields items[i]['Remarks'] = obj.getRemarks() items[i]['Uncertainty'] = '' items[i]['DetectionLimit'] = '' items[i]['retested'] = obj.getRetested() items[i]['class']['retested'] = 'center' items[i]['result_captured'] = self.ulocalized_time( obj.getResultCaptureDate(), long_format=0) items[i]['calculation'] = calculation and True or False try: items[i]['Partition'] = obj.getSamplePartition().getId() except AttributeError: items[i]['Partition'] = '' if obj.portal_type == "ReferenceAnalysis": items[i]['DueDate'] = self.ulocalized_time(obj.aq_parent.getExpiryDate(), long_format=0) else: items[i]['DueDate'] = self.ulocalized_time(obj.getDueDate(), long_format=1) cd = obj.getResultCaptureDate() items[i]['CaptureDate'] = cd and self.ulocalized_time(cd, long_format=1) or '' items[i]['Attachments'] = '' item['allow_edit'] = [] client_or_lab = "" tblrowclass = items[i].get('table_row_class'); if obj.portal_type == 'ReferenceAnalysis': items[i]['st_uid'] = obj.aq_parent.UID() items[i]['table_row_class'] = ' '.join([tblrowclass, 'qc-analysis']); elif obj.portal_type == 'DuplicateAnalysis' and \ obj.getAnalysis().portal_type == 'ReferenceAnalysis': items[i]['st_uid'] = obj.aq_parent.UID() items[i]['table_row_class'] = ' '.join([tblrowclass, 'qc-analysis']); else: sample = None if self.context.portal_type == 'AnalysisRequest': sample = self.context.getSample() elif self.context.portal_type == 'Worksheet': if obj.portal_type in ('DuplicateAnalysis', 'RejectAnalysis'): sample = obj.getAnalysis().getSample() else: sample = obj.aq_parent.getSample() elif self.context.portal_type == 'Sample': sample = self.context st_uid = sample.getSampleType().UID() if sample else '' items[i]['st_uid'] = st_uid if checkPermission(ManageBika, self.context): service_uid = service.UID() latest = rc.lookupObject(service_uid).version_id items[i]['Service'] = service.Title() items[i]['class']['Service'] = "service_title" # Show version number of out-of-date objects # No: This should be done in another column, if at all. # The (vX) value confuses some more fragile forms. # if hasattr(obj, 'reference_versions') and \ # service_uid in obj.reference_versions and \ # latest != obj.reference_versions[service_uid]: # items[i]['after']['Service'] = "(v%s)" % \ # (obj.reference_versions[service_uid]) # choices defined on Service apply to result fields. choices = service.getResultOptions() if choices: item['choices']['Result'] = choices # permission to view this item's results can_view_result = \ getSecurityManager().checkPermission(ViewResults, obj) # permission to edit this item's results # Editing Field Results is possible while in Sample Due. poc = self.contentFilter.get("getPointOfCapture", 'lab') can_edit_analysis = self.allow_edit and context_active and \ ( (poc == 'field' and getSecurityManager().checkPermission(EditFieldResults, obj)) or (poc != 'field' and getSecurityManager().checkPermission(EditResults, obj)) ) allowed_method_states = ['to_be_sampled', 'to_be_preserved', 'sample_received', 'sample_registered', 'sampled', 'assigned'] # Prevent from being edited if the instrument assigned # is not valid (out-of-date or uncalibrated), except if # the analysis is a QC with assigned status can_edit_analysis = can_edit_analysis \ and (obj.isInstrumentValid() \ or (obj.portal_type == 'ReferenceAnalysis' \ and item['review_state'] in allowed_method_states)) if can_edit_analysis: items[i]['allow_edit'].extend(['Analyst', 'Result', 'Remarks']) # if the Result field is editable, our interim fields are too for f in self.interim_fields[obj.UID()]: items[i]['allow_edit'].append(f['keyword']) # if there isn't a calculation then result must be re-testable, # and if there are interim fields, they too must be re-testable. if not items[i]['calculation'] or \ (items[i]['calculation'] and self.interim_fields[obj.UID()]): items[i]['allow_edit'].append('retested') # TODO: Only the labmanager must be able to change the method # can_set_method = getSecurityManager().checkPermission(SetAnalysisMethod, obj) can_set_method = can_edit_analysis \ and item['review_state'] in allowed_method_states method = obj.getMethod() \ if hasattr(obj, 'getMethod') and obj.getMethod() \ else service.getMethod() # Display the methods selector if the AS has at least one # method assigned item['Method'] = '' item['replace']['Method'] = '' if can_set_method: voc = self.get_methods_vocabulary(obj) if voc: # The service has at least one method available item['Method'] = method.UID() if method else '' item['choices']['Method'] = voc item['allow_edit'].append('Method') show_methodinstr_columns = True elif method: # This should never happen # The analysis has set a method, but its parent # service hasn't any method available O_o item['Method'] = method.Title() item['replace']['Method'] = "<a href='%s'>%s</a>" % \ (method.absolute_url(), method.Title()) show_methodinstr_columns = True elif method: # Edition not allowed, but method set item['Method'] = method.Title() item['replace']['Method'] = "<a href='%s'>%s</a>" % \ (method.absolute_url(), method.Title()) show_methodinstr_columns = True # TODO: Instrument selector dynamic behavior in worksheet Results # Only the labmanager must be able to change the instrument to be used. Also, # the instrument selection should be done in accordance with the method selected # can_set_instrument = service.getInstrumentEntryOfResults() and getSecurityManager().checkPermission(SetAnalysisInstrument, obj) can_set_instrument = service.getInstrumentEntryOfResults() \ and can_edit_analysis \ and item['review_state'] in allowed_method_states item['Instrument'] = '' item['replace']['Instrument'] = '' if service.getInstrumentEntryOfResults(): instrument = None # If the analysis has an instrument already assigned, use it if service.getInstrumentEntryOfResults() \ and hasattr(obj, 'getInstrument') \ and obj.getInstrument(): instrument = obj.getInstrument() # Otherwise, use the Service's default instrument elif service.getInstrumentEntryOfResults(): instrument = service.getInstrument() if can_set_instrument: # Edition allowed voc = self.get_instruments_vocabulary(obj) if voc: # The service has at least one instrument available item['Instrument'] = instrument.UID() if instrument else '' item['choices']['Instrument'] = voc item['allow_edit'].append('Instrument') show_methodinstr_columns = True elif instrument: # This should never happen # The analysis has an instrument set, but the # service hasn't any available instrument item['Instrument'] = instrument.Title() item['replace']['Instrument'] = "<a href='%s'>%s</a>" % \ (instrument.absolute_url(), instrument.Title()) show_methodinstr_columns = True elif instrument: # Edition not allowed, but instrument set item['Instrument'] = instrument.Title() item['replace']['Instrument'] = "<a href='%s'>%s</a>" % \ (instrument.absolute_url(), instrument.Title()) show_methodinstr_columns = True else: # Manual entry of results, instrument not allowed item['Instrument'] = _('Manual') msgtitle = t(_( "Instrument entry of results not allowed for ${service}", mapping={"service": safe_unicode(service.Title())}, )) item['replace']['Instrument'] = \ '<a href="#" title="%s">%s</a>' % (msgtitle, t(_('Manual'))) # Sets the analyst assigned to this analysis if can_edit_analysis: analyst = obj.getAnalyst() # widget default: current user if not analyst: analyst = mtool.getAuthenticatedMember().getUserName() items[i]['Analyst'] = analyst item['choices']['Analyst'] = self.getAnalysts() else: items[i]['Analyst'] = obj.getAnalystName() # If the user can attach files to analyses, show the attachment col can_add_attachment = \ getSecurityManager().checkPermission(AddAttachment, obj) if can_add_attachment or can_view_result: attachments = "" if hasattr(obj, 'getAttachment'): for attachment in obj.getAttachment(): af = attachment.getAttachmentFile() icon = af.getBestIcon() attachments += "<span class='attachment' attachment_uid='%s'>" % (attachment.UID()) if icon: attachments += "<img src='%s/%s'/>" % (self.portal_url, icon) attachments += '<a href="%s/at_download/AttachmentFile"/>%s</a>' % (attachment.absolute_url(), af.filename) if can_edit_analysis: attachments += "<img class='deleteAttachmentButton' attachment_uid='%s' src='%s'/>" % (attachment.UID(), "++resource++bika.lims.images/delete.png") attachments += "</br></span>" items[i]['replace']['Attachments'] = attachments[:-12] + "</span>" # Only display data bearing fields if we have ViewResults # permission, otherwise just put an icon in Result column. if can_view_result: items[i]['Result'] = result scinot = self.context.bika_setup.getScientificNotationResults() dmk = self.context.bika_setup.getResultsDecimalMark() items[i]['formatted_result'] = obj.getFormattedResult(sciformat=int(scinot),decimalmark=dmk) # LIMS-1379 Allow manual uncertainty value input # https://jira.bikalabs.com/browse/LIMS-1379 fu = format_uncertainty(obj, result, decimalmark=dmk, sciformat=int(scinot)) fu = fu if fu else '' if can_edit_analysis and service.getAllowManualUncertainty() == True: unc = obj.getUncertainty(result) item['allow_edit'].append('Uncertainty') items[i]['Uncertainty'] = unc if unc else '' items[i]['before']['Uncertainty'] = '± '; items[i]['after']['Uncertainty'] = '<em class="discreet" style="white-space:nowrap;"> %s</em>' % items[i]['Unit']; elif fu: items[i]['Uncertainty'] = fu items[i]['before']['Uncertainty'] = '± '; items[i]['after']['Uncertainty'] = '<em class="discreet" style="white-space:nowrap;"> %s</em>' % items[i]['Unit']; # LIMS-1700. Allow manual input of Detection Limits # LIMS-1775. Allow to select LDL or UDL defaults in results with readonly mode # https://jira.bikalabs.com/browse/LIMS-1700 # https://jira.bikalabs.com/browse/LIMS-1775 if can_edit_analysis and \ hasattr(obj, 'getDetectionLimitOperand') and \ hasattr(service, 'getDetectionLimitSelector') and \ service.getDetectionLimitSelector() == True: isldl = obj.isBelowLowerDetectionLimit() isudl = obj.isAboveUpperDetectionLimit() dlval='' if isldl or isudl: dlval = '<' if isldl else '>' item['allow_edit'].append('DetectionLimit') item['DetectionLimit'] = dlval choices=[{'ResultValue': '<', 'ResultText': '<'}, {'ResultValue': '>', 'ResultText': '>'}] item['choices']['DetectionLimit'] = choices self.columns['DetectionLimit']['toggle'] = True srv = obj.getService() defdls = {'min':srv.getLowerDetectionLimit(), 'max':srv.getUpperDetectionLimit(), 'manual':srv.getAllowManualDetectionLimit()} defin = '<input type="hidden" id="DefaultDLS.%s" value=\'%s\'/>' defin = defin % (obj.UID(), json.dumps(defdls)) item['after']['DetectionLimit'] = defin # LIMS-1769. Allow to use LDL and UDL in calculations. # https://jira.bikalabs.com/browse/LIMS-1769 # Since LDL, UDL, etc. are wildcards that can be used # in calculations, these fields must be loaded always # for 'live' calculations. if can_edit_analysis: dls = {'default_ldl': 'none', 'default_udl': 'none', 'below_ldl': False, 'above_udl': False, 'is_ldl': False, 'is_udl': False, 'manual_allowed': False, 'dlselect_allowed': False} if hasattr(obj, 'getDetectionLimits'): dls['below_ldl'] = obj.isBelowLowerDetectionLimit() dls['above_udl'] = obj.isBelowLowerDetectionLimit() dls['is_ldl'] = obj.isLowerDetectionLimit() dls['is_udl'] = obj.isUpperDetectionLimit() dls['default_ldl'] = service.getLowerDetectionLimit() dls['default_udl'] = service.getUpperDetectionLimit() dls['manual_allowed'] = service.getAllowManualDetectionLimit() dls['dlselect_allowed'] = service.getDetectionLimitSelector() dlsin = '<input type="hidden" id="AnalysisDLS.%s" value=\'%s\'/>' dlsin = dlsin % (obj.UID(), json.dumps(dls)) item['after']['Result'] = dlsin else: items[i]['Specification'] = "" if 'Result' in items[i]['allow_edit']: items[i]['allow_edit'].remove('Result') items[i]['before']['Result'] = \ '<img width="16" height="16" ' + \ 'src="%s/++resource++bika.lims.images/to_follow.png"/>' % \ (self.portal_url) # Everyone can see valid-ranges spec = self.get_analysis_spec(obj) if spec: min_val = spec.get('min', '') min_str = ">{0}".format(min_val) if min_val else '' max_val = spec.get('max', '') max_str = "<{0}".format(max_val) if max_val else '' error_val = spec.get('error', '') error_str = "{0}%".format(error_val) if error_val else '' rngstr = ",".join([x for x in [min_str, max_str, error_str] if x]) else: rngstr = "" items[i]['Specification'] = rngstr # Add this analysis' interim fields to the interim_columns list for f in self.interim_fields[obj.UID()]: if f['keyword'] not in self.interim_columns and not f.get('hidden', False): self.interim_columns[f['keyword']] = f['title'] # and to the item itself items[i][f['keyword']] = f items[i]['class'][f['keyword']] = 'interim' # check if this analysis is late/overdue resultdate = obj.aq_parent.getDateSampled() \ if obj.portal_type == 'ReferenceAnalysis' \ else obj.getResultCaptureDate() duedate = obj.aq_parent.getExpiryDate() \ if obj.portal_type == 'ReferenceAnalysis' \ else obj.getDueDate() items[i]['replace']['DueDate'] = \ self.ulocalized_time(duedate, long_format=1) if items[i]['review_state'] not in ['to_be_sampled', 'to_be_preserved', 'sample_due', 'published']: if (resultdate and resultdate > duedate) \ or (not resultdate and DateTime() > duedate): items[i]['replace']['DueDate'] = '%s <img width="16" height="16" src="%s/++resource++bika.lims.images/late.png" title="%s"/>' % \ (self.ulocalized_time(duedate, long_format=1), self.portal_url, t(_("Late Analysis"))) # Submitting user may not verify results (admin can though) if items[i]['review_state'] == 'to_be_verified' and \ not checkPermission(VerifyOwnResults, obj): user_id = getSecurityManager().getUser().getId() self_submitted = False try: review_history = list(workflow.getInfoFor(obj, 'review_history')) review_history.reverse() for event in review_history: if event.get('action') == 'submit': if event.get('actor') == user_id: self_submitted = True break if self_submitted: items[i]['after']['state_title'] = \ "<img src='++resource++bika.lims.images/submitted-by-current-user.png' title='%s'/>" % \ (t(_("Cannot verify: Submitted by current user"))) except WorkflowException: pass # add icon for assigned analyses in AR views if self.context.portal_type == 'AnalysisRequest': obj = items[i]['obj'] if obj.portal_type in ['ReferenceAnalysis', 'DuplicateAnalysis'] or \ workflow.getInfoFor(obj, 'worksheetanalysis_review_state') == 'assigned': br = obj.getBackReferences('WorksheetAnalysis') if len(br) > 0: ws = br[0] items[i]['after']['state_title'] = \ "<a href='%s'><img src='++resource++bika.lims.images/worksheet.png' title='%s'/></a>" % \ (ws.absolute_url(), t(_("Assigned to: ${worksheet_id}", mapping={'worksheet_id': safe_unicode(ws.id)}))) # the TAL requires values for all interim fields on all # items, so we set blank values in unused cells for item in items: for field in self.interim_columns: if field not in item: item[field] = '' # XXX order the list of interim columns interim_keys = self.interim_columns.keys() interim_keys.reverse() # add InterimFields keys to columns for col_id in interim_keys: if col_id not in self.columns: self.columns[col_id] = {'title': self.interim_columns[col_id], 'input_width': '6', 'input_class': 'ajax_calculate numeric', 'sortable': False} if can_edit_analyses: new_states = [] for state in self.review_states: # InterimFields are displayed in review_state # They are anyway available through View.columns though. # In case of hidden fields, the calcs.py should check calcs/services # for additional InterimFields!! pos = 'Result' in state['columns'] and \ state['columns'].index('Result') or len(state['columns']) for col_id in interim_keys: if col_id not in state['columns']: state['columns'].insert(pos, col_id) # retested column is added after Result. pos = 'Result' in state['columns'] and \ state['columns'].index('Uncertainty') + 1 or len(state['columns']) state['columns'].insert(pos, 'retested') new_states.append(state) self.review_states = new_states # Allow selecting individual analyses self.show_select_column = True # Dry Matter. # The Dry Matter column is never enabled for reference sample contexts # and refers to getReportDryMatter in ARs. if items and \ (hasattr(self.context, 'getReportDryMatter') and \ self.context.getReportDryMatter()): # look through all items # if the item's Service supports ReportDryMatter, add getResultDM(). for item in items: if item['obj'].getService().getReportDryMatter(): item['ResultDM'] = item['obj'].getResultDM() else: item['ResultDM'] = '' if item['ResultDM']: item['after']['ResultDM'] = "<em class='discreet'>%</em>" # modify the review_states list to include the ResultDM column new_states = [] for state in self.review_states: pos = 'Result' in state['columns'] and \ state['columns'].index('Uncertainty') + 1 or len(state['columns']) state['columns'].insert(pos, 'ResultDM') new_states.append(state) self.review_states = new_states self.categories.sort() # self.json_specs = json.dumps(self.specs) self.json_interim_fields = json.dumps(self.interim_fields) self.items = items # Method and Instrument columns must be shown or hidden at the # same time, because the value assigned to one causes # a value reassignment to the other (one method can be performed # by different instruments) self.columns['Method']['toggle'] = show_methodinstr_columns self.columns['Instrument']['toggle'] = show_methodinstr_columns return items
def folderitems(self): rc = getToolByName(self.context, REFERENCE_CATALOG) bsc = getToolByName(self.context, 'bika_setup_catalog') workflow = getToolByName(self.context, 'portal_workflow') mtool = getToolByName(self.context, 'portal_membership') checkPermission = mtool.checkPermission if not self.allow_edit: can_edit_analyses = False else: if self.contentFilter.get('getPointOfCapture', '') == 'field': can_edit_analyses = checkPermission(EditFieldResults, self.context) else: can_edit_analyses = checkPermission(EditResults, self.context) self.allow_edit = can_edit_analyses self.show_select_column = self.allow_edit context_active = isActive(self.context) self.categories = [] items = super(AnalysesView, self).folderitems(full_objects=True) # manually skim retracted analyses from the list new_items = [] for i, item in enumerate(items): # self.contentsMethod may return brains or objects. if not ('obj' in items[i]): continue obj = hasattr(items[i]['obj'], 'getObject') and \ items[i]['obj'].getObject() or \ items[i]['obj'] if workflow.getInfoFor(obj, 'review_state') == 'retracted' \ and not checkPermission(ViewRetractedAnalyses, self.context): continue new_items.append(item) items = new_items methods = self.get_methods_vocabulary() self.interim_fields = {} self.interim_columns = {} self.specs = {} show_methodinstr_columns = False for i, item in enumerate(items): # self.contentsMethod may return brains or objects. obj = hasattr(items[i]['obj'], 'getObject') and \ items[i]['obj'].getObject() or \ items[i]['obj'] if workflow.getInfoFor(obj, 'review_state') == 'retracted' \ and not checkPermission(ViewRetractedAnalyses, self.context): continue result = obj.getResult() service = obj.getService() calculation = service.getCalculation() unit = service.getUnit() keyword = service.getKeyword() if self.show_categories: cat = obj.getService().getCategoryTitle() items[i]['category'] = cat if cat not in self.categories: self.categories.append(cat) # Check for InterimFields attribute on our object, interim_fields = hasattr(obj, 'getInterimFields') \ and obj.getInterimFields() or [] # kick some pretty display values in. for x in range(len(interim_fields)): interim_fields[x]['formatted_value'] = \ format_numeric_result(obj, interim_fields[x]['value']) self.interim_fields[obj.UID()] = interim_fields items[i]['service_uid'] = service.UID() items[i]['Service'] = service.Title() items[i]['Keyword'] = keyword items[i]['Unit'] = format_supsub(unit) if unit else '' items[i]['Result'] = '' items[i]['formatted_result'] = '' items[i]['interim_fields'] = interim_fields items[i]['Remarks'] = obj.getRemarks() items[i]['Uncertainty'] = '' items[i]['DetectionLimit'] = '' items[i]['retested'] = obj.getRetested() items[i]['class']['retested'] = 'center' items[i]['result_captured'] = self.ulocalized_time( obj.getResultCaptureDate(), long_format=0) items[i]['calculation'] = calculation and True or False try: items[i]['Partition'] = obj.getSamplePartition().getId() except AttributeError: items[i]['Partition'] = '' if obj.portal_type == "ReferenceAnalysis": items[i]['DueDate'] = self.ulocalized_time( obj.aq_parent.getExpiryDate(), long_format=0) else: items[i]['DueDate'] = self.ulocalized_time(obj.getDueDate(), long_format=1) cd = obj.getResultCaptureDate() items[i]['CaptureDate'] = cd and self.ulocalized_time( cd, long_format=1) or '' items[i]['Attachments'] = '' item['allow_edit'] = [] client_or_lab = "" tblrowclass = items[i].get('table_row_class') if obj.portal_type == 'ReferenceAnalysis': items[i]['st_uid'] = obj.aq_parent.UID() items[i]['table_row_class'] = ' '.join( [tblrowclass, 'qc-analysis']) elif obj.portal_type == 'DuplicateAnalysis' and \ obj.getAnalysis().portal_type == 'ReferenceAnalysis': items[i]['st_uid'] = obj.aq_parent.UID() items[i]['table_row_class'] = ' '.join( [tblrowclass, 'qc-analysis']) else: sample = None if self.context.portal_type == 'AnalysisRequest': sample = self.context.getSample() elif self.context.portal_type == 'Worksheet': if obj.portal_type in ('DuplicateAnalysis', 'RejectAnalysis'): sample = obj.getAnalysis().getSample() else: sample = obj.aq_parent.getSample() elif self.context.portal_type == 'Sample': sample = self.context st_uid = sample.getSampleType().UID() if sample else '' items[i]['st_uid'] = st_uid if checkPermission(ManageBika, self.context): service_uid = service.UID() latest = rc.lookupObject(service_uid).version_id items[i]['Service'] = service.Title() items[i]['class']['Service'] = "service_title" # Show version number of out-of-date objects # No: This should be done in another column, if at all. # The (vX) value confuses some more fragile forms. # if hasattr(obj, 'reference_versions') and \ # service_uid in obj.reference_versions and \ # latest != obj.reference_versions[service_uid]: # items[i]['after']['Service'] = "(v%s)" % \ # (obj.reference_versions[service_uid]) # choices defined on Service apply to result fields. choices = service.getResultOptions() if choices: item['choices']['Result'] = choices # permission to view this item's results can_view_result = \ getSecurityManager().checkPermission(ViewResults, obj) # permission to edit this item's results # Editing Field Results is possible while in Sample Due. poc = self.contentFilter.get("getPointOfCapture", 'lab') can_edit_analysis = self.allow_edit and context_active and \ ( (poc == 'field' and getSecurityManager().checkPermission(EditFieldResults, obj)) or (poc != 'field' and getSecurityManager().checkPermission(EditResults, obj)) ) allowed_method_states = [ 'to_be_sampled', 'to_be_preserved', 'sample_received', 'sample_registered', 'sampled', 'assigned' ] # Prevent from being edited if the instrument assigned # is not valid (out-of-date or uncalibrated), except if # the analysis is a QC with assigned status can_edit_analysis = can_edit_analysis \ and (obj.isInstrumentValid() \ or (obj.portal_type == 'ReferenceAnalysis' \ and item['review_state'] in allowed_method_states)) if can_edit_analysis: items[i]['allow_edit'].extend(['Analyst', 'Result', 'Remarks']) # if the Result field is editable, our interim fields are too for f in self.interim_fields[obj.UID()]: items[i]['allow_edit'].append(f['keyword']) # if there isn't a calculation then result must be re-testable, # and if there are interim fields, they too must be re-testable. if not items[i]['calculation'] or \ (items[i]['calculation'] and self.interim_fields[obj.UID()]): items[i]['allow_edit'].append('retested') # TODO: Only the labmanager must be able to change the method # can_set_method = getSecurityManager().checkPermission(SetAnalysisMethod, obj) can_set_method = can_edit_analysis \ and item['review_state'] in allowed_method_states method = obj.getMethod() \ if hasattr(obj, 'getMethod') and obj.getMethod() \ else service.getMethod() # Display the methods selector if the AS has at least one # method assigned item['Method'] = '' item['replace']['Method'] = '' if can_set_method: voc = self.get_methods_vocabulary(obj) if voc: # The service has at least one method available item['Method'] = method.UID() if method else '' item['choices']['Method'] = voc item['allow_edit'].append('Method') show_methodinstr_columns = True elif method: # This should never happen # The analysis has set a method, but its parent # service hasn't any method available O_o item['Method'] = method.Title() item['replace']['Method'] = "<a href='%s'>%s</a>" % \ (method.absolute_url(), method.Title()) show_methodinstr_columns = True elif method: # Edition not allowed, but method set item['Method'] = method.Title() item['replace']['Method'] = "<a href='%s'>%s</a>" % \ (method.absolute_url(), method.Title()) show_methodinstr_columns = True # TODO: Instrument selector dynamic behavior in worksheet Results # Only the labmanager must be able to change the instrument to be used. Also, # the instrument selection should be done in accordance with the method selected # can_set_instrument = service.getInstrumentEntryOfResults() and getSecurityManager().checkPermission(SetAnalysisInstrument, obj) can_set_instrument = service.getInstrumentEntryOfResults() \ and can_edit_analysis \ and item['review_state'] in allowed_method_states item['Instrument'] = '' item['replace']['Instrument'] = '' if service.getInstrumentEntryOfResults(): instrument = None # If the analysis has an instrument already assigned, use it if service.getInstrumentEntryOfResults() \ and hasattr(obj, 'getInstrument') \ and obj.getInstrument(): instrument = obj.getInstrument() # Otherwise, use the Service's default instrument elif service.getInstrumentEntryOfResults(): instrument = service.getInstrument() if can_set_instrument: # Edition allowed voc = self.get_instruments_vocabulary(obj) if voc: # The service has at least one instrument available item['Instrument'] = instrument.UID( ) if instrument else '' item['choices']['Instrument'] = voc item['allow_edit'].append('Instrument') show_methodinstr_columns = True elif instrument: # This should never happen # The analysis has an instrument set, but the # service hasn't any available instrument item['Instrument'] = instrument.Title() item['replace']['Instrument'] = "<a href='%s'>%s</a>" % \ (instrument.absolute_url(), instrument.Title()) show_methodinstr_columns = True elif instrument: # Edition not allowed, but instrument set item['Instrument'] = instrument.Title() item['replace']['Instrument'] = "<a href='%s'>%s</a>" % \ (instrument.absolute_url(), instrument.Title()) show_methodinstr_columns = True else: # Manual entry of results, instrument not allowed item['Instrument'] = _('Manual') msgtitle = t( _( "Instrument entry of results not allowed for ${service}", mapping={"service": safe_unicode(service.Title())}, )) item['replace']['Instrument'] = \ '<a href="#" title="%s">%s</a>' % (msgtitle, t(_('Manual'))) # Sets the analyst assigned to this analysis if can_edit_analysis: analyst = obj.getAnalyst() # widget default: current user if not analyst: analyst = mtool.getAuthenticatedMember().getUserName() items[i]['Analyst'] = analyst item['choices']['Analyst'] = self.getAnalysts() else: items[i]['Analyst'] = obj.getAnalystName() # If the user can attach files to analyses, show the attachment col can_add_attachment = \ getSecurityManager().checkPermission(AddAttachment, obj) if can_add_attachment or can_view_result: attachments = "" if hasattr(obj, 'getAttachment'): for attachment in obj.getAttachment(): af = attachment.getAttachmentFile() icon = af.getBestIcon() attachments += "<span class='attachment' attachment_uid='%s'>" % ( attachment.UID()) if icon: attachments += "<img src='%s/%s'/>" % ( self.portal_url, icon) attachments += '<a href="%s/at_download/AttachmentFile"/>%s</a>' % ( attachment.absolute_url(), af.filename) if can_edit_analysis: attachments += "<img class='deleteAttachmentButton' attachment_uid='%s' src='%s'/>" % ( attachment.UID(), "++resource++bika.lims.images/delete.png") attachments += "</br></span>" items[i]['replace'][ 'Attachments'] = attachments[:-12] + "</span>" # Only display data bearing fields if we have ViewResults # permission, otherwise just put an icon in Result column. if can_view_result: items[i]['Result'] = result scinot = self.context.bika_setup.getScientificNotationResults() dmk = self.context.bika_setup.getResultsDecimalMark() items[i]['formatted_result'] = obj.getFormattedResult( sciformat=int(scinot), decimalmark=dmk) # LIMS-1379 Allow manual uncertainty value input # https://jira.bikalabs.com/browse/LIMS-1379 fu = format_uncertainty(obj, result, decimalmark=dmk, sciformat=int(scinot)) fu = fu if fu else '' if can_edit_analysis and service.getAllowManualUncertainty( ) == True: unc = obj.getUncertainty(result) item['allow_edit'].append('Uncertainty') items[i]['Uncertainty'] = unc if unc else '' items[i]['before']['Uncertainty'] = '± ' items[i]['after'][ 'Uncertainty'] = '<em class="discreet" style="white-space:nowrap;"> %s</em>' % items[ i]['Unit'] elif fu: items[i]['Uncertainty'] = fu items[i]['before']['Uncertainty'] = '± ' items[i]['after'][ 'Uncertainty'] = '<em class="discreet" style="white-space:nowrap;"> %s</em>' % items[ i]['Unit'] # LIMS-1700. Allow manual input of Detection Limits # LIMS-1775. Allow to select LDL or UDL defaults in results with readonly mode # https://jira.bikalabs.com/browse/LIMS-1700 # https://jira.bikalabs.com/browse/LIMS-1775 if can_edit_analysis and \ hasattr(obj, 'getDetectionLimitOperand') and \ hasattr(service, 'getDetectionLimitSelector') and \ service.getDetectionLimitSelector() == True: isldl = obj.isBelowLowerDetectionLimit() isudl = obj.isAboveUpperDetectionLimit() dlval = '' if isldl or isudl: dlval = '<' if isldl else '>' item['allow_edit'].append('DetectionLimit') item['DetectionLimit'] = dlval choices = [{ 'ResultValue': '<', 'ResultText': '<' }, { 'ResultValue': '>', 'ResultText': '>' }] item['choices']['DetectionLimit'] = choices self.columns['DetectionLimit']['toggle'] = True srv = obj.getService() defdls = { 'min': srv.getLowerDetectionLimit(), 'max': srv.getUpperDetectionLimit(), 'manual': srv.getAllowManualDetectionLimit() } defin = '<input type="hidden" id="DefaultDLS.%s" value=\'%s\'/>' defin = defin % (obj.UID(), json.dumps(defdls)) item['after']['DetectionLimit'] = defin # LIMS-1769. Allow to use LDL and UDL in calculations. # https://jira.bikalabs.com/browse/LIMS-1769 # Since LDL, UDL, etc. are wildcards that can be used # in calculations, these fields must be loaded always # for 'live' calculations. if can_edit_analysis: dls = { 'default_ldl': 'none', 'default_udl': 'none', 'below_ldl': False, 'above_udl': False, 'is_ldl': False, 'is_udl': False, 'manual_allowed': False, 'dlselect_allowed': False } if hasattr(obj, 'getDetectionLimits'): dls['below_ldl'] = obj.isBelowLowerDetectionLimit() dls['above_udl'] = obj.isBelowLowerDetectionLimit() dls['is_ldl'] = obj.isLowerDetectionLimit() dls['is_udl'] = obj.isUpperDetectionLimit() dls['default_ldl'] = service.getLowerDetectionLimit() dls['default_udl'] = service.getUpperDetectionLimit() dls['manual_allowed'] = service.getAllowManualDetectionLimit( ) dls['dlselect_allowed'] = service.getDetectionLimitSelector( ) dlsin = '<input type="hidden" id="AnalysisDLS.%s" value=\'%s\'/>' dlsin = dlsin % (obj.UID(), json.dumps(dls)) item['after']['Result'] = dlsin else: items[i]['Specification'] = "" if 'Result' in items[i]['allow_edit']: items[i]['allow_edit'].remove('Result') items[i]['before']['Result'] = \ '<img width="16" height="16" ' + \ 'src="%s/++resource++bika.lims.images/to_follow.png"/>' % \ (self.portal_url) # Everyone can see valid-ranges spec = self.get_analysis_spec(obj) if spec: min_val = spec.get('min', '') min_str = ">{0}".format(min_val) if min_val else '' max_val = spec.get('max', '') max_str = "<{0}".format(max_val) if max_val else '' error_val = spec.get('error', '') error_str = "{0}%".format(error_val) if error_val else '' rngstr = ",".join( [x for x in [min_str, max_str, error_str] if x]) else: rngstr = "" items[i]['Specification'] = rngstr # Add this analysis' interim fields to the interim_columns list for f in self.interim_fields[obj.UID()]: if f['keyword'] not in self.interim_columns and not f.get( 'hidden', False): self.interim_columns[f['keyword']] = f['title'] # and to the item itself items[i][f['keyword']] = f items[i]['class'][f['keyword']] = 'interim' # check if this analysis is late/overdue resultdate = obj.aq_parent.getDateSampled() \ if obj.portal_type == 'ReferenceAnalysis' \ else obj.getResultCaptureDate() duedate = obj.aq_parent.getExpiryDate() \ if obj.portal_type == 'ReferenceAnalysis' \ else obj.getDueDate() items[i]['replace']['DueDate'] = \ self.ulocalized_time(duedate, long_format=1) if items[i]['review_state'] not in [ 'to_be_sampled', 'to_be_preserved', 'sample_due', 'published' ]: if (resultdate and resultdate > duedate) \ or (not resultdate and DateTime() > duedate): items[i]['replace']['DueDate'] = '%s <img width="16" height="16" src="%s/++resource++bika.lims.images/late.png" title="%s"/>' % \ (self.ulocalized_time(duedate, long_format=1), self.portal_url, t(_("Late Analysis"))) # Submitting user may not verify results (admin can though) if items[i]['review_state'] == 'to_be_verified' and \ not checkPermission(VerifyOwnResults, obj): user_id = getSecurityManager().getUser().getId() self_submitted = False try: review_history = list( workflow.getInfoFor(obj, 'review_history')) review_history.reverse() for event in review_history: if event.get('action') == 'submit': if event.get('actor') == user_id: self_submitted = True break if self_submitted: items[i]['after']['state_title'] = \ "<img src='++resource++bika.lims.images/submitted-by-current-user.png' title='%s'/>" % \ (t(_("Cannot verify: Submitted by current user"))) except WorkflowException: pass # add icon for assigned analyses in AR views if self.context.portal_type == 'AnalysisRequest': obj = items[i]['obj'] if obj.portal_type in ['ReferenceAnalysis', 'DuplicateAnalysis'] or \ workflow.getInfoFor(obj, 'worksheetanalysis_review_state') == 'assigned': br = obj.getBackReferences('WorksheetAnalysis') if len(br) > 0: ws = br[0] items[i]['after']['state_title'] = \ "<a href='%s'><img src='++resource++bika.lims.images/worksheet.png' title='%s'/></a>" % \ (ws.absolute_url(), t(_("Assigned to: ${worksheet_id}", mapping={'worksheet_id': safe_unicode(ws.id)}))) # the TAL requires values for all interim fields on all # items, so we set blank values in unused cells for item in items: for field in self.interim_columns: if field not in item: item[field] = '' # XXX order the list of interim columns interim_keys = self.interim_columns.keys() interim_keys.reverse() # add InterimFields keys to columns for col_id in interim_keys: if col_id not in self.columns: self.columns[col_id] = { 'title': self.interim_columns[col_id], 'input_width': '6', 'input_class': 'ajax_calculate numeric', 'sortable': False } if can_edit_analyses: new_states = [] for state in self.review_states: # InterimFields are displayed in review_state # They are anyway available through View.columns though. # In case of hidden fields, the calcs.py should check calcs/services # for additional InterimFields!! pos = 'Result' in state['columns'] and \ state['columns'].index('Result') or len(state['columns']) for col_id in interim_keys: if col_id not in state['columns']: state['columns'].insert(pos, col_id) # retested column is added after Result. pos = 'Result' in state['columns'] and \ state['columns'].index('Uncertainty') + 1 or len(state['columns']) state['columns'].insert(pos, 'retested') new_states.append(state) self.review_states = new_states # Allow selecting individual analyses self.show_select_column = True # Dry Matter. # The Dry Matter column is never enabled for reference sample contexts # and refers to getReportDryMatter in ARs. if items and \ (hasattr(self.context, 'getReportDryMatter') and \ self.context.getReportDryMatter()): # look through all items # if the item's Service supports ReportDryMatter, add getResultDM(). for item in items: if item['obj'].getService().getReportDryMatter(): item['ResultDM'] = item['obj'].getResultDM() else: item['ResultDM'] = '' if item['ResultDM']: item['after']['ResultDM'] = "<em class='discreet'>%</em>" # modify the review_states list to include the ResultDM column new_states = [] for state in self.review_states: pos = 'Result' in state['columns'] and \ state['columns'].index('Uncertainty') + 1 or len(state['columns']) state['columns'].insert(pos, 'ResultDM') new_states.append(state) self.review_states = new_states self.categories.sort() # self.json_specs = json.dumps(self.specs) self.json_interim_fields = json.dumps(self.interim_fields) self.items = items # Method and Instrument columns must be shown or hidden at the # same time, because the value assigned to one causes # a value reassignment to the other (one method can be performed # by different instruments) self.columns['Method']['toggle'] = show_methodinstr_columns self.columns['Instrument']['toggle'] = show_methodinstr_columns return items
def __call__(self): pc = self.portal_catalog self.checkPermission = self.context.portal_membership.checkPermission self.now = DateTime() self.SamplingWorkflowEnabled = self.context.bika_setup.getSamplingWorkflowEnabled() # Client details (if client is associated) self.client = None client_uid = hasattr(self.context, 'getClientUID') and self.context.getClientUID() if client_uid: proxies = pc(portal_type='Client', UID=client_uid) if proxies: self.client = proxies[0].getObject() client_address = self.client.getPostalAddress() if self.contact and not client_address: client_address = self.contact.getBillingAddress() if not client_address: client_address = self.contact.getPhysicalAddress() if client_address: _keys = ['address', 'city', 'state', 'zip', 'country'] _list = [client_address.get(v) for v in _keys if client_address.get(v)] self.client_address = "<br/>".join(_list).replace("\n", "<br/>") if self.client_address.endswith("<br/>"): self.client_address = self.client_address[:-5] else: self.client_address = None # Reporter self.member = self.context.portal_membership.getAuthenticatedMember() self.username = self.member.getUserName() self.reporter = self.user_fullname(self.username) self.reporter_email = self.user_email(self.username) self.reporter_signature = "" c = [x for x in self.bika_setup_catalog(portal_type='LabContact') if x.getObject().getUsername() == self.username] if c: sf = c[0].getObject().getSignature() if sf: self.reporter_signature = sf.absolute_url() + "/Signature" # laboratory self.laboratory = self.context.bika_setup.laboratory self.accredited = self.laboratory.getLaboratoryAccredited() lab_address = self.laboratory.getPrintAddress() if lab_address: _keys = ['address', 'city', 'state', 'zip', 'country'] _list = [lab_address.get(v) for v in _keys if lab_address.get(v)] self.lab_address = "<br/>".join(_list).replace("\n", "<br/>") if self.lab_address.endswith("<br/>"): self.lab_address = self.lab_address[:-5] else: self.lab_address = None # Analysis Request results self.ars = [] self.ar_headers = [_("Request ID"), _("Date Requested"), _("Sample Type"), _("Sample Point")] self.analysis_headers = [_("Analysis Service"), _("Method"), _("Result"), _("Analyst")] for ar in self.context.getAnalysisRequests(): datecreated = ar.created() # datereceived = ar.getDateReceived() # datepublished = ar.getDatePublished() datalines = [] for analysis in ar.getAnalyses(full_objects=True): service = analysis.getService() method = service.getMethod() sample = ar.getSample() result = analysis.getResult() formatted_result = format_numeric_result(analysis, result) datalines.append({_("Analysis Service"): analysis.getService().Title(), _("Method"): method and method.Title() or "", _("Result"): formatted_result, _("Analyst"): self.user_fullname(analysis.getAnalyst()), _("Remarks"): analysis.getRemarks()}) self.ars.append({ _("Request ID"): ar.getRequestID(), _("Date Requested"): self.ulocalized_time(datecreated), # requested->created _("Sample Type"): sample.getSampleType() and sample.getSampleType().Title() or '', _("Sample Point"): sample.getSamplePoint() and sample.getSamplePoint().Title() or '', _("datalines"): datalines, }) # Create Report fn = self.context.Title() + " " + self.ulocalized_time(self.now) report_html = self.template() # debug_mode = App.config.getConfiguration().debug_mode "Commented by Yasir" debug_mode = True #"Added by Yasir" if debug_mode: tmp_fd, tmp_fn = tempfile.mkstemp(suffix=".html") logger.debug("Writing HTML for %s to %s" % (self.context, tmp_fn)) tmp_fd.write(report_html) tmp_fd.close() pdf_fd, pdf_fn = tempfile.mkstemp(suffix="pdf") pdf_fd.close() pdf = createPdf(report_html, outfile=pdf_fn) if debug_mode: logger.debug("Wrote PDF for %s to %s" % (self.context, pdf_fn)) else: os.remove(pdf_fn) # XXX Email published batches to who? # Send PDF to browser if not pdf.err: setheader = self.request.RESPONSE.setHeader setheader('Content-Type', 'application/pdf') setheader("Content-Disposition", "attachment;filename=\"%s\"" % fn) self.request.RESPONSE.write(pdf)
def __call__(self): pc = self.portal_catalog self.checkPermission = self.context.portal_membership.checkPermission self.now = DateTime() self.SamplingWorkflowEnabled = self.context.bika_setup.getSamplingWorkflowEnabled( ) # Client details (if client is associated) self.client = None client_uid = hasattr(self.context, 'getClientUID') and self.context.getClientUID() if client_uid: proxies = pc(portal_type='Client', UID=client_uid) if proxies: self.client = proxies[0].getObject() client_address = self.client.getPostalAddress() if self.contact and not client_address: client_address = self.contact.getBillingAddress() if not client_address: client_address = self.contact.getPhysicalAddress() if client_address: _keys = ['address', 'city', 'state', 'zip', 'country'] _list = [ client_address.get(v) for v in _keys if client_address.get(v) ] self.client_address = "<br/>".join(_list).replace( "\n", "<br/>") if self.client_address.endswith("<br/>"): self.client_address = self.client_address[:-5] else: self.client_address = None # Reporter self.member = self.context.portal_membership.getAuthenticatedMember() self.username = self.member.getUserName() self.reporter = self.user_fullname(self.username) self.reporter_email = self.user_email(self.username) self.reporter_signature = "" c = [ x for x in self.bika_setup_catalog(portal_type='LabContact') if x.getObject().getUsername() == self.username ] if c: sf = c[0].getObject().getSignature() if sf: self.reporter_signature = sf.absolute_url() + "/Signature" # laboratory self.laboratory = self.context.bika_setup.laboratory self.accredited = self.laboratory.getLaboratoryAccredited() lab_address = self.laboratory.getPrintAddress() if lab_address: _keys = ['address', 'city', 'state', 'zip', 'country'] _list = [lab_address.get(v) for v in _keys if lab_address.get(v)] self.lab_address = "<br/>".join(_list).replace("\n", "<br/>") if self.lab_address.endswith("<br/>"): self.lab_address = self.lab_address[:-5] else: self.lab_address = None # Analysis Request results self.ars = [] self.ar_headers = [ _("Request ID"), _("Date Requested"), _("Sample Type"), _("Sample Point") ] self.analysis_headers = [ _("Analysis Service"), _("Method"), _("Result"), _("Analyst") ] for ar in self.context.getAnalysisRequests(): datecreated = ar.created() # datereceived = ar.getDateReceived() # datepublished = ar.getDatePublished() datalines = [] for analysis in ar.getAnalyses(full_objects=True): service = analysis.getService() method = service.getMethod() sample = ar.getSample() result = analysis.getResult() formatted_result = format_numeric_result(analysis, result) datalines.append({ _("Analysis Service"): analysis.getService().Title(), _("Method"): method and method.Title() or "", _("Result"): formatted_result, _("Analyst"): self.user_fullname(analysis.getAnalyst()), _("Remarks"): analysis.getRemarks() }) self.ars.append({ _("Request ID"): ar.getRequestID(), _("Date Requested"): self.ulocalized_time(datecreated), # requested->created _("Sample Type"): sample.getSampleType() and sample.getSampleType().Title() or '', _("Sample Point"): sample.getSamplePoint() and sample.getSamplePoint().Title() or '', _("datalines"): datalines, }) # Create Report fn = self.context.Title() + " " + self.ulocalized_time(self.now) report_html = self.template() # debug_mode = App.config.getConfiguration().debug_mode "Commented by Yasir" debug_mode = True #"Added by Yasir" if debug_mode: tmp_fd, tmp_fn = tempfile.mkstemp(suffix=".html") logger.debug("Writing HTML for %s to %s" % (self.context, tmp_fn)) tmp_fd.write(report_html) tmp_fd.close() pdf_fd, pdf_fn = tempfile.mkstemp(suffix="pdf") pdf_fd.close() pdf = createPdf(report_html, outfile=pdf_fn) if debug_mode: logger.debug("Wrote PDF for %s to %s" % (self.context, pdf_fn)) else: os.remove(pdf_fn) # XXX Email published batches to who? # Send PDF to browser if not pdf.err: setheader = self.request.RESPONSE.setHeader setheader('Content-Type', 'application/pdf') setheader("Content-Disposition", "attachment;filename=\"%s\"" % fn) self.request.RESPONSE.write(pdf)
def calculate(self, uid=None): analysis = self.analyses[uid] form_result = self.current_results[uid]['result'] service = analysis.getService() calculation = service.getCalculation() if analysis.portal_type == 'ReferenceAnalysis': deps = {} else: deps = {} for dep in analysis.getDependencies(): deps[dep.UID()] = dep path = '++resource++bika.lims.images' mapping = {} # values to be returned to form for this UID Result = {'uid': uid, 'result': form_result} try: Result['result'] = float(form_result) except: if form_result == "0/0": Result['result'] = "" if calculation: ''' We need first to create the map of available parameters acording to the interims, analyses and wildcards: params = { <as-1-keyword> : <analysis_result>, <as-1-keyword>.<wildcard-1> : <wildcard_1_value>, <as-1-keyword>.<wildcard-2> : <wildcard_2_value>, <interim-1> : <interim_result>, ... } ''' # Get dependent analyses results and wildcard values to the # mapping. If dependent analysis without result found, # break and abort calculation unsatisfied = False for dependency_uid, dependency in deps.items(): if dependency_uid in self.ignore_uids: unsatisfied = True break # LIMS-1769. Allow to use LDL and UDL in calculations. # https://jira.bikalabs.com/browse/LIMS-1769 analysisvalues = {} if dependency_uid in self.current_results: analysisvalues = self.current_results[dependency_uid] else: # Retrieve the result and DLs from the analysis analysisvalues = { 'keyword': dependency.getKeyword(), 'result': dependency.getResult(), 'ldl': dependency.getLowerDetectionLimit(), 'udl': dependency.getUpperDetectionLimit(), 'belowldl': dependency.isBelowLowerDetectionLimit(), 'aboveudl': dependency.isAboveUpperDetectionLimit(), } if analysisvalues['result']=='': unsatisfied = True break; key = analysisvalues.get('keyword',dependency.getService().getKeyword()) # Analysis result # All result mappings must be float, or they are ignored. try: mapping[key] = float(analysisvalues.get('result')) mapping['%s.%s' % (key, 'RESULT')] = float(analysisvalues.get('result')) mapping['%s.%s' % (key, 'LDL')] = float(analysisvalues.get('ldl')) mapping['%s.%s' % (key, 'UDL')] = float(analysisvalues.get('udl')) mapping['%s.%s' % (key, 'BELOWLDL')] = int(analysisvalues.get('belowldl')) mapping['%s.%s' % (key, 'ABOVEUDL')] = int(analysisvalues.get('aboveudl')) except: # If not floatable, then abort! unsatisfied = True break if unsatisfied: # unsatisfied means that one or more result on which we depend # is blank or unavailable, so we set blank result and abort. self.results.append({'uid': uid, 'result': '', 'formatted_result': ''}) return None # Add all interims to mapping for i_uid, i_data in self.item_data.items(): for i in i_data: # if this interim belongs to current analysis and is blank, # return an empty result for this analysis. if i_uid == uid and i['value'] == '': self.results.append({'uid': uid, 'result': '', 'formatted_result': ''}) return None # All interims must be float, or they are ignored. try: i['value'] = float(i['value']) except: pass # all interims are ServiceKeyword.InterimKeyword if i_uid in deps: key = "%s.%s" % (deps[i_uid].getService().getKeyword(), i['keyword']) mapping[key] = i['value'] # this analysis' interims get extra reference # without service keyword prefix if uid == i_uid: mapping[i['keyword']] = i['value'] # Grab values for hidden InterimFields for only for current calculation # we can't allow non-floats through here till we change the eval's # interpolation hidden_fields = [] c_fields = calculation.getInterimFields() s_fields = service.getInterimFields() for field in c_fields: if field.get('hidden', False): hidden_fields.append(field['keyword']) try: mapping[field['keyword']] = float(field['value']) except ValueError: pass # also grab stickier defaults from AnalysisService for field in s_fields: if field['keyword'] in hidden_fields: try: mapping[field['keyword']] = float(field['value']) except ValueError: pass # convert formula to a valid python string, ready for interpolation formula = calculation.getMinifiedFormula() formula = formula.replace('[', '%(').replace(']', ')f') try: formula = eval("'%s'%%mapping" % formula, {"__builtins__": None, 'math': math, 'context': self.context}, {'mapping': mapping}) # calculate result = eval(formula) Result['result'] = result self.current_results[uid]['result'] = result except TypeError as e: # non-numeric arguments in interim mapping? alert = {'field': 'Result', 'icon': path + '/exclamation.png', 'msg': "{0}: {1} ({2}) ".format( t(_("Type Error")), html_quote(str(e.args[0])), formula)} if uid in self.alerts: self.alerts[uid].append(alert) else: self.alerts[uid] = [alert, ] except ZeroDivisionError as e: Result['result'] = '0/0' Result['formatted_result'] = '0/0' self.current_results[uid]['result'] = '0/0' self.results.append(Result) alert = {'field': 'Result', 'icon': path + '/exclamation.png', 'msg': "{0}: {1} ({2}) ".format( t(_("Division by zero")), html_quote(str(e.args[0])), formula)} if uid in self.alerts: self.alerts[uid].append(alert) else: self.alerts[uid] = [alert, ] return None except KeyError as e: alert = {'field': 'Result', 'icon': path + '/exclamation.png', 'msg': "{0}: {1} ({2}) ".format( t(_("Key Error")), html_quote(str(e.args[0])), formula)} if uid in self.alerts: self.alerts[uid].append(alert) else: self.alerts[uid] = [alert, ] # format result belowmin = False abovemax = False # Some analyses will not have AnalysisSpecs, eg, ReferenceAnalysis if hasattr(analysis, 'getAnalysisSpecs'): specs = analysis.getAnalysisSpecs() specs = specs.getResultsRangeDict() if specs is not None else {} specs = specs.get(analysis.getKeyword(), {}) hidemin = specs.get('hidemin', '') hidemax = specs.get('hidemax', '') if Result.get('result', ''): fresult = Result['result'] try: belowmin = hidemin and fresult < float(hidemin) or False except ValueError: belowmin = False pass try: abovemax = hidemax and fresult > float(hidemax) or False except ValueError: abovemax = False pass if belowmin is True: Result['formatted_result'] = '< %s' % hidemin elif abovemax is True: Result['formatted_result'] = '> %s' % hidemax else: try: Result['formatted_result'] = format_numeric_result(analysis, Result['result']) except ValueError: # non-float Result['formatted_result'] = Result['result'] # calculate Dry Matter result # if parent is not an AR, it's never going to be calculable dm = hasattr(analysis.aq_parent, 'getReportDryMatter') and \ analysis.aq_parent.getReportDryMatter() and \ analysis.getService().getReportDryMatter() if dm: dry_service = self.context.bika_setup.getDryMatterService() # get the UID of the DryMatter Analysis from our parent AR dry_analysis = [a for a in analysis.aq_parent.getAnalyses(full_objects=True) if a.getService().UID() == dry_service.UID()] if dry_analysis: dry_analysis = dry_analysis[0] dry_uid = dry_analysis.UID() # get the current DryMatter analysis result from the form if dry_uid in self.current_results: try: dry_result = float(self.current_results[dry_uid]) except: dm = False else: try: dry_result = float(dry_analysis.getResult()) except: dm = False else: dm = False Result['dry_result'] = dm and dry_result and \ '%.2f' % ((Result['result'] / dry_result) * 100) or '' self.results.append(Result) # if App.config.getConfiguration().debug_mode: # logger.info("calc.py: %s->%s %s" % (analysis.aq_parent.id, # analysis.id, # Result)) # LIMS-1808 Uncertainty calculation on DL # https://jira.bikalabs.com/browse/LIMS-1808 flres = Result.get('result', None) if flres and isnumber(flres): flres = float(flres) anvals = self.current_results[uid] isldl = anvals.get('isldl', False) isudl = anvals.get('isudl', False) ldl = anvals.get('ldl',0) udl = anvals.get('udl',0) ldl = float(ldl) if isnumber(ldl) else 0 udl = float(udl) if isnumber(udl) else 10000000 belowldl = (isldl or flres < ldl) aboveudl = (isudl or flres > udl) unc = '' if (belowldl or aboveudl) else analysis.getUncertainty(Result.get('result')) if not (belowldl or aboveudl): self.uncertainties.append({'uid': uid, 'uncertainty': unc}) # maybe a service who depends on us must be recalculated. if analysis.portal_type == 'ReferenceAnalysis': dependents = [] else: dependents = analysis.getDependents() if dependents: for dependent in dependents: dependent_uid = dependent.UID() # ignore analyses that no longer exist. if dependent_uid in self.ignore_uids or \ dependent_uid not in self.analyses: continue self.calculate(dependent_uid) # These self.alerts are just for the json return. # we're placing the entire form's results in kwargs. adapters = getAdapters((analysis, ), IFieldIcons) for name, adapter in adapters: alerts = adapter(result=Result['result'], form_results=self.current_results) if alerts: if analysis.UID() in self.alerts: self.alerts[analysis.UID()].extend(alerts[analysis.UID()]) else: self.alerts[analysis.UID()] = alerts[analysis.UID()]
def calculate(self, uid=None): analysis = self.analyses[uid] form_result = self.current_results[uid]['result'] service = analysis.getService() calculation = service.getCalculation() if analysis.portal_type == 'ReferenceAnalysis': deps = {} else: deps = {} for dep in analysis.getDependencies(): deps[dep.UID()] = dep path = '++resource++bika.lims.images' mapping = {} # values to be returned to form for this UID Result = {'uid': uid, 'result': form_result} try: Result['result'] = float(form_result) except: if form_result == "0/0": Result['result'] = "" if calculation: ''' We need first to create the map of available parameters acording to the interims, analyses and wildcards: params = { <as-1-keyword> : <analysis_result>, <as-1-keyword>.<wildcard-1> : <wildcard_1_value>, <as-1-keyword>.<wildcard-2> : <wildcard_2_value>, <interim-1> : <interim_result>, ... } ''' # Get dependent analyses results and wildcard values to the # mapping. If dependent analysis without result found, # break and abort calculation unsatisfied = False for dependency_uid, dependency in deps.items(): if dependency_uid in self.ignore_uids: unsatisfied = True break # LIMS-1769. Allow to use LDL and UDL in calculations. # https://jira.bikalabs.com/browse/LIMS-1769 analysisvalues = {} if dependency_uid in self.current_results: analysisvalues = self.current_results[dependency_uid] else: # Retrieve the result and DLs from the analysis analysisvalues = { 'keyword': dependency.getKeyword(), 'result': dependency.getResult(), 'ldl': dependency.getLowerDetectionLimit(), 'udl': dependency.getUpperDetectionLimit(), 'belowldl': dependency.isBelowLowerDetectionLimit(), 'aboveudl': dependency.isAboveUpperDetectionLimit(), } if analysisvalues['result'] == '': unsatisfied = True break key = analysisvalues.get('keyword', dependency.getService().getKeyword()) # Analysis result # All result mappings must be float, or they are ignored. try: mapping[key] = float(analysisvalues.get('result')) mapping['%s.%s' % (key, 'RESULT')] = float( analysisvalues.get('result')) mapping['%s.%s' % (key, 'LDL')] = float( analysisvalues.get('ldl')) mapping['%s.%s' % (key, 'UDL')] = float( analysisvalues.get('udl')) mapping['%s.%s' % (key, 'BELOWLDL')] = int( analysisvalues.get('belowldl')) mapping['%s.%s' % (key, 'ABOVEUDL')] = int( analysisvalues.get('aboveudl')) except: # If not floatable, then abort! unsatisfied = True break if unsatisfied: # unsatisfied means that one or more result on which we depend # is blank or unavailable, so we set blank result and abort. self.results.append({ 'uid': uid, 'result': '', 'formatted_result': '' }) return None # Add all interims to mapping for i_uid, i_data in self.item_data.items(): for i in i_data: # if this interim belongs to current analysis and is blank, # return an empty result for this analysis. if i_uid == uid and i['value'] == '': self.results.append({ 'uid': uid, 'result': '', 'formatted_result': '' }) return None # All interims must be float, or they are ignored. try: i['value'] = float(i['value']) except: pass # all interims are ServiceKeyword.InterimKeyword if i_uid in deps: key = "%s.%s" % (deps[i_uid].getService().getKeyword(), i['keyword']) mapping[key] = i['value'] # this analysis' interims get extra reference # without service keyword prefix if uid == i_uid: mapping[i['keyword']] = i['value'] # Grab values for hidden InterimFields for only for current calculation # we can't allow non-floats through here till we change the eval's # interpolation hidden_fields = [] c_fields = calculation.getInterimFields() s_fields = service.getInterimFields() for field in c_fields: if field.get('hidden', False): hidden_fields.append(field['keyword']) try: mapping[field['keyword']] = float(field['value']) except ValueError: pass # also grab stickier defaults from AnalysisService for field in s_fields: if field['keyword'] in hidden_fields: try: mapping[field['keyword']] = float(field['value']) except ValueError: pass # convert formula to a valid python string, ready for interpolation formula = calculation.getMinifiedFormula() formula = formula.replace('[', '%(').replace(']', ')f') try: formula = eval("'%s'%%mapping" % formula, { "__builtins__": None, 'math': math, 'context': self.context }, {'mapping': mapping}) # calculate result = eval(formula) Result['result'] = result self.current_results[uid]['result'] = result except TypeError as e: # non-numeric arguments in interim mapping? alert = { 'field': 'Result', 'icon': path + '/exclamation.png', 'msg': "{0}: {1} ({2}) ".format(t(_("Type Error")), html_quote(str(e.args[0])), formula) } if uid in self.alerts: self.alerts[uid].append(alert) else: self.alerts[uid] = [ alert, ] except ZeroDivisionError as e: Result['result'] = '0/0' Result['formatted_result'] = '0/0' self.current_results[uid]['result'] = '0/0' self.results.append(Result) alert = { 'field': 'Result', 'icon': path + '/exclamation.png', 'msg': "{0}: {1} ({2}) ".format(t(_("Division by zero")), html_quote(str(e.args[0])), formula) } if uid in self.alerts: self.alerts[uid].append(alert) else: self.alerts[uid] = [ alert, ] return None except KeyError as e: alert = { 'field': 'Result', 'icon': path + '/exclamation.png', 'msg': "{0}: {1} ({2}) ".format(t(_("Key Error")), html_quote(str(e.args[0])), formula) } if uid in self.alerts: self.alerts[uid].append(alert) else: self.alerts[uid] = [ alert, ] # format result belowmin = False abovemax = False # Some analyses will not have AnalysisSpecs, eg, ReferenceAnalysis if hasattr(analysis, 'getAnalysisSpecs'): specs = analysis.getAnalysisSpecs() specs = specs.getResultsRangeDict() if specs is not None else {} specs = specs.get(analysis.getKeyword(), {}) hidemin = specs.get('hidemin', '') hidemax = specs.get('hidemax', '') if Result.get('result', ''): fresult = Result['result'] try: belowmin = hidemin and fresult < float(hidemin) or False except ValueError: belowmin = False pass try: abovemax = hidemax and fresult > float(hidemax) or False except ValueError: abovemax = False pass if belowmin is True: Result['formatted_result'] = '< %s' % hidemin elif abovemax is True: Result['formatted_result'] = '> %s' % hidemax else: try: Result['formatted_result'] = format_numeric_result( analysis, Result['result']) except ValueError: # non-float Result['formatted_result'] = Result['result'] # calculate Dry Matter result # if parent is not an AR, it's never going to be calculable dm = hasattr(analysis.aq_parent, 'getReportDryMatter') and \ analysis.aq_parent.getReportDryMatter() and \ analysis.getService().getReportDryMatter() if dm: dry_service = self.context.bika_setup.getDryMatterService() # get the UID of the DryMatter Analysis from our parent AR dry_analysis = [ a for a in analysis.aq_parent.getAnalyses(full_objects=True) if a.getService().UID() == dry_service.UID() ] if dry_analysis: dry_analysis = dry_analysis[0] dry_uid = dry_analysis.UID() # get the current DryMatter analysis result from the form if dry_uid in self.current_results: try: dry_result = float(self.current_results[dry_uid]) except: dm = False else: try: dry_result = float(dry_analysis.getResult()) except: dm = False else: dm = False Result['dry_result'] = dm and dry_result and \ '%.2f' % ((Result['result'] / dry_result) * 100) or '' self.results.append(Result) # if App.config.getConfiguration().debug_mode: # logger.info("calc.py: %s->%s %s" % (analysis.aq_parent.id, # analysis.id, # Result)) # LIMS-1808 Uncertainty calculation on DL # https://jira.bikalabs.com/browse/LIMS-1808 flres = Result.get('result', None) if flres and isnumber(flres): flres = float(flres) anvals = self.current_results[uid] isldl = anvals.get('isldl', False) isudl = anvals.get('isudl', False) ldl = anvals.get('ldl', 0) udl = anvals.get('udl', 0) ldl = float(ldl) if isnumber(ldl) else 0 udl = float(udl) if isnumber(udl) else 10000000 belowldl = (isldl or flres < ldl) aboveudl = (isudl or flres > udl) unc = '' if (belowldl or aboveudl) else analysis.getUncertainty( Result.get('result')) if not (belowldl or aboveudl): self.uncertainties.append({'uid': uid, 'uncertainty': unc}) # maybe a service who depends on us must be recalculated. if analysis.portal_type == 'ReferenceAnalysis': dependents = [] else: dependents = analysis.getDependents() if dependents: for dependent in dependents: dependent_uid = dependent.UID() # ignore analyses that no longer exist. if dependent_uid in self.ignore_uids or \ dependent_uid not in self.analyses: continue self.calculate(dependent_uid) # These self.alerts are just for the json return. # we're placing the entire form's results in kwargs. adapters = getAdapters((analysis, ), IFieldIcons) for name, adapter in adapters: alerts = adapter(result=Result['result'], form_results=self.current_results) if alerts: if analysis.UID() in self.alerts: self.alerts[analysis.UID()].extend(alerts[analysis.UID()]) else: self.alerts[analysis.UID()] = alerts[analysis.UID()]