Skip to content

Commit 6101d0f

Browse files
ramonskixispa
andauthored
Convert LDL/UDL fields to string (senaite#2103)
* Migrate LDL/UDL FixedPoint -> StringField * Changelog updated * Cleanup of the migration steps * Fixed typo * Re-added custom getters * Cut off trailing zeros Co-authored-by: Jordi Puiggené <[email protected]>
1 parent b0267f6 commit 6101d0f

File tree

3 files changed

+100
-32
lines changed

3 files changed

+100
-32
lines changed

CHANGES.rst

+3-1
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ Changelog
55
2.3.0 (unreleased)
66
------------------
77

8+
- #2103 Convert LDL/UDL fields to string
89
- #2101 Add help text for numeric result
9-
- #2097 Fix Attribute Error in Multi- Sample Add form when current user is linked to a client contact
10+
- #2097 Fix Attribute Error in Multi- Sample Add form when current user is a client contact
11+
- #2096 Convert uncertainty field to string
1012
- #2095 Fix rounded uncertainty value is stored in the database
1113
- #2094 Skip Auditlog catalog if disabled for DX types catalog multiplexer
1214
- #2090 Add support for dates before 1900

src/bika/lims/content/abstractbaseanalysis.py

+10-20
Original file line numberDiff line numberDiff line change
@@ -136,16 +136,13 @@
136136
)
137137
)
138138

