DEV Community

Aaron Jones
Aaron Jones

Posted on

Computed Fields Causing Infinite Recomputations(Odoo)

Problem Statement: Poorly designed computed fields trigger unnecessary recomputations or infinite loops due to incorrect dependency declarations (@api.depends), leading to performance issues and unstable behavior.

Why This Problem Happens in Odoo
In Odoo, computed fields are recalculated whenever fields listed in @api.depends change.

Infinite or excessive recomputation occurs when:

  • A computed field depends on itself
  • The compute method writes to other fields
  • ORM write() is used inside compute
  • Dependencies are too broad or incorrect
  • store=True is misused
  • Parent ↔ child fields depend on each other

Result:

  • Slow forms
  • High CPU usage
  • UI hangs
  • Random crashes in production

Step 1 Identify the Problematic Computed Field
Common Symptoms

  • Form view keeps loading
  • Server CPU spikes
  • Logs show repeated “Computing field…”
  • Issue disappears when field is removed

Enable logs:

--log-level=debug

Look for:

Computing field <field_name>

repeating continuously.

Step 2 Identify Common WRONG Patterns
WRONG 1: Field Depends on Itself

@api.depends('total')
def _compute_total(self):
    for rec in self:
        rec.total = rec.price * rec.qty
Enter fullscreen mode Exit fullscreen mode

Causes infinite recomputation.

WRONG 2: Writing Other Fields Inside Compute

@api.depends('price', 'qty')
def _compute_amount(self):
    for rec in self:
        rec.amount = rec.price * rec.qty
        rec.discount = rec.amount * 0.1  #  BAD
Enter fullscreen mode Exit fullscreen mode

Each write triggers recomputation again.

WRONG 3: Using write() Inside Compute

@api.depends('line_ids.amount')
def _compute_total(self):
    for rec in self:
        rec.write({'total': sum(rec.line_ids.mapped('amount'))})
Enter fullscreen mode Exit fullscreen mode

Causes recursion + DB writes.

Step 3 Define Correct and Minimal Dependencies
Correct Dependency Declaration

@api.depends('price', 'qty')
def _compute_total(self):
    for rec in self:
        rec.total = rec.price * rec.qty
Enter fullscreen mode Exit fullscreen mode

Rules:

  • Depend only on source fields
  • Never include the computed field itself
  • Avoid @api.depends('*')

Step 4 Keep Compute Methods PURE (No Side Effects)
Correct Pattern

total = fields.Float(compute='_compute_total', store=True)

@api.depends('price', 'qty')
def _compute_total(self):
    for rec in self:
        rec.total = rec.price * rec.qty if rec.price and rec.qty else 0.0
Enter fullscreen mode Exit fullscreen mode
  • No writes
  • No ORM calls
  • Safe and predictable

Step 5 Split Logic into Multiple Computed Fields
GOOD Design

amount = fields.Float(compute='_compute_amount', store=True)
discount = fields.Float(compute='_compute_discount', store=True)

@api.depends('price', 'qty')
def _compute_amount(self):
    for rec in self:
        rec.amount = rec.price * rec.qty

@api.depends('amount')
def _compute_discount(self):
    for rec in self:
        rec.discount = rec.amount * 0.1
Enter fullscreen mode Exit fullscreen mode
  • No circular dependency
  • Clean separation

Step 6 Use store=True Only When Necessary
BAD

total = fields.Float(compute='_compute_total', store=True)
Enter fullscreen mode Exit fullscreen mode

If:

  • Field is UI-only
  • Not searched or grouped

GOOD

total = fields.Float(compute='_compute_total')
Enter fullscreen mode Exit fullscreen mode

Use store=True only when:

  • Used in search domains
  • Used in reporting / group by

Step 7 Use @api.onchange for UI Logic (NOT compute)
WRONG

@api.depends('qty')
def _compute_price(self):
    for rec in self:
        rec.price = rec.qty * 10
Enter fullscreen mode Exit fullscreen mode

RIGHT

@api.onchange('qty')
def _onchange_qty(self):
    self.price = self.qty * 10
Enter fullscreen mode Exit fullscreen mode
  • No recomputation
  • UI-only behavior

Step 8 Fix Parent–Child Dependency Loops
BAD (Hidden Loop)

@api.depends('line_ids.total')
def _compute_total(self):
    for rec in self:
        rec.total = sum(rec.line_ids.mapped('total'))
Enter fullscreen mode Exit fullscreen mode

If line.total depends on parent → infinite loop.

SAFE VERSION

@api.depends('line_ids.price', 'line_ids.qty')
def _compute_total(self):
    for rec in self:
        rec.total = sum(
            line.price * line.qty for line in rec.line_ids
        )
Enter fullscreen mode Exit fullscreen mode
  • Direct dependency
  • No recursion

Step 9 Use Constraints Instead of Computed Fields for Validation
BAD

@api.depends('qty')
def _compute_check(self):
    if self.qty < 0:
        raise ValidationError("Invalid")
Enter fullscreen mode Exit fullscreen mode

GOOD

@api.constrains('qty')
def _check_qty(self):
    for rec in self:
        if rec.qty < 0:
            raise ValidationError("Quantity cannot be negative")
Enter fullscreen mode Exit fullscreen mode
  • No recompute
  • Correct validation layer

Step 10 Final Safe Template (Best Practice)

total = fields.Float(
    compute='_compute_total',
    store=True,
)

@api.depends('price', 'qty')
def _compute_total(self):
    for rec in self:
        rec.total = (rec.price or 0.0) * (rec.qty or 0.0)
Enter fullscreen mode Exit fullscreen mode
  • Pure compute
  • Correct dependencies
  • Production-safe

Conclusion

In Odoo Development, infinite recomputation issues are almost always caused by impure computed fields fields that write data, depend on themselves, or mix business logic with calculation logic. The fix is strict discipline: pure compute methods, minimal dependencies, no ORM writes, and correct separation of UI logic and validation. When computed fields are treated as read-only calculations, Odoo’s ORM remains fast, stable, and predictable even at scale.

Top comments (0)