1. 程式人生 > 實用技巧 >最好用的免費ERP系統Odoo 12開發手冊 | 第九篇 外部API 整合第三方系統

最好用的免費ERP系統Odoo 12開發手冊 | 第九篇 外部API 整合第三方系統

第九章 外部API-整合第三方系統

1. Odoo伺服器端帶有外部API,可供網頁客戶端和其他客戶端應用使用. (所以Odoo作為一個後臺框架的功能就體現了)
2. 本文中我們將學習如何在我們的客戶端程式中使用Odoo的外部API.
3. 為了避免引入大家所不熟悉的語言,此處我們將使用基於Python的客戶端,但這種RPC呼叫的處理方法也適用於其他程式語言.
4. 我們一起了解如何使用 Odoo RPC呼叫,然後根據所學知識,使用Python建立一個簡單的圖書命令列應用.

使用 XML-RPC 連線 Odoo API

1. 訪問服務的最簡單方法是使用XML-RPC,我們可以使用Python標準庫的xmlrpclib來實現.
2. 不要忘記我們是在編寫客戶端程式連線服務端,因此需執行Odoo服務端例項來供連線.
3. 本例中我們假設Odoo服務端例項在同一臺機器上執行,但你可以使用任意執行服務的其他機器,只要能連線其IP地址或伺服器名.
4. 讓我們來初次連線Oodo外部API. 開啟Ipython或者 jupyter lab輸入以下程式碼:

# 使用Python標準庫中得 xmlrpclib 來實現 XML-RPC
from xmlrpc import client
srv = "http://localhost:8269"
common = client.ServerProxy('%s/xmlrpc/2/common' % srv)
common.version()

執行結果:
{'server_serie': '12.0',
 'server_version': '12.0-20191120',
 'protocol_version': 1,
 'server_version_info': [12, 0, 0, 'final', 0, '']}
      (1) 這裡我們匯入了 xmlrpc.client庫,然後建立了一個包含服務地址和監聽埠資訊的變數.
      (2) 訪問服務端公共服務(無需登入),在終端 /xmlrpc/2/common 上暴露.
      (3) 其中一個可用的方法是 version(),用於檢視服務端版本. 我們使用它確認可與服務端進行通訊.

5. 另外一個公共方法是 authenticate().
      (1) 你可能以為它會建立會話,但實際上不會.
      (2) 該方法僅僅確認使用者名稱和密碼可被接受,請求不使用使用者名稱而是它返回的使用者ID.
      示例如下: 
# authenticate() 是另外一個公共方法
db = "MyERP12"
# 這裡是用賬戶的使用者名稱和密碼,不是資料庫的
user,pwd="admin","123456"
uid = common.authenticate(db,user,pwd,{})
print(uid)

執行結果:
2
      (1) 首先建立變數db,來儲存使用中的資料庫名.
      (2) 如果登入資訊錯誤,不會返回使用者ID,而是返回False值.(是使用者名稱賬號和密碼)
      (3) authenticate() 最後一個引數是使用者代理(User Agent)環境,用於提供客戶端一些元資料(metadata)
            它是必填的,但可以是一個空字典. 

使用 XML-RPC 執行伺服器端方法

1. 使用XML-RPC, 不會維護任何會話,每次請求都會發送驗證資訊.
      這讓協議過重,但是使用簡單.
2. 下一步我們設定訪問需登入才能訪問的服務端方法. 
      暴露的終端地址為 /xmlrpc/2/object,示例如下:

api = client.ServerProxy('%s/xmlrpc/2/object' % srv)
api.execute_kw(db, uid, pwd, 'res.partner', 'search_count', [[]])

執行結果: 
43

(1) 此處我們第一次訪問服務端API,執行了Partner記錄的計數. 
      通過 execute_kw() 來呼叫方法,接受如下引數: 
      excute_kw(連線的資料庫名,連線使用者ID,使用者密碼,目標模型識別符號名稱,呼叫的方法,位置引數列表,可選關鍵字引數)
(2) 上面的例子中對 res.partner 模型呼叫了 search_count 方法,進一個位置引數[],沒有關鍵字引數.
      該位置引數是一個搜尋域,因為我們傳入是一個空列表,它對所有 partner 進行計數.

3. 常用的操作有 搜尋search 和 讀取read.
      (1) 在使用RPC呼叫時,search()返回一個區塊的ID列表.
      (2) browse() 不可用於RPC,而應使用 read() 來得到記錄ID列表並獲取相應資料. 
      示例如下:
domain = [('login','=','demo')]
# 這裡搜尋的是登入使用者的ID  搜尋的是 res.users 這個表
api.execute_kw(db, uid, pwd, 'res.users', 'search', [domain])
執行結果: 
[6]

