Enable Dark Mode!
how-to-migrate-irmodelaccesscsv-and-record-rules-to-iraccesscsv-in-odoo-20.jpg
By: Anaswara S Sunil

How to Migrate ir.model.access.csv and Record Rules to ir.access.csv in Odoo 20

Technical Odoo 20 Odoo Community Odoo Enterprises

In Odoo 20, the entire security architecture is consolidated into a single model; ir.access consolidates both ir.model.access (Access Rights) and ir.rule (Record Rules). Instead of using multiple CSV files and XML record rules for security, it is now consolidated into one file, namely, security/ir.access.csv.

Importance of the Change Beyond Its Appearance

This is one of the most disruptive architectural changes for Odoo 20, which is surprising, in a way, because this isn't a change in the Python API but a data loading change. Two existing security models, namely, ir.model.access (Access Rights) and ir.rule (Record Rules) have been consolidated into one single model: ir.access. Either model no longer exists as such, not even in alias form - any module that provides a security/ir.model.access.csv file or an XML record with model="ir.rule" will fail during installation regardless of whether the module uses the corresponding model in its code or not.

Field Mapping Changes

Odoo 19 & OlderOdoo 20 (ir.access)Purpose / Format
ir.model.accessir.accessAccess control system integrated.
ir.ruleir.accessRecord-level access rules moved into the same model.
model_id:id / model_idmodel_id (no suffix)Important! Strictly refers to the name of the model (e.g., project.task), NOT the XML ID of the model (e.g., model_project_task).
group_id:idgroup_id/idRefers to the security group of users (XML ID).
perm_read, perm_write, perm_create, perm_unlinkoperationString formed from c, r, u, d (crud or r, for example).
domain_force (on ir.rule)domainPython filter for domain, now optional on the same row.

One thing that needs special mention is the way the CSV header uses model_id with no suffix but group_id/id with a suffix. This may appear to be a mistake but is actually how the field expects its data. Furthermore, model_id now requires the actual name of the model (project.task), not the ir.model id (model_project_task).

Step-by-Step Migration Procedure

1. Rename and move your security file

Rename the security/ir.model.access.csv to security/ir.access.csv in the security/ directory.

2. Update the header

id,name,model_id,group_id/id,operation,domain

3. Migrate rows defining old access rights

The older version used four boolean columns (1/0) for CRUD permissions. In the new version you have a single column called 'operation', which consists of the letters c,r,u,d in any combination – meaning that you don’t have to break a row into several letters unless you want to.

Old ir.model.access.csv row using project.task as an example:

access_project_task,project.task,model_project_task,project.group_project_user,1,1,1,0

New ir.access.csv row (the same permissions – read, write, create, without delete – in a single row):

id,name,model_id,group_id/id,operation,domain
access_project_task,project.task,project.task,project.group_project_user,cru,

Do not specify anything in the domain if you are defining model-level permission without record filtering.

4. Migrate XML record rules

Delete your XML files containing <record model="ir.rule">. Replace them with the corresponding rows in ir.access.csv and place the rule’s domain filter in the domain field.

Old XML record rule:

<record id="project_task_personal_rule" model="ir.rule">
    <field name="name">Personal Tasks Only</field>
    <field name="model_id" ref="model_project_task"/>
    <field name="domain_force">[('user_id', '=', user.id)]</field>
    <field name="groups" eval="[(4, ref('project.group_project_user'))]"/>
    <field name="perm_read" eval="1"/>
</record>

New ir.access.csv row:

project_task_personal_rule,Personal Tasks Only,project.task,project.group_project_user,r,"[('user_id', '=', user.id)]"

One structural difference to watch for: ir.rule.groups was a many-to-many field, so one rule could target several groups at once. ir.access.group_id is a single many-to-one - a rule that used to list multiple groups now needs one row per group.

5. Treat Global Rule As Group-less Restriction Rows

In the new system, the global record rules will not have any special flag. Instead, if there is value assigned to group_id, then the meaning of the row differs:

  • The row with group_id will be the permission, as this provides access to that group.
  • The row without group_id will be the restriction, and this will apply to all the users just as ir.rule did before.
id,name,model_id,group_id/id,operation,domain
project_task_no_archived,Hide Archived Tasks,project.task,,r,"[('active', '=', True)]"

Not using group_id for the universal restriction is not an omission but actually the way to specify the universal restriction in the new system. While examining the migrated file, make sure to check every empty group_id/id intentionally since it's easy to miss this and unintentionally create the global restriction out of the group-based one.

6. Run the Automated Rewrite Script

There is a built-in script which creates ir.access.csv based on your module's ir.model.access.csv and ir.rules automatically:

./odoo-bin --addons-path=/path/to/your/addons upgrade_code --script 19.4-00-ir-access.py

This program uses deduction rather than substitution to figure out what your previous permissions and policies were, including any group inheritance you had set. Examine its log file output before trusting the result:

INFO - successfully processed, no additional work required.

WARNING - partially processed; check it over and probably fix it manually.

ERROR - not processed; requires manual translation.

Look carefully for warnings regarding group inheritance. If you previously had a group that was inheriting from a larger group's access policies, and your new row comes out without a group_id, an access policy meant for one group is silently translated into a global access policy allowing everybody in.

7. Update the Module Manifest

# Odoo 19 and earlier
'data': [
    'security/ir.model.access.csv',
    'security/ir_rule.xml',
     # ... views and data files
],
# Odoo 20
'data': [
    'security/ir.access.csv',
    # ... views and data files
],

Discard any XML files that contained ir.rule data solely – that information is now contained in the CSV file.

Summary Checklist

  • Rename security/ir.model.access.csv > security/ir.access.csv
  • Merge perm_read/perm_write/perm_create/perm_unlink into one operation field value
  • Use the model’s name directly (like “project.task”) rather than the XML ID (“model_project_task”) for the model_id field.
  • Convert all nodes to one row in CSV with the domain field value.
  • Break apart any rule that has more than one group into separate rows per group.
  • Execute upgrade_code --script 19.4-00-ir-access.py and read all WARNING/ERROR messages
  • Verify all rows that have blank group_id/id fields - ensure they are intended to be global.
  • Edit __manifest__.py and remove all empty record-rule XML files

To read more about How to Perform Mass Data Migration Using CSV, refer to our blog How to Perform Mass Data Migration Using CSV.


Frequently Asked Questions

Do I have to maintain both ir.model.access.csv and ir.rule XML files in Odoo 20?

Not really. All you need now is the new security/ir.access.csv file. The installation of any module containing either of the old file formats or an ir.rule record will fail in Odoo 20.

What if my record rule should apply to all the users instead of one specific group?

Just leave the field group_id/id empty on that line. An empty field group_id is no error – that's how you create a global restriction (previously an ir.rule without group).

Is my module going to be fully converted using the upgrade_code script?

It brings you close enough, but not quite there. Instead of literally converting your old permissions and record rules, it infers what they mean, so the result needs further examination – especially if there are any WARNING messages concerning the group inheritance, which will cause some rules not to have a group_id, effectively becoming a global rule.

If you need any assistance in odoo, we are online, please chat with us.



0
Comments



Leave a comment



WhatsApp