139-
# TODO: Use a plain string field when converting to DX!
140-
#
141139
# If the value is below this limit, it means that the measurement lacks
142140
# accuracy and this will be shown in manage_results and also on the final
143141
# report.
144-
LowerDetectionLimit = FixedPointField(
142+
LowerDetectionLimit = StringField(
145143
"LowerDetectionLimit",
146144
schemata="Analysis",
147145
default="0.0",
148-
precision=1000, # avoid precision cut-off done by the field
149146
widget=DecimalWidget(
150147
label=_("Lower Detection Limit (LDL)"),
151148
description=_(
@@ -156,16 +153,13 @@
156153
)
157154
)
158155

159-
# TODO: Use a plain string field when converting to DX!
160-
#
161156
# If the value is above this limit, it means that the measurement lacks
162157
# accuracy and this will be shown in manage_results and also on the final
163158
# report.
164-
UpperDetectionLimit = FixedPointField(
159+
UpperDetectionLimit = StringField(
165160
"UpperDetectionLimit",
166161
schemata="Analysis",
167162
default="1000000000.0",
168-
precision=1000, # avoid precision cut-off done by the field
169163
widget=DecimalWidget(
170164
label=_("Upper Detection Limit (UDL)"),
171165
description=_(
@@ -851,28 +845,24 @@ def getAnalysisCategories(self):
851845

852846
@security.public
853847
def getLowerDetectionLimit(self):
854-
"""Get the lower detection limit without trailing zeros
848+
"""Get the lower detection limit
855849
"""
856850
field = self.getField("LowerDetectionLimit")
857851
value = field.get(self)
858-
# NOTE: This is a workaround to avoid the cut-off done by the field
859-
# if the value is lower than the precision
860-
# => we should use a string instead
861-
# remove trailing zeros and possible trailing dot
862-
value = value.rstrip("0").rstrip(".")
852+
# cut off trailing zeros
853+
if "." in value:
854+
value = value.rstrip("0").rstrip(".")
863855
return value
864856

865857
@security.public
866858
def getUpperDetectionLimit(self):
867-
"""Get the upper detection limit without trailing zeros
859+
"""Get the upper detection limit
868860
"""
869861
field = self.getField("UpperDetectionLimit")
870862
value = field.get(self)
871-
# NOTE: This is a workaround to avoid the cut-off done by the field
872-
# if the value is lower than the precision
873-
# => we should use a string instead
874-
# remove trailing zeros and possible trailing dot
875-
value = value.rstrip("0").rstrip(".")
863+
# cut off trailing zeros
864+
if "." in value:
865+
value = value.rstrip("0").rstrip(".")
876866
return value
877867

878868
@security.public

src/senaite/core/upgrade/v02_03_000.py

+87-11
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from senaite.core.catalog import ANALYSIS_CATALOG
2525
from senaite.core.catalog import REPORT_CATALOG
2626
from senaite.core.catalog import SAMPLE_CATALOG
27+
from senaite.core.catalog import SETUP_CATALOG
2728
from senaite.core.catalog.report_catalog import ReportCatalog
2829
from senaite.core.config import PROJECTNAME as product
2930
from senaite.core.setuphandlers import _run_import_step
@@ -79,6 +80,7 @@ def upgrade(tool):
7980
fix_interface_interpretation_template(portal)
8081
fix_unassigned_samples(portal)
8182
move_arreports_to_report_catalog(portal)
83+
migrate_analysis_services_fields(portal)
8284
migrate_analyses_fields(portal)
8385

8486
logger.info("{0} upgraded to version {1}".format(product, version))
@@ -284,8 +286,35 @@ def move_arreports_to_report_catalog(portal):
284286
logger.info("Move ARReports to SENAITE Report Catalog [DONE]")
285287

286288

289+
def migrate_analysis_services_fields(portal):
290+
"""Migrate fields in AnalysisService objects
291+
"""
292+
logger.info("Migrate Analysis Services Fields ...")
293+
cat = api.get_tool(SETUP_CATALOG)
294+
query = {"portal_type": ["AnalysisService"]}
295+
brains = cat.search(query)
296+
total = len(brains)
297+
298+
for num, brain in enumerate(brains):
299+
if num and num % 100 == 0:
300+
logger.info("Migrated {0}/{1} analyses fields".format(num, total))
301+
302+
obj = api.get_object(brain)
303+
304+
# Migrate UDL FixedPointField -> StringField
305+
migrate_udl_field_to_string(obj)
306+
307+
# Migrate LDL FixedPointField -> StringField
308+
migrate_ldl_field_to_string(obj)
309+
310+
# Flush the object from memory
311+
obj._p_deactivate() # noqa
312+
313+
logger.info("Migrate Analysis Services [DONE]")
314+
315+
287316
def migrate_analyses_fields(portal):
288-
"""return all analyses
317+
"""Migrate fields in Analysis/ReferenceAnalysis objects
289318
"""
290319
logger.info("Migrate Analyses Fields ...")
291320
cat = api.get_tool(ANALYSIS_CATALOG)
@@ -297,35 +326,82 @@ def migrate_analyses_fields(portal):
297326
if num and num % 100 == 0:
298327
logger.info("Migrated {0}/{1} analyses fields".format(num, total))
299328

300-
analysis = api.get_object(brain)
329+
obj = api.get_object(brain)
301330

302331
# Migrate Uncertainty FixedPointField -> StringField
303-
migrate_uncertainty_field_to_string(analysis)
332+
migrate_uncertainty_field_to_string(obj)
333+
334+
# Migrate UDL FixedPointField -> StringField
335+
migrate_udl_field_to_string(obj)
336+
337+
# Migrate LDL FixedPointField -> StringField
338+
migrate_ldl_field_to_string(obj)
304339

305340
# Flush the object from memory
306-
analysis._p_deactivate() # noqa
341+
obj._p_deactivate() # noqa
307342

308343
logger.info("Migrate Analyses Fields [DONE]")
309344

310345

311-
def migrate_uncertainty_field_to_string(analysis):
346+
def migrate_udl_field_to_string(obj):
347+
"""Migrate the UDL field to string
348+
"""
349+
field = obj.getField("UpperDetectionLimit")
350+
value = field.get(obj)
351+
352+
# Leave any other value type unchanged
353+
if isinstance(value, tuple):
354+
migrated_value = fixed_point_value_to_string(value, 7)
355+
logger.info("Migrating UDL field of %s: %s -> %s" % (
356+
api.get_path(obj), value, migrated_value))
357+
value = migrated_value
358+
359+
# set the new value
360+
field.set(obj, value)
361+
362+
363+
def migrate_ldl_field_to_string(obj):
364+
"""Migrate the LDL field to string
365+
"""
366+
field = obj.getField("LowerDetectionLimit")
367+
value = field.get(obj)
368+
369+
# Leave any other value type unchanged
370+
if isinstance(value, tuple):
371+
migrated_value = fixed_point_value_to_string(value, 7)
372+
logger.info("Migrating LDL field of %s: %s -> %s" % (
373+
api.get_path(obj), value, migrated_value))
374+
value = migrated_value
375+
376+
# set the new value
377+
field.set(obj, value)
378+
379+
380+
def migrate_uncertainty_field_to_string(obj):
312381
"""Migrate the uncertainty field to string
313382
"""
314-
field = analysis.getField("Uncertainty")
315-
value = field.get(analysis)
383+
field = obj.getField("Uncertainty")
384+
value = field.get(obj)
316385

317386
# Leave any other value type unchanged
318387
if isinstance(value, tuple):
319-
value = fixed_point_value_to_string(value)
388+
migrated_value = fixed_point_value_to_string(value, 10)
389+
logger.info("Migrating Uncertainty field of %s: %s -> %s" % (
390+
api.get_pat(obj), value, migrated_value))
391+
value = migrated_value
320392

321393
# set the new value
322-
field.set(analysis, value)
394+
field.set(obj, value)
323395

324396

325-
def fixed_point_value_to_string(value):
397+
def fixed_point_value_to_string(value, precision):
326398
"""Code taken and modified from FixedPointField get method
399+
400+
IMPORTANT: The precision has to be the same as it was initially
401+
defined in the field when the value was set!
402+
Otherwise, values > 0, e.g. 0.0005 are converted wrong!
327403
"""
328-
template = "%%s%%d.%%0%dd" % 10
404+
template = "%%s%%d.%%0%dd" % precision
329405
front, fra = value
330406
sign = ""
331407
# Numbers between -1 and 0 are store with a negative fraction.

0 commit comments

Comments
 (0)