Skip to content

Common Mistakes & How to Avoid Them

Learning from Mistakes

This section covers the most common mistakes made by functional consultants and how to avoid them. Understanding these pitfalls will save you hours of troubleshooting and help you design better solutions.

Data Model Mistakes

Mistake 1: Using Non-Stored Fields in Group By

Wrong Approach

"I want to group orders by customer country"

Uses: partner_id.country_id (related, not stored)

Result: Error or empty grouping

Correct Approach

Create a stored related field:

python
partner_country_id = fields.Many2one(
    related='partner_id.country_id',
    store=True
)

Result: Works perfectly

Why this happens: Group By uses SQL GROUP BY which requires actual database columns. Non-stored fields have no column.

Mistake 2: Confusing Field Type and Widget

Wrong Thinking

"I need a radio button field"

Creates a new field type

Correct Understanding

"I need a Selection field displayed as radio buttons"

Uses: Selection field with widget="radio" in the view

Key insight: Field type = how data is stored. Widget = how data is displayed.

What You WantField TypeWidget
Radio buttonsSelectionradio
Star ratingSelectionpriority
Progress barFloatprogressbar
TagsMany2manymany2many_tags
User avatarMany2onemany2one_avatar_user

Mistake 3: Over-Using Many2many

Wrong Design

"Product can have multiple suppliers"

Uses: Many2many between product and supplier

Problem: Can't store price per supplier!

Correct Design

Create a "Product Supplier" intermediate model with:

  • Many2one to product
  • Many2one to supplier
  • Price field
  • Lead time field
  • Minimum quantity field

This is what product.supplierinfo does.

Rule: If you need ANY extra data about the relationship, use an intermediate model.

Mistake 4: Forgetting ondelete

The Problem

You have orders linked to customers. If someone deletes a customer, what happens to their orders?

ondelete ValueWhat HappensWhen to Use
set null (default)Field becomes emptyRarely appropriate
cascadeRelated records deleted tooParent-child only
restrictDeletion blockedImportant relationships

Real Consequences

  • set null: Orders remain but "orphaned" - customer field is empty, data corrupted
  • cascade: All customer's orders deleted - massive data loss
  • restrict: Safe - forces user to handle orders first

Best Practice: Always specify ondelete='restrict' for important business relationships.

Mistake 5: Creating Circular Dependencies

Wrong Design

python
# Model A
b_id = fields.Many2one('model.b')

# Model B
a_id = fields.Many2one('model.a')

Both required = can't create either record first!

Solutions

  1. Make one field optional
  2. Create records in two steps (save draft, then link)
  3. Rethink the relationship (maybe one should be computed)

Automation Mistakes

Mistake 6: Creating Infinite Loops

Wrong Automation

Automated Action on sale.order:

  • Trigger: On Update
  • Action: Update a field on the same order

Result: Update triggers automation, which triggers update, which triggers automation... 🔄

Correct Approach

  1. Use Trigger Fields to limit when automation fires
  2. Don't update fields that trigger the same automation
  3. Use a "processing" flag to prevent re-entry

Mistake 7: Expecting AI to Replace Everything

Wrong Assumption

"Odoo 19 AI will predict lead conversion using GPT"

Reality: Lead probability still uses Bayesian PLS algorithm, not LLM

Correct Understanding

AI in Odoo 19 is for:

  • Text generation (emails, descriptions)
  • Document digitization (OCR)
  • Chat assistants

PLS (Predictive Lead Scoring) remains statistical/Bayesian

AI ≠ Machine Learning ≠ Statistical Models

Mistake 8: Using Server Actions for Everything

Wrong Pattern

Using Server Actions for processes that should be automated:

  • Daily cleanup → Should be Scheduled Action
  • On-save notifications → Should be Automated Action
Use CaseCorrect Action Type
User clicks buttonServer Action
Record created/updatedAutomated Action
Daily/weekly taskScheduled Action
Bulk updateServer Action
Email on status changeAutomated Action

Security Mistakes

Mistake 9: Using Domains Instead of Record Rules

Wrong Approach

"Salespeople should only see their leads"

Uses: Domain filter in the view

Problem: Power users can remove the filter, API access ignores it

Correct Approach

Use a Record Rule:

python
[('user_id', '=', user.id)]

Result: Cannot be bypassed by any means

Rule: If it's a security requirement, use Record Rules. If it's just convenience, use Domains.

Mistake 10: Giving Too Many Admin Rights

Wrong Approach

"User needs to see all orders" → Makes them Sales Manager

Problem: Manager can also delete, configure, access reports they shouldn't

Correct Approach

  1. Create custom group: "Sales Viewer"
  2. Grant only Read access to sale.order
  3. Add record rule if needed for filtering

View Mistakes

Mistake 11: Not Using Tracking

