Skip to content

OD116 field-shadows-model-member

A field is named like a method or an attribute every model has.

Category Severity Series Needs the index
Correctness Error all no

What it reports

A field of a model class named like a method of BaseModel that the ORM or its callers use, such as copy, write, search or name_search, like the record attributes env, ids and pool, or the class attribute _table_query, the SQL query a model reads its records from. The finding is at the field's name.

Why it matters

A field is a class attribute of the model, so Python finds it before the method or attribute of the same name that every model inherits from BaseModel. Odoo never checks for it: the model loads, and then record.copy() gets the field's value instead of the method, as does the ORM itself and every RPC call of the method. A field named env breaks every recordset of the model, since building one assigns its env. Rename the field, and give it the label the old name gave if users know it by that.

The list of methods is that of the ORM API reference, with the methods Odoo's own addons override. A name that is not in it is not reported, so a field named like a less common BaseModel method goes unnoticed. A member only some series have counts in those series: check_access and has_access from 18.0, the methods 20.0 adds from 20.0, and those 20.0 drops, such as toggle_active and check_access_rights, up to 18.0. The attribute _table_query counts from saas~13.5 up to the last release before 20.0, and the method _condition_to_sql, which moved to Field, from saas~17.1 to saas~18.3. _read, _name_search and copy_multi, which neither has, never count. A module of unknown series is checked only against the methods that count in every series. Those are the methods 18.0 and 20.0 both have, since the series that added each one is not known: a field named grouped, fetch or search_fetch is reported in every series, even one whose BaseModel lacks the method.

Example

models/parcel_box.py
class ParcelBox(models.Model):
    _name = "parcel.box"
    _description = "Parcel box"

    copy = fields.Boolean()
models/parcel_box.py
class ParcelBox(models.Model):
    _name = "parcel.box"
    _description = "Parcel box"

    is_copy = fields.Boolean(string="Copy")

Suppressing

copy = fields.Boolean()  # odooscan: ignore[OD116]

See suppressing a finding.