Odoo Development
Odoo Coding Standards and a Module Review Checklist
Most Odoo review comments are about the same dozen things. Collecting them into a checklist turns review from an opinion exchange into a pass or fail, which is the only version of review that survives a deadline.
Part of our guide to Odoo Development
Naming
Models are dotted, lowercase and singular: sale.order, not sale.orders. Fields use the type suffix conventions, _id for Many2one and _ids for One2many and Many2many, which lets a reader infer cardinality without opening the definition.
Private methods start with an underscore. This is not cosmetic: a method without one is callable from the web client through RPC, so the underscore is part of your security surface. Anything not deliberately exposed should have it.
Compute methods are named _compute_, inverses _inverse_, searches _search_, onchanges _onchange_. Following this makes the relationship obvious and makes the codebase greppable.
Modules get a consistent prefix so they group together in a long addons path and are recognisably yours.
File organisation
One model per Python file, named after the model. Views in a file named after their model. Security first in the manifest data list, always.
Imports in three groups: standard library, Odoo, then your module. Odoo's own codebase follows this and matching it costs nothing.
The security checks
Every new model has a line in ir.model.access.csv. No exceptions. A model without one is invisible to non-superusers, and because the developer tests as administrator, this is discovered by a user rather than by the developer.
Record rules exist where multi-company or ownership matters. Access rights control which models a group can touch; record rules control which records. Getting the first right and skipping the second means every user of the group sees every record.
Anything callable over RPC is deliberate. A public method on a model is reachable by any authenticated user. If it should not be, prefix it.
No SQL string interpolation. Parameterise queries. This is the one item on the list that is a genuine vulnerability rather than a maintenance problem.
The upgrade checks
These are the ones that cost money later rather than now.
Overrides call super(). An override that reimplements the parent silently stops receiving anything the next version adds to that method.
Views are inherited, not replaced. A copied view stops receiving every change Odoo makes to it, with nothing reporting the divergence.
XPath anchors are structural. Anchor on a field name or a distinctive element, not on a position that reorders between versions.
Deprecated APIs are gone. Each version retires things. odoo.exceptions.Warning was removed and now fails at import. The view type became in 17. The _sql_constraints list stopped working in 19 while logging only a warning. Version-specific traps like these are worth a dedicated pass rather than a general look.
The performance checks
No queries inside loops. The single most common cause of a module that works in testing and collapses in production:
for order in orders:
partner = self.env["res.partner"].browse(order.partner_id.id)That is one query per order. Odoo's prefetching makes order.partner_id.name efficient across a recordset already, so the loop is not only slow, it is unnecessary.
No search() without a limit where the result set is unbounded, particularly in scheduled actions.
Stored computed and related fields are justified. Each one is a write amplification on its source model. Related fields with long chains are the usual offender.
@api.depends is complete. An incomplete dependency list is a correctness bug that presents as stale data much later.
The structural checks
Business logic is in the model layer. Not in controllers, not in views. A rule in a controller cannot be reached by the ORM, cannot be tested without HTTP, and is invisible to any other module that needs it.
The manifest is accurate. Explicit license, a version in the full series form, depends listing what is actually used and nothing more.
Data files use noupdate deliberately. Records the customer edits should be frozen after creation; records the module owns should not be. Neither default is right for both, so the choice should be visible.
Migration scripts exist where data meaning changed. Adding a field needs none. Changing what a field means needs one, and its absence produces no error.
The review checklist
Run it in this order, because the early items are cheap and the late ones are only worth doing on code that passed the early ones.
- Access rights present for every new model
- Record rules where ownership or multi-company applies
- No SQL interpolation, no unintentionally public methods
- Every override calls
super() - Views inherited, XPath anchors structural
- No deprecated APIs for the target version
- No queries in loops, no unbounded searches
- Stored computed and related fields justified,
dependscomplete - Logic in models, not controllers or views
- Manifest accurate,
noupdatedeliberate, migration scripts where needed - Tests exist for anything with non-trivial logic
- The module installs on an empty database, not only as an upgrade
That last one catches load-order bugs that an upgrade hides, because an upgrade finds the referenced records already present from a previous version.
Tests
Tests matter here for a specific reason rather than a general one: they are the only way to know a module survived an upgrade without a human repeating every scenario by hand.
A module with tests is upgraded and verified. A module without them is upgraded and hoped about, and that difference is the single largest variable in what an upgrade costs.
FAQ
Questions, answered.
What are the most common problems in an Odoo code review?
Missing access rights, overrides that do not call super, queries inside loops, replaced views instead of inherited ones, and business logic in controllers. Those five account for most review findings and all of them are upgrade or security problems rather than style.
Where should business logic live in an Odoo module?
In the model layer. Controllers should validate input and delegate; views should not contain logic at all. Logic in a controller is unreachable from the ORM, untestable without HTTP, and invisible to any other module that needs the same rule.
How do I check an Odoo module for performance problems?
Look for reads or searches inside loops over records, stored computed and related fields with wide dependency chains, and searches without a limit. Those three patterns cause most of the slowness that shows up only once real data volumes arrive.
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.