Best Practice

For important fields (status, assigned user, amounts), add tracking=True.

This creates an audit trail in the chatter showing who changed what and when.

Critical for: Financial fields, status changes, assignments

Mistake 12: Ignoring Performance in List Views

Wrong Approach

Adding many computed/related fields to list view without storing them

Result: Slow list loads, timeout errors

Correct Approach

  1. Check if fields are stored: Settings → Technical → Fields
  2. Add store=True to computed/related fields in list views
  3. Limit list view to essential columns

Mistake 13: Making Everything Required

Over-Restriction

Making 15 fields required on a form

Result: Users can't save drafts, abandon forms, work around by entering garbage data

Balanced Approach

  • Required at model level: Only truly mandatory fields
  • Required at state: Add requirements as workflow progresses
  • Required in view: For specific contexts only
xml
<field name="delivery_date" required="state == 'confirmed'"/>

Configuration Mistakes

Mistake 14: Testing in Production

Never Do This

"Let me just try this automation in production..."

Risk: Bad automation can corrupt data, send emails to customers, break workflows

Always

  1. Test in staging first
  2. Use Odoo.sh or create test database
  3. Verify with sample data
  4. Only then deploy to production

Mistake 15: Not Documenting Changes

Common Problem

Making Studio changes or configurations without documentation

Six months later: "Why is this field here? What does this automation do?"

Best Practices

Change TypeDocument Where
Field purposeField help text
Automation logicAutomation description field
View modificationsChangelog document
Custom reportsReport description
Studio changesExport and version control

Integration Mistakes

Mistake 16: Syncing Too Frequently

Over-Integration

Syncing data every minute when hourly is sufficient

Result: Performance degradation, API rate limits, wasted resources

Right Approach

Data TypeSync Frequency
Real-time critical (payments)Webhook/immediate
OrdersEvery 15-30 minutes
Product catalogDaily
Historical dataWeekly/monthly

Mistake 17: Not Handling Errors

Wrong Implementation

API integration that fails silently

Result: Missing orders, duplicate records, data corruption - discovered weeks later

Correct Implementation

  1. Log all API calls and responses
  2. Create error records for failures
  3. Set up alerts for repeated failures
  4. Implement retry logic with backoff

Quick Reference: Error Messages and Solutions

Error MessageLikely CauseSolution
"Field X does not exist"Typo in field name or module not installedCheck spelling, install module
"Access denied"User lacks permissionsCheck access rights and record rules
"Cannot group by X"Field not storedAdd store=True or use different field
"Constraint violation"Required field empty or unique violationFill required fields, check uniqueness
"Recursion error"Circular dependency in computed fieldsCheck @api.depends chains
"Record does not exist"Deleted or wrong IDCheck if record was deleted
"Singleton error"Operation on multiple records expected oneUse ensure_one() or loop
"Validation error"Python constraint failedCheck @api.constrains logic
"Integrity error"Database constraint violatedCheck foreign keys, unique constraints

Checklist: Before Going Live

Data Model Review

  • [ ] All relational fields have appropriate ondelete
  • [ ] Stored fields for list views and filters
  • [ ] No circular dependencies
  • [ ] Proper use of Many2many vs intermediate models

Security Review

  • [ ] Record rules for data isolation
  • [ ] Access rights per group
  • [ ] No excessive admin permissions
  • [ ] Tested with different user roles

Automation Review

  • [ ] No infinite loop potential
  • [ ] Trigger fields specified
  • [ ] Error handling configured
  • [ ] Tested in staging environment

Performance Review

  • [ ] List view fields are stored
  • [ ] Indexes on frequently searched fields
  • [ ] No complex record rules
  • [ ] Reasonable automation triggers

Knowledge Check

Q1: Why can't you group by partner_id.country_id directly?

Answer: It's not stored in the database

Related fields without store=True are computed on-the-fly. SQL GROUP BY requires an actual column. Create a stored related field instead.

Q2: What's wrong with Many2many for Product-Supplier?

Answer: Can't store per-supplier data like price

Many2many only links records. It can't store price, lead time, or other relationship-specific data. Use an intermediate model instead.

Q3: User can clear a domain filter and see all records. Security issue?

Answer: Yes - use Record Rules for security

Domains are for convenience. Record Rules enforce security that cannot be bypassed through UI, API, or any other means.

Q4: Automation triggers itself endlessly. How to fix?

Answer: Add Trigger Fields to limit when it fires

Specify which fields should trigger the automation. If updating field X, don't include X in trigger fields (or use a processing flag).

Q5: When should ondelete='cascade' be used?

Answer: Only for true parent-child relationships where children can't exist alone

Example: Order Lines should be deleted when Order is deleted. But Orders should NOT be deleted when Customer is deleted (use restrict).