OD203 unresolved-field-path¶
A field path does not resolve on its model.
| Category | Severity | Series | Needs the index |
|---|---|---|---|
| Correctness | Error | all | yes |
What it reports¶
A dotted field path, followed one field at a time from its model, names a field that does not
exist, a field from a module missing from depends, or continues past a field that is not
relational. The paths checked are those given to @api.depends, the field names given to
@api.onchange and @api.constrains, a field's related, a One2many's inverse name, a
Many2oneReference's model_field, and the fields named in _order, _rec_name,
_parent_name, _fold_name and _active_name, and from saas~15.3 in _rec_names_search. A module
that Odoo always installs with base, such as web, counts as in depends.
Why it matters¶
Odoo follows related and @api.depends paths when it builds the registry, and a field that is not
there stops the registry from loading: the module cannot be installed, and a database that already
has it fails to start. A bad _rec_name or _active_name stops it too, and so does a bad
model_field from 20.0; in 18.0 that one fails only when a write updates the records the field
points to or a One2many reads them through it. A bad _parent_name stops the install of a new model
with _parent_store, whose parent_path Odoo fills by following it, makes the first create fail on
an existing one, and otherwise waits for the first hierarchy query on the model. A bad _order or
_rec_names_search waits until the first search or name lookup on the model, and a bad _fold_name
leaves the model's groups unfolded in kanban views. A path that reaches a field only through a
module missing from depends works in a database where that module happens to be installed and
fails in one where it is not.
The rule stops at what it cannot see: a path through a model no indexed module defines, or through
a field whose value has no fixed model (Reference, Many2oneReference, Properties, Json),
counts as resolved. Paths in an abstract model are not checked, because they name fields of the
models that inherit it. A related Many2oneReference that sets no model_field takes its target's,
which from 20.0 must also name a field of the related field's model, and that one is not checked.
The rule stays silent on a model that a dependency that is not indexed, such as an Enterprise
module, could add the field to. It still reports a name that fails on a model the module defines
whole, with every class and parent in the module, since that dependency loads first and cannot
extend it. It also still reports a path that continues past a field that is not relational. Add the
checkout of the dependency that is not indexed to --addons-path to check the other models. A class
in scope in a file that does not parse counts as one the scan cannot read, so a path
that fails on its model is left alone.
A field an ir.model.fields record adds counts, as OD304 describes. A path continues
through the relation of the record, and counts as resolved when the record's type takes a run to
tell or a relational type names no relation.
Odoo resolves related, @api.depends, an inverse name, _rec_name, _active_name and from 20.0
model_field as it sets up the model, ahead of the module's own data files. For these only a record
of a dependency counts, whatever the file order: define the field in Python, or move the record to a
dependency.
An unread data file in scope may add a field the same way, so a name missing from a model is left alone, unless the file is a dependency's and the module defines the model whole, or the file is the module's own and the path is one of those Odoo resolves ahead of it.
Example¶
class SaleOrder(models.Model):
_inherit = "sale.order"
partner_country_code = fields.Char(related="partner_id.country_id.cde")
label = fields.Char(compute="_compute_label")
@api.depends("partner_id.name.size")
def _compute_label(self):
...
res.country has no cde field, and name is a Char, so the second path cannot go past it.
class SaleOrder(models.Model):
_inherit = "sale.order"
partner_country_code = fields.Char(related="partner_id.country_id.code")
label = fields.Char(compute="_compute_label")
@api.depends("partner_id.name")
def _compute_label(self):
...
When the field comes from a module the path crosses, add that module to depends in
__manifest__.py.