Skip to content

Commit a1ccebe

Browse files
committed
fix(odoo-spo): #525 codex P2 — bind no-_name _inherit to inherit[0] only
A no-_name Odoo class reopens ONE existing model in place; Odoo binds it to _inherit[0] and treats further entries as mixins. The prior fix bound local relational fields to the whole _inherit list, so build_relation_map() could emit bogus target/reads_field triples for secondary parents (e.g. _inherit = ['account.move', 'mail.thread']). Scope to inherit_models[:1], mirroring parsers/classes.py. Corpus byte-identical: regenerated from base 1ec76f5 (22245) with the fixed enrich -> out=24166, target=842, inverse_name=144 (diff clean). No real scanned-addons class triggers the secondary-mixin path scoped to a corpus-declared model, so odoo_ontology.rs counts are unchanged. Test: test_inherit_list_binds_field_to_first_model_only asserts only inherit[0] is bound. 20/20 python tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CcpLeEC3XK8Eye53GKBVvi
1 parent 87e81e6 commit a1ccebe

3 files changed

Lines changed: 32 additions & 10 deletions

File tree

.claude/board/AGENT_LOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
## 2026-06-18 — PR #525 follow-up: `_inherit`-only binding scoped to `inherit[0]`
2+
3+
**Main thread (Opus) — single implementer**, branch `claude/odoo-spo-fk-target-deep-reads`. Addresses the one unresolved codex P2 on #525: the prior `_inherit`-only fix bound a no-`_name` class's relational fields to the WHOLE `_inherit` list, so a multi-element `_inherit = ['a','b']` would attach local fields to every inherited mixin and let `build_relation_map()` emit bogus `target`/`reads_field` triples for secondary parents.
4+
5+
**Fix:** `spo_enrich.py` no-`_name` case now binds to `inherit[0]` only (`inherit_models[:1]`), matching Odoo in-place extension semantics and the repo's own `parsers/classes.py` collapse. `test_inherit_list_binds_field_to_each_model``..._to_first_model_only` (asserts `sale_order` bound, `purchase_order` NOT). Docstrings reworded.
6+
7+
**Corpus impact: NONE.** Regenerated from base `1ec76f5b` (22 245) with the fixed enrich → `out=24166`, `target=842`, `inverse_name=144`**byte-identical** to the committed corpus (`diff -q` clean). No real scanned-addons class triggers the bogus secondary-mixin binding scoped to a corpus-declared model, so the Rust count assertions (24 166 / 842 / 144 / 3 030) are unchanged; no `odoo_ontology.rs` edit. The fix is defensive tooling-correctness.
8+
9+
**Tests:** `python3 -m unittest tests.test_spo_enrich` 20/20 green.
10+
111
## 2026-06-17 — PR #523 review fixes: spo_enrich multi-emitter + `_inherit`-only
212

313
**Main thread (Opus) — single implementer**, branch `claude/odoo-spo-fk-target-deep-reads` (review-fix commit on top of the enrichment commit, no rebase). Addresses 4 valid review findings (codex P1 + codex P2/CodeRabbit Major + 2 CodeRabbit doc nits). Scope: the lance-graph SPO corpus + the stdlib Python extractor tooling under `tools/odoo-blueprint-extractor/` (no odoo-rs change).

tools/odoo-blueprint-extractor/odoo_blueprint_extractor/spo_enrich.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,12 +194,20 @@ def _scan_file(path: str, relmap: Dict[Tuple[str, str], Tuple[str, Optional[str]
194194
local_fields[target_name] = (comodel, inverse)
195195

196196
# Resolve the model name(s) this class contributes fields to: `_name`
197-
# if present, else the `_inherit` target(s). A class with neither
198-
# contributes nothing.
197+
# if present, else the single in-place `_inherit` target. A class with
198+
# neither contributes nothing.
199+
#
200+
# No-`_name` extension reopens ONE existing model in place. Odoo binds
201+
# such a class to `_inherit[0]`; a multi-element `_inherit` adds the rest
202+
# as mixins, NOT as additional homes for these local fields. Assigning
203+
# the whole list here would attach the fields to every secondary mixin
204+
# and let build_relation_map() emit bogus `target`/`reads_field` triples
205+
# for them. `parsers/classes.py` collapses the no-name case to
206+
# `inherit[0]` for the same reason; mirror it.
199207
if name_model is not None:
200208
model_names = [name_model]
201209
else:
202-
model_names = inherit_models
210+
model_names = inherit_models[:1]
203211

204212
for model_name in model_names:
205213
mu = model_to_underscore(model_name)

tools/odoo-blueprint-extractor/tests/test_spo_enrich.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -295,9 +295,11 @@ class TestInheritOnlyRelationMap(unittest.TestCase):
295295
296296
The common Odoo extension form reopens an existing model via
297297
`_inherit = "some.model"` (string) or `_inherit = ["a", "b"]` (list) WITHOUT
298-
a `_name`; relational fields on such classes must map onto the inherited
299-
model(s), or their target/inverse_name (and any deep hop through them) is
300-
lost.
298+
a `_name`; relational fields on such classes must map onto the single
299+
in-place extension target, or their target/inverse_name (and any deep hop
300+
through them) is lost. Odoo binds the no-`_name` case to `_inherit[0]`; any
301+
further entries are mixins, not additional homes for the local fields, so
302+
they must NOT receive the relational fields (mirrors `parsers/classes.py`).
301303
"""
302304

303305
def _scan_source(self, src: str):
@@ -320,7 +322,11 @@ def test_inherit_string_binds_field_to_inherited_model(self):
320322
("payment.transaction", None),
321323
)
322324

323-
def test_inherit_list_binds_field_to_each_model(self):
325+
def test_inherit_list_binds_field_to_first_model_only(self):
326+
# No-`_name` multi-element `_inherit` extends `_inherit[0]` in place;
327+
# the rest are mixins and must NOT inherit the local relational fields
328+
# (otherwise build_relation_map() emits bogus target/reads_field triples
329+
# for secondary parents). Matches parsers/classes.py inherit[0] collapse.
324330
src = (
325331
"from odoo import fields, models\n"
326332
"class Mixin(models.AbstractModel):\n"
@@ -331,9 +337,7 @@ def test_inherit_list_binds_field_to_each_model(self):
331337
self.assertEqual(
332338
relmap.get(("sale_order", "partner_id")), ("res.partner", None)
333339
)
334-
self.assertEqual(
335-
relmap.get(("purchase_order", "partner_id")), ("res.partner", None)
336-
)
340+
self.assertIsNone(relmap.get(("purchase_order", "partner_id")))
337341

338342
def test_inherit_tuple_form_is_accepted(self):
339343
src = (

0 commit comments

Comments
 (0)