27 Odoo
Anti-Patterns.
Critical mistakes that break enterprise projects. Learn to identify and avoid the most common Odoo development and configuration pitfalls.
Safety Roadmap
Python & ORM Anti-Patterns
Writing Inside a Loop
Calling write() on individual records inside a loop generates N SQL queries.
Updating 10,000 records takes 30 seconds instead of less than 1 second.
for record in records:
record.write({'note': 'X'})records.write({'note': 'X'})search() + filtered() Abuse
Fetching ALL records into memory then filtering in Python instead of the DB.
High memory usage and slow response time on large tables.
all = env['res.partner'].search([])
res = all.filtered(lambda p: p.is_us)res = env['res.partner'].search([('is_us', '=', True)])Overriding create() Without super()
Forgetting to call super() breaks the inheritance chain and other installed modules.
Features from other modules silently stop working, leading to data corruption.
def create(self, vals):
vals['x'] = 1
# Missing return super()...@api.model_create_multi
def create(self, vals_list):
return super().create(vals_list)Using self[0] Without ensure_one()
Assuming a recordset contains only one record without explicitly checking.
Silent errors where logic is applied to the first record of a set, ignoring others.
def do_something(self):
name = self[0].namedef do_something(self):
self.ensure_one()
name = self.nameHardcoding XML IDs
Referencing records by their external ID string instead of using references.
Code breaks if the external ID changes or the module is renamed.
rec = self.env.ref('base.main_company')# Prefer passing via context or using config parameters.Incorrect depends() on Compute Fields
Forgetting a dependency or including a field that doesn't trigger recalculation.
The compute field doesn't update when the data changes, showing stale info.
@api.depends('name')
def _comp(self): ... # Missing 'partner_id'@api.depends('name', 'partner_id.name')Critical Security Mistakes
SQL Injection via f-strings
Building raw SQL queries with string formatting instead of parameters.
Total database compromise via SQL Injection. Sensitive data theft.
cr.execute(f"SELECT * FROM p WHERE n = '{name}'")cr.execute("SELECT * FROM p WHERE n = %s", (name,))Abusing sudo()
Using sudo() to bypass errors instead of fixing underlying access rights.
Data leaks: Normal users seeing or modifying data they shouldn't access.
self.sudo().write({'state': 'done'})# Fix ir.model.access.csv or record rules instead.Unprotected Controller Routes
Defining web routes with auth='none' or auth='public' without internal checks.
Anonymous users can trigger internal Python methods or view private data.
@http.route('/my/data', auth='public')@http.route('/my/data', auth='user')Storing Sensitive Data in Plaintext
Saving passwords or API keys in regular Char fields instead of encrypted fields.
If the DB is compromised, all third-party integration keys are exposed.
api_key = fields.Char("Key")# Use ir.config_parameter or encrypted fields.Frontend & UX Mistakes
Modifying Core Templates with replace
Using xpath position='replace' on standard views.
Breaks other modules that try to xpath into the same elements.
<xpath expr="//field[@name='x']" position="replace"><xpath expr="//field[@name='x']" position="attributes">Heavy Logic in t-esc / t-out
Performing complex Python calls inside QWeb XML templates.
Slow page rendering because logic runs for every line in a report/view.
<t t-esc="doc.get_complex_total()"/># Pre-calculate in Python and pass to the template.Using jQuery in OWL Components
Directly manipulating the DOM with jQuery instead of OWL's reactive state.
UI becomes out of sync with the component state, causing random bugs.
$('#my_div').hide()this.state.isVisible = falseMissing Responsive Classes
Designing views that only look good on 1920x1080 screens.
The mobile app and tablet views become unusable for on-site staff.
<div style="width: 1200px"><div className="col-12 col-md-6">Forgetting the 'String' on Fields
Relying on Odoo to auto-format technical field names as labels.
The UI shows 'X_partner_id' instead of 'Customer Name', looking unprofessional.
my_field = fields.Char()my_field = fields.Char("Customer Name")Performance Killers
N+1 Query in Compute Fields
Accessing relational fields in a loop without prefetching.
Extremely slow form loads and list views (100+ queries per page).
for r in self:
r.val = r.partner_id.name # 1 query per rself.mapped('partner_id')
for r in self:
r.val = r.partner_id.name # Uses cacheAbusing api.onchange for Logic
Placing heavy business logic in UI-only onchange methods.
The UI 'freezes' while typing because the server is processing complex data.
@api.onchange('x')
def _check_all_db(self): ...# Use @api.depends for data logic.Missing Database Indexes
Frequently searched fields without index=True.
Full table scans: Odoo gets slower as the DB grows.
ref = fields.Char("Ref")ref = fields.Char("Ref", index=True)Large Binary Fields in the DB
Storing 10MB+ PDFs/Images directly in the SQL table.
Slow DB backups and high memory usage. Use the filestore instead.
file = fields.Binary(attachment=False)file = fields.Binary(attachment=True)Unoptimized Cron Intervals
Running heavy data processing every 1 minute.
Database lock contention and slow performance for all users.
interval_number: 1, interval_type: 'minutes'# Schedule during off-peak hours (e.g., nightly).Infrastructure & Deployment
21. workers = 0
Running single-threaded in production. One slow user blocks every other user from using Odoo.
22. No Filestore Backup
Restoring DB without /var/lib/odoo/filestore/. All images, PDFs, and attachments are lost forever.
23. Exposed Port 8069
Exposing Odoo directly to the internet without Nginx. No SSL, no rate limits, no buffer.
Project Management Failures
24. Go-Live on Friday
Launching a new ERP system before the weekend when support staff is unavailable.
25. Skipping UAT
Assuming the 'Consultant' tested everything without involving the actual business users.
26. The 'Big Bang' Migration
Trying to move 10 years of messy history into Odoo in one day without a pilot run.
27. Training via Manuals
Giving users a 200-page PDF and expecting them to learn Odoo. They won't read it.
The 7 Golden Rules
- 01If it works in Standard, don't customize it.
- 02If you must customize, make it upgrade-safe.
- 03If you use sudo(), document why in a comment.
- 04If you write SQL, use parameterized queries (%s).
- 05If you write in a loop, you're probably doing it wrong.
- 06If you skip testing, you'll test in production.
- 07If you don't backup the filestore, you don't have a backup.
Is Your Current Odoo System
Slow, Buggy, or Custom-Heavy?
Our certified Odoo architects audit legacy codebases to identify N+1 query loops, SQL bottlenecks, and security gaps. We rescue failing implementations and restore peace of mind.