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 
  • New format:
id,name,model_id,group_id/id,operation,domain access_case_user,Case User,disciplinary.case,my_module.group_user,cru, 

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> 
  • New access domain format:
access_case_employee,Case Employee,disciplinary.case,my_module.group_employee,r,"[('employee_id.user_id', '=', user.id)]" 

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"] 

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  

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

  • Old code:
employee_type = fields.Selection(...) 
  • New code:
employee_type_id = fields.Many2one( "hr.employee.type", string="Employee Type", ) 
  • 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) ) 

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!", ) ] 
  • New:
_unique_policy = models.Constraint( "UNIQUE(name, company_id)", "Policy name must be unique per company!", ) 

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> 

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 

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 

odoo #python #erp