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=Trueis 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
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)
If:
- Field is UI-only
- Not searched or grouped
GOOD
total = fields.Float(compute='_compute_total')
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
RIGHT
@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 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
)
- 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
- 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)
- 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)