A common need for an application is to be able to limit what records are available to each user on a specific model.
This is achieved using record rules. A record rule is a domain filter expression defined on a model that will then be added on every data query made by the affected users.
As an example, we will add a record rule on the Library books model so that the users in the employee group will only have access to books they created in the database.
This recipe assumes you have an instance ready, with my_module
available, as described in Chapter 3, Creating Odoo Modules.
Record rules are added using a data XML file. To do so, perform the following steps:
security/library_security.xml
file is referenced by manifest data
key:'data': [ 'security/library_security.xml', # ... ],
security/library_security.xml
data file, with a <data>
section creating the security group. After it, add this second <data>
section for the record rules:<data noupdate="1"> <record model="ir.rule" id="library_book_user_rule"> <field name="name">Library: see only own books</field> <field name="model_id" ref="model_library_book"/> <field name="groups" eval="[(4, ref('base.group_user'))]"/> <field name="domain_force"> [('create_uid', '=', user.id)]</field> </record> <record model="ir.rule" id="library_book_all_rule"> <field name="name">Library: see all books</field> <field name="model_id" ref="model_library_book"/> <field name="groups" eval="[(4, ref('base.group_system'))]"/> <field name="domain_force">[(1, '=', 1)]</field> </record> </data>
Upgrading the addon module will load the record rules into the Odoo instance.
To keep the recipe simple, we used the core employee and settings security groups. We could instead have used the Library user and manager groups as defined in the Add security access to models recipe. It's a good exercise to follow it and modify this module so that it uses those security groups instead.
Record rules are just data records loaded into the ir.rule
core model. While the file adding them could be anywhere in the module, the convention is for it to be in the security
subdirectory. It is common to have a single XML file with both security groups and record rules.
Unlike groups, in the standard modules, the record rules are loaded in a data
section with the noupdate="1"
attribute. With this, those records will not be reloaded on a module upgrade, meaning that manual customizations on them are safe and will survive later upgrades.
To stay consistent with the official modules, we should also have our record rules inside a <data noupdate="1">
section.
Record rules can be seen from the GUI at the menu option Settings | Technical | Security | Record Rules, as shown in the following screenshot:
Following are the most important record rule fields used in the example:
name
) is a descriptive title for the rule.model_id
) is a reference to the model the rule applied to.groups
) are the security groups that the rule applies to. If none, the rule is considered global and is applied in a different way (more details follow later).domain
) is a domain expression with the record filter to apply.The first record rule we created was for the Employee security group. It uses the domain expression [('create_uid', '=', user.id)]
to select only those books where the creation user is the current user. Thus, they will only be able to see the books created by themselves.
The domain expressions used in the record rules run on the server side using ORM objects. Because of this, dot notation can be used on the fields on the left-hand side (the first tuple element). The right side (third tuple element) is a Python expression, evaluated in a context having available the user
record object, for the current user, and the time
Python library.
For the settings security group, we want it to be able to see all the books, independent of who created them in the database. Since it inherits the Employee group, unless we do something about it, it too will be able to see only the books created by itself.
The several (non global) record rules are joined together using the OR logical operator: each rule adds access and never removes the access. For the Library Manager to have access to all the books, we can add to it a record rule to add access to books created by other users, like this: [('create_uid', '!=', user.id)]
.
We chose to do differently and use the special rule [(1, '=', 1)]
to unconditionally give access to all the book records. While this may seem redundant, remember that otherwise the Library user rule can be customized in a way that would keep some books out of reach to the Settings user. It is special because the first element of a domain tuple must be a field name; this exact case is the only case where that is not true.
When a record rule is not assigned to any security group, it is marked as Global and is handled differently from the other rules.
Global record rules have a stronger effect than group level record rules, and set access restriction that those can't override. Technically, they are joined with an AND
operator. In the standard modules, they are used to implement multi-company security access, so that each user can see only his company's data.
In summary, regular non global record rules are joined together with an OR
operator: they are added together and a record is accessible if any of the rule grants that access. Global record rules then add restrictions to the access given by the regular record rules, using an AND
operator. Restrictions added by global record rules can't be overridden by regular record rules.