Computed Fields Causing Infinite Recomputations (odoo)

Published: (January 7, 2026 at 03:41 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.

Step 1 – Identify the problematic computed field

Typical symptoms

  • Form view never finishes loading
  • CPU spikes when opening records
  • Logs show repeated recomputation
  • Server freezes after record creation/update

Enable logs

--log-level=debug

Look for repeating messages

Computing field x_field_name

Step 2 – Identify common WRONG patterns (most bugs come from here)

1. Computed field depends on itself

@api.depends('total_amount')
def _compute_total_amount(self):
    for rec in self:
        rec.total_amount = rec.price * rec.qty

Result: infinite recomputation.

2. Writing to other fields inside compute

@api.depends('price', 'qty')
def _compute_total(self):
    for rec in self:
        rec.total = rec.price * rec.qty
        rec.subtotal = rec.total * 0.9  # BAD

3. Using write() inside compute

@api.depends('line_ids.amount')
def _compute_amount(self):
    for rec in self:
        rec.write({'total': sum(rec.line_ids.mapped('amount'))})

Result: recursive recompute + database writes.

Step 3 – Define correct dependencies ONLY

@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 depend on the computed field itself.
  • Avoid @api.depends('*').

Step 4 – Never modify other fields inside compute

Separate computed fields

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

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

@api.depends('total')
def _compute_subtotal(self):
    for rec in self:
        rec.subtotal = rec.total * 0.9

Benefits: clean, predictable, no loops.

Step 5 – Use store=True ONLY when required

BAD (unnecessary storage)

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

Use store=True only if the field is:

  • Frequently searched
  • Used for grouping or reporting

GOOD (transient UI value)

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

store=True increases recomputation cost.

Step 6 – Avoid ORM writes in compute (use @api.onchange instead)

WRONG (compute)

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

RIGHT (onchange)

@api.onchange('qty')
def _onchange_qty(self):
    self.price = self.qty * 10

Use onchange for UI‑only logic – no recomputation loops.

Step 7 – Use @api.depends_context when needed

If the computation depends on company, language, or user:

@api.depends_context('company')
@api.depends('amount')
def _compute_tax(self):
    for rec in self:
        rec.tax = rec.amount * rec.company_id.tax_rate

Prevents unnecessary recomputation.

Step 8 – Fix One2many / Many2one loops

COMMON 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 back on the 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)

Step 9 – Use SQL constraints instead of compute for validation

BAD (compute for validation)

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

GOOD (constraint)

@api.constrains('qty')
def _check_qty(self):
    for rec in self:
        if rec.qty < 0:
            raise ValidationError("Quantity cannot be negative")

Step 10 – Final Safe Computed Field 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 * rec.qty if rec.price and rec.qty else 0.0

Key points

  • No writes inside the compute method.
  • No recursion.
  • Minimal, correct dependencies.
  • Safe for production.

Conclusion

In Odoo, infinite recomputation issues are almost always caused by impure computed fields that write data, depend on themselves, or have poorly defined dependencies. The fix is simple but strict:

  1. Keep compute methods pure.
  2. Declare minimal and accurate dependencies.
  3. Avoid ORM writes inside computes; use @api.onchange or constraints instead.
  4. Separate UI‑only logic from stored logic.

Applying these rules eliminates loops, improves performance, and makes your Odoo modules stable and maintainable.

Calculations instead of business logic containers, performance stabilizes, recomputation stops, and your Odoo system becomes predictable and scalable.
Back to Blog

Related posts

Read more »