Skip to content

OD213 required-many2one-set-null

A required Many2one declares ondelete='set null'.

Category Severity Series Needs the index
Correctness Error 13.0 and later yes

What it reports

A Many2one that is required=True and ondelete='set null', whether one definition gives both or they come from different definitions of the field in the module and its dependencies. A module that Odoo always installs with base, such as web, counts as in depends. The field is reported once per module, at its last definition there that sets either, on ondelete when that definition sets it. Related fields are not checked. Modules of earlier series are not checked.

From saas~14.4 to saas~18.2, so in 15.0 to 18.0, a field that may delegate is not reported: one that sets delegate to anything but a literal Python takes as false, such as False, None or 0, or one that an entry of its model's _inherits names. A model merges the _inherits of its classes and of the models it inherits key by key, a later class replacing the field an earlier one gives a parent model; 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 field that only a later listed model's entry for the same parent model names does not delegate and is reported. Odoo replaces the field's ondelete with cascade before it compares it with required, so the combination does not fail. OD218 reports such a field at the entry of its model that names it, or when it has delegate=True and no entry names it. Neither rule reports one that only an entry of an inherited model names, one whose entry sits in a module other than the one scanned (a module that sets ondelete on res.users's partner_id, which base's entry names), or whose delegate is neither True nor a false literal. When an _inherits is not a literal dict, an _inherit the model reaches is not a literal, whose unseen parent may give an entry, or classes in an unknown order give a parent model different fields, which fields the _inherits names is unknown, and those it may name are not reported. 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. Until saas~14.3, so in 13.0 and 14.0, Odoo compares ondelete with required before the replacement and fails, and from saas~18.3, so in 19.0, it refuses a delegating field with set null: OD213 reports the field in both. From saas~18.3, OD218 reports it only at an entry of its own model.

Why it matters

set null empties the field when the record it points to is deleted, which a required field cannot be. Odoo refuses the combination when it sets up the model, and the error stops the registry from loading: the module cannot be installed, and a database that has it does not start. Use restrict to forbid deleting a record still in use, or cascade to delete the records that point to it with it. A required Many2one that gives no ondelete gets restrict, or cascade when its model is transient and the comodel is not.

An attribute takes its value from the last definitions that set it, and is unknown when those disagree, as two definitions in different files of one module may: files load in the order of the imports, which is not indexed. When required or ondelete is unknown, or a definition unpacks **kwargs, the field is not reported, nor is a field whose definitions change its type in an unknown order.

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.

Example

models/fleet_vehicle.py
class FleetVehicle(models.Model):
    _inherit = "fleet.vehicle"

    depot_id = fields.Many2one("stock.warehouse", required=True, ondelete="set null")
models/fleet_vehicle.py
class FleetVehicle(models.Model):
    _inherit = "fleet.vehicle"

    depot_id = fields.Many2one("stock.warehouse", required=True, ondelete="restrict")

Suppressing

depot_id = fields.Many2one("stock.warehouse", required=True, ondelete="set null")  # odooscan: ignore[OD213]

See suppressing a finding.