Sebuah aplikasi umumnya harus bisa CRUD (Create,Read,Update,Delete) begitu juga dengan odoo. Sejauh pengalaman saya sebagai programmer odoo selama ini, hak akses Update atau Edit kadang bisa menjadi masalah yang menjengkelkan, seiring dengan kompleksnya kebutuhan client. Kenapa ?
Berikut ini adalah pengalaman saya mengenai hak akses Update atau Edit dokumen pada odoo. Tolong baca sampai akhir, barangkali Anda bisa memberi beberapa ide atau saran mengenai masalah ini. Malah, kalau Anda jadi terinspirasi setelah membaca tulisan ini, itu lebih bagus. 🙂
Ada beberapa cara untuk menangani hak akses dokumen pada odoo, yang pertama adalah dengan membuat sebuah record dari model ir.model.access. Model ir.model.access digunakan untuk membatasi hak akses CRUD pada suatu model. Sebagai contoh, misal saya memiliki model seperti pada kode di bawah ini.
class ModelOne(models.Model): _name = 'model.one' name = fields.Char() field_one = fields.Integer('Field One') field_two = fields.Integer('Field Two')
Jika kita ingin mengatur hak akses CRUD pada model di atas, biasanya kita harus membuat sebuah file csv dengan nama ir.model.access.csv dengan isi seperti di bawah ini.
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
Atau lewat User Interface dengan masuk menu Settings >> Technical >> Database Structure >> Models. Cari model yang ingin kita atur hak akses CRUD-nya, kemudian tambah atau edit hak akses CRUD model tersebut di tab Access Right seperti pada gambar di bawah ini.

Jika kita ingin membatasi agar user tidak bisa melakukan aksi Update atau Edit dokumen, kita bisa mengubah value dari perm_write pada file csv di atas menjadi 0. Atau jika kita ingin mengubahnya lewat user interface, kita bisa melakukannya dengan cara menghilangkan cetangan pada field Write Access seperti pada gambar di bawah ini.

Hasilnya tombol Edit akan hilang dari semua form yang menampilkan data dari model model.one, seperti pada gambar di bawah ini.

Cara ini memiliki beberapa kelemahan, diantaranya adalah :
- Tidak bisa diterapkan pada model yang dipakai untuk beberapa keperluan. Sebagai contoh, di odoo 14 model account.move digunakan pada dokumen Customer Invoice dan Vendor Bill. Jika client ingin agar user A bisa melihat dokumen Customer Invoice dan Vendor Bill, tetapi masih bisa mengedit dokumen Customer Invoice dan tidak bisa mengedit dokumen Vendor Bill tentu saja cara ini tidak bisa digunakan. Karena tombol Edit akan hilang di kedua dokumen tersebut.
- Semua aksi yang mentrigger method write tidak bisa dilakukan, termasuk saat user mengklik tombol selain tombol edit.
Cara kedua yang bisa dicoba untuk membatasi hak akses CRUD adalah dengan memanfaatkan model ir.rule. Dengan ir.rule kita bisa membuat sebuah domain untuk menentukan hak akses ir.rule yang kita buat hanya diterapkan pada record tertentu saja. Dengan begitu kita bisa membuat rule agar user bisa melihat semua Customer Invoice dan Vendor Bill, tetapi tidak bisa mengubah data Vendor Bill.
Sebagai contoh kasus, pada model model.one di atas kita bisa membuat rule agar user A bisa membuat dan melihat semua dokumen dari model model.one dengan nilai apapun, tetapi tidak bisa mengedit dokumen jika Field One bernilai selain 1 dengan kode seperti di bawah ini.
<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>
Atau kita bisa membuatnya lewat User Interface dengan cara membuka tab Record Rules dari menu yang sama saat kita membuat ir.model.access seperti pada gambar di bawah ini.

Cara kerja ir.rule berbeda dengan ir.model.access. Jika di ir.model.access kita mencentang field Write Access maka kita bisa mengedit dokumen tidak peduli apapun kondisi dokumen tersebut. Tetapi jika kita mencentang field Apply for Write di ir.rule bisa jadi kita malah tidak bisa mengedit dokumen, tergantung domain yang kita tulis.
Pada gambar di atas saya telah mencentang field Apply for Write dengan domain [(‘field_one’, ‘=’, 1)], ini berarti user bisa mengubah dokumen dari model model.one jika nilai dari Field One bernilai 1.
Jika field Field One bernilai selain 1, misal 2 maka user tidak bisa mengubah dokumen tersebut. Odoo akan menampilkan pesan error seperti pada gambar di bawah ini.

Jadi kegunaan ir.rule adalah untuk meloloskan suatu aksi CRUD jika dokumen yang kita olah sesuai dengan kriteria dari domain yang telah ditentukan. Jika dokumen yang kita olah tidak sesuai dengan domain yang ditentukan maka akan muncul pesan error.
Dengan ir.rule kita bisa menyelesaikan masalah pertama dari ir.model.access di atas. Dengan ir.rule tombol Edit tetap tampil sehingga user tetap bisa mengubah dokumen, tetapi tidak akan bisa disimpan jika kondisi dari domain yang telah ditentukan tidak terpenuhi.
Tetapi ir.rule tidak menyelesaikan masalah kedua. Misal jika saya memiliki sebuah tombol di form dari model model.one yang memanggil method di bawah ini.
def action_change_field(self): for rec in self: rec.field_two = 5
Saat user mengklik tombol tersebut, method di atas akan men-trigger method write dari model model.one sehingga ir.rule yang telah dibuat sebelumnya akan dieksekusi, dan odoo akan menampilka pesan error jika nilai dari Field One selain dari 1.
Sehingga cara ini tidak bisa menyelesaikan kebutuhan client jika kebutuhan client adalah tidak bisa mengedit Vendor Bill dari user interface tetapi masih bisa memposting dan melakukan pembayaran pada Vendor Bill tersebut. Karena kedua aksi tersebut sudah dipastikan akan mentrigger method write.
Cara ketiga yang bisa dilakukan adalah dengan mengoverride method write dan memanfaatkan context. Seperti pada kode di bawah ini.
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
Dengan cara ini saat user mengedit dokumen pada model model.two kemudian mengklik tombol Save, odoo akan mentrigger method write, karena saat klik tombol Save tentu saja tidak ada context custom ‘ignore_write_access’, karena ini bukan context bawaan odoo, maka odoo akan melakukan pengecekan nila field Field One sebelum disimpan. Jika nilai field Field One bernilai 1 tentu saja bisa disimpan. Tetapi jika nilanya selain 1 makan akan muncul pesan error seperti pada gambar di bawah ini.

