Skip to content

OD218 delegation-field-not-required

A field that delegates to a parent model is not required with ondelete cascade or restrict.

Category Severity Series Needs the index
Correctness Warning, Error from saas~18.3 all yes

What it reports

An entry of a class's _inherits that names a Many2one that is not both required and given ondelete="cascade" or "restrict", reported at the entry, once per module. Up to 18.0 (before saas~18.3), a Many2one with delegate=True that no _inherits entry names is held to the same, and reported once per module, at its last definition that sets delegate, required or ondelete, unless OD228 reports it. From saas~12.2 until saas~14.3, so in 13.0 and 14.0, a required field with no ondelete, whether unset, None, False, zero or "", is not reported, and a field that is not required is told to add required=True alone. The exception, from saas~13.1, is a field of a model that is not transient to a comodel that refuses restrict, which still needs ondelete. A model has the _inherits entries of the models it inherits with _inherit, so an entry of a mixin names the field too, unless a later class gives the entry's parent model another field. Of the models one class lists in _inherit, the first listed one's entry for a parent model wins (checked in 12.0, 13.0 and 15.0 to 20.0), so a delegate=True field that only a later listed model's entry for the same parent model names is held to the same rule as a lone one before saas~18.3. The message names what the field lacks.

Why it matters

A model that delegates to a parent through _inherits stores each of its records alongside a record of the parent, and reads the parent's fields through the delegating field. A record without a parent, or a parent deleted from under it, breaks every one of those fields. Up to 18.0, Odoo forces the field an _inherits entry names required with ondelete='cascade' and logs a warning, an unset ondelete included. A delegate=True field that no entry names is forced required with a warning, and an ondelete other than cascade or restrict is silently replaced with cascade. Either way, the definition in the code no longer says what the database does. From saas~12.2 until saas~14.3, so in 13.0 and 14.0, Odoo gives a field with no ondelete its default before it checks the field, with no warning: restrict on a required field, or from saas~12.5 cascade on a required field of a transient model to a regular one. The same series refuse a required field with ondelete='set null' before they replace the value, which OD213 reports from 13.0. From saas~13.1 until saas~14.3, the default itself fails with ValueError when it is restrict and the comodel is ir.model, ir.model.data, ir.model.fields, ir.model.fields.selection, ir.model.relation, ir.model.constraint or ir.module.module. From saas~14.4 until saas~18.2, a delegate=True field outside _inherits with no ondelete is worse: Odoo fails with AttributeError, which OD228 reports. From saas~18.3, so in 19.0, Odoo refuses the model, so the module cannot be installed and a database that has it does not start.

The field takes each attribute from the last definitions that set it, in this module or in its dependencies, so a required or an ondelete given in another class counts. An attribute is unknown when it is not a literal, a definition unpacks **kwargs, or the last definitions that set it disagree, as two in different files of one module may: files load in the order of the imports, which is not indexed. An unknown attribute is never reported, only one that no definition sets or a literal that is wrong. Neither is an entry naming no field or a field of another type, or a related field with delegate=True, nor a delegate=True field of a model whose _inherits may name it: an _inherits that is not a literal dict, an _inherit the model reaches that is not a literal, whose unseen parent may give the entry, or classes in an unknown order that give a parent model different fields. The order of the entries of two listed models is unknown when different classes of the model list them, and when the class giving an entry is reached through two listed models. From saas~18.3, so in 19.0, Odoo refuses every delegate=True field that no _inherits entry names, whatever its attributes; this rule does not report that.

The rule does not run for a module when the module or one of its dependencies depends on a module that is not indexed. A model is skipped when the class that introduces it, with _name or as a class Odoo names, is in a module outside the dependencies, or 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, a parent whose fields the scan cannot see. A module that Odoo always installs with base, such as web, counts as in depends.

Example

models/parcel_label.py
class ParcelLabel(models.Model):
    _name = "parcel.label"
    _description = "Parcel label"
    _inherits = {"res.partner": "partner_id"}

    partner_id = fields.Many2one("res.partner", required=True)

partner_id gives no ondelete: 18.0 forces 'cascade' and logs a warning, and 19.0 refuses the model.

models/parcel_label.py
class ParcelLabel(models.Model):
    _name = "parcel.label"
    _description = "Parcel label"
    _inherits = {"res.partner": "partner_id"}

    partner_id = fields.Many2one("res.partner", required=True, ondelete="cascade")

Suppressing

_inherits = {"res.partner": "partner_id"}  # odooscan: ignore[OD218]

See suppressing a finding.