diff --git a/docs/changelog.rst b/docs/changelog.rst index 3478b8d..fc582cf 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -15,6 +15,7 @@ Changelog applied. Uninstalling from the same panel removes the PAS plugin and the per-user JWT signing secrets. +- #110 Fix /registry route failing on datetime registry values - #109 Add a partition operation endpoint - #106 Fix single-valued UID reference fields not settable through the JSON API - #112 Allow updating the Laboratory through the API diff --git a/src/senaite/jsonapi/api/settings.py b/src/senaite/jsonapi/api/settings.py index 28bf208..9d8ca54 100644 --- a/src/senaite/jsonapi/api/settings.py +++ b/src/senaite/jsonapi/api/settings.py @@ -33,6 +33,7 @@ from Products.CMFPlone.interfaces.controlpanel import IMaintenanceSchema from Products.CMFPlone.interfaces.controlpanel import ISecuritySchema from Products.CMFPlone.interfaces.controlpanel import IUserGroupsSettingsSchema # noqa: E501 +from senaite.core.api import dtime from zope.component import getAdapter from zope.schema import getFieldNames @@ -46,6 +47,28 @@ } +def to_json_value(value): + """Coerce a registry value into a JSON serializable form. + + Registry records may hold dates or datetimes (and containers of them), + which the JSON encoder used by the route cannot serialize. + + :param value: The raw registry value + :returns: A JSON serializable value + """ + if isinstance(value, (list, tuple)): + return [to_json_value(item) for item in value] + if isinstance(value, dict): + return {key: to_json_value(val) for key, val in value.items()} + # datetime and Zope DateTime + if dtime.is_dt(value) or dtime.is_DT(value): + return dtime.to_iso_format(value) + # pure date, which to_iso_format does not handle + if dtime.is_d(value): + return value.isoformat() + return value + + def get_registry_records_by_keyword(keyword=None): """Return registry records whose name contains `keyword`. @@ -55,7 +78,8 @@ def get_registry_records_by_keyword(keyword=None): records = {} for record in portal_reg.records: if keyword is None or keyword.lower() in record.lower(): - records[record] = bika_api.get_registry_record(record) + value = bika_api.get_registry_record(record) + records[record] = to_json_value(value) return records diff --git a/src/senaite/jsonapi/tests/doctests/api_settings.rst b/src/senaite/jsonapi/tests/doctests/api_settings.rst index 7c0f434..4fb3342 100644 --- a/src/senaite/jsonapi/tests/doctests/api_settings.rst +++ b/src/senaite/jsonapi/tests/doctests/api_settings.rst @@ -137,6 +137,31 @@ filtered subset: True +Non JSON serializable values are coerced +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Some registry records hold `datetime` values, which the JSON encoder used +by the route cannot serialize. Such values are coerced to ISO strings: + + >>> from datetime import datetime + >>> from plone.registry import field + >>> from plone.registry.record import Record + >>> reg = ploneapi.portal.get_tool("portal_registry") + >>> reg.records["senaite.jsonapi.test_datetime"] = Record( + ... field.Datetime(title=u"Test"), + ... datetime(2014, 8, 14, 0, 0, 0, 3)) + >>> transaction.commit() + + >>> hits = api_settings.get_registry_records_by_keyword("test_datetime") + >>> hits["senaite.jsonapi.test_datetime"] + '2014-08-14T00:00:00.000003' + +So the whole record set is now JSON serializable: + + >>> ignored = json.dumps( + ... api_settings.get_registry_records_by_keyword(None)) + + get_settings_by_keyword via the /settings route ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~