You might have noticed that, upon loading, our module is getting a warning message in the server log:
The model todo.task has no access rules, consider adding one.
The message is pretty clear: our new model has no access rules, so it can't be used by anyone other than the admin superuser. As a superuser, the admin
ignores data access rules, and that's why we were able to use the form without errors. But we must fix this before other users can use our model.
Another issue we have yet to address is that we want the to-do tasks to be private to each user. Odoo supports row-level access rules, which we will use to implement that.
In fact, our tests should be failing right now due to the missing access rules. They aren't because they are done with the admin user. So, we should change them so that they use the Demo user instead.
For this, we should edit the tests/test_todo.py
file to add a setUp
method:
# class TestTodo(TransactionCase): def setUp(self, *args, **kwargs): result = super(TestTodo, self).setUp(*args, \ **kwargs) user_demo = self.env.ref('base.user_demo') self.env= self.env(user=user_demo) return result
This first instruction calls the setUp
code of the parent class. The next ones change the environment used to run the tests, self.env
, to a new one using the Demo user. No further changes are needed to the tests we already wrote.
We should also add a test case to make sure that users can see only their own tasks. For this, first, add an additional import at the top:
from odoo.exceptions import AccessError
Next, add an additional method to the test class:
def test_record_rule(self): "Test per user record rules" Todo = self.env['todo.task'] task = Todo.sudo().create({'name': 'Admin Task'}) with self.assertRaises(AccessError): Todo.browse([task.id]).name
Since our env
method is now using the Demo user, we used the sudo()
method to change the context to the admin user. We then use it to create a task that should not be accessible to the Demo user.
When trying to access this task data, we expect an AccessError
exception to be raised.
If we run the tests now, they should fail, so let's take care of that.
To get a picture of what information is needed to add access rules to a model, use the web client and go to Settings | Technical | Security | Access Controls List:
This information has to be provided by the module using a data file to load the lines into the ir.model.access
model. We will add full access to the employee group on the model. Employee is the basic access group nearly everyone belongs to.
This is done using a CSV file named security/ir.model.access.csv
. Let's add it with the following content:
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink acess_todo_task_group_user,todo.task.user,model_todo_task,base.group_user,1,1,1,1
The filename corresponds to the model to load the data into, and the first line of the file has the column names. These are the columns provided in our CSV file:
id
is the record external identifier (also known as XML ID). It should be unique in our module.name
is a description title. It is only informative and it's best if it's kept unique. Official modules usually use a dot-separated string with the model name and the group. Following this convention, we used todo.task.user
.model_id
is the external identifier for the model we are giving access to. Models have XML IDs automatically generated by the ORM: for todo.task
, the identifier is model_todo_task
.group_id
identifies the security group to give permissions to. The most important ones are provided by the base
module. The Employee group is such a case and has the identifier base.group_user
.perm
fields flag the access to grant read
, write
, create
, or unlink
(delete) access.We must not forget to add the reference to this new file in the __manifest__.py
descriptor's data
attribute. It should look like this:
'data': [ 'security/ir.model.access.csv', 'views/todo_view.xml', 'views/todo_menu.xml', ],
As before, upgrade the module for these additions to take effect. The warning message should be gone, and we can confirm that the permissions are OK by logging in with the user demo
(password is also demo
). If we run our tests now they should only fail the test_record_rule
test case.
We can find the Record Rules option in the Technical menu, alongside Access Control List.
Record rules are defined in the ir.rule
model. As usual, we need to provide a distinctive name. We also need the model they operate on and the domain filter to use for the access restriction. The domain filter uses the usual list of tuples syntax used across Odoo.
Usually, rules apply to some particular security groups. In our case, we will make it apply to the Employees group. If it applies to no security group, in particular, it is considered global (the global
field is automatically set to True
). Global rules are different because they impose restrictions that non-global rules can't override.
To add the record rule, we should create a security/todo_access_rules.xml
file with the following content:
<?xml version="1.0" encoding="utf-8"?> <odoo> <data noupdate="1"> <record id="todo_task_user_rule" model="ir.rule"> <field name="name">ToDo Tasks only for owner</field> <field name="model_id" ref="model_todo_task"/> <field name="domain_force"> [('create_uid','=',user.id)] </field> <field name="groups" eval=" [(4,ref('base.group_user'))]"/> </record> </data> </odoo>
Notice the noupdate="1"
attribute. It means this data will not be updated in module upgrades. This will allow it to be customized later since module upgrades won't destroy user-made changes. But be aware that this will also be the case while developing, so you might want to set noupdate="0"
during development until you're happy with the data file.
In the groups
field, you will also find a special expression. It's a one-to-many relational field, and they have a special syntax to operate with. In this case, the (4, x)
tuple indicates to append x
to the records, and here x
is a reference to the Employees group, identified by base.group_user
. This one-to-many writing special syntax is discussed in more detail in Chapter 4, Module Data.
As before, we must add the file to __manifest__.py
before it can be loaded into the module:
'data': [ 'security/ir.model.access.csv', 'security/todo_access_rules.xml', 'todo_view.xml', 'todo_menu.xml', ],
If we did everything right, we can run the module tests and now they should pass.