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).

Ảnh chụp mã hai script migration nền tối trong thư mục migrations 19.0.1.0.1. Phần trên pre-migrate.py chạy trước schema cũ còn nguyên. def migrate cr version, if not version return cài mới bỏ qua, version là phiên bản cũ đang cài ví dụ 19.0.1.0.0, cứu dữ liệu trước khi cột bị đổi xoá, cr execute SELECT count từ quan_the_thanh_vien, logger info đang có bao nhiêu thẻ trước khi nâng cấp. Phần dưới post-migrate.py chạy sau schema mới đã sẵn sàng. def migrate cr version if not version return, điền cột mới chuyển đổi giá trị sau khi schema đã cập nhật, cr execute UPDATE quan_the_thanh_vien SET hang_the bac WHERE hang_the IS NULL, logger info đã chuẩn hoá hang_the bao nhiêu dòng. Chú thích openupgradelib cho migration phức tạp rename_columns map_values rename_models m2o_to_x2m

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ự:

Ảnh chụp terminal nền tối nâng cấp quan_ca_phe từ version 19.0.1.0.0 sang 19.0.1.0.1 thứ tự chạy thật. Lệnh odoo d blog19 u quan_ca_phe stop-after-init. Log INFO loading Loading module quan_ca_phe 94 trên 175. INFO pre-migrate màu xanh MIGRATE PRE từ version 19.0.1.0.0 schema CŨ còn nguyên. INFO pre-migrate MIGRATE PRE đang có 4 thẻ trước khi nâng cấp. INFO registry màu cam module quan_ca_phe creating or updating database tables chú thích Odoo đổi schema ở đây. INFO post-migrate màu xanh dương MIGRATE POST từ version 19.0.1.0.0 schema MỚI đã sẵn sàng. INFO post-migrate MIGRATE POST đã chuẩn hoá hang_the 0 dòng. Chú thích thứ tự PRE schema cũ rồi Odoo cập nhật bảng rồi POST schema mới rõ ràng

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ọi env[...] 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-migrate cho việc liên module. Khi cần dữ liệu của module khác đã ở trạng thái mới, đặt ở end chứ không post.

Ba ý mang về

  1. 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àm migrate(cr, version) với version là phiên bản cũ.
  2. Đã 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.
  3. 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 openupgradelib cho 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).