API trong Odoo - XML-RPC

  Aug 23, 2020      2m
   

Tut 9: API trong Odoo - XML-RPC

API trong Odoo - XML-RPC

Trước khi xem bài viết này, bạn vui lòng hoàn thành hướng dẫn ở Tut 8: Thừa kế controller trong Odoo hoặc download mã nguồn ở cuối bài Tut 8.

Bài viết này sẽ hướng dẫn bạn:

  • Thông tin về XML-RPC do Odoo cung cấp
  • Gọi API của Odoo bằng XML-RPC

Odoo API: XML-RPC

XML-RPC là giao thức cho phép gọi thủ tục (procedure) từ xa đến backend - RPC (remote procedure call), thông qua XML.

Công tâm mà nói thì đây là giao thức đã "cũ kĩ"; Odoo đã hỗ trợ cung cấp web service này từ những phiên bản đầu tiên của Odoo. Đến bây giờ vẫn chưa có cải tiến gì mới mẻ, nhưng nó vẫn hoạt động tốt.

Tài liệu tham khảo chính thức của Odoo về XML-RPC: https://www.odoo.com/documentation/13.0/webservices/odoo.html

Gọi API của Odoo bằng XML-RPC

Sau khi biết sơ qua về XML-RPC, chúng ta hãy tiến hành thử sử dụng nó nhé.

Đầu tiên, bạn phải có một Odoo server đang chạy. Trên Odoo, Minh đã cài module my_pet như đã hướng dẫn ở cuối Tut 8. Bài thực hành này mình sẽ tiến hành viết một client nhỏ gọi Odoo server này để truy vấn thông tin thú cưng, cũng như thực hiện vài thao tác cơ bản như "insert", "update". Code ví dụ mình sẽ viết bằng Python 3 - xmlrpc là thư viện built-in.

Quy trình sử dụng XML-RPC:

  1. Xác thực với Odoo server. Bạn phải nắm đầy đủ các thông tin sau: URL web của Odoo, tên database, user name đăng nhập, mật khẩu của user đó.
import xmlrpc.client

url = 'https://<odoo-domain-name>/' # or 'http://localhost:8069/'
db = '<database name>'
username = '<user>'
password = '<password>'

common = xmlrpc.client.ServerProxy('{}/xmlrpc/2/common'.format(url))
uid = common.authenticate(db, username, password, {}) # <- use it later
models = xmlrpc.client.ServerProxy('{}/xmlrpc/2/object'.format(url)) # <- user it later
  1. Thực hiện RPC đến một phương thức trong model của Odoo. Nếu đã xác thực thành công người dùng ở bước 1, bạn có thể gọi phương thức trong model của Odoo.
model_name = 'my.pet'
method = 'search_read'
param1 = [[('id', '>', 1)]]
param2 = {}
models.execute_kw(db, uid, password, model_name, method, param1, param2)

Xem thêm các ví dụ RPC bạn tham khảo link tài liệu của Odoo nhé: https://www.odoo.com/documentation/13.0/webservices/odoo.html

Mình có gói XML-RPC thành một class cho dễ dùng, mấy ông có thể tham khảo và sử dụng:

xml_rpc.py

import xmlrpc.client

ODOO_BACKEND = 'http://localhost:8069'
ODOO_DB = 'my_pet'
ODOO_USER = 'admin'
ODOO_PASS = 'admin'

def myprint(data_list, title=''):
    if title:
        print(title)
    for line in data_list:
        print('-', line)
    pass

