Skip to content

Fix sample template records stored in types the schema refuses - #3049

Open
ramonski wants to merge 3 commits into
2.xfrom
fix/sampletemplate-record-types
Open

ramonski wants to merge 3 commits into
2.xfrom
fix/sampletemplate-record-types

Conversation

@ramonski

@ramonski ramonski commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Description of the issue/feature this PR addresses

SampleTemplate.setPartitions stores the single valued reference sub-fields of a partition as the bare UID, where the record schema holds a list of one. The sub-fields are UIDReferenceField, which derives from zope.schema.List; getPartitions already knows this and reads them back with a first() helper, since #2630.

Both setters also store the partition id as a native string, where the record schema has a TextLine.

Current behavior before PR

Nothing can set partitions. The object is refused on whole-object validation:

{"portal_type": "SampleTemplate", "partitions": [{"part_id": "part-1", "container": "<uid>"}]}
-> {"message": "No objects could be created: {\"partitions\": \"WrongContainedType\"}"}

Passing a list instead does not help, because setPartitions normalises it away:

-> {"message": "No objects could be created: [u'<uid>'] is not supported."}

The two callers that do not validate store the bare UID and keep it. getPartitions then reads items[0] off a string and hands back the first character of the UID:

  • senaite/core/upgrade/v02_06_000.py, the Archetypes migration, which does target.setPartitions(src.getPartitions())
  • senaite/core/exportimport/setupdata/__init__.py, the setup data importer

So every template that came over from Archetypes with a partition scheme has one that points at nothing, and auto-partitioning on reception uses it.

The doctest did not catch it because it only asserted the number of partitions, never a value.

Desired behavior after PR is merged

setPartitions stores what the record schema holds: the references as a list of one UID, and the partition id as text. setServices stores its partition id as text as well.

The list of one is how a UIDReferenceField stores a single reference, so the helper that builds it, to_uid_list, sits beside the field in senaite.core.schema.uidreferencefield rather than in the content type that needed it first. It takes an object, a UID or a list of either. (api.to_list does not fit: it wraps a value without resolving an object to its UID, and turns an empty string into [""] where the field wants [].)

The upgrade step puts the stored records of existing templates into the same shape. Base steps are untouched; only what the two callers above wrote is repaired.

Verification

The doctest now asserts the values rather than the count, and that the stored records satisfy the field:

>>> part1, part2 = template1.getPartitions()
>>> part1["container"] == part2["container"] == api.get_uid(container)
True
>>> field = ISampleTemplateSchema["partitions"]
>>> field.validate(field.get(template1))

Both fail on the current code with False and WrongContainedType([WrongType('ff59...', <type 'list'>, 'sampletype')]).

Checked on a running instance as well: creating a template with partitions through the JSON API now succeeds and reads back as

[{'part_id': 'part-1',
  'container': ['6570d90d5e4f48789db69f58191cd86c'],
  'preservation': [],
  'sampletype': ['576614796c324e32af258d4431f3d312']}]

Full senaite.core test suite: 229 tests, 0 failures, 0 errors.

The other two row schemas that hold UIDReferenceField sub-fields, IWorksheetLayoutSchema and worksheettemplate.ILayoutRecord, pass their values through unchanged and are not affected.

--
I confirm I have tested this PR thoroughly and coded it according to PEP8
and Plone's Python styleguide standards.

setPartitions stored the single valued reference sub-fields of a
partition as the bare UID, where the record schema holds a list of one.
Nothing could set partitions: the object was refused on validation with
WrongContainedType, and the two callers that skip validation, the 2.6.0
Archetypes migration and the setup data importer, stored the bare UID,
after which getPartitions read back the first character of it.

Both setters also stored the partition id as a native string, where the
record schema holds text.

The shape a single reference is stored in belongs to the field that
defines it, so to_uid_list sits beside UIDReferenceField rather than in
the content type that happened to need it first.

The upgrade step puts the stored records of existing templates into the
types the schema holds.
@ramonski

ramonski commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

Moved to_uid_reference out of the content type and renamed it.

The list of one UID is how a UIDReferenceField stores a single reference, which is the field's business rather than the sample template's; the template is only the first type that needed to write one into a datagrid row. It is now to_uid_list in senaite.core.schema.uidreferencefield, beside the field itself.

api.to_list was the obvious candidate to reuse and does not fit: it wraps a value without resolving an object to its UID, and turns an empty string into [""] where the field wants [].

@ramonski
ramonski force-pushed the fix/sampletemplate-record-types branch from 5b0d659 to 2191bad Compare October 8, 2026 08:29
@ramonski
ramonski requested a review from xispa October 8, 2026 08:54
@ramonski ramonski added the Bug 🐞 Something is broken label Oct 8, 2026
@ramonski

ramonski commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

Note on the overlap with #3045, which neither blocks nor is blocked by this one.

The two touch the same two files in different places. #3045 changes the field types of a record (IServiceRecord.uid from TextLine to UIDField, services from schema.List to DataGridField); this one changes what the setters write. No overlapping hunk, and they merge cleanly in either order.

Worth knowing while reviewing: a service record has two sub-fields written in the wrong type, and the two pull requests fix one each.

uid part_id
2.x native str in a TextLine native str in a TextLine
#3045 alone ok, the field takes both still wrong
#3049 alone still wrong ok, written as text
both ok ok

So ISampleTemplateSchema["services"] validates only once both are in. Each is right on its own and neither makes the record whole by itself. The partitions field, which is what this pull request is actually about, is independent of #3045: it stays a schema.List, so the normalize_rows that #3045 adds to DataGridField never sees it.

to_uid_list and the to_uid that #3045 adds sit side by side in uidreferencefield.py and do not overlap: one hands back the native string a UID is stored as, the other the list of one that a reference field holds. to_uid_list needs no to_uid, because api.get_uid already returns the native string.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug 🐞 Something is broken

Development

Successfully merging this pull request may close these issues.

1 participant