Paradigma pemrograman Model View Controller (MVC) juga ada di Odoo. Tapi seberapa sering kita membuat Controller ?
Pertama kali saya bekerja sebagai developer Odoo, saya sempat menganggap bahwa Odoo tidak menganut MVC. Di dokumentasi, mereka menyebutkan bahwa Odoo menganut MVC (lihat disini) tapi selama berkerja hampir setahun waktu itu saya tidak pernah membuat controller sama sekali. Cukup buat Model dan View dan aplikasi sudah bisa digunakan. Controller ? Entahlah sepertinya berjalan otomatis. Bagi saya yang sebelumnya sebagai programmer PHP (Yii Framework 2) hal ini sangat menyenangkan, karena berkurang satu pekerjaan, yaitu membuat Controller. GOOD JOB ODOO !!!!!
Tapi semakin lama bekerja semakin banyak proyek yang memaksa saya harus membuat controller, seperti e-commerce, POS, membuat widget-widget khusus seperti untuk menangani peta dll. Akhirnya saya tersadar bahwa Controller di Odoo adalah nyata. Berikut ini hal-hal yang saya ketahui mengenai Controller di Odoo.
Membuat Controller dari 0
Untuk membuat Controller kita harus membuat class yang meng-inherit class http.Controller. Class http.Controller ini berbeda dengan class untuk model seperti class models.Model atau models.TransientModel. Seperti tidak adanya property _name dan _inherit. Jadi beda caranya jika kita ingin membuat Controller baru atau meng-inherit Controller yang sudah ada. Berikut ini contoh kode sederhana Controller :
# -*- coding: utf-8 -*- import odoo from odoo import http, models, fields, _ from odoo.http import request import json class LatihanController(http.Controller): @http.route('/sale') def get_sale(self, **kwargs): value = { 'order_id': 'S0001', 'customer': 'Agus Budianto', 'total': 4000000 } return json.dumps(value)
Routing
Membuat routing pada Odoo dapat dilakukan dengan menggunakan decorator @http.route dengan argument berupa string atau array. Kemudian diikuti method dengan nama bebas. Nama method ini harus unik. Pada contoh diatas kita bisa mengakses routing tersebut lewat link http://localhost:8069/sale.
Dalam satu method kita bisa membuat beberapa routing seperti kode dibawah ini
@http.route([ '/sale2', #routing 1 '/sale2/<int:order_id>', #routing 2 '/sale2/<int:order_id>/<int:show_detail>' # routing 3 ]) def get_sale2(self,order_id=None,show_detail=None, **kwargs): """ Perhatikan kode <int:order_id>. Kode ini disebut placeholder Placeholder adalah bagian yang dinamis dari suatu routing User bisa memasukkan data apa saja asalkan sesuai rule Misal jika rule-nya adalah interger jika dimasukkan string maka akan error 404 Jika user mengakses http://localhost:8069/sale2 artinya routing 1 yang dipanggil Maka nilai order_id dan show_detail tidak terisi atau None Jika user mengakses http://localhost:8069/sale2/5 artinya routing 2 yang dipanggil Nilai 5 akan dimasukkan sebagai argumen order_id pada method get_sale2 Jadi nilai order_id akan jadi 5 dan show_detail tetap None Jika user mengakses http://localhost:8069/sale2/S0001 akan error 404 Karena tipe data yang dibutuhkan oleh placeholder order_id adalah interger bukan string Jika user mengakses http://localhost:8069/sale2/5/1 artinya routing 3 yang dipanggil Jadi nila argumen order_id = 5 dan show_detail = 1 """ # return value ini jika dipanggil http://localhost:8069/sale2 value = [ { 'order_id': 'S0001', 'customer': 'Agus Budianto', 'total': 4000000 }, { 'order_id': 'S0002', 'customer': 'Dani', 'total': 5000000 } ] if order_id: # return value ini jika dipanggil http://localhost:8069/sale2/5 value = { 'order_id': order_id, 'customer': 'Customer with order id' + str(order_id), 'total': 4000000 } if show_detail: # return value ini jika dipanggil http://localhost:8069/sale2/5/1 value['details'] = [ {'product': 'Indomie Goreng', 'qty': 2}, {'product': 'Kecap Bango', 'qty': 1} ] return json.dumps(value)
Ada beberapa tipe data yang bisa digunakan dalam placeholder, silakan lihat disini.
Tidak Ada self.env
Pada controller kita tidak bisa melakukan pengolahan data dari database dengan kode self.env karena controller tidak inherit ke model. Gunakan request.env tetapi kita harus import module request terlebih dahulu dengan kode
from odoo.http import request
Berikut ini contoh cara ambil data dari database lewat controller.
@http.route([ '/sale3', #routing 1 '/sale3/<int:order_id>', #routing 2 '/sale3/<int:order_id>/<int:show_detail>' # routing 3 ]) def get_sale3(self,order_id=None,show_detail=None, **kwargs): value = [] if not order_id and not show_detail: # jika order_id dan show_detail = None (panggil routing 1) # kita return semua SO dari database orders = request.env['sale.order'].search([]) for order in orders: value.append({ 'order_id' : order.name, 'customer': order.partner_id.name, 'total': order.amount_total }) else: # jika ada order_id (panggil routing 2) # kita tampilkan hanya satu SO sesuai id yang direquest order = request.env['sale.order'].search([('id','=',order_id)]) if order: value = { 'order_id' : order.name, 'customer': order.partner_id.name, 'total': order.amount_total } if show_detail: # jika ada show_detail (panggil routing 3) # kita tampilkan detail product pada SO details = [] for line in order.order_line: details.append({ 'product': line.product_id.name, 'qty': line.product_uom_qty }) value.update({'details': details}) return json.dumps(value)
Placeholder Model
Selain placeholder bawaan werkzeug seperti disini odoo juga punya placeholder bernama model. Dengan placeholder ini parameter yang kita masukkan akan otomatis diconvert jadi objek model, sehingga kita tidak perlu melakukan perintah self.env[‘nama.model’].search lagi. Berikut ini contoh penggunaanya.
# pada routing 2 dan 3 kita sertakan juga domain untuk model sale.order @http.route([ '''/sale4''', #routing 1 '''/sale4/<model("sale.order", "[('state', 'not in', ('cancel'))]"):order_id>''', #routing 2 '''/sale4/<model("sale.order", "[('state', 'not in', ('cancel'))]"):order_id>/<int:show_detail>''' # routing 3 ]) def get_sale4(self,order_id=None,show_detail=None, **kwargs): value = [] if not order_id and not show_detail: # jika order_id dan show_detail = None (panggil routing 1) # kita return semua SO dari database orders = request.env['sale.order'].search([]) for order in orders: value.append({ 'order_id' : order.name, 'customer': order.partner_id.name, 'total': order.amount_total }) else: # jika ada order_id (panggil routing 2) # kita tampilkan hanya satu SO sesuai id yang direquest # karena sudah menggunakan placeholder model, kita tidak perlu melakukan search # order_id sudah merupakan object mode yang merupakan representasi data dari database if order_id: value = { 'order_id' : order_id.name, 'customer': order_id.partner_id.name, 'total': order_id.amount_total } if show_detail: # jika ada show_detail (panggil routing 3) # kita tampilkan detail product pada SO details = [] for line in order_id.order_line: details.append({ 'product': line.product_id.name, 'qtys': line.product_uom_qty }) value.update({'details': details}) return json.dumps(value)
Tipe request
Ada 2 pilihan yaitu json atau http. Jika diisi json maka kita harus menyertakan header Content-Type : application/json dan tidak bisa diakses langsung dari browser. Jika http jangan sertakan Content-Type : application/json pada request header.
Authentication
Authentication digunakan untuk mengatur routing / controller yang kita buat bisa diakses oleh siapa saja. Ada 3 pilihan yaitu user, public dan none. Jika kita pilih user maka controller hanya bisa diakses oleh user yang sudah login.
Jika kita pilih public maka controller bisa diakses oleh siapa saja (public user), tapi jika controller anda mengolah data dari database / model pastikan public user punya hak akses ke model tersebut, jika tidak gunakan sudo. Pilihan user dan public terhubung dengan database. Jadi jika module dimana controller ini ditulis belum diinstall maka controller tidak bisa diakses.
Jika kita pilih none artinya controller selalu aktif dan bisa diakses oleh siapa saja, tapi tidak bisa mengolah data dari database atau model.
CSRF
Opsi ini bisa diisi True atau False. Jika diisi True maka controller tidak bisa diakses dari aplikasi lain misal dari android atau postman.
Berikut ini contoh penggunaan argument type, auth, dan csrf
@http.route([ '/sale5', #routing 1 '/sale5/<int:order_id>', #routing 2 '/sale5/<int:order_id>/<int:show_detail>' # routing 3 ], type="http", # tipe request bisa diisi http / json auth="public", # Authentication bisa diisi user, public atau none csrf=False, # True atau False ) def get_sale5(self,order_id=None,show_detail=None, **kwargs): value = [] if not order_id and not show_detail: # jika order_id dan show_detail = None (panggil routing 1) # kita return semua SO dari database # karena kita belum set hak akses untuk public user kita gunakan perintah sudo orders = request.env['sale.order'].sudo().search([]) for order in orders: value.append({ 'order_id' : order.name, 'customer': order.partner_id.name, 'total': order.amount_total }) else: # jika ada order_id (panggil routing 2) # kita tampilkan hanya satu SO sesuai id yang direquest order = request.env['sale.order'].sudo().search([('id','=',order_id)]) if order: value = { 'order_id' : order.name, 'customer': order.partner_id.name, 'total': order.amount_total } if show_detail: # jika ada show_detail (panggil routing 3) # kita tampilkan detail product pada SO details = [] for line in order.order_line: details.append({ 'product': line.product_id.name, 'qty': line.product_uom_qty }) value.update({'details': details}) return json.dumps(value)