Skip to content

Commit

Permalink
Implemented Corrections
Browse files Browse the repository at this point in the history
Implemented corrections from #2201 (review)
  • Loading branch information
RML-IAEA committed Jan 13, 2023
1 parent f16bb68 commit ac745f9
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 129 deletions.
150 changes: 99 additions & 51 deletions src/bika/lims/browser/analyses/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,13 +134,7 @@ def __init__(self, context, request, **kwargs):
("Calculation", {
"title": _("Calculation"),
"sortable": False,
"toggle": False}),
("UnitChoices", {
"title": _("Unit Selection"),
"sortable": False,
"ajax": True,
"on_change": "_on_unit_choice_change",
"toggle": True}),
"toggle": False}),
("Analyst", {
"title": _("Analyst"),
"sortable": False,
Expand All @@ -165,6 +159,12 @@ def __init__(self, context, request, **kwargs):
"title": _("+-"),
"ajax": True,
"sortable": False}),
("Unit", {
"title": _("Unit"),
"sortable": False,
"ajax": True,
"on_change": "_on_unit_change",
"toggle": True}),
("Specification", {
"title": _("Specification"),
"sortable": False}),
Expand Down Expand Up @@ -460,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 @@ -670,8 +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 choices field
self._folder_item_unit_choices(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 @@ -708,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 @@ -786,7 +811,7 @@ 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["UnitChoices"]["toggle"] = self.is_unit_choices_column_required()
self.columns["Unit"]["toggle"] = self.is_unit_selection_column_required()

return items

Expand Down Expand Up @@ -901,6 +926,14 @@ def _folder_item_result(self, analysis_brain, item):
if self.is_result_edition_allowed(analysis_brain):
item["allow_edit"].append("Result")

# Display the DL operand (< or >) in the results entry field if
# the manual entry of DL is set, but DL selector is hidden
allow_manual = obj.getAllowManualDetectionLimit()
selector = obj.getDetectionLimitSelector()
if allow_manual and not selector:
operand = obj.getDetectionLimitOperand()
item["Result"] = "{} {}".format(operand, result).strip()

# Prepare result options
choices = obj.getResultOptions()
if choices:
Expand Down Expand Up @@ -1054,42 +1087,20 @@ def _folder_item_calculation(self, analysis_brain, item):
item["interimfields"] = interim_fields
self.interim_fields[analysis_brain.UID] = interim_fields

def _folder_item_unit_choices(self, analysis_brain, item):
"""Set the unit choices to the item passed in.
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.has_permission(ViewResults, analysis_brain):
# Hide unit_choices if user cannot view results
if not self.is_analysis_edition_allowed(analysis_brain):
return
is_editable = self.is_analysis_edition_allowed(analysis_brain)
if is_editable:
# Set unit_choices. The default unit is also included.
analysis_obj = self.get_object(analysis_brain)
# get unit_choice_fields
unit_choice_fields = analysis_obj.getUnitChoices() or list()
if unit_choice_fields:
# Get the keys for the dictionary
unit_choice_fields_key=set().union(*(d.keys() for d in unit_choice_fields))
if 'unitchoice' in unit_choice_fields_key:
# Get the default unit
unit=analysis_obj.getUnit()
# Copy to prevent to avoid persistent changes
unit_choice_fields = deepcopy(unit_choice_fields)
# Get the {value:text} dict
# Remove leading white space on front of unit choices.
choices=["{}:{}".format(x['unitchoice'],x['unitchoice'].lstrip()) for x in unit_choice_fields]
# Add default unit to list of choices
# Space in {}: {} ensures that the default unit is always on top of the selection.
choices.append("{}: {}".format(unit,unit))
# Create a dictionary of the choices
choices = dict(map(lambda ch: ch.strip().split(":"), choices))
# Generate the display list
# [{"ResultValue": value, "ResultText": text},]
headers = ["ResultValue", "ResultText"]
dl = map(lambda it: dict(zip(headers, it)), choices.items())
item["choices"]["UnitChoices"] = dl
item["allow_edit"].append("UnitChoices")

# 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):
Expand Down Expand Up @@ -1117,10 +1128,17 @@ def _on_unit_choice_change(self, uid=None, value=None, item=None, **kw):
"""
item["Unit"] = value
unit = item.get("Unit")
if unit:
item["after"]["Result"] = self.render_unit(unit)

return item
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):