# https://www.odoo.com/documentation/13.0/webservices/odoo.html
class XMLRPC_API():
    def __init__(self, url, db, username='admin', password='admin'):
        self.url = url
        self.db = db
        self.username = username
        self.password = password
        common = xmlrpc.client.ServerProxy('{}/xmlrpc/2/common'.format(self.url))
        self.uid = common.authenticate(self.db, self.username, self.password, {})
        self.models = xmlrpc.client.ServerProxy('{}/xmlrpc/2/object'.format(self.url))
        pass

    # get fields names of the model
    def get_fields(self, model_name, required=False):
        data = self.models.execute_kw(self.db, 
            self.uid, 
            self.password, 
            model_name, 
            'fields_get',
            [], {'attributes': ['string', 'type', 'required', 'readonly']})
        
        if required:
            key_list = list(data.keys())
            for k in key_list:
                if not data[k].get('required', False):
                    data.pop(k)
                pass
        return data

    def search(self, model_name, conditions=[()]):
        return self.models.execute_kw(self.db, self.uid, self.password,
            model_name, 
            'search',
            [conditions])  

    # Create
    def create(self, model_name, data_dict):
        """
        Eg.
            model_name: 'res.users'
            data_dict: { 'name': "Minh", 'age': 27 }
        """
        id = self.models.execute_kw(self.db, self.uid, self.password, model_name, 'create', [data_dict])
        return id

    # Read
    def read(self, model_name, conditions=[()], params={}):
        """
        Eg.
            model_name: 'res.users'
            conditions: [('id', '>', 1)]
            params: {'fields': ['name', 'country_id', 'comment'], 'limit': 5}
        """
        return self.models.execute_kw(self.db, self.uid, self.password,
            model_name, 
            'search_read',
            [conditions],
            params)       
    
    # Update
    def update(self, model_name, id_list, new_data_dict):
        """
        Eg.
            model_name: 'res.users'
            id_list: [7]
            new_data_dict: { 'name': "Newer partner", 'age': 27 }
        """
        self.models.execute_kw(self.db, 
            self.uid, 
            self.password, 
            model_name, 
            'write', 
            [id_list, new_data_dict])

    # Delete
    def delete(self, model_name, id_list):
        self.models.execute_kw(self.db, self.uid, self.password, model_name, 'unlink', [id_list])
        pass

    # Soft delete
    def soft_delete(self, model_name, id_list):
        self.update(model_name, id_list, {
            'active': False,
        })

    # Aug 01, 2019
    def call(self, model_name, method, params=[]):
        return self.models.execute_kw(self.db, self.uid, self.password, model_name, method, params)

    def call2(self, model_name, method, param1, param2):
        return self.models.execute_kw(self.db, self.uid, self.password, model_name, method, param1, param2)

def main():
    client = XMLRPC_API(url=ODOO_BACKEND, db=ODOO_DB, username=ODOO_USER, password=ODOO_PASS)

    # list vendor accounts
    myprint(client.read(model_name='my.pet', 
        conditions=[('id', '>=', 1)], 
        params={ 'fields': ['name', 'nickname'], }), title='Read My Pet')
    
    myprint(client.call2(model_name="my.pet", method="search_read", param1=[[('id', '>=', 1)]], param2={}), title='General Call')

    client.create(model_name="my.pet", data_dict={"name": "Minh", "nickname": "Kyz"})
    print("Created new pet")

    client.update(model_name="my.pet", id_list=[1], new_data_dict={"name": "Kitte", "nickname": "Sugar Baby"})
    print("Update new pet")

    pass

if __name__ == '__main__':
    main()

Chạy bằng lệnh sau: python3 xml_rpc.py

Kết quả thực thi:

Read My Pet
- {'id': 1, 'name': 'Kitte', 'nickname': 'Sugar Baby'}
General Call
- {'product_ids': [], 'display_name': 'Kitte', 'description': False, 'write_date': '2020-08-23 08:14:12', 'basic_price': 0.0, 'write_uid': [2, 'Mitchell Admin'], 'weight': 0.0, 'pet_image': False, 'owner_id': [10, 'Deco Addict'], 'nickname': 'Sugar Baby', 'dob': False, 'create_date': '2020-08-19 14:38:04', 'age': 1, 'create_uid': [2, 'Mitchell Admin'], '__last_update': '2020-08-23 08:14:12', 'id': 1, 'name': 'Kitte', 'gender': 'male'}
Created new pet
Update new pet

Viết thành class rất tiện dụng, mấy ông có thể import vào để sử dụng. Minh đã viết các hàm CRUD cho tiện sử dụng:

  • Create: create()
  • Read: read()
  • Update: update()
  • Delete: delete()
  • Bonus: soft_delete() sẽ set field active bằng False, lúc này record vẫn nằm trong database nhưng nó ẩn khỏi các câu query => không hiển thị lên giao diện Odoo.

Sắp tới khi hoàn tất chỉnh chu, Minh sẽ bán một module cho phép expose JSON API một cách dễ dàng bằng cách thao tác trên giao diện Odoo. Khi đó mình sẽ thêm link module trên Odoo Apps vào bài viết này, để quý anh chị nào cần có thể mua về sử dụng cho dự án của mình. Hiện tại chưa có nhe, Minh chỉ nhá hàng trước thôi ^^; mong sẽ được mọi người ủng hộ.

Bài viết tiếp theo: Tut 10: Hiện thực wizard trong Odoo


minhng.info dự định sẽ mở khóa học training lập trình Odoo OFFLINE ở TP. HCM, nếu bạn nào có nhu cầu giúp mình tham gia khảo sát nhé.

https://forms.gle/K9t2kjx5SCQWftkm8

Cài đặt Odoo:

Danh sách bài viết series Odoo:

Tham gia ngay group trên Facebook để cùng thảo luận với đồng bọn nhé: