DEV Community

Zahidul Islam
Zahidul Islam

Posted on

Odoo 19 Odoo 20 Migration Notes & Fixes

1. KeyError: 'ir.model.access'

If your module fails to load with a KeyError: 'ir.model.access', check your security folder. Odoo 20 has changed how access rights are structured and named.

  • What changed: The file security/ir.model.access.csv has been replaced by security/ir.access.csv, and the columns have been streamlined.
  • Old format:
id,name,model_id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
access_case_user,Case User,model_disciplinary_case,my_module.group_user,1,1,1,0

Enter fullscreen mode Exit fullscreen mode
  • New format:
id,name,model_id,group_id/id,operation,domain
access_case_user,Case User,disciplinary.case,my_module.group_user,cru,

Enter fullscreen mode Exit fullscreen mode

Permission Mapping Quick Reference:

  • r $\rightarrow$ read
  • ru $\rightarrow$ read + update
  • cru $\rightarrow$ create + read + update
  • crud $\rightarrow$ full access

2. KeyError: 'ir.rule'

If your security configuration breaks during registry loading, check your record rules alongside your access rights.

  • Old record rule (XML):
<record id="rule_employee_case" model="ir.rule">
    <field name="domain_force">
        [('employee_id.user_id', '=', user.id)]
    </field>
</record>

Enter fullscreen mode Exit fullscreen mode
  • New access domain format:
access_case_employee,Case Employee,disciplinary.case,my_module.group_employee,r,"[('employee_id.user_id', '=', user.id)]"

Enter fullscreen mode Exit fullscreen mode

3. employee_type became employee_type_id (And changed type!)

This one caught me off guard because it wasn't just a simple rename.

If you run:

self.env["hr.employee"]._fields["employee_type"]

Enter fullscreen mode Exit fullscreen mode

You will hit a KeyError: 'employee_type'. Running a shell check reveals the new field:

[f for f in env['hr.employee']._fields if 'type' in f.lower()]
# Result: employee_type_id

Enter fullscreen mode Exit fullscreen mode

The catch: It is no longer a Selection field; it's a Many2one relation.

  • Old code:
employee_type = fields.Selection(...)

Enter fullscreen mode Exit fullscreen mode
  • New code:
employee_type_id = fields.Many2one(
    "hr.employee.type",
    string="Employee Type",
)

Enter fullscreen mode Exit fullscreen mode
  • Don't forget your domains: Update your logic wherever the field is evaluated, e.g.:
if self.employee_type_id:
    domain.append(
        ("employee_type_id", "=", self.employee_type_id.id)
    )

Enter fullscreen mode Exit fullscreen mode

4. _sql_constraints warning

Odoo 20 deprecates the old dictionary-based SQL constraints in favor of explicit model constraints.

  • Old:
_sql_constraints = [
    (
        "unique_policy",
        "unique(name, company_id)",
        "Policy name must be unique per company!",
    )
]

Enter fullscreen mode Exit fullscreen mode
  • New:
_unique_policy = models.Constraint(
    "UNIQUE(name, company_id)",
    "Policy name must be unique per company!",
)

Enter fullscreen mode Exit fullscreen mode

Tip: Quickly find lingering old constraints across your custom codebase using:
grep -R "_sql_constraints" custom_addons20


5. report_file broke QWeb reports

When running PDF reports, you might run into this error:

ValueError: Invalid field 'report_file' in 'ir.actions.report'

  • Fix: Remove <field name="report_file">...</field> entirely from your report actions. Keep only report_name:
<field name="report_name">
    hr_gratuity.report_gratuity_settlement
</field>

Enter fullscreen mode Exit fullscreen mode

Test your PDF outputs thoroughly. For standard fields use <span t-field="doc.name"/>, and for computed expressions use <t t-out="doc.basic_percentage or 0"/>.


6. Check technical model names carefully

A single-character typo can completely block a module from installing. Always double-check your model definitions. For example:

  • Wrong: disciplinary.action.action_type $\rightarrow$ Correct: disciplinary.action.type
  • Wrong: disciplinary.employee_response $\rightarrow$ Correct: disciplinary.employee.response

Verify your technical names with:

grep -R "_name =" custom_addons20/your_module/models -n

Enter fullscreen mode Exit fullscreen mode

Quick Migration Checklist

Before pushing an upgraded Odoo 20 module to production, run through this mental or automated checklist:

  • [ ] Security files (ir.access.csv)
  • [ ] ir.rule usage & inline domains
  • [ ] Model technical names
  • [ ] Renamed fields (employee_type $\rightarrow$ employee_type_id)
  • [ ] Changed field types (Selection to Many2one)
  • [ ] _sql_constraints to models.Constraint
  • [ ] XML views & form layouts
  • [ ] Report actions (cleaned report_file)
  • [ ] QWeb PDF output rendering
  • [ ] Domains and onchange methods
  • [ ] User/group permissions

Handy search command to scan for outdated definitions:

grep -R "ir.model.access\|ir.rule\|_sql_constraints\|report_file" custom_addons20/your_module -n

Enter fullscreen mode Exit fullscreen mode

odoo #python #erp

Top comments (0)