Skip to content

OD228 delegate-without-ondelete

A delegate=True Many2one has no ondelete, so Odoo fails when it sets up the registry.

Category Severity Series Needs the index
Correctness Error saas~14.4 and later, before saas~18.3 yes

What it reports

A Many2one with delegate=True that no _inherits entry names and whose ondelete is unset, None, False or zero, reported once per module, at its last definition that sets delegate, required or ondelete. The message names what the field lacks, required=True included.

Why it matters

From saas~14.4 until saas~18.2, so in 15.0 to 18.0, _inherits_check calls .lower() on the ondelete of a delegate=True field before the field's setup gives it a default. With no value there, Odoo fails with AttributeError, so the registry does not load and the database does not start.

OD218 reports what Odoo only warns about or replaces. ondelete="" has a .lower(), so Odoo replaces it with cascade and OD218 reports it. So does a field that an _inherits entry names: Odoo guards the read of an entry's ondelete, and forces the field required with cascade. Before saas~14.4, an unset ondelete has its default by then, as an explicit None, False or zero does from saas~12.2. Up to saas~12.1 such an explicit value fails the same way, and OD218 reports it. From saas~18.3, so in 19.0, Odoo refuses every delegate=True field that no entry names, whatever its attributes, which neither rule reports.

The field takes each attribute from the last definitions that set it, in this module or in its dependencies, so 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. A field with an unknown ondelete or delegate is never reported. Neither is a related field, nor a field of a model whose _inherits may name it: OD218 lists when that is unknown.

The rule does not run for a module of unknown series, or 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"

    partner_id = fields.Many2one("res.partner", delegate=True, required=True)
models/parcel_label.py
class ParcelLabel(models.Model):
    _name = "parcel.label"
    _description = "Parcel label"

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

Suppressing

partner_id = fields.Many2one("res.partner", delegate=True, required=True)  # odooscan: ignore[OD228]

See suppressing a finding.