Computed Fields Causing Infinite Recomputations(Odoo)

Published: (January 9, 2026 at 12:00 AM EST)
3 min read
Source: Dev.to

Source: Dev.to

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

  • A computed field depends on itself
  • The compute method writes to other fields
  • write() is used inside the compute method
  • 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 …” messages
  • Issue disappears when the field is removed

Enable logs:

--log-level=debug

Look for:

Computing field 

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

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

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'))})

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

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
  • 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
  • No circular dependency
  • Clean separation

Step 6 – Use store=True Only When Necessary

BAD

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

Use store=True only if the field is:

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

GOOD

total = fields.Float(compute='_compute_total')

When the field is UI‑only, not searched or grouped.

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
@api.onchange('qty')
def _onchange_qty(self):
    self.price = self.qty * 10
  • 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'))

If line.total depends on the parent, an infinite loop occurs.

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
        )
  • 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")

GOOD

@api.constrains('qty')
def _check_qty(self):
    for rec in self:
        if rec.qty < 0:
            raise ValidationError("Quantity cannot be negative")
  • No recompute
  • Proper 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)
  • Pure compute
  • Correct dependencies
  • Production‑safe

Conclusion

In Odoo development, infinite recomputation issues are almost always caused by impure computed 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 proper 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.

Back to Blog

Related posts

Read more »

A Eficiência do Cache no SQLite

A Eficiência do Cache no SQLite !Cover image for A Eficiência do Cache no SQLitehttps://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=aut...