Nhìn các dòng chi tiết đơn hàng, các bước trong quy trình, hay danh sách công việc trong Odoo — nhiều chỗ cho bạn giữ một cái tay nắm ở đầu dòng rồi kéo lên/xuống để sắp lại thứ tự. Trải nghiệm đó trông có vẻ cầu kỳ, nhưng cơ chế thì tối giản: một field số tên sequence, một dòng _order, và widget handle. Bài này gắn tay nắm kéo–thả đó vào danh sách "lịch sử điểm" của module demo, rồi kéo thật để xem thứ tự đổi.
Đúng hai thứ ở model
Widget handle không tự lưu thứ tự vào đâu cả — nó chỉ là cái tay nắm. Thứ tự thật nằm ở một field số nguyên mà quy ước đặt tên là sequence, và model phải sắp xếp theo nó qua _order:

Hình 1: Hai mảnh bắt buộc. Model có sequence (Integer) và _order = 'sequence, id' để danh sách luôn sắp theo số thứ tự. View đặt widget="handle" lên chính field sequence, để cột đầu tiên. Thiếu _order thì kéo xong thứ tự không được giữ — đây là lỗi hay gặp nhất.
Lưu ý quan trọng: widget="handle" gắn lên đúng field sequence, và nên để nó ở cột đầu để cái tay nắm nằm bên trái. Field này thường không cần cho người dùng thấy giá trị số — widget biến nó thành biểu tượng kéo.
Cái tay nắm trên danh sách thật
Sau khi nâng cấp module, mở tab "Lịch sử điểm" của thẻ VIP-0001. Mỗi dòng giờ có một biểu tượng kéo ở đầu:

Hình 2: Trước khi kéo. Biểu tượng tay nắm (các chấm dọc ⣿) hiện ở đầu mỗi dòng nhờ widget="handle". Thứ tự hiện tại: "Mua 5 ly cà phê" → "Đổi combo Tết" → "Giới thiệu bạn" (sequence 10, 20, 30). Giữ chuột lên tay nắm rồi kéo là đổi được vị trí.
Kéo, và thứ tự đổi
Bây giờ giữ tay nắm của dòng cuối ("Giới thiệu bạn") và kéo lên đầu bảng:

Hình 3: Sau khi kéo. "Giới thiệu bạn" đã nhảy lên đầu. Khi bạn thả, Odoo tính lại giá trị sequence cho các dòng bị ảnh hưởng và lưu — nên lần mở sau thứ tự này vẫn giữ nguyên (nhờ _order='sequence, id'). Không có một dòng code JavaScript nào phải viết.
Điều đáng chú ý: bạn không hề đụng tới con số sequence bằng tay. Kéo–thả là thao tác của người dùng, còn Odoo lo phần dịch nó thành các giá trị số và ghi xuống. Đó là toàn bộ "phép thuật" của widget handle.
Vài lưu ý thực chiến
_orderlà bắt buộc. Thiếu nó, danh sách sắp theoid— kéo xong nhìn thì đổi, nhưng mở lại là về thứ tự cũ. Rất dễ tưởng widget hỏng.defaultcho sequence. Đặtdefault=10(hoặc một số) để dòng tạo mới không rơi vàosequence=0chen lên đầu. Nhiều module dùng bước nhảy 10 để chèn giữa dễ.- Handle hợp cả list độc lập lẫn One2many trong form. Ở đây là One2many (dòng con của thẻ), nhưng một list cấp cao nhất (ví dụ các giai đoạn CRM) cũng dùng đúng cách này.
- Thứ tự là dữ liệu chung, không theo người dùng.
sequencelà một cột trong bảng — ai kéo cũng đổi cho mọi người. Nếu cần thứ tự riêng từng người thì phải mô hình khác, không phải handle.
Ba ý mang về
- Widget handle = field
sequence(Integer) +_order='sequence, id'+<field name="sequence" widget="handle"/>để cột đầu. Ba mảnh, không cần JavaScript. - Kéo–thả tay nắm, Odoo tự tính lại
sequencevà lưu;_orderđảm bảo thứ tự được giữ giữa các lần mở — thiếu nó là lỗi phổ biến nhất. - Handle dùng cho cả One2many trong form lẫn list độc lập;
defaultcho sequence giúp dòng mới không chen lên đầu, và nhớ rằng thứ tự là dữ liệu dùng chung cho mọi người.
Tay nắm lo phần sắp xếp. Bài sau ta gom một chùm widget nhỏ mà hữu dụng cho việc hiển thị trạng thái: Phần sau về badge, priority (sao) và boolean_toggle (công tắc) — ba widget khiến field trạng thái và cờ đúng/sai trực quan hơn nhiều.