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.csvhas been replaced bysecurity/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 onlyreport_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.ruleusage & inline domains - [ ] Model technical names
- [ ] Renamed fields (
employee_type$\rightarrow$employee_type_id) - [ ] Changed field types (Selection to Many2one)
- [ ]
_sql_constraintstomodels.Constraint - [ ] XML views & form layouts
- [ ] Report actions (cleaned
report_file) - [ ] QWeb PDF output rendering
- [ ] Domains and
onchangemethods - [ ] 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
Top comments (0)