# 這裡使用 read 方法讀取指定欄位
api.execute_kw(db, uid, pwd, 'res.users', 'read', [[6]],{'fields':["login","state","email","lang"]})
執行結果: 
[{'id': 6,
  'lang': 'zh_CN',
  'login': 'demo',
  'email': '[email protected]',
  'state': 'new'}]
      (1) 這裡查詢了demo使用者,並且通過第二行的程式碼,返回使用者的位置資訊為[6].
      (2) 然後第4行使用 read 方法,將位置引數傳入,並且在最後使用欄位傳遞我們需要的欄位資訊.

4. 我們是通過兩個步驟才檢視到了demo使用者的相關欄位的資訊,需要 search 和 read 方法相配合.
      (1) search 和 read 相配合這樣的需求非常多
      (2) Odoo提供了將這個兩個方法融合到一起的方法: search_read(), 示例程式碼: 
# 上面需要 search() 和 read() 兩個方法相互配合
# search_read() 將這兩個方法合二為一
api.execute_kw(db, uid, pwd, 'res.users', 'search_read', [domain],{'fields':["login","state","email","lang"]})
      (1) search_read 和 read 相似,但需要 domain 代替 id列表 來作為第一個位置引數.
      (2) 需要說明在 read 和 search_read 中 fields 引數並非必須的.
            1) 如果不傳入,則獲取所有欄位,這可能會帶來對函式欄位的大量計算,並獲得大量可能用不到的資料.
            2) 通常建議明確傳入欄位列表       

搜尋和讀取API方法

1. 一些模型方法可用於更具體的操作,如: 
      (1) read([field]) 類似於 browse方法,但返回的不是記錄集,而是包含按引數指定的欄位的各行資料列表.
            每一行都是一個字典,他提供可供RPC協議使用的資料的序列化展示,設計用於客戶端程式中而非服務端邏輯中.      
      (2) search_read 在讀取結果記錄列表之後執行搜尋操作.
            設計用於RPC客戶端,避免了反覆進行讀取結果和搜尋的操作.
2. 所有其他模型方法都對RPC暴露,但是以下劃線開頭的除外(_是protected,__是private)
      也就是說我們可以像下面這樣使用 create,write,和unlike 修改服務端資料:
# 新建立一個記錄
x = api.execute_kw(db,uid,pwd,'res.partner','create',[{"name":'Packt Pub'}])
print(x)
執行結果:
63

# 更改ID為63的名字
api.execute_kw(db,uid,pwd,'res.partner','write',[[x],{'name':'Packt Publishing'}])
執行結果: 
True

# 讀取相關記錄
api.execute_kw(db,uid,pwd,'res.partner','read',[[x],['id','name']])
執行結果: 
[{'id': 63, 'name': 'Packt Publishing'}]

3. XML-RPC的一個缺陷是它不支援None值.
      (1) 有一個XML-RPC擴充套件可以支援None值,但這取決於我們客戶端說依賴的具體XML-RPC庫.
      (2) 不放回任何值的方法不能在XML-RPC中使用,因為預設返回的是None.
            這也是為什麼方法在結束時至少帶有一個return True 語句.
      (3) 另外一個方案是使用Odoo同時支援的JSON-RPC.

4. 應反覆強調Odoo的外部API可在大部分程式語言中使用.(這裡使用Python是為了方便)
      (1) 官方文件中我們可以看到Ruby,PHP和Java實際示例.
      (2) 常見的示例是先使用Django或Flask連線到Odoo,然後外部前端在再來連線Django和Flask

圖書客戶端XML-RPC介面(封裝外部操作)

1. 下面就來實現圖客戶端應用. 
      (1) 我們將使用兩個檔案: 一個處理服務端的介面: library_api.py
      (2) 另一個處理應用的使用者介面: library.py 
      (3) 然後我們會使用現有的OdooRPC庫來提供一個替代的實現方法.
2. 我們將建立類來配置與Odoo服務端的連線,以及 讀取/寫入圖書資料. 這將暴露基本的增刪改查方法:
      (1) search_read() 獲取圖書資料
      (2) create() 建立圖書
      (3) write() 更新圖書
      (4) unlink() 刪除圖書
3. 選擇一個目錄來放置應用檔案並建立 library_api.py 檔案. 首先新增類的構造方法,程式碼如下: 
from xmlrpc import client

