Phần trước ta lướt qua migration script. Nhưng đây là một trong những phần dễ sai nhất khi nâng cấp module thật: nếu bạn đổi cấu trúc dữ liệu (xoá cột, đổi kiểu, tách bảng), làm sai thứ tự là mất dữ liệu vĩnh viễn. Odoo giải bài toán này bằng ba loại script chạy ở ba thời điểm khác nhau quanh lúc cập nhật schema. Bài này chạy thật một lần nâng cấp và đọc log để thấy chính xác thứ tự đó.
Ba thời điểm, ba loại script
Khi bạn tăng version của module trong __manifest__.py và chạy -u, Odoo tìm thư mục migrations/<version_mới>/ và chạy các script trong đó, tại ba thời điểm:
pre-migrate.py— chạy TRƯỚC khi Odoo cập nhật schema. Lúc này schema cũ còn nguyên. Dùng để cứu dữ liệu trước khi cột bị đổi/xoá (đọc và lưu tạm giá trị).post-migrate.py— chạy SAU khi schema mới đã được load. Dùng để điền cột mới, chuyển đổi giá trị, dọn dữ liệu theo cấu trúc mới.end-migrate.py— chạy CUỐI cùng, sau khi tất cả module (không chỉ module này) đã cập nhật xong. Dùng cho việc cần toàn bộ hệ thống ở trạng thái mới.
Mỗi script có hàm migrate(cr, version): cr là con trỏ CSDL, version là phiên bản CŨ đang cài (rỗng nếu cài mới — thường return ngay).

Hình 1: Hai script trong migrations/19.0.1.0.1/. pre-migrate đọc số thẻ trước khi schema đổi (cứu dữ liệu). post-migrate UPDATE để chuẩn hoá cột sau khi schema mới sẵn sàng. Cả hai kiểm if not version: return để bỏ qua khi cài mới. Migration phức tạp thì dùng openupgradelib — thư viện với rename_columns, map_values, rename_models, m2o_to_x2m...
Chạy thật: đọc thứ tự trong log
Mình thêm hai script trên vào quan_ca_phe, tăng version từ 19.0.1.0.0 lên 19.0.1.0.1, rồi -u. Log phơi bày đúng thứ tự:

Hình 2: Thật, đọc từ log. Thứ tự rõ như ban ngày: PRE chạy ngay sau "Loading module" (schema cũ, đọc được 4 thẻ) → "creating or updating database tables" (Odoo đổi schema tại đây) → POST (schema mới đã sẵn sàng, chạy UPDATE chuẩn hoá — 0 dòng vì không thẻ nào có hang_the NULL). version truyền vào là 19.0.1.0.0 — phiên bản cũ, đúng như tài liệu. Sau khi chụp, mình đã gỡ migration và trả version về cũ.
Vì sao thứ tự này quan trọng
Hãy tưởng tượng bạn muốn đổi tên cột diem thành diem_tich_luy. Nếu chỉ đổi trong model và -u, Odoo sẽ tạo cột mới diem_tich_luy rỗng và để nguyên cột diem cũ — dữ liệu điểm không tự chuyển sang, coi như mất. Cách đúng dùng pre-migrate:
# pre-migrate.py: đổi tên cột TRƯỚC khi Odoo tạo cột mới
def migrate(cr, version):
if not version:
return
# openupgradelib giữ nguyên dữ liệu khi đổi tên
from openupgradelib import openupgrade
openupgrade.rename_columns(cr, {'quan_the_thanh_vien': [('diem', 'diem_tich_luy')]})
Đặt ở pre-migrate (trước khi schema đổi), cột được đổi tên giữ nguyên dữ liệu thay vì tạo mới rỗng. Đặt nhầm ở post-migrate thì đã muộn — Odoo đã tạo cột mới rỗng rồi.
Vài lưu ý
- Chỉ dùng
cr.execute(SQL thô), không dùng ORM trong pre-migrate. Ởpre, model mới chưa được nạp; gọienv[...]có thể lỗi hoặc thấy schema cũ. SQL trực tiếp an toàn hơn. - Version phải tăng thì migration mới chạy. Odoo so version trong manifest với version đã ghi trong CSDL; không tăng thì thư mục
migrations/bị bỏ qua. - Test migration trên bản sao trước. Migration chạy một chiều; sai là phải phục hồi từ backup. Luôn thử trên bản copy production (đã neutralize) trước khi chạy thật.
end-migratecho việc liên module. Khi cần dữ liệu của module khác đã ở trạng thái mới, đặt ởendchứ khôngpost.
Ba ý mang về
- Ba loại script chạy ở ba thời điểm:
pre-migrate(trước khi đổi schema, cứu dữ liệu),post-migrate(sau khi schema mới sẵn sàng, điền/chuyển dữ liệu),end-migrate(cuối cùng, sau khi mọi module xong). Hàmmigrate(cr, version)vớiversionlà phiên bản cũ. - Đã thấy thứ tự thật trong log: PRE (schema cũ, 4 thẻ) → "creating or updating database tables" → POST (schema mới). Đặt sai chỗ là mất dữ liệu — ví dụ đổi tên cột phải ở
pre. - Dùng SQL thô trong pre-migrate (model mới chưa nạp), tăng version để migration chạy, test trên bản sao trước, và dùng
openupgradelibcho các thao tác đổi tên/chuyển đổi phức tạp.
Đây là bài áp chót. Ta đã đi từ ORM, controller, OWL, website, tới test, hiệu năng và triển khai. Phần sau — bài cuối cùng của sê-ri — khép lại bằng thứ biến một module "chạy được" thành một module chuyên nghiệp: đóng gói chuẩn OCA và kiểm chất lượng (pylint-odoo, cấu trúc thư mục, README, license).