Ir al contenido

Quitar modelos y campos en Odoo sin romper la actualización

Tablas huérfanas, registros noupdate que no se borran y defaults que reescriben el pasado
16 de octubre de 2026 por
Quitar modelos y campos en Odoo sin romper la actualización
ATEMI, Aitor Atencia

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.

Logo Odoo
El ORM gestiona el esquema hacia delante; la limpieza hacia atrás es cosa tuya.

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"
CambioQué hace el -uQué te toca a ti
Quitar un modeloBorra metadatos, vistas, menús y ACLDROP TABLE en end-migrate
Quitar un campoBorra ir.model.fieldsOpcional: DROP COLUMN
Quitar un registro noupdateNadaenv.ref(..., raise_if_not_found=False).unlink()
Campo nuevo con defaultRellena todas las filas con el defaultCrear la columna en pre-migrate si el default no vale para el pasado
Campo calculado almacenado nuevoLo calcula para todas las filasNada
Renombrar un campoCrea uno nuevo vacíoRENAME 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.

en Odoo
Vistas kanban en Odoo 19: la plantilla card y dos errores que los tests no ven
Estructura card/menu, campos que hay que declarar y por qué un kanban «de dashboard» se cuela en otras acciones