Skip to content

Commit

Permalink
migration iinterims fix & naming issue
Browse files Browse the repository at this point in the history
  • Loading branch information
toropok committed Dec 28, 2024
1 parent 29d682f commit bb2fc1a
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 62 deletions.
13 changes: 13 additions & 0 deletions src/bika/lims/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from collections import OrderedDict
from datetime import datetime
from datetime import timedelta
from functools import reduce
from itertools import groupby

import Missing
Expand Down Expand Up @@ -2058,3 +2059,15 @@ def to_list(value):
if not isinstance(value, (list, tuple, set)):
value = [value]
return list(value)


def deep_get(dictionary, *keys):
"""Get value from arbitrary nested dicts
:param dictionary: source dict
:param keys: enumeration of keys to traverse and return value
:returns: the retrieved value or None
"""
def _inner_get(d, key):
return d.get(key, None) if isinstance(d, dict) else None
return reduce(lambda d, key: _inner_get(d, key), keys, dictionary)
64 changes: 29 additions & 35 deletions src/senaite/core/browser/form/adapters/calculation.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
# Some rights reserved, see README and LICENSE.

import re
from functools import reduce

from bika.lims import api
from bika.lims.api.analysisservice import get_by_keyword
Expand All @@ -43,12 +42,6 @@
FIELD_INTERIM_VALUE = "form.widgets.interims.{}.widgets.value"


def deep_get(dictionary, *keys):
def _inner_get(d, key):
return d.get(key, None) if isinstance(d, dict) else None
return reduce(lambda d, key: _inner_get(d, key), keys, dictionary)


class EditForm(EditFormAdapterBase):
"""Edit form adapter for Calculation
"""
Expand All @@ -66,26 +59,15 @@ def initialized(self, data):
return self.data

def modified(self, data):
errors = FormulaValidator(
self.context,
self.request,
None,
ICalculationSchema,
None).validate(
{"formula": deep_get(data, "form", FIELD_FORMULA) or "",
"interims":
[{"keyword": i} for i in self.get_interim_keywords(data)]})
errors = self.validate_formula(data)
if errors:
err_msg = "; ".join([err.message for err in errors])
self.add_error_field(FIELD_FORMULA, err_msg)
return self.data

# clean prev error messages if validation passed
self.add_error_field(FIELD_FORMULA, "")
keywords = self.process_keywords(data)
self.add_update_field("form.widgets.raw_test_keywords",
",".join(keywords.keys()))

return self.data
return self.update_form(data)

def callback(self, data):
name = data.get("name")
Expand All @@ -102,19 +84,8 @@ def update_form(self, data):
",".join(keywords.keys()))
return self.data

def calculate_result(self, data, parameters=None, imports=None):
form = data.get("form")
formula = " ".join(form.get(FIELD_FORMULA, "").splitlines())
if parameters is None:
parameters = self.get_test_keywords(data)
if imports is None:
imports = self.get_imports(data)
result = calculate_formula(formula, parameters, imports)
self.add_update_field(FIELD_TEST_RESULT, result)
return self.data

def process_keywords(self, data):
interim_keywords = self.get_interim_keywords(data)
interim_keywords = self.get_interimfields_keywords(data)
formula_keywords = self.process_formula(data, interim_keywords)
test_keywords = self.get_test_keywords(data)

Expand Down Expand Up @@ -145,13 +116,14 @@ def update_test_parameters(self, data):
return self.data

def process_formula(self, data, interim_keywords):
formula = deep_get(data, "form", FIELD_FORMULA)
form = data.get("form")
formula = form.get(FIELD_FORMULA) or ""
formula_keywords = self.parse_formula(formula)
result_keywords = {kw: interim_keywords.get(
kw, "") for kw in formula_keywords}
return result_keywords

