Part of the research plan for #4350 (see anchor comment there for the full decomposition).
Relations are the highest-confidence track: the target oid is derivable without waking the target (intids.refs[to_id].oid, stored as a plain attribute by five.intid, five/intid/keyreference.py:103, five.intid 3.1.0), and production numbers in #4350 already show the win for tiles/logos rendered from relations.
Resolution chain (why every access is a load)
RelationValue.to_object → z3c/relationfield/relation.py:50,123-133 (z3c.relationfield 3.1) → intids.getObject(id) → zope/intid/__init__.py:92-98 (zope.intid 6.0): self.refs[id]() = one object load per access. to_path, to_interfaces, and notably RelationValue.__lt__ (relation.py:84-95) all go through to_object — sorting a relation list loads every target.
Call sites found (Plone 6.2 coredev scan, 2026-07-06)
| # |
Call site |
Pattern |
| A1 |
plone.app.layout 6.0.1 viewlets/content.py:336-355 related-items viewlet |
r.to_path per relation |
| A2 |
plone.restapi 10.0.1 serializer/relationfield.py:49 |
[el for el in value if el.to_object] |
| A3 |
plone.restapi services/relations/get.py:97-135 |
from_object/to_object per relation |
| A4 |
plone.app.relationfield 5.0.0 widget.py:125-131,162-168 data managers |
rel.to_object per item |
| A5 |
plone.api relation.py:111-133, 291-329 |
from_object/to_object per relation |
| A6 |
Products.CMFPlone relationhelper.py:62-73, 77-119 |
resolveRelationToken per token (stats/rebuild) |
| A7 |
zc.relation 3.0 catalog.py:734-736 + z3c.relationfield index.py:46-50 |
resolveRelationTokens generator — one intids.getObject per token |
| A8 |
plone.app.linkintegrity utils.py:11-43 callers |
back-relations, .from_object per hit |
Tasks
Part of the research plan for #4350 (see anchor comment there for the full decomposition).
Relations are the highest-confidence track: the target oid is derivable without waking the target (
intids.refs[to_id].oid, stored as a plain attribute byfive.intid,five/intid/keyreference.py:103, five.intid 3.1.0), and production numbers in #4350 already show the win for tiles/logos rendered from relations.Resolution chain (why every access is a load)
RelationValue.to_object→z3c/relationfield/relation.py:50,123-133(z3c.relationfield 3.1) →intids.getObject(id)→zope/intid/__init__.py:92-98(zope.intid 6.0):self.refs[id]()= one object load per access.to_path,to_interfaces, and notablyRelationValue.__lt__(relation.py:84-95) all go throughto_object— sorting a relation list loads every target.Call sites found (Plone 6.2 coredev scan, 2026-07-06)
plone.app.layout6.0.1viewlets/content.py:336-355related-items viewletr.to_pathper relationplone.restapi10.0.1serializer/relationfield.py:49[el for el in value if el.to_object]plone.restapiservices/relations/get.py:97-135from_object/to_objectper relationplone.app.relationfield5.0.0widget.py:125-131,162-168data managersrel.to_objectper itemplone.apirelation.py:111-133, 291-329from_object/to_objectper relationProducts.CMFPlonerelationhelper.py:62-73, 77-119resolveRelationTokenper token (stats/rebuild)zc.relation3.0catalog.py:734-736+z3c.relationfieldindex.py:46-50resolveRelationTokensgenerator — oneintids.getObjectper tokenplone.app.linkintegrityutils.py:11-43callers.from_objectper hitTasks
plone.basehelper (sibling sub-issue) in measured-impact order: related-items viewlet (A1), restapi relation serializer +@relationsservice (A2, A3), widgets (A4).resolveRelationTokens-driven paths to prefetch a token window (A7) — that transparently covers A5, A6, A8. Needs upstream buy-in in zc.relation / z3c.relationfield; fallback is call-site helpers in the Plone packages only.__lt__) can sort onto_path-from-catalog or prefetch first.