Expand Down Expand Up @@ -1172,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.
"""
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 @@ -1201,7 +1232,8 @@ def _folder_item_attachments(self, obj, item):
url = "{}/at_download/AttachmentFile".format(api.get_url(at))
link = get_link(url, at_file.filename, tabindex="-1")
attachments_html.append(link)
attachments_names.append(at_file.filename)
filename = attachment.getFilename()
attachments_names.append(filename)

if attachments_html:
item["replace"]["Attachments"] = "<br/>".join(attachments_html)
Expand All @@ -1211,6 +1243,14 @@ def _folder_item_attachments(self, obj, item):
img = get_image("warning.png", title=_("Attachment required"))
item["replace"]["Attachments"] = img

def get_attachment_link(self, attachment):
"""Returns a well-formed link for the attachment passed in
"""
filename = attachment.getFilename()
att_url = api.get_url(attachment)
url = "{}/at_download/AttachmentFile".format(att_url)
return get_link(url, filename, tabindex="-1")

def _folder_item_uncertainty(self, analysis_brain, item):
"""Fills the analysis' uncertainty to the item passed in.
Expand All @@ -1231,7 +1271,11 @@ 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

result = obj.getResult()
Expand All @@ -1240,7 +1284,10 @@ def _folder_item_uncertainty(self, analysis_brain, item):
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 @@ -1610,6 +1657,7 @@ def is_unit_choices_required(self, analysis):
analysis = self.get_object(analysis)
if analysis.getUnitChoices():
return True
return False

def is_method_column_required(self):
"""Returns whether the method column has to be rendered or not.
Expand All @@ -1633,10 +1681,10 @@ def is_instrument_column_required(self):
return True
return False

def is_unit_choices_column_required(self):
"""Returns whether the unit_choices column has to be rendered or not.
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_choices selection to be rendered
the list for unit selection to be rendered
"""
for item in self.items:
obj = item.get("obj")
Expand Down
43 changes: 11 additions & 32 deletions src/bika/lims/content/abstractbaseanalysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,13 @@
)
)


# 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=_("Default Unit"),
description=_(
Expand All @@ -110,39 +112,30 @@
)
)

# A selection of units that are able to update Unit.
UnitChoices = RecordsField(
"UnitChoices",
schemata="Description",
type="UnitChoices",
subfields=(
"unitchoice",
),
required_subfields=(
"unitchoice",
"value",
),
subfield_labels={
"unitchoice": _("Units"),
},
subfield_descriptions={
"unitchoice": _("Please provide a list of units"),
"value": _(" "),
},
subfield_types={
"unitchoice": "string",
"value": "string",
},
subfield_sizes={
"unitchoice": 20,
},
subfield_validators={
"unitchoice": "service_unitchoices_validator",
"value": 20,
},
subfield_maxlength={
"unitchoice": 50,
"description": 200,
"value": 50,
},
widget=RecordsWidget(
label=_("Multiple Unit Selection"),
label=_("Units for Selection"),
description=_(
"Provide a list of units that are suitable for the analysis."
"Provide a list of units that are suitable for the analysis. Ensure to include the default unit in this list. "
),
)
)
Expand Down Expand Up @@ -783,21 +776,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 getUnitChoices(self):
'''Returns the UnitChoices
:returns: List of UnitChoices objects
'''
return self.Schema().getField("UnitChoices").get(self) or ""


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

validation.register(ServiceConditionsValidator())

class ServiceUnitChoicesValidator(object):
"""Validate AnalysisService UnitChoices field
"""
implements(IValidator)
name = "service_unitchoices_validator"

def __call__(self, field_value, **kwargs):
instance = kwargs["instance"]
request = kwargs.get("REQUEST", {})
translate = getToolByName(instance, "translation_service").translate
field_name = kwargs["field"].getName()

# This value in request prevents running once per subfield value.
# self.name returns the name of the validator. This allows other
# subfield validators to be called if defined (eg. in other add-ons)
key = "{}-{}-{}".format(self.name, instance.getId(), field_name)
if instance.REQUEST.get(key, False):
return True

# Walk through all records set for this records field
field_name_value = "{}_value".format(field_name)
records = request.get(field_name_value, [])
for record in records:
# Validate the record
msg = self.validate_record(record)
if msg:
return to_utf8(translate(msg))

instance.REQUEST[key] = True
return True

def validate_record(self, record):
unit_choice = record.get("unitchoice")
# Check that the unit choice is a string
if not unit_choice:
return _("Validation failed: '{}' is not a string").format(
unit_choice)


validation.register(ServiceUnitChoicesValidator())
Loading

0 comments on commit ac745f9

Please sign in to comment.