def get_interim_keywords(self, data):
def get_interimfields_keywords(self, data):
form = data.get("form")
keywords = {}
for k, v in form.items():
Expand Down Expand Up @@ -194,3 +166,25 @@ def get_count_test_rows(self, data):
form = data.get("form")
positions = [k for k in form.keys() if test_keyword_regex.search(k)]
return len(positions)

def get_interim_fields(self, data):
return [{"keyword": i} for i in self.get_interimfields_keywords(data)]

def validate_formula(self, data):
form = data.get("form")
formula = form.get(FIELD_FORMULA) or ""
validator = FormulaValidator(
self.context, self.request, None, ICalculationSchema, None)
return validator.validate({"formula": formula,
"interims": self.get_interim_fields(data)})

def calculate_result(self, data, parameters=None, imports=None):
form = data.get("form")
formula = " ".join(form.get(FIELD_FORMULA, "").splitlines())
if parameters is None:
parameters = self.get_test_keywords(data)
if imports is None:
imports = self.get_imports(data)
result = calculate_formula(formula, parameters, imports)
self.add_update_field(FIELD_TEST_RESULT, result)
return self.data
30 changes: 16 additions & 14 deletions src/senaite/core/content/calculation.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@
from senaite.core.interfaces import ICalculation
from senaite.core.schema.fields import DataGridField
from senaite.core.schema.fields import DataGridRow
from senaite.core.schema.interimsfield import InterimsField
from senaite.core.schema.interimfields import InterimFields
from senaite.core.validators.formula import FormulaValidator
from senaite.core.validators.interimsfield import InterimsFieldValidator
from senaite.core.validators.interimsfield import InterimsFieldValidationErrorView
from senaite.core.validators.interimfields import InterimFieldsValidator
from senaite.core.validators.interimfields import InterimFieldsValidationErrorView
from senaite.core.schema.uidreferencefield import UIDReferenceField
from senaite.core.z3cform.widgets.datagrid import DataGridWidgetFactory
from zope import component
Expand Down Expand Up @@ -205,13 +205,13 @@ class ICalculationSchema(model.Schema):
)

