Skip to content

OD210 unknown-domain-field

A literal domain or group_by names a field its model does not have.

Category Severity Series Needs the index
Correctness Error all yes

What it reports

A field path in a domain written as a literal, or in a group_by, that does not resolve on the model the domain filters. Paths are followed as OD203 follows them. The domains read are those of:

  • a relational field's domain, on its comodel;
  • search, search_count, search_read, search_fetch, filtered_domain, read_group and their private forms, called on self or on env["x.y"], on that model;
  • a window action's domain and the group_by of its context, on its res_model;
  • a view's domain and filter_domain attributes and the group_by of a filter's context, on the model at that place in the arch;
  • an ir.rule's domain_force, and an ir.access's domain, written by a <record> or a CSV row, on its model_id.

Why it matters

Odoo checks a domain's fields only when it uses the domain. A search in Python raises ValueError when the line runs, a field's domain fails when a user opens its dropdown, and an action fails when it opens. From saas~13.1 a domain or group_by in a view arch, filter_domain aside, is checked when the view is validated, so the install fails. Fields renamed or removed between versions are the usual cause after a migration.

From 15.0 Odoo validates a record rule's domain each time it writes one, so a wrong field fails the install with a ValidationError, even when a later file replaces the domain. A demo file differs, here and above: see load failures. Before 15.0 nothing checks it until a query applies the rule, which then fails for the users it covers, so only the domain left after the whole load counts. A demo write does not count for a data value, as a database may hold no demo data.

A domain the code builds at runtime is skipped as a whole: a term whose field is not a string literal, a domain added to another, or one unpacked from a variable. A term such as (1, "=", 1) names no field. Searches in code that runs only once a check at runtime has shown that a model, a field or a module is there, as OD206 describes, and searches on self in an abstract model are not checked. A granularity such as date:month is dropped from a group_by before the field is looked up.

A field of a soft dependency is reported as out of depends unless such a check reaches the search: add the module to depends, or test for it around the search.

A field of another module that Odoo auto-installs with the module is not reported in a domain that runs once the install is done, as the field is there by then. Another module counts when every depend it waits on lies in the module's depends closure, with what Odoo always installs, or among the modules already counted, repeating until nothing is added. The depends it does not wait on come with it. auto_install: True, and a list before saas~12.4, waits on every depend. From saas~17.1 a module with a countries list does not count, as whether it installs rests on the companies of the database, so its field stays reported: add it to depends. An auto-installed module whose version names no series takes the series of the module being checked, as the two install on one Odoo. When neither names one, the countries list counts, so a field read on a series before saas~17.1 gets a false report: pass --odoo-version.

A domain Odoo checks while the module loads needs the field earlier, so an auto-installed module counts there only when it loads first: less deep in the depends graph, or as deep with a name that sorts first. These are a domain or group_by in a view from saas~13.1, a relational field's string domain from the same series, as a view that lets a user edit the field checks it, and a record rule from 15.0. A field of a module that loads after is reported, as a database that installs the two together refuses the module: add the other one to depends. A filter_domain and a field's list domain wait for a user on every series, as does any domain before those series.

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 model may hold a field in a parent the scan cannot read, so a field missing from a model is not reported when a class of it, or of a model it inherits or delegates to, has a plain Python base class or an _inherit element that is not a literal. Odoo gives a model the fields of a model class used as a base on every series, while a class that is no model holds fields only before saas~14.4, and the scan cannot always tell the two apart. A base defined above the model in the same file is read when the model also inherits or delegates to it, unless it is a class of base, so to keep such a model under the check, define the parent there and name it in _inherit as well. A plain class the file defines above the model, which the scan can read as holding no field, hides nothing, so a typo on that model is reported. A path is judged at the model where it fails, not the one it starts from: a typo past a relation is reported when the comodel hides no parent, whatever the model of the domain. A field the model has but depends does not reach, and a path that continues past a field that is not relational, are reported whatever the parents. A class in scope in a file that does not parse counts as one the scan cannot read, so such a field is left alone on its model.

A field missing from a model is not reported either when the model, or one it inherits or delegates to, has a parent no indexed module defines or delegates to a parent the scan cannot read, or when a module in scope loads an unread data file, which may add a field to any model. One of a dependency loads first, so it cannot add to a model the module defines whole. A model the module builds at runtime, as OD202 describes, is skipped too, for a field missing from it and for one depends does not reach, unless a class in scope introduces it with _name. Put the module that defines the parent on the addons path to keep it under the check.

A record rule is checked on the last model_id written to its id, in a ref, an eval that is one ref(), a search of one ('model', '=', name) term, a :id or /id column, or the plain name of an ir.access CSV. It is skipped when that value takes a run to tell, when the record is nested in a one2many, or when its last active is false or an expression. A domain Odoo reads through ref, search, file or type, a <function> that writes one, and a domain with %(id)d are not read. A scope with a dependency that is not indexed, a module that names models at runtime, a model outside depends and a model with an _inherit no module defines skip the check too.

A field is what Odoo takes as one: a class attribute that holds an instance of a field class. The scan knows such a class by its own name, across the whole scan. The name is that of a public class of Odoo's fields module, or of a class of a scanned module that derives from one through fields, directly or through other classes of the scan. A class of another module that shares the name counts too, and when two such classes differ in type, a path stops at the field and its type is not judged. A class renamed on import is not known by that name, and neither is one that derives from a private class of fields or from another module of the ORM. A class the scan does not know stays unknown, so a domain on an attribute built from it is reported: put the module that defines the class on the addons path, use the class under its own name, and derive it from a public class of fields.

A field an ir.model.fields record adds counts in every domain, as OD304 describes, and OD203 tells how a path continues through it.

A subview an extension adds inside a field of the parent view is read on that field's comodel. A spec locates the first field of that name anywhere, so such a subview is skipped when a subview of the inheritance tree is on a model that may have that field too.

Example

models/sale_order.py
class SaleOrder(models.Model):
    _inherit = "sale.order"

    referrer_id = fields.Many2one("res.partner", domain=[("customer", "=", True)])

    def _late_orders(self):
        return self.search([("state", "=", "sale"), ("commitment_dat", "<", fields.Datetime.now())])

res.partner has had no customer field since 13.0, and commitment_dat is a typo.

models/sale_order.py
class SaleOrder(models.Model):
    _inherit = "sale.order"

    referrer_id = fields.Many2one("res.partner", domain=[("customer_rank", ">", 0)])

    def _late_orders(self):
        return self.search([("state", "=", "sale"), ("commitment_date", "<", fields.Datetime.now())])

A record rule fails the same way:

security/rules.xml
<record id="sale_order_own" model="ir.rule">
    <field name="model_id" ref="sale.model_sale_order"/>
    <field name="domain_force">[('salesman_id', '=', user.id)]</field>
</record>

sale.order has no salesman_id, so 15.0 and later refuse to install the module.

security/rules.xml
<record id="sale_order_own" model="ir.rule">
    <field name="model_id" ref="sale.model_sale_order"/>
    <field name="domain_force">[('user_id', '=', user.id)]</field>
</record>

Suppressing

referrer_id = fields.Many2one("res.partner", domain=[("x_vip", "=", True)])  # odooscan: ignore[OD210]

See suppressing a finding.