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 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 getFormattedResult(self, specs=None, decimalmark='.'): """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>} """ 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 precision = service.getPrecision() if not precision: precision = '' return formatDecimalMark(str("%%.%sf" % precision) % result, decimalmark)
def _analysis_data(self, analysis): """ Returns a dict that represents the analysis """ decimalmark = analysis.aq_parent.aq_parent.getDecimalMark() keyword = analysis.getKeyword() andict = { 'obj': analysis, 'id': analysis.id, 'title': analysis.Title(), 'keyword': keyword, 'scientific_name': analysis.getScientificName(), 'accredited': analysis.getAccredited(), 'point_of_capture': to_utf8(POINTS_OF_CAPTURE.getValue(analysis.getPointOfCapture())), 'category': to_utf8(analysis.getCategoryTitle()), 'result': analysis.getResult(), 'unit': to_utf8(analysis.getUnit()), 'formatted_unit': format_supsub(to_utf8(analysis.getUnit())), 'capture_date': analysis.getResultCaptureDate(), 'request_id': analysis.aq_parent.getId(), 'formatted_result': '', 'uncertainty': analysis.getUncertainty(), 'formatted_uncertainty': '', 'retested': analysis.isRetest(), 'remarks': to_utf8(analysis.getRemarks()), 'outofrange': False, 'type': analysis.portal_type, 'reftype': analysis.getReferenceType() if hasattr( analysis, 'getReferenceType') else None, 'worksheet': None, 'specs': {}, 'formatted_specs': '', 'review_state': api.get_workflow_status_of(analysis), } andict['refsample'] = analysis.getSample().id \ if IReferenceAnalysis.providedBy(analysis) \ else analysis.getRequestID() specs = analysis.getResultsRange() andict['specs'] = specs scinot = self.context.bika_setup.getScientificNotationReport() andict['formatted_result'] = analysis.getFormattedResult(specs=specs, sciformat=int(scinot), decimalmark=decimalmark) fs = '' if specs.get('min', None) and specs.get('max', None): fs = '%s - %s' % (specs['min'], specs['max']) elif specs.get('min', None): fs = '> %s' % specs['min'] elif specs.get('max', None): fs = '< %s' % specs['max'] andict['formatted_specs'] = formatDecimalMark(fs, decimalmark) andict['formatted_uncertainty'] = format_uncertainty(analysis, analysis.getResult(), decimalmark=decimalmark, sciformat=int(scinot)) # Out of range? andict['outofrange'] = is_out_of_range(analysis)[0] return andict
def get_formatted_specs(self, analysis): specs = analysis.getResultsRange() fs = '' if specs.get('min', None) and specs.get('max', None): fs = '%s - %s' % (specs['min'], specs['max']) elif specs.get('min', None): fs = '> %s' % specs['min'] elif specs.get('max', None): fs = '< %s' % specs['max'] return formatDecimalMark(fs, self.decimal_mark)
def test_format_decimal_mark(self): # testing the function bika.lims.utils.analysis.get_significant_digits() from bika.lims.utils import formatDecimalMark self.assertEqual(formatDecimalMark(1), '1') self.assertEqual(formatDecimalMark(1.2), '1.2') self.assertEqual(formatDecimalMark('1.34'), '1.34') self.assertEqual(formatDecimalMark('0.0021',decimalmark=','), '0,0021') self.assertEqual(formatDecimalMark('2'), '2') self.assertEqual(formatDecimalMark('< 2.1', decimalmark=','),'< 2,1') self.assertEqual(formatDecimalMark('> 2.1', decimalmark=','),'> 2,1')
def _folder_item_calculation(self, analysis_brain, item): """Set the analysis' calculation and interims to the item passed in. :param analysis_brain: Brain that represents an analysis :param item: analysis' dictionary counterpart that represents a row """ if not self.has_permission(ViewResults, analysis_brain): # Hide interims and calculation if user cannot view results return is_editable = self.is_analysis_edition_allowed(analysis_brain) # Set interim fields. Note we add the key 'formatted_value' to the list # of interims the analysis has already assigned. interim_fields = analysis_brain.getInterimFields or list() for interim_field in interim_fields: interim_keyword = interim_field.get('keyword', '') if not interim_keyword: continue interim_value = interim_field.get("value", "") interim_formatted = formatDecimalMark(interim_value, self.dmk) interim_field['formatted_value'] = interim_formatted item[interim_keyword] = interim_field item['class'][interim_keyword] = 'interim' # Note: As soon as we have a separate content type for field # analysis, we can solely rely on the field permission # "senaite.core: Field: Edit Analysis Result" if is_editable: if self.has_permission(FieldEditAnalysisResult, analysis_brain): item['allow_edit'].append(interim_keyword) # Add this analysis' interim fields to the interim_columns list interim_hidden = interim_field.get('hidden', False) if not interim_hidden: interim_title = interim_field.get('title') self.interim_columns[interim_keyword] = interim_title item['interimfields'] = interim_fields self.interim_fields[analysis_brain.UID] = interim_fields # Set calculation calculation_uid = analysis_brain.getCalculationUID has_calculation = calculation_uid and True or False item['calculation'] = has_calculation
def _folder_item_calculation(self, analysis_brain, item): """Set the analysis' calculation and interims to the item passed in. :param analysis_brain: Brain that represents an analysis :param item: analysis' dictionary counterpart that represents a row """ is_editable = self.is_analysis_edition_allowed(analysis_brain) # Set interim fields. Note we add the key 'formatted_value' to the list # of interims the analysis has already assigned. interim_fields = analysis_brain.getInterimFields or list() for interim_field in interim_fields: interim_keyword = interim_field.get('keyword', '') if not interim_keyword: continue interim_value = interim_field.get('value', '') interim_formatted = formatDecimalMark(interim_value, self.dmk) interim_field['formatted_value'] = interim_formatted item[interim_keyword] = interim_field item['class'][interim_keyword] = 'interim' if is_editable: item['allow_edit'].append(interim_keyword) # Add this analysis' interim fields to the interim_columns list interim_hidden = interim_field.get('hidden', False) if not interim_hidden: interim_title = interim_field.get('title') self.interim_columns[interim_keyword] = interim_title item['interimfields'] = interim_fields self.interim_fields[analysis_brain.UID] = interim_fields # Set calculation calculation_uid = analysis_brain.getCalculationUID has_calculation = calculation_uid and True or False item['calculation'] = has_calculation if is_editable and (not has_calculation or interim_fields): # If the analysis is editable and doesn't have a calculation or it # does, but has interim fields, it must be re-testable. item['allow_edit'].append('retested')
def format_uncertainty(analysis, result, decimalmark='.', sciformat=1): """ Returns the formatted uncertainty according to the analysis, result and decimal mark specified following these rules: If the "Calculate precision from uncertainties" is enabled in the Analysis service, and a) If the the non-decimal number of digits of the result is above the service's ExponentialFormatPrecision, the uncertainty will be formatted in scientific notation. The uncertainty exponential value used will be the same as the one used for the result. The uncertainty will be rounded according to the same precision as the result. Example: Given an Analysis with an uncertainty of 37 for a range of results between 30000 and 40000, with an ExponentialFormatPrecision equal to 4 and a result of 32092, this method will return 0.004E+04 b) If the number of digits of the integer part of the result is below the ExponentialFormatPrecision, the uncertainty will be formatted as decimal notation and the uncertainty will be rounded one position after reaching the last 0 (precision calculated according to the uncertainty value). Example: Given an Analysis with an uncertainty of 0.22 for a range of results between 1 and 10 with an ExponentialFormatPrecision equal to 4 and a result of 5.234, this method will return 0.2 If the "Calculate precision from Uncertainties" is disabled in the analysis service, the same rules described above applies, but the precision used for rounding the uncertainty is not calculated from the uncertainty neither the result. The fixed length precision is used instead. For further details, visit https://jira.bikalabs.com/browse/LIMS-1334 If the result is not floatable or no uncertainty defined, returns an empty string. The default decimal mark '.' will be replaced by the decimalmark specified. :param analysis: the analysis from which the uncertainty, precision and other additional info have to be retrieved :param result: result of the analysis. Used to retrieve and/or calculate the precision and/or uncertainty :param decimalmark: decimal mark to use. By default '.' :param sciformat: 1. The sci notation has to be formatted as aE^+b 2. The sci notation has to be formatted as ax10^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 :return: the formatted uncertainty """ try: result = float(result) except ValueError: return "" objres = None try: objres = float(analysis.getResult()) except ValueError: pass service = analysis.getService() uncertainty = None if result == objres: # To avoid problems with DLs uncertainty = analysis.getUncertainty() else: uncertainty = analysis.getUncertainty(result) if uncertainty is None or uncertainty == 0: return "" # Scientific notation? # Get the default precision for scientific notation threshold = service.getExponentialFormatPrecision() # Current result precision is above the threshold? sig_digits = get_significant_digits(result) negative = sig_digits < 0 sign = '-' if negative else '' sig_digits = abs(sig_digits) sci = sig_digits >= threshold and sig_digits > 0 formatted = '' if sci: # Scientific notation # 3.2014E+4 if negative == True: res = float(uncertainty) * (10**sig_digits) else: res = float(uncertainty) / (10**sig_digits) res = float(str("%%.%sf" % (sig_digits - 1)) % res) res = int(res) if res.is_integer() else res if sciformat in [2, 3, 4, 5]: if sciformat == 2: # ax10^b or ax10^-b formatted = "%s%s%s%s" % (res, "x10^", sign, sig_digits) elif sciformat == 3: # ax10<super>b</super> or ax10<super>-b</super> formatted = "%s%s%s%s%s" % (res, "x10<sup>", sign, sig_digits, "</sup>") elif sciformat == 4: # ax10^b or ax10^-b formatted = "%s%s%s%s" % (res, "·10^", sign, sig_digits) elif sciformat == 5: # ax10<super>b</super> or ax10<super>-b</super> formatted = "%s%s%s%s%s" % (res, "·10<sup>", sign, sig_digits, "</sup>") else: # Default format: aE^+b sig_digits = "%02d" % sig_digits formatted = "%s%s%s%s" % (res, "e", sign, sig_digits) #formatted = str("%%.%se" % sig_digits) % uncertainty else: # Decimal notation prec = analysis.getPrecision(result) prec = prec if prec else '' formatted = str("%%.%sf" % prec) % uncertainty return formatDecimalMark(formatted, decimalmark)
def format_numeric_result(analysis, result, decimalmark='.', sciformat=1): """ Returns the formatted number part of a results value. This is responsible for deciding the precision, and notation of numeric values in accordance to the uncertainty. If a non-numeric result value is given, the value will be returned unchanged. The following rules apply: If the "Calculate precision from uncertainties" is enabled in the Analysis service, and a) If the non-decimal number of digits of the result is above the service's ExponentialFormatPrecision, the result will be formatted in scientific notation. Example: Given an Analysis with an uncertainty of 37 for a range of results between 30000 and 40000, with an ExponentialFormatPrecision equal to 4 and a result of 32092, this method will return 3.2092E+04 b) If the number of digits of the integer part of the result is below the ExponentialFormatPrecision, the result will be formatted as decimal notation and the resulta will be rounded in accordance to the precision (calculated from the uncertainty) Example: Given an Analysis with an uncertainty of 0.22 for a range of results between 1 and 10 with an ExponentialFormatPrecision equal to 4 and a result of 5.234, this method will return 5.2 If the "Calculate precision from Uncertainties" is disabled in the analysis service, the same rules described above applies, but the precision used for rounding the result is not calculated from the uncertainty. The fixed length precision is used instead. For further details, visit https://jira.bikalabs.com/browse/LIMS-1334 The default decimal mark '.' will be replaced by the decimalmark specified. :param analysis: the analysis from which the uncertainty, precision and other additional info have to be retrieved :param result: result to be formatted. :param decimalmark: decimal mark to use. By default '.' :param sciformat: 1. The sci notation has to be formatted as aE^+b 2. The sci notation has to be formatted as ax10^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 :return: the formatted result """ try: result = float(result) except ValueError: return result # continuing with 'nan' result will cause formatting to fail. if math.isnan(result): return result service = analysis.getService() # Scientific notation? # Get the default precision for scientific notation threshold = service.getExponentialFormatPrecision() # Current result precision is above the threshold? sig_digits = get_significant_digits(result) negative = sig_digits < 0 sign = '-' if negative else '' sig_digits = abs(sig_digits) sci = sig_digits >= threshold formatted = '' if sci: # Scientific notation if sciformat in [2, 3, 4, 5]: if negative == True: res = float(result) * (10**sig_digits) else: res = float(result) / (10**sig_digits) res = float(str("%%.%sf" % (sig_digits - 1)) % res) # We have to check if formatted is an integer using "'.' in formatted" # because ".is_integer" doesn't work with X.0 res = int(res) if '.' not in res else res if sciformat == 2: # ax10^b or ax10^-b formatted = "%s%s%s%s" % (res, "x10^", sign, sig_digits) elif sciformat == 3: # ax10<super>b</super> or ax10<super>-b</super> formatted = "%s%s%s%s%s" % (res, "x10<sup>", sign, sig_digits, "</sup>") elif sciformat == 4: # ax10^b or ax10^-b formatted = "%s%s%s%s" % (res, "·10^", sign, sig_digits) elif sciformat == 5: # ax10<super>b</super> or ax10<super>-b</super> formatted = "%s%s%s%s%s" % (res, "·10<sup>", sign, sig_digits, "</sup>") else: # Default format: aE^+b formatted = str("%%.%se" % sig_digits) % result else: # Decimal notation prec = analysis.getPrecision(result) prec = prec if prec else '' formatted = str("%%.%sf" % prec) % result # We have to check if formatted is an integer using "'.' in formatted" # because ".is_integer" doesn't work with X.0 formatted = str(int( float(formatted))) if '.' not in formatted else formatted return formatDecimalMark(formatted, decimalmark)
def format_numeric_result(analysis, result, decimalmark='.', sciformat=1): """ Returns the formatted number part of a results value. This is responsible for deciding the precision, and notation of numeric values in accordance to the uncertainty. If a non-numeric result value is given, the value will be returned unchanged. The following rules apply: If the "Calculate precision from uncertainties" is enabled in the Analysis service, and a) If the non-decimal number of digits of the result is above the service's ExponentialFormatPrecision, the result will be formatted in scientific notation. Example: Given an Analysis with an uncertainty of 37 for a range of results between 30000 and 40000, with an ExponentialFormatPrecision equal to 4 and a result of 32092, this method will return 3.2092E+04 b) If the number of digits of the integer part of the result is below the ExponentialFormatPrecision, the result will be formatted as decimal notation and the resulta will be rounded in accordance to the precision (calculated from the uncertainty) Example: Given an Analysis with an uncertainty of 0.22 for a range of results between 1 and 10 with an ExponentialFormatPrecision equal to 4 and a result of 5.234, this method will return 5.2 If the "Calculate precision from Uncertainties" is disabled in the analysis service, the same rules described above applies, but the precision used for rounding the result is not calculated from the uncertainty. The fixed length precision is used instead. For further details, visit https://jira.bikalabs.com/browse/LIMS-1334 The default decimal mark '.' will be replaced by the decimalmark specified. :param analysis: the analysis from which the uncertainty, precision and other additional info have to be retrieved :param result: result to be formatted. :param decimalmark: decimal mark to use. By default '.' :param sciformat: 1. The sci notation has to be formatted as aE^+b 2. The sci notation has to be formatted as ax10^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: should be a string to preserve the decimal precision. :return: the formatted result as string """ try: result = float(result) except ValueError: return result # continuing with 'nan' result will cause formatting to fail. if math.isnan(result): return result service = analysis.getService() # Scientific notation? # Get the default precision for scientific notation threshold = service.getExponentialFormatPrecision() precision = analysis.getPrecision(result) formatted = _format_decimal_or_sci(result, precision, threshold, sciformat) return formatDecimalMark(formatted, decimalmark)
def _analysis_data(self, analysis, decimalmark=None): if analysis.UID() in self._cache['_analysis_data']: return self._cache['_analysis_data'][analysis.UID()] keyword = analysis.getKeyword() service = analysis.getService() andict = {'obj': analysis, 'id': analysis.id, 'title': analysis.Title(), 'keyword': keyword, 'scientific_name': service.getScientificName(), 'accredited': service.getAccredited(), 'point_of_capture': to_utf8(POINTS_OF_CAPTURE.getValue(service.getPointOfCapture())), 'category': to_utf8(service.getCategoryTitle()), 'result': analysis.getResult(), 'isnumber': isnumber(analysis.getResult()), 'unit': to_utf8(service.getUnit()), 'formatted_unit': format_supsub(to_utf8(service.getUnit())), 'capture_date': analysis.getResultCaptureDate(), 'request_id': analysis.aq_parent.getId(), 'formatted_result': '', 'uncertainty': analysis.getUncertainty(), 'formatted_uncertainty': '', 'retested': analysis.getRetested(), 'remarks': to_utf8(analysis.getRemarks()), 'resultdm': to_utf8(analysis.getResultDM()), 'outofrange': False, 'type': analysis.portal_type, 'reftype': analysis.getReferenceType() \ if hasattr(analysis, 'getReferenceType') else None, 'worksheet': None, 'specs': {}, 'formatted_specs': ''} if analysis.portal_type == 'DuplicateAnalysis': andict['reftype'] = 'd' ws = analysis.getBackReferences('WorksheetAnalysis') andict['worksheet'] = ws[0].id if ws and len(ws) > 0 else None andict['worksheet_url'] = ws[0].absolute_url if ws and len(ws) > 0 else None andict['refsample'] = analysis.getSample().id \ if analysis.portal_type == 'Analysis' \ else '%s - %s' % (analysis.aq_parent.id, analysis.aq_parent.Title()) if analysis.portal_type == 'ReferenceAnalysis': # The analysis is a Control or Blank. We might use the # reference results instead other specs uid = analysis.getServiceUID() specs = analysis.aq_parent.getResultsRangeDict().get(uid, {}) else: # Get the specs directly from the analysis. The getResultsRange # function already takes care about which are the specs to be used: # AR, client or lab. specs = analysis.getResultsRange() andict['specs'] = specs scinot = self.context.bika_setup.getScientificNotationReport() fresult = analysis.getFormattedResult(specs=specs, sciformat=int(scinot), decimalmark=decimalmark) # We don't use here cgi.encode because results fields must be rendered # using the 'structure' wildcard. The reason is that the result can be # expressed in sci notation, that may include <sup></sup> html tags. # Please note the default value for the 'html' parameter from # getFormattedResult signature is set to True, so the service will # already take into account LDLs and UDLs symbols '<' and '>' and escape # them if necessary. andict['formatted_result'] = fresult; fs = '' if specs.get('min', None) and specs.get('max', None): fs = '%s - %s' % (specs['min'], specs['max']) elif specs.get('min', None): fs = '> %s' % specs['min'] elif specs.get('max', None): fs = '< %s' % specs['max'] andict['formatted_specs'] = formatDecimalMark(fs, decimalmark) andict['formatted_uncertainty'] = format_uncertainty(analysis, analysis.getResult(), decimalmark=decimalmark, sciformat=int(scinot)) # Out of range? if specs: adapters = getAdapters((analysis, ), IResultOutOfRange) bsc = getToolByName(self.context, "bika_setup_catalog") for name, adapter in adapters: ret = adapter(specification=specs) if ret and ret['out_of_range']: andict['outofrange'] = True break self._cache['_analysis_data'][analysis.UID()] = andict return andict
def format_numeric_result(analysis, result, decimalmark='.', sciformat=1): """ Returns the formatted number part of a results value. This is responsible for deciding the precision, and notation of numeric values in accordance to the uncertainty. If a non-numeric result value is given, the value will be returned unchanged. The following rules apply: If the "Calculate precision from uncertainties" is enabled in the Analysis service, and a) If the non-decimal number of digits of the result is above the service's ExponentialFormatPrecision, the result will be formatted in scientific notation. Example: Given an Analysis with an uncertainty of 37 for a range of results between 30000 and 40000, with an ExponentialFormatPrecision equal to 4 and a result of 32092, this method will return 3.2092E+04 b) If the number of digits of the integer part of the result is below the ExponentialFormatPrecision, the result will be formatted as decimal notation and the resulta will be rounded in accordance to the precision (calculated from the uncertainty) Example: Given an Analysis with an uncertainty of 0.22 for a range of results between 1 and 10 with an ExponentialFormatPrecision equal to 4 and a result of 5.234, this method will return 5.2 If the "Calculate precision from Uncertainties" is disabled in the analysis service, the same rules described above applies, but the precision used for rounding the result is not calculated from the uncertainty. The fixed length precision is used instead. For further details, visit https://jira.bikalabs.com/browse/LIMS-1334 The default decimal mark '.' will be replaced by the decimalmark specified. :param analysis: the analysis from which the uncertainty, precision and other additional info have to be retrieved :param result: result to be formatted. :param decimalmark: decimal mark to use. By default '.' :param sciformat: 1. The sci notation has to be formatted as aE^+b 2. The sci notation has to be formatted as ax10^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 :return: the formatted result """ try: result = float(result) except ValueError: return result # continuing with 'nan' result will cause formatting to fail. if math.isnan(result): return result service = analysis.getService() # Scientific notation? # Get the default precision for scientific notation threshold = service.getExponentialFormatPrecision() # Current result precision is above the threshold? sig_digits = get_significant_digits(result) negative = sig_digits < 0 sign = '-' if negative else '' sig_digits = abs(sig_digits) sci = sig_digits >= threshold formatted = '' if sci: # Scientific notation if sciformat in [2,3,4,5]: if negative == True: res = float(result)*(10**sig_digits) else: res = float(result)/(10**sig_digits) res = float(str("%%.%sf" % (sig_digits-1)) % res) # We have to check if formatted is an integer using "'.' in formatted" # because ".is_integer" doesn't work with X.0 res = int(res) if '.' not in res else res if sciformat == 2: # ax10^b or ax10^-b formatted = "%s%s%s%s" % (res,"x10^",sign,sig_digits) elif sciformat == 3: # ax10<super>b</super> or ax10<super>-b</super> formatted = "%s%s%s%s%s" % (res,"x10<sup>",sign,sig_digits,"</sup>") elif sciformat == 4: # ax10^b or ax10^-b formatted = "%s%s%s%s" % (res,"·10^",sign,sig_digits) elif sciformat == 5: # ax10<super>b</super> or ax10<super>-b</super> formatted = "%s%s%s%s%s" % (res,"·10<sup>",sign,sig_digits,"</sup>") else: # Default format: aE^+b formatted = str("%%.%se" % sig_digits) % result else: # Decimal notation prec = analysis.getPrecision(result) prec = prec if prec else '' formatted = str("%%.%sf" % prec) % result # We have to check if formatted is an integer using "'.' in formatted" # because ".is_integer" doesn't work with X.0 formatted = str(int(float(formatted))) if '.' not in formatted else formatted return formatDecimalMark(formatted, decimalmark)
def getFormattedResult(self, specs=None, decimalmark='.', sciformat=1, html=True): """Formatted result: 1. If the result is a detection limit, returns '< LDL' or '> UDL' 2. Print ResultText of matching ResultOptions 3. If the result is not floatable, return it without being formatted 4. If the analysis specs has hidemin or hidemax enabled and the result is out of range, render result as '<min' or '>max' 5. If the result is below Lower Detection Limit, show '<LDL' 6. If the result is above Upper Detecion Limit, show '>UDL' 7. Otherwise, render numerical value :param specs: Optional result specifications, a dictionary as follows: {'min': <min_val>, 'max': <max_val>, 'error': <error>, 'hidemin': <hidemin_val>, 'hidemax': <hidemax_val>} :param decimalmark: The string to be used as a decimal separator. default is '.' :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 :param html: if true, returns an string with the special characters escaped: e.g: '<' and '>' (LDL and UDL for results like < 23.4). """ result = self.getResult() # 1. The result is a detection limit, return '< LDL' or '> UDL' dl = self.getDetectionLimitOperand() if dl: try: res = float(result) # required, check if floatable res = drop_trailing_zeros_decimal(res) fdm = formatDecimalMark(res, decimalmark) hdl = cgi.escape(dl) if html else dl return '%s %s' % (hdl, fdm) except (TypeError, ValueError): logger.warn( "The result for the analysis %s is a detection limit, " "but not floatable: %s" % (self.id, result)) return formatDecimalMark(result, decimalmark=decimalmark) choices = self.getResultOptions() # 2. Print ResultText of matching ResulOptions match = [x['ResultText'] for x in choices if str(x['ResultValue']) == str(result)] if match: return match[0] # 3. If the result is not floatable, return it without being formatted try: result = float(result) except (TypeError, ValueError): return formatDecimalMark(result, decimalmark=decimalmark) # 4. If the analysis specs has enabled hidemin or hidemax and the # result is out of range, render result as '<min' or '>max' specs = specs if specs else self.getResultsRange() hidemin = specs.get('hidemin', '') hidemax = specs.get('hidemax', '') try: belowmin = hidemin and result < float(hidemin) or False except (TypeError, ValueError): belowmin = False try: abovemax = hidemax and result > float(hidemax) or False except (TypeError, ValueError): abovemax = False # 4.1. If result is below min and hidemin enabled, return '<min' if belowmin: fdm = formatDecimalMark('< %s' % hidemin, decimalmark) return fdm.replace('< ', '< ', 1) if html else fdm # 4.2. If result is above max and hidemax enabled, return '>max' if abovemax: fdm = formatDecimalMark('> %s' % hidemax, decimalmark) return fdm.replace('> ', '> ', 1) if html else fdm # Below Lower Detection Limit (LDL)? ldl = self.getLowerDetectionLimit() if result < ldl: # LDL must not be formatted according to precision, etc. # Drop trailing zeros from decimal ldl = drop_trailing_zeros_decimal(ldl) fdm = formatDecimalMark('< %s' % ldl, decimalmark) return fdm.replace('< ', '< ', 1) if html else fdm # Above Upper Detection Limit (UDL)? udl = self.getUpperDetectionLimit() if result > udl: # UDL must not be formatted according to precision, etc. # Drop trailing zeros from decimal udl = drop_trailing_zeros_decimal(udl) fdm = formatDecimalMark('> %s' % udl, decimalmark) return fdm.replace('> ', '> ', 1) if html else fdm # Render numerical values return format_numeric_result(self, self.getResult(), decimalmark=decimalmark, sciformat=sciformat)
def getFormattedResult(self, specs=None, decimalmark='.', sciformat=1): """Formatted result: 1. Print ResultText of matching ResultOptions 2. If the result is not floatable, return it without being formatted 3. If the analysis specs has hidemin or hidemax enabled and the result is out of range, render result as '<min' or '>max' 4. Otherwise, render numerical value 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() choices = service.getResultOptions() # 1. Print ResultText of matching ResulOptions match = [x['ResultText'] for x in choices if str(x['ResultValue']) == str(result)] if match: return match[0] # 2. If the result is not floatable, return it without being formatted try: result = float(result) except: return result # 3. 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: specs = self.getAnalysisSpecs() specs = specs.getResultsRangeDict() if specs is not None else {} specs = specs.get(self.getKeyword(), {}) 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 # 3.1. If result is below min and hidemin enabled, return '<min' if belowmin: return formatDecimalMark('< %s' % hidemin, decimalmark) # 3.2. If result is above max and hidemax enabled, return '>max' if abovemax: return formatDecimalMark('> %s' % hidemax, decimalmark) # Render numerical values return formatDecimalMark(format_numeric_result(self, result, sciformat=sciformat), decimalmark=decimalmark)
def getFormattedResult(self, specs=None, decimalmark='.'): """Formatted result: 1. Print ResultText of matching ResultOptions 2. If the result is not floatable, return it without being formatted 3. If the analysis specs has hidemin or hidemax enabled and the result is out of range, render result as '<min' or '>max' 4. Otherwise, render numerical value specs param is optional. A dictionary as follows: {'min': <min_val>, 'max': <max_val>, 'error': <error>, 'hidemin': <hidemin_val>, 'hidemax': <hidemax_val>} """ result = self.getResult() service = self.getService() choices = service.getResultOptions() # 1. Print ResultText of matching ResulOptions match = [x['ResultText'] for x in choices if str(x['ResultValue']) == str(result)] if match: return match[0] # 2. If the result is not floatable, return it without being formatted try: result = float(result) except: return result # 3. 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: specs = self.getAnalysisSpecs() specs = specs.getResultsRangeDict() if specs is not None else {} specs = specs.get(self.getKeyword(), {}) 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 # 3.1. If result is below min and hidemin enabled, return '<min' if belowmin: return formatDecimalMark('< %s' % hidemin, decimalmark) # 3.2. If result is above max and hidemax enabled, return '>max' if abovemax: return formatDecimalMark('> %s' % hidemax, decimalmark) # Render numerical value return formatDecimalMark(format_numeric_result(self, result), decimalmark)
def _analysis_data(self, analysis): """ Returns a dict that represents the analysis """ decimalmark = analysis.aq_parent.aq_parent.getDecimalMark() keyword = analysis.getKeyword() service = analysis.getService() andict = {'obj': analysis, 'id': analysis.id, 'title': analysis.Title(), 'keyword': keyword, 'scientific_name': service.getScientificName(), 'accredited': service.getAccredited(), 'point_of_capture': to_utf8(POINTS_OF_CAPTURE.getValue(service.getPointOfCapture())), 'category': to_utf8(service.getCategoryTitle()), 'result': analysis.getResult(), 'unit': to_utf8(service.getUnit()), 'formatted_unit': format_supsub(to_utf8(service.getUnit())), 'capture_date': analysis.getResultCaptureDate(), 'request_id': analysis.aq_parent.getId(), 'formatted_result': '', 'uncertainty': analysis.getUncertainty(), 'formatted_uncertainty': '', 'retested': analysis.getRetested(), 'remarks': to_utf8(analysis.getRemarks()), 'resultdm': to_utf8(analysis.getResultDM()), 'outofrange': False, 'type': analysis.portal_type, 'reftype': analysis.getReferenceType() \ if hasattr(analysis, 'getReferenceType') else None, 'worksheet': None, 'specs': {}, 'formatted_specs': ''} andict['refsample'] = analysis.getSample().id \ if analysis.portal_type == 'Analysis' \ else '%s - %s' % (analysis.aq_parent.id, analysis.aq_parent.Title()) # Which analysis specs must be used? # Try first with those defined at AR Publish Specs level if analysis.portal_type == 'ReferenceAnalysis': # The analysis is a Control or Blank. We might use the # reference results instead other specs uid = analysis.getServiceUID() specs = analysis.aq_parent.getResultsRangeDict().get(uid, {}) else: # Get the specs directly from the analysis. The getResultsRange # function already takes care about which are the specs to be used: # AR, client or lab. specs = analysis.getResultsRange() andict['specs'] = specs scinot = self.context.bika_setup.getScientificNotationReport() andict['formatted_result'] = analysis.getFormattedResult( specs=specs, sciformat=int(scinot), decimalmark=decimalmark) fs = '' if specs.get('min', None) and specs.get('max', None): fs = '%s - %s' % (specs['min'], specs['max']) elif specs.get('min', None): fs = '> %s' % specs['min'] elif specs.get('max', None): fs = '< %s' % specs['max'] andict['formatted_specs'] = formatDecimalMark(fs, decimalmark) andict['formatted_uncertainty'] = format_uncertainty( analysis, analysis.getResult(), decimalmark=decimalmark, sciformat=int(scinot)) # Out of range? if specs: adapters = getAdapters((analysis, ), IResultOutOfRange) bsc = getToolByName(self.context, "bika_setup_catalog") for name, adapter in adapters: ret = adapter(specification=specs) if ret and ret['out_of_range']: andict['outofrange'] = True break return andict
def _analysis_data(self, analysis): """ Returns a dict that represents the analysis """ decimalmark = analysis.aq_parent.aq_parent.getDecimalMark() keyword = analysis.getKeyword() andict = { 'obj': analysis, 'id': analysis.id, 'title': analysis.Title(), 'keyword': keyword, 'scientific_name': analysis.getScientificName(), 'accredited': analysis.getAccredited(), 'point_of_capture': to_utf8(POINTS_OF_CAPTURE.getValue(analysis.getPointOfCapture())), 'category': to_utf8(analysis.getCategoryTitle()), 'result': analysis.getResult(), 'unit': to_utf8(analysis.getUnit()), 'formatted_unit': format_supsub(to_utf8(analysis.getUnit())), 'capture_date': analysis.getResultCaptureDate(), 'request_id': analysis.aq_parent.getId(), 'formatted_result': '', 'uncertainty': analysis.getUncertainty(), 'formatted_uncertainty': '', 'retested': analysis.getRetested(), 'remarks': to_utf8(analysis.getRemarks()), 'resultdm': to_utf8(analysis.getResultDM()), 'outofrange': False, 'type': analysis.portal_type, 'reftype': analysis.getReferenceType() if hasattr( analysis, 'getReferenceType') else None, 'worksheet': None, 'specs': {}, 'formatted_specs': '', 'review_state': api.get_workflow_status_of(analysis), } andict['refsample'] = analysis.getSample().id \ if analysis.portal_type == 'Analysis' \ else '%s - %s' % (analysis.aq_parent.id, analysis.aq_parent.Title()) # Which analysis specs must be used? # Try first with those defined at AR Publish Specs level if analysis.portal_type == 'ReferenceAnalysis': # The analysis is a Control or Blank. We might use the # reference results instead other specs uid = analysis.getServiceUID() specs = analysis.aq_parent.getResultsRangeDict().get(uid, {}) else: # Get the specs directly from the analysis. The getResultsRange # function already takes care about which are the specs to be used: # AR, client or lab. specs = analysis.getResultsRange() andict['specs'] = specs scinot = self.context.bika_setup.getScientificNotationReport() andict['formatted_result'] = analysis.getFormattedResult(specs=specs, sciformat=int(scinot), decimalmark=decimalmark) fs = '' if specs.get('min', None) and specs.get('max', None): fs = '%s - %s' % (specs['min'], specs['max']) elif specs.get('min', None): fs = '> %s' % specs['min'] elif specs.get('max', None): fs = '< %s' % specs['max'] andict['formatted_specs'] = formatDecimalMark(fs, decimalmark) andict['formatted_uncertainty'] = format_uncertainty(analysis, analysis.getResult(), decimalmark=decimalmark, sciformat=int(scinot)) # Out of range? if specs: adapters = getAdapters((analysis, ), IResultOutOfRange) for name, adapter in adapters: ret = adapter(specification=specs) if ret and ret['out_of_range']: andict['outofrange'] = True break return andict
def format_uncertainty(analysis, result, decimalmark='.', sciformat=1): """ Returns the formatted uncertainty according to the analysis, result and decimal mark specified following these rules: If the "Calculate precision from uncertainties" is enabled in the Analysis service, and a) If the the non-decimal number of digits of the result is above the service's ExponentialFormatPrecision, the uncertainty will be formatted in scientific notation. The uncertainty exponential value used will be the same as the one used for the result. The uncertainty will be rounded according to the same precision as the result. Example: Given an Analysis with an uncertainty of 37 for a range of results between 30000 and 40000, with an ExponentialFormatPrecision equal to 4 and a result of 32092, this method will return 0.004E+04 b) If the number of digits of the integer part of the result is below the ExponentialFormatPrecision, the uncertainty will be formatted as decimal notation and the uncertainty will be rounded one position after reaching the last 0 (precision calculated according to the uncertainty value). Example: Given an Analysis with an uncertainty of 0.22 for a range of results between 1 and 10 with an ExponentialFormatPrecision equal to 4 and a result of 5.234, this method will return 0.2 If the "Calculate precision from Uncertainties" is disabled in the analysis service, the same rules described above applies, but the precision used for rounding the uncertainty is not calculated from the uncertainty neither the result. The fixed length precision is used instead. For further details, visit https://jira.bikalabs.com/browse/LIMS-1334 If the result is not floatable or no uncertainty defined, returns an empty string. The default decimal mark '.' will be replaced by the decimalmark specified. :param analysis: the analysis from which the uncertainty, precision and other additional info have to be retrieved :param result: result of the analysis. Used to retrieve and/or calculate the precision and/or uncertainty :param decimalmark: decimal mark to use. By default '.' :param sciformat: 1. The sci notation has to be formatted as aE^+b 2. The sci notation has to be formatted as ax10^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 :return: the formatted uncertainty """ try: result = float(result) except ValueError: return "" objres = None try: objres = float(analysis.getResult()) except ValueError: pass service = analysis.getService() uncertainty = None if result == objres: # To avoid problems with DLs uncertainty = analysis.getUncertainty() else: uncertainty = analysis.getUncertainty(result) if uncertainty is None or uncertainty == 0: return "" # Scientific notation? # Get the default precision for scientific notation threshold = service.getExponentialFormatPrecision() precision = analysis.getPrecision(result) formatted = _format_decimal_or_sci(uncertainty, precision, threshold, sciformat) return formatDecimalMark(formatted, decimalmark)
def _analysis_data(self, analysis, decimalmark=None): keyword = analysis.getKeyword() service = analysis.getService() andict = {'obj': analysis, 'id': analysis.id, 'title': analysis.Title(), 'keyword': keyword, 'accredited': service.getAccredited(), 'point_of_capture': to_utf8(POINTS_OF_CAPTURE.getValue(service.getPointOfCapture())), 'category': to_utf8(service.getCategoryTitle()), 'result': analysis.getResult(), 'unit': to_utf8(service.getUnit()), 'capture_date': analysis.getResultCaptureDate(), 'request_id': analysis.aq_parent.getId(), 'formatted_result': '', 'uncertainty': analysis.getUncertainty(), 'formatted_uncertainty': '', 'retested': analysis.getRetested(), 'remarks': to_utf8(analysis.getRemarks()), 'resultdm': to_utf8(analysis.getResultDM()), 'outofrange': False, 'type': analysis.portal_type, 'reftype': analysis.getReferenceType() \ if hasattr(analysis, 'getReferenceType') else None, 'worksheet': None, 'specs': {}, 'formatted_specs': ''} if analysis.portal_type == 'DuplicateAnalysis': andict['reftype'] = 'd' ws = analysis.getBackReferences('WorksheetAnalysis') andict['worksheet'] = ws[0].id if ws and len(ws) > 0 else None andict['worksheet_url'] = ws[0].absolute_url if ws and len(ws) > 0 else None andict['refsample'] = analysis.getSample().id \ if analysis.portal_type == 'Analysis' \ else '%s - %s' % (analysis.aq_parent.id, analysis.aq_parent.Title()) # Which analysis specs must be used? # Try first with those defined at AR Publish Specs level if analysis.portal_type == 'ReferenceAnalysis': # The analysis is a Control or Blank. We might use the # reference results instead other specs uid = analysis.getServiceUID() specs = analysis.aq_parent.getResultsRangeDict().get(uid, {}) elif analysis.portal_type == 'DuplicateAnalysis': specs = analysis.getAnalysisSpecs(); else: ar = analysis.aq_parent specs = ar.getPublicationSpecification() if not specs or keyword not in specs.getResultsRangeDict(): specs = analysis.getAnalysisSpecs() specs = specs.getResultsRangeDict().get(keyword, {}) \ if specs else {} andict['specs'] = specs andict['formatted_result'] = analysis.getFormattedResult(specs, decimalmark) fs = '' if specs.get('min', None) and specs.get('max', None): fs = '%s - %s' % (specs['min'], specs['max']) elif specs.get('min', None): fs = '> %s' % specs['min'] elif specs.get('max', None): fs = '< %s' % specs['max'] andict['formatted_specs'] = formatDecimalMark(fs, decimalmark) andict['formatted_uncertainty'] = formatDecimalMark(str(analysis.getUncertainty()), decimalmark) # Out of range? if specs: adapters = getAdapters((analysis, ), IResultOutOfRange) bsc = getToolByName(self.context, "bika_setup_catalog") for name, adapter in adapters: ret = adapter(specification=specs) if ret and ret['out_of_range']: andict['outofrange'] = True break return andict
def format_uncertainty(analysis, result, decimalmark='.', sciformat=1): """ Returns the formatted uncertainty according to the analysis, result and decimal mark specified following these rules: If the "Calculate precision from uncertainties" is enabled in the Analysis service, and a) If the the non-decimal number of digits of the result is above the service's ExponentialFormatPrecision, the uncertainty will be formatted in scientific notation. The uncertainty exponential value used will be the same as the one used for the result. The uncertainty will be rounded according to the same precision as the result. Example: Given an Analysis with an uncertainty of 37 for a range of results between 30000 and 40000, with an ExponentialFormatPrecision equal to 4 and a result of 32092, this method will return 0.004E+04 b) If the number of digits of the integer part of the result is below the ExponentialFormatPrecision, the uncertainty will be formatted as decimal notation and the uncertainty will be rounded one position after reaching the last 0 (precision calculated according to the uncertainty value). Example: Given an Analysis with an uncertainty of 0.22 for a range of results between 1 and 10 with an ExponentialFormatPrecision equal to 4 and a result of 5.234, this method will return 0.2 If the "Calculate precision from Uncertainties" is disabled in the analysis service, the same rules described above applies, but the precision used for rounding the uncertainty is not calculated from the uncertainty neither the result. The fixed length precision is used instead. For further details, visit https://jira.bikalabs.com/browse/LIMS-1334 If the result is not floatable or no uncertainty defined, returns an empty string. The default decimal mark '.' will be replaced by the decimalmark specified. :param analysis: the analysis from which the uncertainty, precision and other additional info have to be retrieved :param result: result of the analysis. Used to retrieve and/or calculate the precision and/or uncertainty :param decimalmark: decimal mark to use. By default '.' :param sciformat: 1. The sci notation has to be formatted as aE^+b 2. The sci notation has to be formatted as ax10^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 :return: the formatted uncertainty """ try: result = float(result) except ValueError: return "" objres = None try: objres = float(analysis.getResult()) except ValueError: pass service = analysis.getService() uncertainty = None if result == objres: # To avoid problems with DLs uncertainty = analysis.getUncertainty() else: uncertainty = analysis.getUncertainty(result) if uncertainty is None or uncertainty == 0: return "" # Scientific notation? # Get the default precision for scientific notation threshold = service.getExponentialFormatPrecision() # Current result precision is above the threshold? sig_digits = get_significant_digits(result) negative = sig_digits < 0 sign = '-' if negative else '' sig_digits = abs(sig_digits) sci = sig_digits >= threshold and sig_digits > 0 formatted = '' if sci: # Scientific notation # 3.2014E+4 if negative == True: res = float(uncertainty)*(10**sig_digits) else: res = float(uncertainty)/(10**sig_digits) res = float(str("%%.%sf" % (sig_digits-1)) % res) res = int(res) if res.is_integer() else res if sciformat in [2,3,4,5]: if sciformat == 2: # ax10^b or ax10^-b formatted = "%s%s%s%s" % (res,"x10^",sign,sig_digits) elif sciformat == 3: # ax10<super>b</super> or ax10<super>-b</super> formatted = "%s%s%s%s%s" % (res,"x10<sup>",sign,sig_digits,"</sup>") elif sciformat == 4: # ax10^b or ax10^-b formatted = "%s%s%s%s" % (res,"·10^",sign,sig_digits) elif sciformat == 5: # ax10<super>b</super> or ax10<super>-b</super> formatted = "%s%s%s%s%s" % (res,"·10<sup>",sign,sig_digits,"</sup>") else: # Default format: aE^+b sig_digits = "%02d" % sig_digits formatted = "%s%s%s%s" % (res,"e",sign,sig_digits) #formatted = str("%%.%se" % sig_digits) % uncertainty else: # Decimal notation prec = analysis.getPrecision(result) prec = prec if prec else '' formatted = str("%%.%sf" % prec) % uncertainty return formatDecimalMark(formatted, decimalmark)
def _analysis_data(self, analysis, decimalmark=None): keyword = analysis.getKeyword() service = analysis.getService() andict = {'obj': analysis, 'id': analysis.id, 'title': analysis.Title(), 'keyword': keyword, 'scientific_name': service.getScientificName(), 'accredited': service.getAccredited(), 'point_of_capture': to_utf8(POINTS_OF_CAPTURE.getValue(service.getPointOfCapture())), 'category': to_utf8(service.getCategoryTitle()), 'result': analysis.getResult(), 'isnumber': isnumber(analysis.getResult()), 'unit': to_utf8(service.getUnit()), 'formatted_unit': format_supsub(to_utf8(service.getUnit())), 'capture_date': analysis.getResultCaptureDate(), 'request_id': analysis.aq_parent.getId(), 'formatted_result': '', 'uncertainty': analysis.getUncertainty(), 'formatted_uncertainty': '', 'retested': analysis.getRetested(), 'remarks': to_utf8(analysis.getRemarks()), 'resultdm': to_utf8(analysis.getResultDM()), 'outofrange': False, 'type': analysis.portal_type, 'reftype': analysis.getReferenceType() \ if hasattr(analysis, 'getReferenceType') else None, 'worksheet': None, 'specs': {}, 'formatted_specs': ''} if analysis.portal_type == 'DuplicateAnalysis': andict['reftype'] = 'd' ws = analysis.getBackReferences('WorksheetAnalysis') andict['worksheet'] = ws[0].id if ws and len(ws) > 0 else None andict['worksheet_url'] = ws[0].absolute_url if ws and len( ws) > 0 else None andict['refsample'] = analysis.getSample().id \ if analysis.portal_type == 'Analysis' \ else '%s - %s' % (analysis.aq_parent.id, analysis.aq_parent.Title()) # Which analysis specs must be used? # Try first with those defined at AR Publish Specs level if analysis.portal_type == 'ReferenceAnalysis': # The analysis is a Control or Blank. We might use the # reference results instead other specs uid = analysis.getServiceUID() specs = analysis.aq_parent.getResultsRangeDict().get(uid, {}) elif analysis.portal_type == 'DuplicateAnalysis': specs = analysis.getAnalysisSpecs() else: ar = analysis.aq_parent specs = ar.getPublicationSpecification() if not specs or keyword not in specs.getResultsRangeDict(): specs = analysis.getAnalysisSpecs() specs = specs.getResultsRangeDict().get(keyword, {}) \ if specs else {} andict['specs'] = specs scinot = self.context.bika_setup.getScientificNotationReport() andict['formatted_result'] = analysis.getFormattedResult( specs=specs, sciformat=int(scinot), decimalmark=decimalmark) fs = '' if specs.get('min', None) and specs.get('max', None): fs = '%s - %s' % (specs['min'], specs['max']) elif specs.get('min', None): fs = '> %s' % specs['min'] elif specs.get('max', None): fs = '< %s' % specs['max'] andict['formatted_specs'] = formatDecimalMark(fs, decimalmark) andict['formatted_uncertainty'] = format_uncertainty( analysis, analysis.getResult(), decimalmark=decimalmark, sciformat=int(scinot)) # Return specs of current analysis andict['specs_dict'] = analysis.getSpecification().getResultsRangeDict( ).get(analysis.id) # Out of range? if specs: adapters = getAdapters((analysis, ), IResultOutOfRange) bsc = getToolByName(self.context, "bika_setup_catalog") for name, adapter in adapters: ret = adapter(specification=specs) if ret and ret['out_of_range']: andict['outofrange'] = True break return andict
def _analysis_data(self, analysis, decimalmark=None): keyword = analysis.getKeyword() service = analysis.getService() andict = {'obj': analysis, 'id': analysis.id, 'title': analysis.Title(), 'keyword': keyword, 'scientific_name': service.getScientificName(), 'accredited': service.getAccredited(), 'point_of_capture': to_utf8(POINTS_OF_CAPTURE.getValue(service.getPointOfCapture())), 'category': to_utf8(service.getCategoryTitle()), 'result': analysis.getResult(), 'isnumber': isnumber(analysis.getResult()), 'unit': to_utf8(service.getUnit()), 'formatted_unit': format_supsub(to_utf8(service.getUnit())), 'capture_date': analysis.getResultCaptureDate(), 'request_id': analysis.aq_parent.getId(), 'formatted_result': '', 'uncertainty': analysis.getUncertainty(), 'formatted_uncertainty': '', 'retested': analysis.getRetested(), 'remarks': to_utf8(analysis.getRemarks()), 'resultdm': to_utf8(analysis.getResultDM()), 'outofrange': False, 'type': analysis.portal_type, 'reftype': analysis.getReferenceType() \ if hasattr(analysis, 'getReferenceType') else None, 'worksheet': None, 'specs': {}, 'formatted_specs': ''} if analysis.portal_type == 'DuplicateAnalysis': andict['reftype'] = 'd' ws = analysis.getBackReferences('WorksheetAnalysis') andict['worksheet'] = ws[0].id if ws and len(ws) > 0 else None andict['worksheet_url'] = ws[0].absolute_url if ws and len(ws) > 0 else None andict['refsample'] = analysis.getSample().id \ if analysis.portal_type == 'Analysis' \ else '%s - %s' % (analysis.aq_parent.id, analysis.aq_parent.Title()) if analysis.portal_type == 'ReferenceAnalysis': # The analysis is a Control or Blank. We might use the # reference results instead other specs uid = analysis.getServiceUID() specs = analysis.aq_parent.getResultsRangeDict().get(uid, {}) else: # Get the specs directly from the analysis. The getResultsRange # function already takes care about which are the specs to be used: # AR, client or lab. specs = analysis.getResultsRange() andict['specs'] = specs scinot = self.context.bika_setup.getScientificNotationReport() andict['formatted_result'] = analysis.getFormattedResult(specs=specs, sciformat=int(scinot), decimalmark=decimalmark) fs = '' if specs.get('min', None) and specs.get('max', None): fs = '%s - %s' % (specs['min'], specs['max']) elif specs.get('min', None): fs = '> %s' % specs['min'] elif specs.get('max', None): fs = '< %s' % specs['max'] andict['formatted_specs'] = formatDecimalMark(fs, decimalmark) andict['formatted_uncertainty'] = format_uncertainty(analysis, analysis.getResult(), decimalmark=decimalmark, sciformat=int(scinot)) # Out of range? if specs: adapters = getAdapters((analysis, ), IResultOutOfRange) bsc = getToolByName(self.context, "bika_setup_catalog") for name, adapter in adapters: ret = adapter(specification=specs) if ret and ret['out_of_range']: andict['outofrange'] = True break return andict
def folderitems(self): rc = getToolByName(self.context, REFERENCE_CATALOG) bsc = getToolByName(self.context, 'bika_setup_catalog') analysis_categories = bsc(portal_type="AnalysisCategory", sort_on="sortable_title") analysis_categories_order = dict([(b.Title, "{:04}".format(a)) for a, b in enumerate(analysis_categories)]) 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) member = mtool.getAuthenticatedMember() # 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 dmk = self.context.bika_setup.getResultsDecimalMark() 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() cat_order = analysis_categories_order.get(cat) items[i]['category'] = cat if (cat, cat_order) not in self.categories: self.categories.append((cat, cat_order)) # 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'] = \ formatDecimalMark(interim_fields[x]['value'], dmk) 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.icon 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() 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']; items[i]['structure'] = False; 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']; items[i]['structure'] = True # 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"))) after_icons = [] # Submitting user may not verify results unless the user is labman # or manager and the AS has isSelfVerificationEnabled set to True if items[i]['review_state'] == 'to_be_verified': # If multi-verification required, place an informative icon numverifications = obj.getNumberOfRequiredVerifications() if numverifications > 1: # More than one verification required, place an icon # Get the number of verifications already done: done = obj.getNumberOfVerifications() pending = numverifications - done ratio = float(done)/float(numverifications) \ if done > 0 else 0 scale = '' if ratio < 0.25 else '25' \ if ratio < 0.50 else '50' \ if ratio < 0.75 else '75' anchor = "<a href='#' title='%s %s %s' " \ "class='multi-verification scale-%s'>%s/%s</a>" anchor = anchor % (t(_("Multi-verification required")), str(pending), t(_("verification(s) pending")), scale, str(done), str(numverifications)) after_icons.append(anchor) username = member.getUserName() allowed = api.user.has_permission(VerifyPermission, username=username) if allowed and not obj.isUserAllowedToVerify(member): after_icons.append( "<img src='++resource++bika.lims.images/submitted-by-current-user.png' title='%s'/>" % (t(_("Cannot verify, submitted or verified by current user before"))) ) elif allowed: if obj.getSubmittedBy() == member.getUser().getId(): after_icons.append( "<img src='++resource++bika.lims.images/warning.png' title='%s'/>" % (t(_("Can verify, but submitted by current user"))) ) #If analysis Submitted and Verified by the same person, then warning icon will appear. submitter=obj.getSubmittedBy() if submitter and obj.wasVerifiedByUser(submitter): after_icons.append( "<img src='++resource++bika.lims.images/warning.png' title='%s'/>" % (t(_("Submited and verified by the same user- "+ submitter))) ) # 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] after_icons.append("<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)})))) items[i]['after']['state_title'] = ' '.join(after_icons) # 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 if self.show_categories: self.categories = map(lambda x: x[0], sorted(self.categories, key=lambda x: x[1])) else: 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): bsc = getToolByName(self.context, 'bika_setup_catalog') analysis_categories = bsc(portal_type="AnalysisCategory", sort_on="sortable_title") analysis_categories_order = dict([ (b.Title, "{:04}".format(a)) for a, b in enumerate(analysis_categories) ]) 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) member = mtool.getAuthenticatedMember() self.interim_fields = {} self.interim_columns = {} self.specs = {} show_methodinstr_columns = False dmk = self.context.bika_setup.getResultsDecimalMark() for item in items: if 'obj' not in item: logger.warn( "Missing 'obj' key in Analysis item '{}'".format(item)) continue # self.contentsMethod may return brains or objects. obj = api.get_object(item["obj"]) if workflow.getInfoFor(obj, 'review_state') == 'retracted' \ and not checkPermission(ViewRetractedAnalyses, self.context): logger.info("Skipping retracted analysis {}".format( obj.getId())) continue result = obj.getResult() service = obj.getService() calculation = service.getCalculation() unit = service.getUnit() keyword = service.getKeyword() if self.show_categories: cat = obj.getService().getCategoryTitle() cat_order = analysis_categories_order.get(cat) item['category'] = cat if (cat, cat_order) not in self.categories: self.categories.append((cat, cat_order)) # 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'] = \ formatDecimalMark(interim_fields[x]['value'], dmk) self.interim_fields[obj.UID()] = interim_fields item['service_uid'] = service.UID() item['Service'] = service.Title() item['Keyword'] = keyword item['Unit'] = format_supsub(unit) if unit else '' item['Result'] = '' item['formatted_result'] = '' item['interim_fields'] = interim_fields item['Remarks'] = obj.getRemarks() item['Uncertainty'] = '' item['DetectionLimit'] = '' item['retested'] = obj.getRetested() item['class']['retested'] = 'center' item['result_captured'] = self.ulocalized_time( obj.getResultCaptureDate(), long_format=0) item['calculation'] = calculation and True or False try: item['Partition'] = obj.getSamplePartition().getId() except AttributeError: item['Partition'] = '' if obj.portal_type == "ReferenceAnalysis": item['DueDate'] = self.ulocalized_time( obj.aq_parent.getExpiryDate(), long_format=0) else: item['DueDate'] = self.ulocalized_time(obj.getDueDate(), long_format=1) cd = obj.getResultCaptureDate() item['CaptureDate'] = cd and self.ulocalized_time( cd, long_format=1) or '' item['Attachments'] = '' item['allow_edit'] = [] tblrowclass = item.get('table_row_class') if obj.portal_type == 'ReferenceAnalysis': item['st_uid'] = obj.aq_parent.UID() item['table_row_class'] = ' '.join( [tblrowclass, 'qc-analysis']) elif obj.portal_type == 'DuplicateAnalysis' and \ obj.getAnalysis().portal_type == 'ReferenceAnalysis': item['st_uid'] = obj.aq_parent.UID() item['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 '' item['st_uid'] = st_uid if checkPermission(ManageBika, self.context): # service_uid = service.UID() # latest = rc.lookupObject(service_uid).version_id item['Service'] = service.Title() item['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: item['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()]: item['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 item['calculation'] or \ (item['calculation'] and self.interim_fields[obj.UID()]): item['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() item['Analyst'] = analyst item['choices']['Analyst'] = self.getAnalysts() else: item['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.icon 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>" item['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: item['Result'] = result scinot = self.context.bika_setup.getScientificNotationResults() item['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( ) is True: unc = obj.getUncertainty(result) item['allow_edit'].append('Uncertainty') item['Uncertainty'] = unc if unc else '' item['before']['Uncertainty'] = '± ' item['after'][ 'Uncertainty'] = '<em class="discreet" style="white-space:nowrap;"> %s</em>' % item[ 'Unit'] item['structure'] = False elif fu: item['Uncertainty'] = fu item['before']['Uncertainty'] = '± ' item['after'][ 'Uncertainty'] = '<em class="discreet" style="white-space:nowrap;"> %s</em>' % item[ 'Unit'] item['structure'] = True # 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() is 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: item['Specification'] = "" if 'Result' in item['allow_edit']: item['allow_edit'].remove('Result') item['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 = "" item['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 item[f['keyword']] = f item['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() item['replace']['DueDate'] = \ self.ulocalized_time(duedate, long_format=1) if item['review_state'] not in [ 'to_be_sampled', 'to_be_preserved', 'sample_due', 'published' ]: if (resultdate and resultdate > duedate) \ or (not resultdate and DateTime() > duedate): item['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"))) after_icons = [] # Submitting user may not verify results unless the user is labman # or manager and the AS has isSelfVerificationEnabled set to True if item['review_state'] == 'to_be_verified': # If multi-verification required, place an informative icon numverifications = obj.getNumberOfRequiredVerifications() if numverifications > 1: # More than one verification required, place an icon # Get the number of verifications already done: done = obj.getNumberOfVerifications() pending = numverifications - done ratio = float(done) / float(numverifications) \ if done > 0 else 0 scale = '' if ratio < 0.25 else '25' \ if ratio < 0.50 else '50' \ if ratio < 0.75 else '75' anchor = "<a href='#' title='%s %s %s' " \ "class='multi-verification scale-%s'>%s/%s</a>" anchor = anchor % (t( _("Multi-verification required")), str(pending), t(_("verification(s) pending")), scale, str(done), str(numverifications)) after_icons.append(anchor) username = member.getUserName() allowed = ploneapi.user.has_permission(VerifyPermission, username=username) if allowed and not obj.isUserAllowedToVerify(member): after_icons.append( "<img src='++resource++bika.lims.images/submitted-by-current-user.png' title='%s'/>" % (t( _("Cannot verify, submitted or verified by current user before" )))) elif allowed: if obj.getSubmittedBy() == member.getUser().getId(): after_icons.append( "<img src='++resource++bika.lims.images/warning.png' title='%s'/>" % (t(_("Can verify, but submitted by current user"))) ) # If analysis Submitted and Verified by the same person, then warning icon will appear. submitter = obj.getSubmittedBy() if submitter and obj.wasVerifiedByUser(submitter): after_icons.append( "<img src='++resource++bika.lims.images/warning.png' title='%s'/>" % (t( _("Submited and verified by the same user- " + submitter)))) # add icon for assigned analyses in AR views if self.context.portal_type == 'AnalysisRequest': obj = item['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] after_icons.append( "<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) })))) item['after']['state_title'] = ' '.join(after_icons) # 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 if self.show_categories: self.categories = map(lambda x: x[0], sorted(self.categories, key=lambda x: x[1])) else: 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