Odoo Edit Access Right and Its Problems

An application generally must be able to do the CRUD (Create, Read, Update, Delete) operations, as well as odoo. As far as my experience as an odoo programmer, Update or Edit access rights sometimes can be an annoying problem, along with the complexity of the client needs. Why ?

This is my experience regarding the Update or Edit access rights in odoo. Please read to the end, maybe you can give some ideas or suggestions on this matter. But, if you get inspired after reading this article, it’s better. 🙂

There are several ways to handle the document access rights in odoo, the first way that we can try is to create a record from the ir.model.access model. The ir.model.access model is used to restrict CRUD access rights to a model. For example, let’s assume I have a model like the code below.

class ModelOne(models.Model):
    _name = 'model.one'

    name = fields.Char()
    field_one = fields.Integer('Field One')
    field_two = fields.Integer('Field Two')

If we want to configure the CRUD access rights in the model above, usually we have to create a csv file with the ir.model.access.csv name, with the contents as shown below.

id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_model_one,model.one.user,tutorial_edit_access_right.model_model_one,tutorial_edit_access_right.group_tutorial_edit,1,1,1,1

Or through the User Interface by entering the Settings >> Technical >> Database Structure >> Models menu. Then find the model that we want to configure its CRUD access rights, then add or edit the CRUD access rights for the model in the Access Right tab as shown below.

Model access right configuration in odoo

If we want to limit the user to not being able to Update or Edit the document, we can change the value from the perm_write column in the csv file above to 0. Or if we want to change it through the user interface, we can do it by removing the checkbox in the Write Access field as shown below.

Remove the odoo write access right from a model

As a result, the Edit button will disappear from all forms/views from the model.one model, like in the image below.

an odoo view withoud edit button

This method has several weaknesses, including:

  1. Cannot be applied to models that are used for several purposes. For example, in odoo 14 the account.move model is used both in the Customer Invoice and the Vendor Bill documents. If the client wants that user A able to view the Customer Invoice and the Vendor Bill documents, but still able to edit the Customer Invoice documents and cannot edit the Vendor Bill documents, of course, this method cannot be used. Because the Edit button will disappear in both documents.
  2. All actions that trigger the write method cannot be executed, including when the user clicks a button other than the edit button, like posting the journal entries.

The second way that we can try to limit the CRUD access rights is to use the ir.rule model. With ir.rule model we can create a domain to determine the access rights to be applied to certain records. With this way, we can create a rule so that the user can see all Customer Invoice and Vendor Bill, but cannot change the Vendor Bill data.

As an example, in the model.one above we can create a rule so that user can create and view all documents from the model.one with any value, but cannot edit the document if Field One has value other than 1 with code like below.

<record id="model_one_can_create_and_read_all_value" model="ir.rule">
    <field name="name">Can Create and Read All Value</field>
    <field name="model_id" ref="tutorial_edit_access_right.model_model_one"/>
    <field name="domain_force">[(1, '=', 1)]</field>
    <field name="perm_create" eval="True"/>
    <field name="perm_read" eval="True"/>
    <field name="perm_write" eval="False"/>
    <field name="perm_unlink" eval="False"/>
    <field name="groups" eval="[(4, ref('tutorial_edit_access_right.group_tutorial_edit'))]"/>
</record>

<record id="model_one_can_edit_if_value_one" model="ir.rule">
    <field name="name">Can edit if field_one value == 1</field>
    <field name="model_id" ref="tutorial_edit_access_right.model_model_one"/>
    <field name="domain_force">[('field_one', '=', 1)]</field>
    <field name="perm_create" eval="False"/>
    <field name="perm_read" eval="False"/>
    <field name="perm_write" eval="True"/>
    <field name="perm_unlink" eval="False"/>
    <field name="groups" eval="[(4, ref('tutorial_edit_access_right.group_tutorial_edit'))]"/>
</record>

Or we can create it through the User Interface by opening the Record Rules tab from the same menu when we create the ir.model.access like in the image below.

Creata record rules in odoo

ir.rule works differently compared to the ir.model.access. If in the ir.model.access we check the Write Access field its make we can edit the document no matter what the document condition is. But if we check the Apply for Write field in ir.rule we may not able to edit the document, depending on the domain that we write.

In the image above I have checked the Apply for Write field with the [(‘field_one’, ‘=’, 1)] domain, this means that the user can edit the document from model.one if the value of Field One is 1.

If the Field One has value other than 1, for example 2 then the user cannot edit the document. Odoo will display an error message as shown below.

Odoo display an error message because ir.rule

So the functionality of the ir.rule is to pass a CRUD action if the document that we are processing matches the criteria of the predetermined domain. If the document that we are processing does not match the specified domain, an error message will appear.

With the ir.rule we can solve the first problem of the ir.model.access above. With the ir.rule the Edit button remains displayed so that the user can edit the document, but it will not be saved if the document data not met with the conditions of the specified domain.

But the ir.rule doesn’t solve the second problem. For example, if I have a button in the form of model.one that calls the method below.

def action_change_field(self):
    for rec in self:
        rec.field_two = 5

When the user clicks the button, the above method will trigger the write method of the model.one, so the ir.rule that was created earlier will be executed, and odoo will display an error message if the value of the Field One is other than 1.

So this method can not solve the client’s needs if the client’s need is that they cannot edit the Vendor Bill from the user interface but can posting and make payments to the Vendor Bill. Because both actions will trigger the write method.

