Wizard một bước thì đơn giản: điền, bấm, xong. Nhưng nhiều tác vụ cần dẫn người dùng qua từng màn hình — chọn ở bước 1, xem lại và xác nhận ở bước 2. Odoo không có "wizard nhiều trang" dựng sẵn; thay vào đó có một mẫu rất gọn: một field state quyết định đang ở bước nào, view ẩn/hiện nhóm theo state, và một mẹo khiến method mở lại chính wizard để đi bước tiếp mà không mất dữ liệu. Bài này biến wizard cộng điểm thành hai bước.

state + mở lại chính mình

Thêm một field state (Selection các bước). Nút "Tiếp" gọi method đổi state rồi trả về một action act_window mở lại chính bản ghi wizard này (res_id=self.id):

Ảnh chụp đoạn mã nền tối, phần model khai field state Selection với chon và xac_nhan default chon; method _reopen trả về act_window với res_model là chính wizard, res_id self.id, view_mode form, target new; method action_next ghi state xac_nhan rồi trả _reopen với chú thích mở lại wizard cùng res_id giữ dữ liệu; phần view form có field state invisible, hai group ẩn hiện theo state chon và xac_nhan, footer ba nút action_next Tiếp, action_back Quay lại, action_cong_diem Xác nhận đều invisible theo state; kèm chú thích cùng một bản ghi mở lại nên dữ liệu vẫn còn, cách khác mỗi bước một TransientModel riêng

Hình 1: state mặc định 'chon'. action_next ghi state sang 'xac_nhan' rồi _reopen() — mở lại act_window với res_id=self.id. Vì là cùng một bản ghi, mọi dữ liệu đã nhập vẫn còn. View dùng <group invisible="state != '...'"> để hiện đúng nhóm của bước, và nút cũng invisible theo state.

Mấu chốt của mẹo: bước tiếp không tạo wizard mới, mà mở lại wizard cũ đã đổi state. Nhờ res_id=self.id, người dùng thấy đúng dữ liệu họ vừa nhập, chỉ khác là màn hình chuyển sang nhóm của bước mới.

Bước 1 — Chọn

Mở wizard, ta thấy bước 1: chọn thẻ và số điểm:

Ảnh chụp giao diện Odoo thật hộp thoại Cộng điểm hàng loạt ở Bước 1 Chọn thẻ và số điểm, có ô Số điểm cộng thêm giá trị 10, danh sách Thẻ áp dụng chứa VIP-0001 với 150 điểm, dưới cùng là hai nút Tiếp mũi tên phải và Huỷ

Hình 2: Bước 1 — nhóm "Chọn thẻ và số điểm" hiện ra vì state='chon'. Chỉ có nút "Tiếp →" và "Huỷ" (các nút của bước 2 đang bị invisible). Người dùng chọn thẻ, đặt số điểm, rồi bấm "Tiếp →".

Bước 2 — Xác nhận

Bấm "Tiếp →", state thành 'xac_nhan', wizard mở lại và hiện nhóm của bước 2 — một bản tóm tắt để duyệt trước khi chạy:

Ảnh chụp giao diện Odoo thật hộp thoại ở Bước 2 Xác nhận, một khối thông báo xanh ghi Sẽ cộng 10 điểm cho thẻ VIP-0001 Bạc Nguyễn Văn An, ô Lý do trống với gợi ý mờ, dưới cùng là ba nút Quay lại mũi tên trái, Xác nhận cộng điểm màu tím, và Huỷ

Hình 3: Bước 2 — nhóm "Xác nhận" hiện ra (state đổi). Khối tóm tắt cho biết sẽ cộng bao nhiêu điểm cho thẻ nào — chính là dữ liệu từ bước 1, còn nguyên nhờ mở lại cùng res_id. Nút giờ là "← Quay lại" (về bước 1) và "Xác nhận cộng điểm" (chạy thật). Bấm Quay lại là action_back ghi state về 'chon'.

Hai cách dựng wizard nhiều bước

  • Một TransientModel, nhiều state (cách ở đây): gọn, mọi bước dùng chung dữ liệu. Hợp cho luồng tuyến tính vài bước. Nút mở lại chính wizard bằng res_id=self.id.
  • Mỗi bước một TransientModel riêng: bước trước lưu id (hoặc truyền qua context), method trả act_window mở wizard của bước sau. Hợp khi các bước khác nhau nhiều về dữ liệu, hoặc muốn tách bạch.

Với đa số trường hợp, mẫu state một model là đủ và dễ đọc nhất.

Vài lưu ý

  • invisible="state != '...'" viết thẳng biểu thức (Odoo 19) — không bọc attrs.
  • Dữ liệu còn vì cùng bản ghi. Nếu lỡ tạo wizard mới ở mỗi bước, dữ liệu nhập trước sẽ mất — nhớ res_id=self.id.
  • Đặt state mặc định cho bước đầu, nếu không wizard mở ra trống trơn (không nhóm nào hiện).
  • Có thể thêm bước kiểm tra: ở action_next, validate dữ liệu trước khi cho sang bước sau; sai thì raise UserError.

Ba ý mang về

  1. Wizard nhiều bước = một field state + view ẩn/hiện nhóm theo state; mỗi bước một <group invisible="state != '...'">, nút cũng invisible theo state.
  2. Method đổi bước ghi state rồi trả act_window mở lại chính wizard (res_id=self.id) — dữ liệu người dùng nhập vẫn còn vì là cùng một bản ghi.
  3. Hai cách dựng: một TransientModel nhiều state (gọn, chung dữ liệu) hay mỗi bước một TransientModel (tách bạch); nhớ đặt state mặc định và validate ở bước chuyển.

Wizard đến giờ đều tự đóng hoặc mở lại chính nó. Nhưng method wizard trả được bất kỳ action nào — kể cả mở một màn hình khác. Bài sau: Phần sau về wizard trả về action tiếp theo — chạy xong rồi đưa người dùng thẳng tới danh sách kết quả, một báo cáo, hay một bản ghi vừa tạo.