Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 117 additions & 7 deletions src/bika/lims/browser/analyses/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def __init__(self, context, request, **kwargs):
("Calculation", {
"title": _("Calculation"),
"sortable": False,
"toggle": False}),
"toggle": False}),
("Analyst", {
"title": _("Analyst"),
"sortable": False,
Expand All @@ -155,13 +155,19 @@ def __init__(self, context, request, **kwargs):
"input_class": "ajax_calculate numeric",
"ajax": True,
"sortable": False}),
("Specification", {
"title": _("Specification"),
"sortable": False}),
("Uncertainty", {
"title": _("+-"),
"ajax": True,
"sortable": False}),
("Unit", {
"title": _("Unit"),
"sortable": False,
"ajax": True,
"on_change": "_on_unit_change",
"toggle": True}),
("Specification", {
"title": _("Specification"),
"sortable": False}),
("retested", {
"title": _("Retested"),
"type": "boolean",
Expand Down Expand Up @@ -454,6 +460,32 @@ def get_methods_vocabulary(self, analysis_brain):
})
return vocab

def get_unit_vocabulary(self, analysis_brain):
"""Returns a vocabulary with all the units available for the passed in
analysis.

The vocabulary is a list of dictionaries. Each dictionary has the
following structure:

{'ResultValue': <method_UID>,
'ResultText': <method_Title>}

:param analysis_brain: A single Analysis brain
:type analysis_brain: CatalogBrain
:returns: A list of dicts
"""
obj = self.get_object(analysis_brain)
# Get unit choices
unit_choices = obj.getUnitChoices()
vocab = []
for unit in unit_choices:
vocab.append({
"ResultValue": unit['value'],
"ResultText": unit['value'],
})
return vocab


def get_instruments_vocabulary(self, analysis, method=None):
"""Returns a vocabulary with the valid and active instruments available
for the analysis passed in.
Expand Down Expand Up @@ -664,6 +696,8 @@ def folderitem(self, obj, item, index):
self._folder_item_result(obj, item)
# Fill calculation and interim fields
self._folder_item_calculation(obj, item)
# Fill unit field
self._folder_item_unit(obj, item)
# Fill method
self._folder_item_method(obj, item)
# Fill instrument
Expand Down Expand Up @@ -700,7 +734,6 @@ def folderitem(self, obj, item, index):
self._folder_item_remarks(obj, item)
# Renders the analysis conditions
self._folder_item_conditions(obj, item)

return item

def folderitems(self):
Expand Down Expand Up @@ -778,14 +811,15 @@ def folderitems(self):
# analyses requires them to be displayed for selection
self.columns["Method"]["toggle"] = self.is_method_column_required()
self.columns["Instrument"]["toggle"] = self.is_instrument_column_required()
self.columns["Unit"]["toggle"] = self.is_unit_selection_column_required()

return items

def render_unit(self, unit, css_class=None):
"""Render HTML element for unit
"""
if css_class is None:
css_class = "unit d-inline-block py-2 small text-secondary"
css_class = "unit d-inline-block py-2 small text-secondary text-nowrap"
return "<span class='{css_class}'>{unit}</span>".format(
unit=unit, css_class=css_class)

Expand Down Expand Up @@ -1053,6 +1087,22 @@ def _folder_item_calculation(self, analysis_brain, item):
item["interimfields"] = interim_fields
self.interim_fields[analysis_brain.UID] = interim_fields

def _folder_item_unit(self, analysis_brain, item):
"""Fills the analysis' unit 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.is_analysis_edition_allowed(analysis_brain):
return

# Edition allowed
voc = self.get_unit_vocabulary(analysis_brain)
if voc:
item["choices"]["Unit"] = voc
item["allow_edit"].append("Unit")


def _folder_item_method(self, analysis_brain, item):
"""Fills the analysis' method to the item passed in.

Expand All @@ -1073,7 +1123,25 @@ def _folder_item_method(self, analysis_brain, item):
item["Method"] = api.get_title(method)
item["replace"]["Method"] = get_link_for(method, tabindex="-1")

def _on_unit_choice_change(self, uid=None, value=None, item=None, **kw):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need this if you do it differently. Hint: You don't need a "UnitChoices" column, but a "Unit" column that becomes editable (with a choices list) when the analysis have multiple units set. Otherwise, the column "Unit" is not rendered.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed UnitChoice column. Unit it column is only rendered when needed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can remove this function _on_unit_choice_change, cause on_unit_change is used instead

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, removed.

""" updates the unit on selection of unit_choices.
"""
item["Unit"] = value
unit = item.get("Unit")

item["after"]["Result"] = self.render_unit(unit)
uncertainty = item.get("Uncertainty")
if uncertainty:
item["after"]["Uncertainty"] = self.render_unit(unit)

elif "Uncertainty" in item["allow_edit"]:
item["after"]["Uncertainty"] = self.render_unit(unit)

return item


def _on_method_change(self, uid=None, value=None, item=None, **kw):

"""Update instrument and calculation when the method changes

:param uid: object UID
Expand Down Expand Up @@ -1122,6 +1190,19 @@ def _folder_item_instrument(self, analysis_brain, item):
else:
item["Instrument"] = _("Manual")


def _on_unit_change(self, uid=None, value=None, item=None, **kw):
""" updates the rendered unit on selection of unit.
"""
Comment thread
RML-IAEA marked this conversation as resolved.
item["after"]["Result"] = self.render_unit(value)
uncertainty = item.get("Uncertainty")
if uncertainty:
item["after"]["Uncertainty"] = self.render_unit(value)

