Người dùng tick vài dòng trong danh sách, mở menu ⚙ Tác vụ, chọn một lệnh — và cả loạt bản ghi được xử lý cùng lúc. Xoá hàng loạt, đổi trạng thái cả lô, cộng điểm cho mọi thẻ đã chọn. Cơ chế đứng sau gọn hơn bạn tưởng: lựa chọn của người dùng được đóng gói thành active_ids, và code của bạn nhận về một recordset chứa toàn bộ — chỉ việc lặp.
Server action gắn vào danh sách
Để một lệnh xuất hiện trong menu Tác vụ, ta khai một ir.actions.server bind vào model, khai binding_view_types để nó hiện ở list (và/hoặc form):

Hình 1: Server action "Cộng 5 điểm tri ân". binding_model_id + binding_view_types="list,form" khiến nó hiện trong menu Tác vụ của model. Trong code (state="code"), biến records đã là recordset chứa mọi dòng người dùng tick — Odoo dựng nó từ active_ids trong context. Lưu ý cái bẫy đã gặp: sandbox của server action cấm gán thuộc tính (the.diem += 5), phải dùng the.write({'diem': the.diem + 5}).
Điểm cốt lõi: bạn không viết vòng lặp lấy từng id rồi browse tay. Odoo đã gói lựa chọn thành active_ids và đưa cho bạn sẵn records. Một for the in records là chạm tới từng bản ghi; và vì records là recordset, bạn cũng có thể thao tác cả lô một phát: records.write({...}).
Menu ⚙ Tác vụ khi chọn nhiều dòng
Tick vài dòng trong danh sách, thanh trên hiện "N đã chọn" và nút Tác vụ — mở ra thấy lệnh tuỳ biến nằm cạnh các lệnh dựng sẵn:

Hình 2: Chọn cả 4 thẻ ("Tất cả 4 đã chọn"), menu Tác vụ mở ra. Cạnh các lệnh có sẵn (Xuất, Nhân bản, Xoá) là "Cộng 5 điểm tri ân" — chính server action ta gắn. Bấm nó, Odoo truyền active_ids = 4 id đã chọn vào action. (Lệnh In ở nút riêng bên trái mới là nơi các report bám vào — hai menu tách nhau.)
Đo thật: một lần chạy, cả lô cùng đổi
Không đoán — chạy action với active_ids là 3 thẻ và đo điểm trước/sau:

Hình 3: Gọi sa.with_context(active_ids=[1,46,48]).run() — đúng cách menu ⚛ truyền lựa chọn. Cả ba thẻ cùng +5 (150→155, 85→90, 0→5), delta đồng loạt bằng 5 trong một lần chạy. Đây là điểm mạnh của xử lý theo recordset: một câu lệnh áp cho cả tập. (Sau khi đo tôi đã hoàn nguyên điểm về 150/85/0 để giữ dữ liệu demo sạch.)
active_ids và active_id — đừng lẫn
active_ids(số nhiều): list id của mọi bản ghi đã chọn — dùng cho thao tác hàng loạt từ list.active_id(số ít): id của bản ghi đang mở — có khi chạy từ form. Nếu code chỉ đọcactive_idmà người dùng chọn nhiều dòng, bạn xử lý nhầm chỉ một cái.active_model: tên model, hữu ích khi một server action dùng chung cho nhiều model.
Trong server action state="code", Odoo lo sẵn: records = mọi bản ghi (từ active_ids), record = bản ghi đầu. Còn nếu bạn viết một method trên model rồi cho action gọi, thì self chính là recordset nhiều bản ghi đó — for rec in self là lặp cả lô, không cần đụng tới context.
Ba ý mang về
- Chọn nhiều dòng + menu ⚙ Tác vụ = chạy action trên cả lô. Lựa chọn được gói thành
active_ids; server action nhận sẵnrecordslà recordset chứa tất cả — chỉ việcfor the in recordshoặc thao tác cả tập. - Gắn action vào list bằng
binding_model_id+binding_view_types="list"; sandboxstate="code"cấm gán thuộc tính, dùngwrite(). - Phân biệt
active_ids(nhiều, từ list) vớiactive_id(một, từ form) — đọc nhầm là xử lý thiếu bản ghi; method model thì dùng thẳngselflàm recordset.
Ta vừa gắn action vào menu Tác vụ của list. Nhưng còn gắn vào đâu nữa, và điều khiển nó hiện ở chỗ nào? Phần sau đào sâu binding — gắn action vào menu ngữ cảnh: binding_model_id, binding_view_types, và cả report lẫn server action cùng cơ chế.