Skip to content

OD221 self-dependency-not-recursive

A computed field depends on itself through other records without recursive=True.

Category Severity Series Needs the index
Correctness Warning saas~14.4 and later yes

What it reports

A path given to @api.depends that leads back to the computed field itself on another record of its model, such as child_ids.total_weight on the method that computes total_weight, when the field does not set recursive=True. The fields checked are those whose compute names the decorated method, that are not related and give no depends. A field is reported once per module, at the first such path.

Why it matters

A field that depends on itself on other records, such as a total over a tree, must be recomputed along the whole hierarchy when one record changes, which Odoo does for a field it knows to be recursive. When it resolves the dependencies of the fields, Odoo finds the field among its own, marks it recursive itself and emits a UserWarning that it should be declared with recursive=True. Declare it, so that the code says what the registry does and the warning goes. Before saas~14.4, Odoo marks the field recursive without a warning, and the rule does not run.

Odoo resolves no dependencies for an abstract model, so a mixin is not reported, and from a transient model it does not follow a path into a regular model. The walk stops at a field the index does not find, or one that is not relational. A depends parameter on the field replaces the decorator's paths, so such a field is not checked, and a self-dependency in that parameter is not reported. A field whose compute is not a literal method name is not checked either.

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_pallet.py
class ParcelPallet(models.Model):
    _name = "parcel.pallet"
    _description = "Parcel pallet"

    parent_id = fields.Many2one("parcel.pallet")
    child_ids = fields.One2many("parcel.pallet", "parent_id")
    weight = fields.Float()
    total_weight = fields.Float(compute="_compute_total_weight", store=True)

    @api.depends("weight", "child_ids.total_weight")
    def _compute_total_weight(self):
        for pallet in self:
            pallet.total_weight = pallet.weight + sum(pallet.child_ids.mapped("total_weight"))
models/parcel_pallet.py
class ParcelPallet(models.Model):
    _name = "parcel.pallet"
    _description = "Parcel pallet"

    parent_id = fields.Many2one("parcel.pallet")
    child_ids = fields.One2many("parcel.pallet", "parent_id")
    weight = fields.Float()
    total_weight = fields.Float(compute="_compute_total_weight", store=True, recursive=True)

    @api.depends("weight", "child_ids.total_weight")
    def _compute_total_weight(self):
        for pallet in self:
            pallet.total_weight = pallet.weight + sum(pallet.child_ids.mapped("total_weight"))

Suppressing

@api.depends("weight", "child_ids.total_weight")  # odooscan: ignore[OD221]

See suppressing a finding.