Odoo Development
Odoo Computed Fields: Store, Depends and Inverse
A computed field is a method that assigns a value to every record it is given. Almost all the difficulty is in two decisions: whether to store it, and whether the depends list is complete. Get the second wrong and the field is right on save and wrong forever after.
Part of our guide to Odoo Development
The basic shape
from odoo import api, fields, models
class SaleOrder(models.Model):
_inherit = "sale.order"
line_count = fields.Integer(compute="_compute_line_count", store=True)
@api.depends("order_line")
def _compute_line_count(self):
for order in self:
order.line_count = len(order.order_line)Three obligations, and all three matter.
Assign to every record in self. A compute method receives a recordset, not one record. Leaving any record unassigned raises an error, and forgetting the loop entirely is the classic first mistake.
Declare @api.depends accurately. This is the entire recomputation contract.
Decide on store. It changes what the field can do and what it costs.
depends is the whole contract
Odoo recomputes when, and only when, a field named in depends changes. Anything you leave out is a dependency the framework does not know about.
That failure is quiet. The value is correct when the record is created, because everything computes then. It goes wrong later, when the missing input changes and nothing triggers a recompute. The field then holds a value that was true once, and no error is raised at any point.
Dotted paths follow relations, so @api.depends("order_line.price_total") recomputes when a line's total changes, not merely when lines are added or removed. Those are different triggers and confusing them is common.
Two specific traps:
Time is not a dependency. A field computed from today's date cannot be stored meaningfully, because nothing changes to trigger recomputation. Leave it unstored or compute it in a scheduled action.
Recursion must be declared. If the field depends on itself through a relation, such as parent_id.total, you must pass recursive=True. The framework requires this to be explicit to guarantee the recomputation is correct, and without it you get either wrong values or an error.
Stored or not
Stored means a real column. It can be searched, sorted and grouped, and it is read like any other column. The cost is that every dependency change triggers a write, and on a hot model that is real database traffic.
Unstored means computed on read, every time. Nothing to keep in sync, but it cannot appear in a search domain, an _order, or a group by, and it is recalculated on every access.
The practical rule: store it if you need to search, sort or group by it, or if it is expensive relative to how often its inputs change. Otherwise do not.
Unstored is also the correct answer whenever the value depends on who is asking or when they are asking, since a stored value would be frozen at whoever last triggered the recompute.
Making a computed field editable
Add an inverse method. It receives the written value and is responsible for writing to the underlying fields:
total_with_tax = fields.Monetary(
compute="_compute_total_with_tax",
inverse="_inverse_total_with_tax",
store=True,
)
@api.depends("amount_untaxed", "tax_rate")
def _compute_total_with_tax(self):
for rec in self:
rec.total_with_tax = rec.amount_untaxed * (1 + rec.tax_rate)
def _inverse_total_with_tax(self):
for rec in self:
if rec.tax_rate:
rec.amount_untaxed = rec.total_with_tax / (1 + rec.tax_rate)The field becomes writable in the interface. Be careful: an inverse that cannot uniquely determine the inputs will produce surprising results, and a field computed from three inputs rarely inverts cleanly into all three.
Making it searchable without storing it
If you need search but not the storage cost, provide a search method. It receives the operator and value and returns a domain:
overdue = fields.Boolean(compute="_compute_overdue", search="_search_overdue")
def _search_overdue(self, operator, value):
today = fields.Date.context_today(self)
return [("date_due", "<", today)]This lets the field appear in filters while remaining computed on read. Handle the operator properly if the field can be searched more than one way.
compute_sudo, and its non-obvious default
compute_sudo recomputes the field as superuser, bypassing access rights. It matters whenever the computation reads records the current user cannot see, such as a total across companies or a count of restricted records.
The default is not uniform: True for stored fields and False for non-stored ones. That asymmetry catches people. A field that computed correctly while stored can start raising access errors after you set store=False, with no other change.
precompute, and when it backfires
precompute=True computes the field before the record is inserted rather than at flush, saving a round trip.
Two conditions limit it. A default value disables it, even with the flag set, because precomputation only happens when no explicit and no default value is supplied to create(). And it can be slower than not using it: without precompute, records created in bulk compute in one batch with prefetching, while precomputed fields compute one by one. It pays off mainly on one2many lines, which the ORM does create in batch.
Treat it as an optimisation to apply after measuring, not a default.
Where computed fields go wrong at scale
The performance problem is rarely one field. It is a chain: field A depends on B, B depends on C, and a write to C triggers a cascade across thousands of records inside the user's save.
When a save gets slow, follow the dependency chain rather than optimising the method body. The fix is usually to break the chain or narrow a depends, not to make the computation faster. Related fields are a specific case of the same problem and are worth understanding separately.
FAQ
Questions, answered.
Should an Odoo computed field be stored?
Store it if you need to search, sort or group by it, or if the computation is expensive relative to how often the inputs change. Leave it unstored if it is cheap, or if it depends on the user or the current time, because a stored value would be frozen at whatever it was when last recomputed.
Why is my computed field not updating?
The depends list is incomplete. Odoo recomputes a stored field only when a field named in depends changes, so a dependency you did not declare means the value silently goes stale. It looked correct at creation, which is why the bug survives testing.
Can users edit a computed field?
Only if you give it an inverse method. A computed field is readonly by default; adding inverse names a method that takes the written value and writes back to the underlying fields, which makes the field editable in the interface.
Keep reading
More on Odoo Development.
The full guide, plus the other articles in this cluster.
With CODEerts
Need something built properly?
We are certified Odoo partners. We build custom Odoo modules that extend the framework rather than fight it, and we publish our own apps on the Odoo Store.
See how we can helpBook a callReady to make Odoo work the way your business does?
Book a free callCODEerts is a team of certified Odoo partners and full-stack engineers. We implement, customise and support Odoo ERP, then build the software around it.