Skip to content

Fix Odoo Module Upgrade Field Errors: Column, Constraint, and Migration Issues

DeployMonkey Team · March 23, 2026 10 min read

The Module Upgrade Problem

Running odoo-bin -u my_module and hitting errors is one of the most common Odoo developer experiences. Module upgrades modify the database schema — adding columns, changing types, creating constraints — and things go wrong when the existing data does not match the new schema.

Common Upgrade Errors

Error 1: Column Already Exists

ProgrammingError: column "my_field" of relation "my_model" already exists

# Cause: You added a field that already exists in the database
# (from a previous failed upgrade or another module)

# Fix: The field already exists, just update the module:
# Usually this means a previous upgrade partially succeeded
# Try: Drop and recreate the problematic constraint instead

# Check the column:
psql -d mydb -c "SELECT column_name, data_type FROM information_schema.columns WHERE table_name='my_model' AND column_name='my_field';"

Error 2: Column Does Not Exist

ProgrammingError: column "my_model.old_field" does not exist

# Cause: Code references a field that was removed from the model
# but still exists in views, reports, or Python code

# Fix: Search for all references to the removed field:
grep -r 'old_field' /path/to/module/
# Remove references from:
# - XML views (tree, form, search, kanban)
# - Python code (@api.depends, domain filters)
# - Reports (QWeb templates)
# - Security rules (ir.rule domains)

Error 3: NOT NULL Constraint Violation

IntegrityError: null value in column "my_field" violates not-null constraint
DETAIL: Failing row contains (1, null, ...)

# Cause: You added a required field (required=True) without a default value
# Existing records have NULL for this column

# Fix Option 1: Add a default value
my_field = fields.Char(required=True, default='N/A')

# Fix Option 2: Use a pre-migration script
# migrations/X.Y.Z/pre-migrate.py:
def migrate(cr, version):
    cr.execute("""
        UPDATE my_model SET my_field = 'N/A'
        WHERE my_field IS NULL
    """)

# Fix Option 3: Fill data before adding the constraint
# Set required=False first, fill data, then set required=True

Error 4: Data Type Mismatch

ProgrammingError: column "amount" cannot be cast automatically to type integer
HINT: You might need to specify "USING amount::integer"

# Cause: Changed field type (e.g., Float to Integer) and PostgreSQL
# cannot auto-convert the existing data

# Fix: Use a pre-migration script:
# migrations/X.Y.Z/pre-migrate.py:
def migrate(cr, version):
    cr.execute("""
        ALTER TABLE my_model
        ALTER COLUMN amount TYPE integer
        USING amount::integer
    """)

Error 5: Foreign Key Constraint

IntegrityError: insert or update on table "my_model" violates
foreign key constraint "my_model_partner_id_fkey"
DETAIL: Key (partner_id)=(999) is not present in table "res_partner"

# Cause: A Many2one field references a record that was deleted

# Fix: Clean up orphaned references:
psql -d mydb -c "
    UPDATE my_model SET partner_id = NULL
    WHERE partner_id NOT IN (SELECT id FROM res_partner);
"

Error 6: Unique Constraint Violation

IntegrityError: duplicate key value violates unique constraint "my_model_name_uniq"
DETAIL: Key (name)=(Test) already exists.

# Cause: Added a unique constraint but duplicate data already exists

# Fix: Remove duplicates before upgrading:
psql -d mydb -c "
    -- Find duplicates:
    SELECT name, COUNT(*) FROM my_model
    GROUP BY name HAVING COUNT(*) > 1;

    -- Remove duplicates (keep lowest ID):
    DELETE FROM my_model WHERE id NOT IN (
        SELECT MIN(id) FROM my_model GROUP BY name
    );
"

Migration Scripts

For complex upgrades, Odoo supports migration scripts that run before and after the module update.

# Directory structure:
my_module/
  migrations/
    19.0.1.2.0/      # Match the version in __manifest__.py
      pre-migrate.py  # Runs before the module update
      post-migrate.py # Runs after the module update

# pre-migrate.py — fix data before schema changes:
def migrate(cr, version):
    # cr is a raw database cursor (no ORM)
    cr.execute("UPDATE my_model SET status = 'draft' WHERE status IS NULL")

# post-migrate.py — fix data after schema changes:
def migrate(cr, version):
    from odoo import api, SUPERUSER_ID
    env = api.Environment(cr, SUPERUSER_ID, {})
    records = env['my.model'].search([('new_field', '=', False)])
    for rec in records:
        rec.new_field = rec._compute_new_field_value()

# IMPORTANT: The version in __manifest__.py must change for
# migration scripts to run. Odoo compares versions to decide.

Safe Upgrade Process

  1. Backup first: Always backup the database before upgrading
  2. Test on a copy: Clone the production database and test the upgrade there
  3. Check logs carefully: Some errors are warnings that do not stop the upgrade but cause issues later
  4. Use migration scripts: For any schema change that affects existing data
  5. Upgrade one module at a time: Easier to isolate which module causes the error
# Backup before upgrade:
pg_dump -U odoo mydb > /tmp/mydb_backup_$(date +%Y%m%d).sql

# Upgrade with verbose logging:
./odoo-bin -d mydb -u my_module --log-level=debug --stop-after-init 2>&1 | tee upgrade.log

# If it fails, restore:
dropdb mydb && createdb mydb && psql mydb < /tmp/mydb_backup_*.sql

Common Pitfalls

  • Changing _name of a model — creates a new table, loses all data
  • Removing a field without removing it from views first
  • Adding store=True to a computed field without a migration to fill existing rows
  • Changing a field from Char to Many2one — incompatible types
  • Renaming a field — Odoo sees this as removing one field and adding another