Odoo ORM Expert
Overview
This skill teaches you Odoo's Object Relational Mapper (ORM) in depth. It covers reading/writing records, building domain filters, working with relational fields, and avoiding common performance pitfalls like N+1 queries.
When to Use This Skill
- Writing
search(), browse(), create(), write(), or unlink() calls.
- Building complex domain filters for views or server actions.
- Implementing computed, stored, and related fields.
- Debugging slow queries or optimizing bulk operations.
How It Works
- Activate: Mention
@odoo-orm-expert and describe what data operation you need.
- Get Code: Receive correct, idiomatic Odoo ORM code with explanations.
- Optimize: Ask for performance review on existing ORM code.
Examples
Example 1: Search with Domain Filters
# Find all confirmed sale orders for a specific customer, created this year
import datetime
start_of_year = datetime.date.today().replace(month=1, day=1).strftime('%Y-%m-%d')
orders = self.env['sale.order'].search([
('partner_id', '=', partner_id),
('state', '=', 'sale'),
('date_order', '>=', start_of_year),
], order='date_order desc', limit=50)
# Note: pass dates as 'YYYY-MM-DD' strings in domains,
# NOT as fields.Date objects — the ORM serializes them correctly.
Example 2: Computed Field
total_order_count = fields.Integer(
string='Total Orders',
compute='_compute_total_order_count',
store=True
)
@api.depends('sale_order_ids')
def _compute_total_order_count(self):
for record in self:
record.total_order_count = len(record.sale_order_ids)
Example 3: Safe Bulk Write (avoid N+1)
# ✅ GOOD: One query for all records
partners = self.env['res.partner'].search([('country_id', '=', False)])
partners.write({'country_id': self.env.ref('base.us').id})
# ❌ BAD: Triggers a separate query per record
for partner in partners:
partner.country_id = self.env.ref('base.us').id
Best Practices
- ✅ Do: Use
mapped(), filtered(), and sorted() on recordsets instead of Python loops.
- ✅ Do: Use
sudo() sparingly and only when you understand the security implications.
- ✅ Do: Prefer
search_count() over len(search(...)) when you only need a count.
- ✅ Do: Use
with_context(...) to pass context values cleanly rather than modifying self.env.context directly.
- ❌ Don't: Call
search() inside a loop — this is the #1 Odoo performance killer.
- ❌ Don't: Use raw SQL unless absolutely necessary; use ORM for all standard operations.
- ❌ Don't: Pass Python
datetime/date objects directly into domain tuples — always stringify them as 'YYYY-MM-DD'.
Limitations
- Does not cover
cr.execute() raw SQL patterns in depth — use the Odoo performance tuner skill for SQL-level optimization.
- Stored computed fields can cause significant write overhead at scale; this skill does not cover partitioning strategies.
- Does not cover transient models (
models.TransientModel) or wizard patterns.
- ORM behavior can differ slightly between Odoo SaaS and On-Premise due to config overrides.
1---2name: odoo-orm-expert3description: Master Odoo ORM patterns: search, browse, create, write, domain filters, computed fields, and performance-safe query techniques.4license: MIT5---6
7# Odoo ORM Expert
8
9## Overview
10
11This skill teaches you Odoo's Object Relational Mapper (ORM) in depth. It covers reading/writing records, building domain filters, working with relational fields, and avoiding common performance pitfalls like N+1 queries.
12
13## When to Use This Skill
14
15- Writing `search()`, `browse()`, `create()`, `write()`, or `unlink()` calls.
16- Building complex domain filters for views or server actions.
17- Implementing computed, stored, and related fields.
18- Debugging slow queries or optimizing bulk operations.
19
20## How It Works
21
221. **Activate**: Mention `@odoo-orm-expert` and describe what data operation you need.
232. **Get Code**: Receive correct, idiomatic Odoo ORM code with explanations.
243. **Optimize**: Ask for performance review on existing ORM code.
25
26## Examples
27
28### Example 1: Search with Domain Filters
29
30```python
31# Find all confirmed sale orders for a specific customer, created this year
32import datetime
33
34start_of_year = datetime.date.today().replace(month=1, day=1).strftime('%Y-%m-%d')
35
36orders = self.env['sale.order'].search([
37 ('partner_id', '=', partner_id),
38 ('state', '=', 'sale'),
39 ('date_order', '>=', start_of_year),
40], order='date_order desc', limit=50)
41
42# Note: pass dates as 'YYYY-MM-DD' strings in domains,
43# NOT as fields.Date objects — the ORM serializes them correctly.
44```
45
46### Example 2: Computed Field
47
48```python
49total_order_count = fields.Integer(
50 string='Total Orders',
51 compute='_compute_total_order_count',
52 store=True
53)
54
55@api.depends('sale_order_ids')
56def _compute_total_order_count(self):
57 for record in self:
58 record.total_order_count = len(record.sale_order_ids)
59```
60
61### Example 3: Safe Bulk Write (avoid N+1)
62
63```python
64# ✅ GOOD: One query for all records
65partners = self.env['res.partner'].search([('country_id', '=', False)])
66partners.write({'country_id': self.env.ref('base.us').id})
67
68# ❌ BAD: Triggers a separate query per record
69for partner in partners:
70 partner.country_id = self.env.ref('base.us').id
71```
72
73## Best Practices
74
75- ✅ **Do:** Use `mapped()`, `filtered()`, and `sorted()` on recordsets instead of Python loops.
76- ✅ **Do:** Use `sudo()` sparingly and only when you understand the security implications.
77- ✅ **Do:** Prefer `search_count()` over `len(search(...))` when you only need a count.
78- ✅ **Do:** Use `with_context(...)` to pass context values cleanly rather than modifying `self.env.context` directly.
79- ❌ **Don't:** Call `search()` inside a loop — this is the #1 Odoo performance killer.
80- ❌ **Don't:** Use raw SQL unless absolutely necessary; use ORM for all standard operations.
81- ❌ **Don't:** Pass Python `datetime`/`date` objects directly into domain tuples — always stringify them as `'YYYY-MM-DD'`.
82
83## Limitations
84
85- Does not cover **`cr.execute()` raw SQL** patterns in depth — use the Odoo performance tuner skill for SQL-level optimization.
86- **Stored computed fields** can cause significant write overhead at scale; this skill does not cover partitioning strategies.
87- Does not cover **transient models** (`models.TransientModel`) or wizard patterns.
88- ORM behavior can differ slightly between Odoo SaaS and On-Premise due to config overrides.