elif "Uncertainty" in item["allow_edit"]:
item["after"]["Uncertainty"] = self.render_unit(value)
return item

def _folder_item_analyst(self, obj, item):
obj = self.get_object(obj)
analyst = obj.getAnalyst()
Expand Down Expand Up @@ -1189,15 +1270,22 @@ def _folder_item_uncertainty(self, analysis_brain, item):
allow_edit = self.is_uncertainty_edition_allowed(analysis_brain)
if allow_edit:
item["Uncertainty"] = obj.getUncertainty()
item["before"]["Uncertainty"] = "± "
item["allow_edit"].append("Uncertainty")
unit = item.get("Unit")
if unit:
item["after"]["Uncertainty"] = self.render_unit(unit)
return

formatted = format_uncertainty(
obj, decimalmark=self.dmk, sciformat=int(self.scinot))
if formatted:
item["replace"]["Uncertainty"] = formatted
item["before"]["Uncertainty"] = "± "
item["after"]["Uncertainty"] = obj.getUnit()
unit = item.get("Unit")
if unit:
item["after"]["Uncertainty"] = self.render_unit(unit)
return

def _folder_item_detection_limits(self, analysis_brain, item):
"""Fills the analysis' detection limits to the item passed in.
Expand Down Expand Up @@ -1567,6 +1655,17 @@ def is_instrument_required(self, analysis):
# a method is selected
return len(instruments) > 0

def is_unit_choices_required(self, analysis):
"""Returns whether the render of the unit choice selection list is
required for the analysis passed-in.
:param analysis: Brain or object that represents an analysis
"""
# Always return true if the analysis has unitchoices
analysis = self.get_object(analysis)
if analysis.getUnitChoices():
return True
Comment thread
xispa marked this conversation as resolved.
return False

def is_method_column_required(self):
"""Returns whether the method column has to be rendered or not.
Returns True if at least one of the analyses from the listing requires
Expand All @@ -1588,3 +1687,14 @@ def is_instrument_column_required(self):
if self.is_instrument_required(obj):
return True
return False

def is_unit_selection_column_required(self):
"""Returns whether the unit column has to be rendered or not.
Returns True if at least one of the analyses from the listing requires
the list for unit selection to be rendered
"""
for item in self.items:
obj = item.get("obj")
if self.is_unit_choices_required(obj):
return True
return False
42 changes: 33 additions & 9 deletions src/bika/lims/content/abstractbaseanalysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,19 +97,49 @@
)
)


# The units of measurement used for representing results in reports and in
# manage_results screen.
Unit = StringField(
'Unit',
schemata="Description",
write_permission=FieldEditAnalysisResult,
widget=StringWidget(
label=_("Unit"),
label=_("Default Unit"),
description=_(
"The measurement units for this analysis service' results, e.g. "
"mg/l, ppm, dB, mV, etc."),
)
)

# A selection of units that are able to update Unit.
UnitChoices = RecordsField(
"UnitChoices",
schemata="Description",
type="UnitChoices",
subfields=(
"value",
),
subfield_labels={
"value": _(" "),
},
subfield_types={
"value": "string",
},
subfield_sizes={
"value": 20,
},
subfield_maxlength={
"value": 50,
},
widget=RecordsWidget(
label=_("Units for Selection"),
description=_(
"Provide a list of units that are suitable for the analysis. Ensure to include the default unit in this list. "
),
)
)

# Decimal precision for printing normal decimal results.
Precision = IntegerField(
'Precision',
Expand Down Expand Up @@ -686,6 +716,7 @@
ProtocolID,
ScientificName,
Unit,
UnitChoices,
Precision,
ExponentialFormatPrecision,
LowerDetectionLimit,
Expand Down Expand Up @@ -744,14 +775,7 @@ def _getCatalogTool(self):
@security.public
def Title(self):
return _c(self.title)

@security.public
def getUnit(self):
"""Returns the Unit
"""
unit = self.Schema().getField("Unit").get(self) or ""
return unit.strip()


@security.public
def getDefaultVAT(self):
"""Return default VAT from bika_setup
Expand Down
1 change: 1 addition & 0 deletions src/bika/lims/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -1507,3 +1507,4 @@ def validate_record(self, record):


validation.register(ServiceConditionsValidator())

12 changes: 6 additions & 6 deletions src/senaite/core/browser/fields/record.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,12 +213,12 @@ def getSubfieldViews(self,instance,joinWith=', '):

# this is really special purpose and in no ways generic
def hideEmail(self,email='',instance=None):
masked = 'email: ' + \
email.replace('@', ' (at) ').replace('.', ' (dot) ')
membertool = getToolByName(instance,'portal_membership',None)
if membertool is None or membertool.isAnonymousUser():
return masked
return "<a href='mailto:%s'>%s</a>" % (email,email)
masked = 'email: ' + \
email.replace('@', ' (at) ').replace('.', ' (dot) ')
membertool = getToolByName(instance,'portal_membership',None)
if membertool is None or membertool.isAnonymousUser():
return masked
return "<a href='mailto:%s'>%s</a>" % (email,email)

def labelPhone(self,phone=''):
return 'phone: ' + phone
Expand Down
1 change: 1 addition & 0 deletions src/senaite/core/catalog/analysis_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"getServiceUID",
"getSubmittedBy",
"getUnit",
"getUnitChoices",
"getVerificators",
"isSelfVerificationEnabled",
]
Expand Down