Quick Answer
Stop storing calculated values (order totals, tax amounts, profit margins). Use computed fields: calculate once, on-demand, always accurate, zero storage overhead. Naive approach: store everything, update manually. Result: slow, buggy, out-of-sync data. Right approach: @api.depends + compute method. Save $40,000-$100,000 in debugging and data cleanup.
The Computed Field Problem
Your D2C order needs to calculate:
❏ Order total (sum of line items)
❏ Tax amount (based on customer country and product type)
❏ Profit margin (price - cost / price)
❏ Days since order (today - order date)
❏ Customer lifetime value (sum of all orders from this customer)
The Naive Approach
Store all these in database fields. Update them every time something changes.
Result: Slow, buggy, out-of-sync data. A customer makes a purchase—lifetime value doesn't update. Tax rate changes globally—all old orders have wrong tax. Data cleanup script runs—profit margins are suddenly wrong.
The Right Approach
Use computed fields. Calculate once, on-demand. Always accurate. Zero storage overhead.
We've implemented 150+ Odoo systems. The ones where developers master computed fields? Their systems are fast, accurate, and maintainable. The ones who store everything? They're constantly debugging out-of-sync data and fixing calculation mistakes. That's $40,000-$100,000 in unnecessary debugging and data cleanup.
Computed Field Basics
What it is: A field whose value is calculated from other fields, not stored in database.
class SaleOrder(models.Model):
_inherit = 'sale.order'
# Regular field (stored)
amount_subtotal = fields.Float()
# Computed field (not stored)
amount_tax = fields.Float(
compute='_compute_amount_tax' # Method that calculates it
)
@api.depends('amount_subtotal') # Triggers when this field changes
def _compute_amount_tax(self):
"""Calculate tax based on subtotal."""
for order in self:
# Tax = 8% of subtotal
order.amount_tax = order.amount_subtotal * 0.08
How It Works
1. User creates order with subtotal = $100
2. Odoo sees amount_tax depends on amount_subtotal
3. Odoo triggers _compute_amount_tax()
4. Method calculates: $100 × 0.08 = $8
5. amount_tax = $8 (shown to user, NOT stored in database)
6. User changes subtotal to $200 → Odoo automatically recalculates: $16
Stored vs Non-Stored (Critical Decision)
| Aspect | store=False (Default) | store=True |
|---|---|---|
| Storage | Not saved to database | Saved to database |
| Performance | Fast (no database write) | Slower (database write) |
| Searchable | Cannot filter/search | Can filter/search |
| Use When | Depends on current date/time, rarely accessed | Used in reports, filters, frequently accessed |
class Product(models.Model):
_inherit = 'product.product'
# Non-stored: Calculated real-time, not searched
# (Changes based on current date, so can't store)
days_until_expiry = fields.Integer(
compute='_compute_days_until_expiry',
store=False # Can't store (depends on today's date)
)
# Stored: Calculated once, used in reports/filters
# (Profit margin is stable, frequently accessed)
profit_margin_pct = fields.Float(
compute='_compute_profit_margin',
store=True # Store (stable value, often searched)
)
@api.depends('expiry_date')
def _compute_days_until_expiry(self):
from datetime import date
for product in self:
if product.expiry_date:
days = (product.expiry_date - date.today()).days
product.days_until_expiry = days
else:
product.days_until_expiry = 0
@api.depends('cost', 'list_price')
def _compute_profit_margin(self):
for product in self:
if product.list_price > 0:
margin = ((product.list_price - product.cost) / product.list_price) * 100
product.profit_margin_pct = margin
else:
product.profit_margin_pct = 0
The @api.depends Decorator (Critical!)
What it does: Tells Odoo which fields trigger recalculation.
❌ WRONG (No @api.depends)
# ❌ WRONG - Missing @api.depends
def _compute_profit(self):
"""Calculate profit."""
for order in self:
order.profit = order.amount_total - order.amount_cost
Why it breaks:
• With store=True: Stored value NEVER updates (no dependency tracking)
• Field only recalculates in specific scenarios (creation, save)
• User changes amount_total, profit doesn't update ❌
✓ RIGHT (With @api.depends)
# ✅ RIGHT - Declares dependencies
@api.depends('amount_total', 'amount_cost')
def _compute_profit(self):
"""Calculate profit."""
for order in self:
order.profit = order.amount_total - order.amount_cost
Complex Dependencies (Traversing Relationships)
@api.depends(
'order_line.product_id.cost', # Cost from product
'order_line.product_qty', # Qty on line
'order_line.price_unit', # Price on line
)
def _compute_total_profit(self):
"""Calculate total profit for order."""
for order in self:
total_profit = 0
for line in order.order_line:
line_profit = (line.price_unit - line.product_id.cost) * line.product_qty
total_profit += line_profit
order.total_profit = total_profit
Advanced: Inverse Methods (Editable Computed Fields)
Problem: Computed fields are read-only. User can't edit them.
Solution: Add an inverse method to handle edits.
class SaleOrder(models.Model):
_inherit = 'sale.order'
subtotal = fields.Float()
discount_percent = fields.Float()
# Computed field: discount in dollars
discount_amount = fields.Float(
compute='_compute_discount_amount',
inverse='_inverse_discount_amount', # Handle edits
store=True
)
@api.depends('subtotal', 'discount_percent')
def _compute_discount_amount(self):
"""Calculate discount in dollars."""
for order in self:
order.discount_amount = order.subtotal * (order.discount_percent / 100)
def _inverse_discount_amount(self):
"""Handle when user edits discount_amount directly."""
for order in self:
if order.subtotal > 0:
# Back-calculate discount_percent from discount_amount
order.discount_percent = (order.discount_amount / order.subtotal) * 100
else:
order.discount_percent = 0
Performance Optimization (Critical for Large Datasets)
❌ Anti-Pattern: Heavy Computation Inside Loop
# ❌ SLOW - Searches inside compute
@api.depends('order_line')
def _compute_customer_ltv(self):
for order in self:
# THIS SEARCHES 1,000 times for 1,000 orders!
all_orders = self.env['sale.order'].search([
('partner_id', '=', order.partner_id.id)
])
order.customer_ltv = sum(o.total for o in all_orders)
✓ Fix: Batch Operations, Cache Results
# ✅ FAST - Search once, reuse
def _compute_customer_ltv(self):
# Fetch all data at once
all_orders_by_customer = {}
all_orders = self.env['sale.order'].search([
('partner_id', 'in', self.mapped('partner_id').ids)
])
for order in all_orders:
customer_id = order.partner_id.id
if customer_id not in all_orders_by_customer:
all_orders_by_customer[customer_id] = []
all_orders_by_customer[customer_id].append(order)
# Now use cached data
for order in self:
orders = all_orders_by_customer.get(order.partner_id.id, [])
order.customer_ltv = sum(o.total for o in orders)
When to Use store=True
# ✓ USE store=True when:
# - Computed frequently in reports
# - Filtered/searched in lists
# - Performance-critical
# - Value is relatively stable
profit_margin = fields.Float(
compute='_compute_profit_margin',
store=True # Frequently used
)
# ✗ AVOID store=True when:
# - Depends on current date/time
# - Depends on too many fields
# - Expensive to compute
# - Rarely accessed
days_until_expiry = fields.Integer(
compute='_compute_days',
store=False # Depends on today
)
Action Items: Implement Computed Fields
Plan Computed Fields
❏ Identify calculations (totals, percentages, aggregates)
❏ Decide: store=True or store=False?
❏ List all dependencies (@api.depends)
❏ Determine if field needs to be editable (inverse method)
Implement
❏ Add field definition with compute parameter
❏ Add @api.depends decorator with correct dependencies
❏ Write compute method (loop with self)
❏ Add store=True only if needed
❏ Test that field updates when dependencies change
Optimize
❏ Profile slow compute methods
❏ Move searches outside loops
❏ Use batch operations (mapped, read_group)
❏ Cache expensive results
❏ Only store when necessary
Frequently Asked Questions
When should I use store=True vs store=False for computed fields?
Use store=True when the field is frequently used in reports, filters, searches, and has stable values. Use store=False when the field depends on current date/time, is expensive to compute, or is rarely accessed.
What happens if I forget @api.depends on a computed field?
With store=True, the stored value never updates (no dependency tracking). Without store, the field only recalculates on creation/save. Users change dependent fields, but the computed field doesn't update—causing out-of-sync data.
How do I make a computed field editable?
Add an inverse parameter with a method name. The inverse method handles user edits and back-calculates the dependent fields. Example: edit discount amount → inverse calculates discount percent.
How much can proper computed fields save vs. storing everything?
Systems with proper computed fields are fast, accurate, and maintainable. Systems that store everything spend $40,000-$100,000 debugging out-of-sync data, fixing calculation mistakes, and running data cleanup scripts.
Free Computed Fields Optimization Workshop
Stop computing values inefficiently. We'll identify which fields should be computed, optimize slow compute methods, design proper @api.depends declarations, decide store=True vs store=False, and implement inverse methods where needed. Most D2C brands have 5-10 computed fields they're storing unnecessarily. Proper design saves $15,000-$40,000 in database space and performance.