class LibraryAPI():
	def __init__(self, srv, port, db, user, pwd):
		common = client.ServerProxy(
			'http://%s:%d/xmlrpc/2/common' % (srv, port))
		self.api = client.ServerProxy(
			'http://%s:%d/xmlrpc/2/object' % (srv, port))
		self.uid = common.authenticate(db, user, pwd, {})
		self.pwd = pwd
		self.db = db
		self.model = 'library.book'

	def execute(self, method, arg_list, kwarg_dict=None):
		return self.api.execute_kw(
			self.db, self.uid, self.pwd, self.model,
			method, arg_list, kwarg_dict or {})

	def search_read(self, text=None):
		domain = [('name', 'ilike', text)] if text else []
		fields = ['id', 'name']
		return self.execute('search_read', [domain, fields])

	def create(self, title):
		vals = {'name': title}
		return self.execute('create', [vals])

	def write(self, title, id):
		vals = {'name': title}
		return self.execute('write', [[id], vals])

	def unlink(self, id):
		return self.execute('unlink', [[id]])

測試程式碼: 
# 執行一段測試程式碼
srv, db, port = 'localhost', 'MyERP12', 8269
user, pwd = 'admin', '123456'
api = LibraryAPI(srv, port, db, user, pwd)
from pprint import pprint
pprint(api.search_read())

使用OdooRPC庫

1. 另外一個可以考慮的客戶端是OdooRPC. 
      (1) 它是一個更流行的客戶端庫,使用JSON-RPC協議而不是XML-RPC. 
      (2) 事實上Odoo官方客戶端使用的就是JSON-RPC,XML-RPC更多是用於支援先後相容性.
            (舊時很多程式都是使用XML,現在基本都是使用JSON)
2. OdooRPC庫可以通過PyPI安裝: 
      pip install odoorpc
3. 不管是使用JSON-RPC還是XML-RPC,Odoo API的使用方法並沒有什麼分別. 
      我們可以看到一些細節可能有區別,但是客戶端的使用方式並沒有什麼分別.

4. 
(1) OdooRPC庫建立新的odoorpc.ODOO物件時建立服務端連線,然後應使用ODOO.login()來建立使用者會話.
      注意: XML-RPC是不會建立會話的,但是這個 OdooRPC是會建立會話的
(2) 和服務端相似,會話有一個帶有會話環境的env屬性,包含 ID-uid 和 上下文.

5. 我們使用OdooRPC來提供實現對 libray_app 的介面,建立 library_odoorpc.py,新增如下程式碼: 
from odoorpc import ODOO

class LibraryAPI():
	def __init__(self, srv, port, db, user, pwd):
		self.api = ODOO(srv, port=port)
		self.api.login(db, user, pwd)
		self.uid = self.api.env.uid
		self.model = 'library.book'
		self.Model = self.api.env[self.model]

	def execute(self, method, arg_list, kwarg_dict=None):
		return self.api.execute(
			self.model,
			method, *arg_list, **kwarg_dict)
      (1) OdooRPC庫實現了Model 和 Recordset 物件來模擬服務端對應的功能.
            目標是實現客戶端程式設計與服務端程式設計基本一致.
      (2) 客戶端使用的方法將通過儲存在 self.Model 屬性中的圖書模型來利用這點.
      (3) 這裡實現的execute() 方法並會在我們客戶端中使用,僅用於本文中討論的其他實現進行對比.

4. 下面我們來實現 search_read(),create(),write()和unlink()這些客戶端方法.
	def search_read(self, text=None):
		domain = [('name', 'ilike', text)] if text else []
		fields = ['id', 'name']
		return self.Model.search_read(domain, fields)

	def create(self, title):
		vals = {'name': title}
		return self.Model.create(vals)

	def write(self, title, id):
		vals = {'name': title}
		self.Model.write(id, vals)

	def unlink(self, id):
		return self.Model.unlink(id)
      (1) 注意這段程式碼和Odoo服務端程式碼相似,因為它使用了與Odoo中外掛寫法相近的API.
      (2) 再次執行測試程式碼,執行效果應該和之前的一致. 

瞭解ERPpeek客戶端

1. ERPpeek是一個多功能工具,即可以作為互動式命令列介面(CLI)也可以作為Python庫,它提供了xmlrpc庫更便捷的API.
2. 通過pip進行安裝: pip install erppeek
...

總結

1. 本文的目標是學習外部API如何運作以及它們能做些什麼. 
      (1) 一開始我們通過一個簡單的Python XML-RPC客戶端來進行探討.
      (2) 但外部API可用於其他語言,事實上官方文件中包含了Java,PHP和Ruby的程式碼示例.
2. 有很多庫可處理 XML-RPC或JSON-RPC,有些是通用的,有些僅適用於Odoo.我們使用了一個指定庫OdooRPC.
3. 以上給我們就總結了本文有關程式設計API和業務邏輯的學習.