Odoo · Migraciones · Upgrade
Añadir cosas a un módulo es fácil: Odoo crea tablas y columnas al actualizar. Quitar o cambiar lo que ya existe es otra historia. El -u limpia los metadatos, pero deja datos físicos atrás, respeta registros que tú querías borrar y, en ciertos casos, rellena columnas nuevas con valores que no significan nada. Estas son las tres situaciones que nos encontramos al actualizar un conector en producción, y cómo resolver cada una con un script de migración.
Caso 1
Quitar un modelo deja su tabla con datos
Cuando un modelo desaparece del código, el -u borra su ir.model, sus campos, vistas, menús y permisos. Pero la tabla de PostgreSQL sigue ahí, con todas sus filas, ocupando espacio y confundiendo a quien mire la base de datos. Hay que borrarla en un end-migrate, que se ejecuta cuando Odoo ya ha terminado de actualizar todos los módulos y limpiado los metadatos.
# migrations/19.0.1.11.0/end-migrate.py
def migrate(cr, version):
cr.execute("DROP TABLE IF EXISTS fleet_trip_legacy_log")
Caso 2
Los registros noupdate sobreviven a su XML
Un registro dentro de <data noupdate="1"> (crons, secuencias, funciones de cola, plantillas de email) se crea en la instalación y Odoo no lo vuelve a tocar. Eso incluye no borrarlo cuando lo quitas del XML. Si un cron apunta a un método que ya no existe, seguirá ejecutándose y fallando cada intervalo. Bórralo tú en la migración:
# migrations/19.0.1.11.0/post-migrate.py
from odoo import SUPERUSER_ID, api
def migrate(cr, version):
env = api.Environment(cr, SUPERUSER_ID, {})
cron = env.ref("fleet_trip.ir_cron_legacy_sync", raise_if_not_found=False)
if cron:
cron.unlink()
El raise_if_not_found=False hace la migración idempotente: si alguien ya lo borró a mano, o el script se ejecuta dos veces, no falla.
Caso 3
Un default nuevo reescribe el pasado
Al crear la columna de un campo almacenado nuevo, Odoo rellena las filas existentes con su default. Con default=fields.Datetime.now, todos los registros antiguos quedan marcados con la hora exacta del upgrade, y ya no puedes distinguir «procesado hoy» de «nunca procesado». La solución es crear tú la columna vacía en un pre-migrate: como ya existe cuando el ORM llega, no la rellena.
# migrations/19.0.1.11.0/pre-migrate.py
def migrate(cr, version):
cr.execute("""
ALTER TABLE fleet_trip
ADD COLUMN IF NOT EXISTS last_synced_at timestamp
""")
# migrations/19.0.1.11.0/post-migrate.py
def migrate(cr, version):
# Solo las filas sin valor: idempotente si se ejecuta dos veces
cr.execute("""
UPDATE fleet_trip t
SET last_synced_at = l.max_date
FROM (SELECT trip_id, max(create_date) AS max_date
FROM fleet_trip_sync_log GROUP BY trip_id) l
WHERE l.trip_id = t.id AND t.last_synced_at IS NULL
""")
El caso contrario también merece saberse: un campo calculado con store=True no necesita backfill, porque Odoo lo calcula para todas las filas al crear la columna. Escribir un post-migrate para eso es código muerto.
Probar
Ensaya el upgrade desde la versión anterior
Ninguno de estos casos aparece al instalar desde cero, que es lo que hacen los tests. Para verlos hay que instalar la versión anterior, sembrar datos y actualizar a la nueva. Con Git es rápido:
# 1. Extraer la versión publicada del módulo
mkdir -p /tmp/old && git archive v1.10.0 addons/fleet_trip | tar -x -C /tmp/old
# 2. Instalarla en una base de datos de prueba y sembrar datos
odoo-bin -c odoo.conf -d upg_test --addons-path=/tmp/old/addons,... -i fleet_trip --stop-after-init
odoo-bin shell -c odoo.conf -d upg_test --no-http < scripts/seed_trips.py
# 3. Actualizar con el código actual y revisar el log
odoo-bin -c odoo.conf -d upg_test -u fleet_trip --stop-after-init 2>&1 | grep -E "WARNING|ERROR"
| Cambio | Qué hace el -u | Qué te toca a ti |
|---|---|---|
| Quitar un modelo | Borra metadatos, vistas, menús y ACL | DROP TABLE en end-migrate |
| Quitar un campo | Borra ir.model.fields | Opcional: DROP COLUMN |
Quitar un registro noupdate | Nada | env.ref(..., raise_if_not_found=False).unlink() |
Campo nuevo con default | Rellena todas las filas con el default | Crear la columna en pre-migrate si el default no vale para el pasado |
| Campo calculado almacenado nuevo | Lo calcula para todas las filas | Nada |
| Renombrar un campo | Crea uno nuevo vacío | RENAME COLUMN en pre-migrate |
Resumen
El -u crea esquema pero no lo destruye: las tablas de modelos eliminados se borran en un end-migrate, los registros noupdate obsoletos se borran a mano y las columnas cuyo default no tiene sentido para filas antiguas se crean vacías en un pre-migrate. Y todo se ensaya actualizando desde la versión anterior, no instalando desde cero. Más sobre los hooks en scripts de migración.