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=TrueError 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
- Backup first: Always backup the database before upgrading
- Test on a copy: Clone the production database and test the upgrade there
- Check logs carefully: Some errors are warnings that do not stop the upgrade but cause issues later
- Use migration scripts: For any schema change that affects existing data
- 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_*.sqlCommon Pitfalls
- Changing
_nameof a model — creates a new table, loses all data - Removing a field without removing it from views first
- Adding
store=Trueto a computed field without a migration to fill existing rows - Changing a field from
ChartoMany2one— incompatible types - Renaming a field — Odoo sees this as removing one field and adding another