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:

Adding access control security

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:

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> 

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 4Module 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.