Ở 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:

Ảnh chụp mã Python nền tối một method model Odoo action_dong_bo_ty_gia gọi API ngoài bằng requests. Import logging requests, from odoo import models fields, from odoo exceptions import UserError. Class TheThanhVien inherit quan the thanh vien. Method dựng url, khối try gọi requests get với params base USD và timeout 10, r raise_for_status ném HTTPError nếu 4xx 5xx, data bằng r json trả về dict. Khối except requests exceptions Timeout ghi log warning rồi raise UserError dịch vụ không phản hồi. Khối except requests exceptions RequestException as e ghi log exception rồi raise UserError không gọi được. Cuối cùng gán self diem bằng data rate nhân self diem. Chú thích nhấn timeout bắt buộc thiếu là worker treo

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:

Ảnh chụp terminal nền tối odoo shell blog19 chạy thật ba phần. Phần 1 requests get httpbin org json timeout 10 trả status 200 slideshow title Sample Slide Show slideshow author Yours Truly so slides 2. Phần 2 requests post tới chính jsonrpc của Odoo từ bên trong Odoo trả status 200 so the 4 gồm VIP-0001 bac 150 VIP-0003 vang 150 VIP-0002 bac 85 VIP-0004 bac 0. Phần 3 timeout bắt buộc gọi endpoint trễ 5s với timeout 1 kết quả bắt được Timeout đúng như mong đợi ReadTimeout. Cuối cùng requests version 2.31.0

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ề

  1. Odoo gọi ra dịch vụ ngoài bằng requests trong một method model/controller: r = requests.get/post(url, json=..., timeout=10), r.raise_for_status(), data = r.json().
  2. timeout là bắt buộc. requests mặ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ắt Timeout/RequestException riêng và raise UserError cho thông báo tử tế.
  3. Đừ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.exception khi 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.