The Object Relational Mapping (ORM) layer is one of the core strengths of Odoo’s framework. As Odoo brings optimizations and refinements, writing clean, efficient, and maintainable ORM code is more important than ever for developers aiming to build reliable modules and scalable systems.
In this article, we’ll break down key tips and practices for leveraging Odoo’s ORM like a pro.
Why the ORM Matters
Odoo’s ORM abstracts the database layer, allowing developers to interact with PostgreSQL using Pythonic object-based code. With proper use, you can:
- Avoid raw SQL
- Ensure data consistency
- Reduce boilerplate code
- Write more readable and secure code
1. Use sudo() Sparingly
sudo() is powerful but risky. It bypasses access rights and record rules, so overusing it can introduce security vulnerabilities.
Bad Example:
self.env['res.partner'].sudo().search([...])
Better Practice:
- Use sudo() only when absolutely necessary
- Document why it’s used
- Prefer check_access_rights() or proper record rules if applicable
2. Prefer filtered() Over Complex search() When Using Existing Records
If you already have a recordset, don’t query the database again unnecessarily.
partners = self.env['res.partner'].search([('customer_rank', '>', 0)])
vat_partners = partners.filtered(lambda p: p.vat)
- More readable and efficient in-memory filtering.
3. Avoid search([]) Without Limits
Calling search([]) retrieves all records from the table.
Bad:
all_products = self.env['product.product'].search([])
Better:
- Use limit=, or only fetch required fields with read(), or use search_count() if counting.
4. Use exists() to Avoid Errors with Invalid Records
When working with potentially empty or deleted recordsets, exists() prevents errors:
if some_partner.exists():
...
5. Use mapped() for Clean, Fast Data Extraction
Instead of list comprehensions, use mapped() for field extraction:
emails = partners.mapped('email')
6. Write Batch-Oriented Code (Avoid Loops Over Recordsets)
Don’t write ORM code that loops over individual records making separate database calls:
Bad:
for order in orders:
order.do_something()
Better (if method allows batch):
orders.do_something()
- Many methods in Odoo ORM are designed to handle multiple records at once.
7. Use Prefetching When Needed
Odoo prefetches fields automatically but custom prefetching may be needed for optimization:
orders = self.env['sale.order'].search([...]).with_prefetch()
Or explicitly control fields:
orders = self.env['sale.order'].search([...]).read(['name', 'amount_total'])
8. Clean Domain Usage
Write readable domains:
domain = [
('state', '=', 'sale'),
'|',
('amount_total', '>', 1000),
('partner_id.country_id.code', '=', 'KW')
]
9. Know When to Use SQL
Odoo ORM is powerful, but in reporting, heavy joins, or aggregations, direct SQL may be better:
self.env.cr.execute("SELECT COUNT(*) FROM res_partner WHERE active = true")
- Always profile queries before optimizing prematurely.
10. Profile and Test Your Code
Use Odoo’s built-in logging and profiling tools:
_logger = logging.getLogger(__name__)
_logger.info("Processing orders: %s", orders.ids)
- Use --log-level=debug_sql to review SQL queries generated by ORM.
Conclusion
Mastering Odoo’s ORM is not just about knowing its functions — it’s about writing code that is clean, secure, scalable, and easy to maintain. By following these practices, you can avoid common pitfalls and ensure that your modules perform well, even as your system scales.
At Alientics, we help businesses build smart, scalable solutions on top of Odoo, bringing together expertise, efficiency, and future-ready technology.
Need help optimizing your Odoo modules? Contact us for consultation or custom development!