Ở bài 194, các trường của model là dữ liệu đơn lẻ (chữ, số). Nhưng dữ liệu thật luôn liên kết: một thẻ thành viên thuộc về một khách, có nhiều lần tích điểm, và được áp nhiều ưu đãi. Odoo mô tả các mối nối đó bằng ba loại trường quan hệ. Nắm ba loại này là nắm phần "khó nhằn" nhưng quan trọng nhất khi lập trình Odoo.

Ba loại quan hệ

Mã nguồn Python định nghĩa ba loại trường quan hệ: partner_id là Many2one tới res.partner, lich_su_ids là One2many tới quan.lich.su.diem qua the_id, uu_dai_ids là Many2many tới quan.uu.dai; kèm model con LichSuDiem có the_id Many2one ondelete cascade và model UuDai

Hình 1: Ba trường quan hệ trên model thẻ. Many2one (partner_id): nhiều thẻ trỏ về một khách. One2many (lich_su_ids): một thẻ có nhiều dòng lịch sử điểm — nó là "mặt ngược" của Many2one the_id bên model con LichSuDiem. Many2many (uu_dai_ids): một thẻ có nhiều ưu đãi, và một ưu đãi cho nhiều thẻ.

Hiểu bản chất từng loại:

  • Many2one ("nhiều-về-một"): lưu một khoá ngoại trỏ tới một bản ghi bảng khác. Ví dụ: mỗi thẻ ghi nó thuộc khách nào. Đây là quan hệ nền tảng nhất.
  • One2many ("một-nhiều"): không tạo cột mới trong CSDL — nó chỉ là cách nhìn ngược của một Many2one. Thẻ "có nhiều lịch sử" chính vì mỗi dòng lịch sử có the_id trỏ ngược về thẻ. Cần khai the_id bên kia thì One2many mới hoạt động.
  • Many2many ("nhiều-nhiều"): hai bên đều có thể có nhiều bên kia. Odoo tự tạo một bảng trung gian để lưu các cặp — bạn không phải làm gì thêm.

ondelete='cascade' ở the_id: xoá thẻ thì xoá luôn lịch sử điểm của nó — tránh dữ liệu mồ côi.

Cài vào Odoo — quan hệ thành giao diện

Điều đẹp nhất: khai xong quan hệ, Odoo dựng widget giao diện tương ứng. Mở form thẻ:

Form thẻ thành viên VIP-0001 trong Odoo: Khách hàng Nguyễn Văn An; ô Ưu đãi áp dụng hiển thị ba thẻ tag Giảm 10% đồ uống, Freeship nội thành, Tặng bánh sinh nhật; tab Lịch sử điểm là bảng ba dòng ngày 16 thg 9 với điểm cộng 50/30/40 và ghi chú, có nút Thêm một dòng

Hình 2: Cả ba quan hệ trên một form thật. Khách hàng (Many2one) là ô chọn một đối tác. Ưu đãi áp dụng (Many2many, widget many2many_tags) hiển thị nhiều thẻ tag — thêm/bớt thoải mái. Tab Lịch sử điểm (One2many) là một bảng con ngay trong form: 3 dòng tích điểm, nút "Thêm một dòng" để nhập trực tiếp. Dữ liệu con nằm gọn trong bản ghi cha.

Từ vài dòng khai báo, ta có ô chọn, tag, và bảng con nhập liệu — không viết một dòng giao diện phức tạp nào.

Chọn loại nào cho tình huống nào?

  • Mỗi bản ghi thuộc về đúng một cái khác → Many2one (đơn hàng thuộc khách, dòng thuộc đơn).
  • Một bản ghi sở hữu một danh sách con → One2many (đơn có nhiều dòng hàng) — nhớ có Many2one ngược bên model con.
  • Hai bên nhiều-nhiều, không bên nào "sở hữu" → Many2many (sản phẩm có nhiều thẻ, khoá học có nhiều học viên).

Hầu hết model Odoo (kể cả Bán hàng, Kho) chỉ là các model nối nhau bằng đúng ba loại này.

Lưu ý khi dùng trường quan hệ

  • One2many cần Many2one đối ứng — khai One2many('model.con', 'ten_field_nguoc'); ten_field_nguoc phải tồn tại bên model con.
  • ondelete trên Many2one quyết định số phận khi bản ghi cha bị xoá: cascade (xoá theo), set null (bỏ trống), restrict (chặn xoá).
  • Many2many tự tạo bảng trung gian — không cần khai bảng đó; muốn đặt tên riêng thì thêm tham số.
  • Đặt tên field theo quy ước: Many2one kết thúc _id, One2many/Many2many kết thúc _ids — đọc code là biết loại.

Nhớ ba ý

  1. Many2one (_id) lưu khoá ngoại "thuộc về một"; One2many (_ids) là mặt ngược của một Many2one ("có nhiều", không tạo cột mới); Many2many (_ids) là "nhiều-nhiều" (Odoo tự tạo bảng trung gian).
  2. Khai quan hệ xong, Odoo dựng sẵn widget giao diện: ô chọn, tags, bảng con nhập liệu trong form.
  3. One2many cần Many2one đối ứng bên model con; dùng ondelete để xử lý dữ liệu con khi xoá cha.

Còn một kỹ thuật mạnh nữa để tái dùng và mở rộng thứ có sẵn thay vì viết lại. Phần sau nói về kế thừa model và view (inherit) — thêm trường/sửa giao diện của app khác mà không đụng mã gốc.