Kemudian pada setiap kode yang mentrigger method write, misal pada tombol yang memanggil method action_change_field di atas, kita bisa menambah context ‘ignore_write_access’ sehingga pengecekan nilai dari field Field One tidak dilakukan oleh odoo dan tidak muncul error.
Dengan cara ini kita bisa membatasi agar user tidak bisa melakukan edit dokumen pada Vendor Bill jika dia melakukan klik pada tombol Edit kemudian klik Save, tetapi masih bisa melakukan proses posting dan melakukan pembayaran jika kode yang menjalankan aksi posting dan pembayaran sudah ditambah context ‘ignore_write_access’. Perlu kita ingat, pemilihan nama context ini sangat penting, karena bisa jadi nama context yang kita gunakan sudah digunakan oleh module lain untuk fungsi yang berbeda. Saran saya tambah prefix dengan inisial atau nama website perusahaan anda agar unik.
Tetapi cara ini masih memiliki kelemahan. Pada model yang diakses oleh banyak module, seperti model account.move, stock.picking, atau res.partner dimana method write bisa jadi bakal ditrigger oleh banyak method dari module lain cara ini agak sedikit mustahil. Karena kita harus mengeluarkan tenaga extra untuk menambah context di setiap method itu.
OK. Sekarang adalah cara keempat yang mungkin bisa kita coba. Yaitu dengan menambah logic menggunakan javascript di tombol Edit. Ada 2 cara yang bisa kita coba, yaitu dengan menyembunyikan tombol Edit pada kondisi tertentu, tetapi saya tidak menyarankan cara ini, karena dari hasil percobaan saya, testingnya sedikit ribet. Saya lebih menganjurkan cara yang kedua, yaitu dengan memunculkan sebuah pesan kepada user jika dia menekan tombol Edit tetapi kondisi yang diperlukan tidak terpenuhi. Bagaimana caranya ?
Pertama, silakan lihat method yang dipanggil odoo saat user menekan tombol edit pada baris ke-538 di file form_controller.js ini. Kita akan mengoverride method tersebut untuk membatasi hak akses tombol Edit dengan kode seperti ini.
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"); } }, }); });
Jika kondisi tidak sesuai maka saat user mengklik tombol Edit akan muncul pesan error seperti pada gambar di bawah ini.

Sedangkan jika dokumen itu diubah dari method yang dipanggil saat menekan tombol maka tidak akan menyebabkan error, tanpa harus menambah context tertentu. Menyelesaikan masalah pada cara sebelumnya.
Tapi cara ini juga memiliki kelemahan. Misal jika client ingin agar user A tidak bisa edit Vendor Bill jika dokumen tersebut sudah diproses di dokumen lain atau di aplikasi lain, maka kita harus melakukan ajax request ke server dan harus mengatur promise. Ribet. Kelemahan kedua, jika dokumen yang client ingin beri hak akses serupa banyak, dan dengan kondisi yang cukup kompleks maka akan menambah ukuran file javascript. Yang bisa saja menambah waktu bagi browser untuk meload file tersebut. Terkahir, jika di tim anda kurang koordinasi, dimana ada programmer lain yang juga mengoverride method _onEdit dan dengan sembrono tanpa memanggil method super, hal ini bisa menyebabkan bencana.
Cara terakhir, yang baru saja saya sadari. Saya agak menyesal kenapa saya begitu bodohnya baru menyadari bisa menggunakan cara ini. 🙂
Pada tab Network di developer tools, perhatikan saat kita menekan tombol Save ketika telah mengedit dokumen, kira-kira akan terlihat seperti ini.

Sedangkan jika kita menekan sebuah tombol akan terlihat seperti ini.

Terlihat berbeda bukan ? Oleh karena itu kenapa tidak mengoverride controller yang dipanggil saat user menekan tombol Save ketika mengedit dokumen dan memberinya sebuah context, untuk menandai bahwa dokumen itu diedit dari user interface. Seperti pada kode di bawah ini.
# -*- 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)
Kemudian mari kita override method write untuk mengecek dokumen di edit dari user interface atau tidak dengan mengecek ada tidaknya context, seperti pada kode di bawah ini.
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)
Menurut saya ini adalah cara yang terbaik. Satu-satunya kelemahan dari cara ini adalah kurangnya koordinasi, sehingga ada kemungkinan anggota tim yang lain juga mengoverride method call_kw di atas. Saat ini saya masih mencoba untuk mengetes cara ini, untuk mencari bug, dan kekurangan-kekurangan lainnya.
Bagaimana dengan anda ? Punya cara yang lebih baik ? Tulis di komentar ya.
Download Source Code