Add _inherit (ExtendModel) + Inverse fields + sale extends partner

ORM:
- ExtendModel(name) retrieves existing model for extension (mirrors
  Python _inherit without _name). Panics on missing model.
- RegisterInverse(fieldName, fn) convenience for computed write-back
- Inverse field handling in Write(): caches new value, calls inverse
  method so computed fields can be written back

Sale module:
- Extends res.partner with sale_order_ids (O2M) and sale_order_count
  (computed) via ExtendModel — demonstrates real _inherit pattern

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc
2026-04-03 13:23:40 +02:00
parent 912abc4b97
commit 2e5a550069
3 changed files with 93 additions and 0 deletions

View File

@@ -276,6 +276,39 @@ func (m *Model) Extend(fields ...*Field) *Model {
return m
}
// ExtendModel retrieves an existing model for extension by another module.
// Mirrors: Python Odoo's _inherit = 'model.name' (without _name).
//
// This is the formal mechanism for cross-module model extension. It provides:
// 1. Explicit intent (extending vs creating)
// 2. Panic on missing model (catch registration order bugs early)
// 3. Python Odoo compatibility pattern
//
// Usage:
//
// m := orm.ExtendModel("res.partner")
// m.AddFields(orm.Char("industry", orm.FieldOpts{String: "Industry"}))
// m.RegisterMethod("action_custom", func(rs *orm.Recordset, args ...interface{}) (interface{}, error) { ... })
func ExtendModel(name string) *Model {
m := Registry.Get(name)
if m == nil {
panic(fmt.Sprintf("orm: cannot extend unregistered model %q", name))
}
return m
}
// RegisterInverse registers an inverse method for a computed field.
// The method is auto-named "_inverse_<fieldName>" and linked to the field's
// Inverse property so that Write() calls it automatically.
// Mirrors: odoo/orm/fields.py Field.inverse
func (m *Model) RegisterInverse(fieldName string, fn MethodFunc) {
inverseName := "_inverse_" + fieldName
m.RegisterMethod(inverseName, fn)
if f := m.GetField(fieldName); f != nil {
f.Inverse = inverseName
}
}
// CreateTableSQL generates the CREATE TABLE statement for this model.
// Mirrors: odoo/orm/models.py BaseModel._table_exist / init()
func (m *Model) CreateTableSQL() string {