Skip to content

OD216 selection-add-without-ondelete

selection_add leaves a value of a required Selection with ondelete 'set null'.

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

What it reports

A definition of a required Selection whose selection_add adds a value the field does not have yet without giving it an ondelete policy, or that adds one and gives any value the policy 'set null'. The values the field has come from the definitions in the dependencies, in the class that introduces the model, and earlier in the same file: those of the last selection list, which replaces the values before it, and of the selection_add of that definition and those after it. A selection list in the same definition replaces them too, before its selection_add applies. A field with a single definition in scope is not checked: from 15.0 Odoo ignores the selection_add of a lone definition. A module that Odoo always installs with base, such as web, counts as in depends. Modules of earlier series are not checked.

Why it matters

When a module that adds values to a selection is uninstalled, Odoo applies each value's ondelete policy to the records that hold it, and a value without one gets 'set null'. A required field cannot be emptied, so Odoo refuses the definition when it sets up the model. The error stops the registry from loading: the module cannot be installed, and a database that has it does not start. Give each added value 'cascade', 'set default' when the field has a default, or a function that receives the records holding the value.

A selection_add that only reorders values the field has is not reported, even when it gives one of them 'set null': Odoo only checks a definition that adds a value. The field is not reported when its values are computed, or are a tuple before saas~18.1, which Odoo refuses next to selection_add; when a definition that sets them may come before or after this one, or before or after the last selection list, so that it is unknown which list applies last or what follows it; when required is not a literal or is unknown; or when a definition unpacks **kwargs. 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.

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/parcel_route.py
class ParcelRoute(models.Model):
    _name = "parcel.route"
    _description = "Parcel route"

    state = fields.Selection([("draft", "Draft"), ("done", "Done")], required=True, default="draft")


class ParcelRouteExpress(models.Model):
    _inherit = "parcel.route"

    state = fields.Selection(selection_add=[("express", "Express")])

state is required, so the value express needs a policy other than 'set null'.

models/parcel_route.py
class ParcelRouteExpress(models.Model):
    _inherit = "parcel.route"

    state = fields.Selection(
        selection_add=[("express", "Express")],
        ondelete={"express": "set default"},
    )

Suppressing

state = fields.Selection(selection_add=[("express", "Express")])  # odooscan: ignore[OD216]

See suppressing a finding.