From 88f21c20fc033942dc2c82e4ecc76ce8519ca3dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Wed, 3 Jun 2026 13:03:09 +0200 Subject: [PATCH 1/3] Support for multi-schema DX types --- src/senaite/jsonapi/api.py | 11 ++--------- src/senaite/jsonapi/dataproviders.py | 9 ++++----- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/senaite/jsonapi/api.py b/src/senaite/jsonapi/api.py index 4a61208..de77f71 100644 --- a/src/senaite/jsonapi/api.py +++ b/src/senaite/jsonapi/api.py @@ -749,15 +749,8 @@ def get_fields(brain_or_object): # The portal object has no schema if is_root(obj): return {} - schema = get_schema(obj) - if is_dexterity_content(obj): - names = schema.names() - fields = map(lambda name: schema.get(name), names) - schema_fields = dict(zip(names, fields)) - # update with behavior fields - schema_fields.update(get_behaviors(obj)) - return schema_fields - return dict(zip(schema.keys(), schema.fields())) + # rely on core's api + return api.get_fields(obj) def get_field(brain_or_object, name, default=None): diff --git a/src/senaite/jsonapi/dataproviders.py b/src/senaite/jsonapi/dataproviders.py index 456b544..ede6126 100644 --- a/src/senaite/jsonapi/dataproviders.py +++ b/src/senaite/jsonapi/dataproviders.py @@ -207,9 +207,8 @@ def __init__(self, context): super(DexterityDataProvider, self).__init__(context) # get the behavior and schema fields from the data manager - schema = api.get_schema(context) - behaviors = api.get_behaviors(context) - self.keys = schema.names() + behaviors.keys() + fields = api.get_fields(context) + self.keys = fields.keys() class ATDataProvider(Base): @@ -222,8 +221,8 @@ def __init__(self, context): super(ATDataProvider, self).__init__(context) # get the schema fields from the data manager - schema = api.get_schema(context) - self.keys = schema.keys() + fields = api.get_fields(context) + self.keys = fields.keys() class AnalysisDataProvider(Base): From 185d8945043ef2ed1e532b42db6b736241d19714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Wed, 3 Jun 2026 13:14:57 +0200 Subject: [PATCH 2/3] Added regression doctest --- .../doctests/dexterity_inherited_fields.rst | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 src/senaite/jsonapi/tests/doctests/dexterity_inherited_fields.rst diff --git a/src/senaite/jsonapi/tests/doctests/dexterity_inherited_fields.rst b/src/senaite/jsonapi/tests/doctests/dexterity_inherited_fields.rst new file mode 100644 index 0000000..1e7f3c1 --- /dev/null +++ b/src/senaite/jsonapi/tests/doctests/dexterity_inherited_fields.rst @@ -0,0 +1,107 @@ +DEXTERITY INHERITED FIELDS +-------------------------- + +Regression test for multi-schema Dexterity types. + +When the searched object is a Dexterity (DX) type whose schema interface +inherits from another schema interface, the fields declared by the *parent* +schema must be returned too. The previous implementation relied on +``schema.names()``, which only returns the fields declared directly on the +interface (inherited fields require ``names(all=True)``), so inherited fields +were silently dropped from the API response. + +``senaite.core.content.supplier.Supplier`` is a good example of a multi-schema +DX type: its schema ``ISupplierSchema`` inherits from ``IOrganizationSchema``. + +Running this test from the buildout directory: + + bin/test test_doctests -t dexterity_inherited_fields + + +Test Setup +~~~~~~~~~~ + +Needed Imports: + + >>> import json + >>> import transaction + >>> from plone.app.testing import setRoles + >>> from plone.app.testing import TEST_USER_ID + + >>> from bika.lims import api + >>> from senaite.jsonapi import api as japi + +Functional Helpers: + + >>> def get(url): + ... browser.open("{}/{}".format(api_url, url)) + ... return browser.contents + +Variables: + + >>> portal = self.portal + >>> setup = portal.setup + >>> portal_url = portal.absolute_url() + >>> api_url = "{}/@@API/senaite/v1".format(portal_url) + >>> browser = self.getBrowser() + >>> setRoles(portal, TEST_USER_ID, ["LabManager", "Manager"]) + >>> transaction.commit() + +Create a Supplier (DX type with an inherited schema): + + >>> supplier = api.create(setup.suppliers, "Supplier", Name="Naralabs") + >>> uid = api.get_uid(supplier) + >>> transaction.commit() + + +The DX type is genuinely multi-schema +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The schema interface inherits from a parent schema interface: + + >>> from senaite.core.content.supplier import ISupplierSchema + >>> from senaite.core.content.organization import IOrganizationSchema + >>> IOrganizationSchema in ISupplierSchema.__bases__ + True + +``tax_number`` is declared by the *parent* ``IOrganizationSchema``, while +``lab_account_number`` is declared *directly* on ``ISupplierSchema``: + + >>> "tax_number" in IOrganizationSchema.names() + True + >>> "tax_number" in ISupplierSchema.names() + False + >>> "lab_account_number" in ISupplierSchema.names() + True + + +Inherited fields are returned by get_fields +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The fields mapping must contain both the directly declared field and the +inherited one: + + >>> fields = japi.get_fields(supplier) + >>> "lab_account_number" in fields + True + >>> "tax_number" in fields + True + +``get_field`` resolves the inherited field too (it previously returned the +default because the field was missing from the mapping): + + >>> japi.get_field(supplier, "tax_number") is not None + True + + +Inherited fields are present in the API response +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +End to end, fetching the object exposes the inherited field as a key: + + >>> response = get(uid) + >>> data = json.loads(response) + >>> "lab_account_number" in data + True + >>> "tax_number" in data + True From da664e8076d37a0ae598f3e2472b424070050e35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jordi=20Puiggen=C3=A9?= Date: Wed, 3 Jun 2026 13:19:41 +0200 Subject: [PATCH 3/3] changelog --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 64384e2..3956165 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,7 @@ Changelog 2.7.0 (unreleased) ------------------ +- #89 Fix inherited schema fields missing for multi-schema Dexterity types - #87 Inject additional analyses fields - #85 Allow field projection - #81 Support second-level precision on searches against DateIndex