The third way that can we try is to override the write method and take advantage of a context. Like in the code below.

class ModelTwo(models.Model):
    _name = 'model.two'

    name = fields.Char()
    field_one = fields.Integer('Field One')
    field_two = fields.Integer('Field Two')

    def write(self, vals):
        if 'ignore_write_access' not in self._context:
            if self.field_one != 1:
                raise exceptions.UserError('You can not edit this document')

        return super(ModelTwo,self).write(vals)

    def action_change_field(self):
        for rec in self:
            rec.with_context({'ignore_write_access': 1}).field_two = 5

In this way when the user edits the document on the model.two then clicks the Save button, odoo will trigger the write method, because when you click the Save button of course there is no ‘ignore_write_access’ custom context, because this is not the default odoo context. Odoo will check the value of the Field One before saving it. If the value of the Field One is 1 of course it can be saved. But if the value is other than 1 then an error message will appear like in the image below.

Odoo error message because write access right

Then in each code that triggers the write method, for example, on the button that calls the action_change_field method above, we can add the ‘ignore_write_access’ context, so odoo will not check the value of Field One, so the changes can be saved.

In this way we can limit the user not being able to edit the Vendor Bill document if he clicks on the Edit button then click Save. But he can post the Vendor Bill and make payments if the code that does the posting and payment actions has been added with the ‘ignore_write_access’ context. We need to remember, choosing the context name is very important because it can turn out that the context that we choose has already been used by other modules for different functions. My suggestion is to add a prefix with the initials of your company or your website name to make it unique.

But this method still has a weakness. On models accessed by multiple modules, such as the account.move, stock.picking, or res.partner where the write method might be triggered by many methods from other modules, this method is a bit impossible. Because we have to spend extra effort adding a context to each of those methods.

OK. Now it is the fourth way we might try. It is by adding a logic using javascript to the Edit button. There are 2 ways that we can try, the first one is by hiding the Edit button under certain conditions, but I don’t recommend this method, because, from the results of my experiments, the testing is a little complicated. I recommend the second way, which is to display a message to the user if he presses the Edit button but the necessary conditions are not met. How to do it ?

First, please take a look at the method that called by odoo when the user presses the edit button on line 538 in the form_controller.js file. We will override these methods to limit the access rights of the Edit button with code like this.

odoo.define('tutorial_edit_access_right.form_controller', function (require) {
"use strict";
    // import odoo original form_controller
    var FormController = require('web.FormController');

    // import odoo original dialog, to show the error message
    var Dialog = require('web.Dialog');

    // let's override it
    FormController.include({
    	_onEdit: function () {
	        // print the current model value
	        // so we can make conditions based on it
	        console.log(this);

	        // write the edit access right logic here
	        var is_can_edit = true;
	        if(this.modelName == 'model.three'){
	        	if(this.model.localData[this.handle].data.field_one != 1){
	        		is_can_edit = false
	        	}
	        }

	        if(is_can_edit){
	        	// if user can edit, call the super method
	        	this._super.apply(this, arguments);
	        }else{
	        	// if user can not edit, show a message
	        	Dialog.alert(this, "Sorry you can not edit this document");
	        }
	    },
    });

});

If the condition does not match, when the user clicks the Edit button an error message will appear like in the image below.

Odoo display an error message when click the edit button

Meanwhile, if the document is changed from the method called by a button when the user clicks it, it will not cause an error, without having to add a specific context. Resolved the problem in the previous way.

But this method also has drawbacks. For example, if the client wants that user A can not edit the Vendor Bill if the document has been processed in another document or another application, then we have to make an ajax request to the server and have to set up a promise. It is complicated. The second weakness is, if the client wants to grant similar access rights to other documents and the conditions are quite complex, it will increase the size of the javascript file. Which could increase the time for the browser to load the file. Finally, if your team lacks coordination, there are possibilities that other programmers also override the _onEdit method and carelessly without calling the super method, this can lead to disaster.

The last way, which I just realized. I regret why I was so stupid that I just realized that I could use this method. 🙂

On the Network tab in the developer tools, pay attention when we press the Save button while editing the document, it will look like this.

Odoo controller when editting a document

Meanwhile, if we press a button it will look like this.

Odoo controller when press a button

Looks different, right ? So why not override the controller that was called when the user pressed the Save button while editing the document and gave it a context, to indicate that the document was edited from the user interface. Like in the code below.

# -*- coding: utf-8 -*-

from odoo import http
# import odoo controller
from odoo.addons.web.controllers.main import DataSet

# let's override it
class DataSetInherit(DataSet):

    @http.route(['/web/dataset/call_kw', '/web/dataset/call_kw/<path:path>'], type='json', auth="user")
    def call_kw(self, model, method, args, kwargs, path=None):
        # add new unique context
        kwargs['context']['from_ui'] = 1
        # call the super method
        return super(DataSetInherit,self)._call_kw(model, method, args, kwargs)

Then let’s override the write method to check whether the document is edited from the user interface or not by checking the specific context is exist or not, like in the code below.

def write(self, vals):
    if 'from_ui' in self._context:
        if self.field_one != 1:
            raise exceptions.UserError('You can not edit this document')

    return super(ModelFour,self).write(vals)

I think this is the best way. The only drawback of this method is the lack of coordination, so it is possible that other team members also override the call_kw method above. Currently, I am still trying to test this method, to find bugs and other deficiencies.

How about you ? Got a better way ? Please write in the comments.

Download the Source Code
Related Article

Leave a Reply