directives.widget(
"interims",
"interim_fields",
DataGridWidgetFactory,
allow_insert=True,
allow_delete=True,
allow_reorder=True,
auto_append=True)
interims = InterimsField(
interim_fields = InterimFields(
title=_(u"label_calculation_interims",
default=u"Calculation Interim Fields"),
description=_(u"description_calculation_imports",
Expand Down Expand Up @@ -340,7 +340,7 @@ def _getModuleMember(self, dotted_name, member):

@security.protected(permissions.View)
def getInterimFields(self):
accessor = self.accessor("interims")
accessor = self.accessor("interim_fields")
return accessor(self) or []

@security.protected(permissions.ModifyPortalContent)
Expand Down Expand Up @@ -376,7 +376,7 @@ def setInterimFields(self, value):
if new_interims:
service.setInterimFields(service_interims)

mutator = self.mutator("interims")
mutator = self.mutator("interim_fields")
mutator(self, new_value)

def getCalculationDependencies(self, flat=False, deps=None):
Expand Down Expand Up @@ -585,18 +585,20 @@ def setDependentServices(self, value):


validator.WidgetsValidatorDiscriminators(
FormulaValidator, schema=util.getSpecification(ICalculationSchema, force=True))
FormulaValidator,
schema=util.getSpecification(ICalculationSchema,
force=True))
component.provideAdapter(FormulaValidator)

validator.WidgetValidatorDiscriminators(
InterimsFieldValidator,
field=ICalculationSchema["interims"],
InterimFieldsValidator,
field=ICalculationSchema["interim_fields"],
)
component.provideAdapter(InterimsFieldValidator)
component.provideAdapter(InterimFieldsValidator)

error.ErrorViewDiscriminators(
InterimsFieldValidationErrorView,
InterimFieldsValidationErrorView,
error=error.MultipleErrors,
field=ICalculationSchema["interims"],
field=ICalculationSchema["interim_fields"],
)
component.provideAdapter(InterimsFieldValidationErrorView)
component.provideAdapter(InterimFieldsValidationErrorView)
2 changes: 1 addition & 1 deletion src/senaite/core/schema/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,6 @@ class ISelectOtherField(INativeString):
"""


class IInterimsField(IList):
class IInterimFields(IList):
"""Senaite Interims field field
"""
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,21 @@

from senaite.core.schema.fields import DataGridField
from senaite.core.schema.fields import DataGridRow
from senaite.core.schema.interfaces import IInterimsField
from senaite.core.schema.interfaces import IInterimFields
from zope import schema
from zope.interface import implementer
from zope.interface import Interface


class IInterimRow(Interface):
class IInterimField(Interface):

directives.widget("keyword", style=u"width: 150px!important;")
keyword = schema.TextLine(
title=_(
u"label_interim_keyword",
default=u"Keyword"
),
required=False,
required=True,
default=u""
)

Expand Down Expand Up @@ -133,12 +133,12 @@ class IInterimRow(Interface):
)


@implementer(IInterimsField)
class InterimsField(DataGridField):
@implementer(IInterimFields)
class InterimFields(DataGridField):

value_type = DataGridRow(schema=IInterimRow)
value_type = DataGridRow(schema=IInterimField)

def __init__(self, **kwargs):
default = kwargs.get("default")
kwargs["default"] = default or []
super(InterimsField, self).__init__(**kwargs)
super(InterimFields, self).__init__(**kwargs)
2 changes: 1 addition & 1 deletion src/senaite/core/tests/test_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from senaite.core.content.calculation import ICalculationSchema
from senaite.core.tests.base import DataTestCase
from senaite.core.validators.formula import FormulaValidator
from senaite.core.validators.interimsfield import InterimsFieldValidator
from senaite.core.validators.interimfields import InterimsFieldValidator
from z3c.form.error import MultipleErrors


Expand Down
9 changes: 8 additions & 1 deletion src/senaite/core/upgrade/v02_06_000.py
Original file line number Diff line number Diff line change
Expand Up @@ -2768,12 +2768,19 @@ def migrate_calculation_to_dx(src, destination=None):
# NOTE: always convert string values to unicode for dexterity fields!
target.title = api.safe_unicode(src.Title() or "")
target.description = api.safe_unicode(src.Description() or "")
target.setInterimFields(src.getInterimFields() or [])
target.setPythonImports(src.getPythonImports() or [])
target.setFormula(src.getFormula())
target.setTestParameters(src.getTestParameters() or [])
target.setTestResult(src.getTestResult() or "")

target_interims = []
for src_interim in src.getInterimFields():
src_interim["unit"] = src_interim.get("unit") or ""
src_interim["result_type"] = src_interim.get("result_type") or "numeric"
src_interim["choices"] = src_interim.get("choices") or ""
target_interims.append(src_interim)
target.setInterimFields(target_interims)

# Migrate the contents from AT to DX
migrator = getMultiAdapter(
(src, target), interface=IContentMigrator)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ def validate(data):
return validate


class InterimsFieldValidator(validator.SimpleFieldValidator):
class InterimFieldsValidator(validator.SimpleFieldValidator):

def validate(self, value):

Expand Down Expand Up @@ -310,10 +310,10 @@ def update(self):
pass


class InterimsFieldValidationErrorView(MultipleErrorViewSnippet):
class InterimFieldsValidationErrorView(MultipleErrorViewSnippet):

def __init__(self, error, request, widget, field, form, content):
super(InterimsFieldValidationErrorView, self).__init__(
super(InterimFieldsValidationErrorView, self).__init__(
error, request, widget, field, form, content)
err_snippet = partial(InterimErrorViewSnippet,
error, request, widget, field, form)
Expand Down

0 comments on commit bb2fc1a

Please sign in to comment.