Ở XML-RPC và JSON-RPC, bên ngoài gọi vào Odoo — Odoo đóng vai server. Bài này lật chiều: Odoo là client, gọi RA một dịch vụ khác. Đây là việc hằng ngày của tích hợp thật — gọi cổng thanh toán, gửi SMS, tra cước giao vận, đồng bộ tỷ giá. Công cụ chỉ là thư viện Python quen thuộc: requests (Odoo đóng gói sẵn). Nhưng gọi mạng bên trong một web app có những cái bẫy mà bỏ qua là hỏng cả hệ thống, không chỉ một request.
Mẫu chuẩn: gọi trong một method của model
Hình dung một nút "Đồng bộ tỷ giá" trên thẻ thành viên. Bấm nút → một method gọi ra API tỷ giá rồi cập nhật. Đây là khung chuẩn:

Hình 1: Method gọi ra API ngoài. Bốn điểm cốt: (1) timeout=10 — không bao giờ bỏ; (2) raise_for_status() để 4xx/5xx thành exception thay vì âm thầm nhận rác; (3) r.json() đọc thân JSON; (4) bắt Timeout và RequestException riêng, ghi log, rồi raise UserError để người dùng thấy thông báo tử tế thay vì trang lỗi 500.
Vì sao timeout là bắt buộc, không phải tùy chọn
Đây là điều quan trọng nhất cả bài. requests mặc định không có timeout — nghĩa là chờ vô hạn. Odoo chạy trên một số hữu hạn worker; mỗi request người dùng chiếm một worker tới khi xong. Nếu method gọi ra một API đang treo mà không đặt timeout, worker đó đứng mãi mãi. Vài request như vậy là cạn sạch worker, và cả Odoo đứng hình — kể cả người dùng chẳng liên quan gì tới tính năng đó.
Đặt timeout=10 biến "treo vô hạn" thành "chờ tối đa 10 giây rồi ném Timeout" — mà ta bắt được và xử lý gọn.
Chạy thật requests từ trong Odoo
Không nói suông. Mình mở odoo shell trên blog19 và gọi requests thật — cả gọi ra Internet lẫn gọi vòng lại chính Odoo:

Hình 2: Thật. (1) requests.get httpbin → 200, đọc được slideshow.title. (2) requests.post vòng lại /jsonrpc của chính Odoo → 200, đúng 4 thẻ (khép vòng với bài trước: giờ Odoo vừa là server vừa là client). (3) Gọi một endpoint cố tình trễ 5 giây với timeout=1 → requests ném ReadTimeout, ta bắt đúng nó. requests bản 2.31.0 — bản Odoo 19 đóng gói sẵn.
Bắt exception cho đúng lớp
requests có một cây exception; bắt sai lớp là bỏ lọt lỗi hoặc nuốt nhầm:
import requests
try:
r = requests.post(url, json=payload, headers={'Authorization': token}, timeout=10)
r.raise_for_status()
data = r.json()
except requests.exceptions.Timeout:
# riêng timeout — thường nên thử lại sau
...
except requests.exceptions.ConnectionError:
# không nối được (DNS, mạng, cổng đóng)
...
except requests.exceptions.RequestException as e:
# cha của MỌI lỗi requests (gồm cả HTTPError từ raise_for_status)
_logger.exception("Gọi API ngoài lỗi: %s", e)
RequestException là gốc của cả họ, nên đặt nó cuối cùng làm lưới hứng. Đừng bắt trần except Exception — nó nuốt cả lỗi lập trình của chính bạn.
Đừng chặn request người dùng
Gọi mạng là chậm và không chắc chắn. Vài nguyên tắc rút từ vận hành thật:
- Đừng gọi API ngoài trong một vòng lặp lớn đồng bộ. Đồng bộ 500 bản ghi, mỗi bản một lời gọi 300ms là 2,5 phút người dùng ngồi nhìn màn hình treo — và rủi ro timeout của chính Odoo. Việc hàng loạt nên đẩy vào cron (
ir.cron) hoặc hàng đợi. - Việc không cần tức thì thì làm nền. Gửi SMS xác nhận đơn không nên chặn nút "Xác nhận"; đánh dấu rồi để một cron quét gửi sau.
- Luôn ghi log.
_logger.exception(...)khi lỗi để còn lần ra khi tích hợp trục trặc — lỗi mạng thường không tái hiện được, log là bằng chứng duy nhất. - Đừng tin mù thân phản hồi. Bên kia có thể trả 200 với JSON thiếu khóa; đọc
data.get('rate')và kiểm trước khi dùng.
Hai chiều, một bức tranh
| Chiều vào (adv-186/187) | Chiều ra (bài này) | |
|---|---|---|
| Odoo đóng vai | Server | Client |
| Ai khởi xướng | Hệ thống bên ngoài | Chính Odoo |
| Công cụ | /xmlrpc/2, /jsonrpc |
requests |
| Ví dụ | ERP khác đọc đơn hàng | Odoo gọi cổng thanh toán |
Một tích hợp hoàn chỉnh thường dùng cả hai chiều: đối tác gọi vào Odoo để đẩy dữ liệu, và Odoo gọi ra để lấy trạng thái.
Ba ý mang về
- Odoo gọi ra dịch vụ ngoài bằng
requeststrong một method model/controller:r = requests.get/post(url, json=..., timeout=10),r.raise_for_status(),data = r.json(). timeoutlà bắt buộc.requestsmặc định chờ vô hạn; thiếu timeout là một API treo có thể cạn worker và làm đứng cả Odoo. BắtTimeout/RequestExceptionriêng vàraise UserErrorcho thông báo tử tế.- Đừng chặn luồng người dùng: việc hàng loạt hay không cần tức thì đẩy vào cron/hàng đợi, luôn
_logger.exceptionkhi lỗi, và đừng tin mù thân phản hồi.
Gọi ra thì phải xác thực với bên kia — thường là bằng token trong header, và không được để lộ. Phần sau lật lại chiều tích hợp còn thiếu: webhook — để một hệ thống khác chủ động gọi vào Odoo báo "có việc mới xảy ra", thay vì Odoo phải liên tục hỏi thăm.