But real-world Odoo systems don’t fail because someone forgot a field.
They fail because of:
- N+1 queries
- Incorrect override patterns
- Misused sudo()
- Heavy logic inside onchange
- Poor aggregation strategies
- Breaking accounting integrity
If you're building production-grade modules, these patterns are not optional — they’re mandatory.
1️⃣ The N+1 Query Trap (And How to Eliminate It)
❌ Anti-Pattern
for partner in partners:
moves = self.env['account.move.line'].search([
('partner_id', '=', partner.id)
])
If you have 1,000 partners, you execute 1,000 queries.
That’s how you silently destroy performance.
✅ Proper Pattern
move_lines = self.env['account.move.line'].search([
('partner_id', 'in', partners.ids)
])
grouped = {}
for line in move_lines:
grouped.setdefault(line.partner_id.id, []).append(line)
Rule: Query once. Process in memory.
2️⃣ Use read_group() for Aggregations (Let PostgreSQL Work)
Instead of:
total = sum(
self.env['account.move.line']
.search(domain)
.mapped('debit')
)
Use:
result = self.env['account.move.line'].read_group(
domain=domain,
fields=['debit:sum'],
groupby=[]
)
Why?
Because read_group() pushes aggregation to PostgreSQL using GROUP BY.
Python loops should not replace database engines.
3️⃣ @api.onchange Is Not Business Logic
Many developers mistakenly treat onchange as persistent logic.
It is not.
It runs in memory, client-side.
❌ Dangerous Pattern
@api.onchange('picking_ids')
def _onchange_picking(self):
for picking in self.picking_ids:
self.line_ids += self.env['my.line'].new({
'name': picking.name
})
Risks:
- Duplicate lines
- Different behavior via RPC/API
- Inconsistent data
✅ Safe Approach
Move logic to create() / write():
@api.model_create_multi
def create(self, vals_list):
records = super().create(vals_list)
for record in records:
record._generate_lines()
return records
Rule: Onchange = UI helper.
Create/Write = Business logic.
4️⃣ Override create() and write() the Right Way
Incorrect overrides break multi-record operations.
❌ Wrong
def create(self, vals):
record = super().create(vals)
✅ Correct
@api.model_create_multi
def create(self, vals_list):
records = super().create(vals_list)
for rec in records:
rec._post_create_logic()
return records
Odoo often creates multiple records at once.
Ignoring this breaks imports and batch operations.
5️⃣ Computed Fields: Store or Not Store?
Non-stored:
- Always recomputed
- Heavy for large datasets
Stored:
- Faster reads
- Requires correct dependency declaration
amount_total = fields.Float(
compute='_compute_amount',
store=True
)
@api.depends('line_ids.price_subtotal')
def _compute_amount(self):
for rec in self:
rec.amount_total = sum(
rec.line_ids.mapped('price_subtotal')
)
If dependencies are wrong, your data silently becomes inaccurate.
6️⃣ When to Use Raw SQL
ORM is powerful — but not magical.
For large financial reports:
self.env.cr.execute("""
SELECT partner_id, SUM(debit - credit)
FROM account_move_line
WHERE date BETWEEN %s AND %s
GROUP BY partner_id
""", (date_from, date_to))
data = self.env.cr.fetchall()
Use SQL when:
- Handling millions of rows
- Complex joins
- Heavy aggregation
But remember:
- Security rules are bypassed
- Multi-company logic must be handled manually
7️⃣ sudo() Is Not a Shortcut
❌ Bad Practice
self.env['res.partner'].sudo().search([])
This bypasses access rules entirely.
Use sudo() only for:
- Scheduled jobs
- System-level automation
- Controlled internal processes
Never use it to “fix access errors”.
8️⃣ Protect Accounting Integrity
Accounting in Odoo is interconnected through:
- account_move
- account_move_line
- account_partial_reconcile
Deleting or directly modifying records via SQL can break financial consistency permanently.
Best practices:
- Use reversal instead of delete
- Never update posted entries manually
- Respect reconciliation structures
🚀 Final Thought
Writing Odoo code is easy.
Writing scalable, safe, and accounting-compliant Odoo code is a different skill entirely.
The difference between a junior developer and a system architect in Odoo is not about syntax.
It’s about:
- Understanding ORM internals
- Respecting accounting flows
- Avoiding hidden performance killers
- Designing for scale
If you master these patterns, you’re not just customizing Odoo.
You’re engineering financial infrastructure.
