Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New function for interim fields formatting #2365

Merged
merged 5 commits into from
Aug 25, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Changelog
2.5.0 (unreleased)
------------------

- #2365 New function for interim fields formatting
- #2364 Support for fieldname-prefixed values on sample header submit
- #2363 Auto-hide non-multivalued reference widget input on value selection
- #2359 Improve sample create performance
Expand Down
29 changes: 29 additions & 0 deletions src/bika/lims/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
# Some rights reserved, see README and LICENSE.

import copy
import json
import re
from collections import OrderedDict
from datetime import datetime
Expand Down Expand Up @@ -1787,3 +1788,31 @@ def is_string(thing):
:returns: True if the object is a string
"""
return isinstance(thing, six.string_types)


def parse_json(thing, default=""):
"""Parse from JSON

:param thing: thing to parse
:param default: value to return if cannot parse
:returns: the object representing the JSON or default
"""
try:
return json.loads(thing)
except (TypeError, ValueError):
return default


def to_list(value):
"""Converts the value to a list

:param value: the value to be represented as a list
:returns: a list that represents or contains the value
"""
if is_string(value):
val = parse_json(value)
if isinstance(val, (list, tuple, set)):
value = val
if not isinstance(value, (list, tuple, set)):
value = [value]
return list(value)
Copy link
Contributor

Choose a reason for hiding this comment

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

Please split this function into 2, e.g. something like this:

def parse_json(thing, default=""):
    """Parse from JSON
    """
    try:
        return json.loads(thing)
    except (TypeError, ValueError):
        return default

def to_list(value):
    """Converts the value to a list
    :param value: the value to be represented as a list
    :returns: a list that represents or contains the value
    """
    if is_string(value):
        value = parse_json(value, [])
    if not isinstance(value, (list, tuple, set)):
        value = [value]
    return list(value)

Copy link
Member Author

Choose a reason for hiding this comment

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

Done with 4134d9b

16 changes: 5 additions & 11 deletions src/bika/lims/browser/analyses/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

from bika.lims import api
from bika.lims import bikaMessageFactory as _
from bika.lims import deprecated
from bika.lims import logger
from bika.lims.api.analysis import get_formatted_interval
from bika.lims.api.analysis import is_out_of_range
Expand Down Expand Up @@ -1011,18 +1012,11 @@ def is_multi_interim(self, interim):
result_type = interim.get("result_type", "")
return result_type.startswith("multi")

@deprecated("Use api.to_list instead")
def to_list(self, value):
"""Converts the value to a list
"""
try:
val = json.loads(value)
if isinstance(val, (list, tuple, set)):
value = val
except (ValueError, TypeError):
pass
if not isinstance(value, (list, tuple, set)):
value = [value]
return value
return api.to_list(value)

def _folder_item_calculation(self, analysis_brain, item):
"""Set the analysis' calculation and interims to the item passed in.
Expand Down Expand Up @@ -1090,7 +1084,7 @@ def _folder_item_calculation(self, analysis_brain, item):
choices = interim_field.get("choices")
if choices:
# Process the value as a list
interim_value = self.to_list(interim_value)
interim_value = api.to_list(interim_value)

# Get the {value:text} dict
choices = choices.split("|")
Expand Down Expand Up @@ -1120,7 +1114,7 @@ def _folder_item_calculation(self, analysis_brain, item):

elif self.is_multi_interim(interim_field):
# Process the value as a list
interim_value = self.to_list(interim_value)
interim_value = api.to_list(interim_value)

# Set the text as the formatted value
text = "<br/>".join(filter(None, interim_value))
Expand Down
41 changes: 41 additions & 0 deletions src/bika/lims/utils/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
# Copyright 2018-2021 by it's authors.
# Some rights reserved, see README and LICENSE.

import copy
import math

from bika.lims import api
Expand All @@ -26,6 +27,7 @@
from bika.lims.interfaces import IReferenceSample
from bika.lims.interfaces.analysis import IRequestAnalysis
from bika.lims.utils import formatDecimalMark
from bika.lims.utils import format_supsub


def create_analysis(context, source, **kwargs):
Expand Down Expand Up @@ -416,3 +418,42 @@ def generate_analysis_id(instance, keyword):
new_id = "{}-{}".format(keyword, count)
count += 1
return new_id


def format_interim(interim_field, html=True):
"""Returns a copy of the interim field plus additional attributes suitable
for visualization, like formatted_result and formatted_unit
"""
separator = "<br/>" if html else ", "

# copy to prevent persistent changes
item = copy.deepcopy(interim_field)

# get the raw value
value = item.get("value", "")
values = filter(None, api.to_list(value))

# if choices, display texts instead of values
choices = item.get("choices")
if choices:
# generate a {value:text} dict
choices = choices.split("|")
choices = dict(map(lambda ch: ch.strip().split(":"), choices))

# set the text as the formatted value
texts = [choices.get(v, "") for v in values]
values = filter(None, texts)

else:
# default formatting
setup = api.get_setup()
decimal_mark = setup.getResultsDecimalMark()
values = [formatDecimalMark(val, decimal_mark) for val in values]

item["formatted_value"] = separator.join(values)

# unit formatting
unit = item.get("unit", "")
item["formatted_unit"] = format_supsub(unit) if html else unit

return item
54 changes: 54 additions & 0 deletions src/senaite/core/tests/doctests/API.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2097,3 +2097,57 @@ It works for Dexterity types as well::

>>> sample_container_copy.getCapacity()
'50 ml'


Parse to JSON
.............

>>> api.parse_json('["a", "b", "c"]')
[u'a', u'b', u'c']

>>> obj = api.parse_json('{"a": 1, "b": 2, "c": 3}')
>>> [obj[key] for key in 'abc']
[1, 2, 3]

>>> obj = api.parse_json('{"a": 1, "b": ["one", "two", 3], "c": 3}')
>>> [obj[key] for key in 'abc']
[1, [u'one', u'two', 3], 3]

>>> api.parse_json("ko")
''

>>> api.parse_json("ko", default="ok")
'ok'

Convert to list
...............

>>> api.to_list(None)
[None]

>>> api.to_list(["a", "b", "c"])
['a', 'b', 'c']

>>> api.to_list('["a", "b", "c"]')
[u'a', u'b', u'c']

>>> api.to_list("a, b, c")
['a, b, c']

>>> api.to_list([{"a": 1}, {"b": 2}, {"c": 3}])
[{'a': 1}, {'b': 2}, {'c': 3}]

>>> api.to_list('[{"a": 1}, {"b": 2}, {"c": 3}]')
[{u'a': 1}, {u'b': 2}, {u'c': 3}]

>>> api.to_list({"a": 1})
[{'a': 1}]

>>> api.to_list('{"a": 1, "b": ["one", "two", 3], "c": 3}')
['{"a": 1, "b": ["one", "two", 3], "c": 3}']

>>> api.to_list(["[1, 2, 3]", "b", "c"])
['[1, 2, 3]', 'b', 'c']

>>> api.to_list('["[1, 2, 3]", "b", "c"]')
[u'[1, 2, 3]', u'b', u'c']