[Django]-Django migrations using RunPython to commit changes

48👍

This happens because Django creates constraints as DEFERRABLE INITIALLY DEFERRED:

ALTER TABLE my_app_site
ADD CONSTRAINT "[constraint_name]"
FOREIGN KEY (template_id)
REFERENCES my_app_template(id)
DEFERRABLE INITIALLY DEFERRED;

This tells PostgreSQL that the foreign key does not need to be checked right after every command, but can be deferred until the end of transactions.

So, when a transaction modifies content and structure, the constraints are checked on parallel with the structure changes, or the checks are scheduled to be done after altering the structure. Both of these states are bad and the database will abort the transaction instead of making any assumptions.

You can instruct PostgreSQL to check constraints immediately in the current transaction by calling SET CONSTRAINTS ALL IMMEDIATE, so structure changes won’t be a problem (refer to SET CONSTRAINTS documentation). Your migration should look like this:

operations = [
    migrations.RunSQL('SET CONSTRAINTS ALL IMMEDIATE',
                      reverse_sql=migrations.RunSQL.noop),

    # ... the actual migration operations here ...

    migrations.RunSQL(migrations.RunSQL.noop,
                      reverse_sql='SET CONSTRAINTS ALL IMMEDIATE'),
]

The first operation is for applying (forward) migrations, and the last one is for unapplying (backwards) migrations.

EDIT: Constraint deferring is useful to avoid insertion sorting, specially for self-referencing tables and tables with cyclic dependencies. So be careful when bending Django.

LATE EDIT: on Django 1.7 and newer versions there is a special SeparateDatabaseAndState operation that allows data changes and structure changes on the same migration. Try using this operation before resorting to the "set constraints all immediate" method above. Example:

operations = [
    migrations.SeparateDatabaseAndState(database_operations=[
            # put your sql, python, whatever data migrations here
        ],
        state_operations=[
            # field/model changes goes here
        ]),
]
👤eric

19👍

Yes, I’d say it’s the transaction bounds which are preventing the data change in your migration being committed before the ALTER is run.

I’d do as @danielcorreia says and implement it as two migrations, as it looks like the even the SchemaEditor is bound by transactions, via the the context manager you’d be obliged to use.

0👍

Adding null to the field giving you a problem should fix it. In your case the "template" field. Just add null=True to the field. The migrations should than look like this:

class Migration(migrations.Migration):

dependencies = [
    ('my_app', '0021_auto_20150210_1008'),
]

operations = [
    migrations.RunPython(add_default_template),
    migrations.AlterField(
        model_name='site',
        name='template',
        field=models.ForeignKey(to='my_app.Template', null=True),
        preserve_default=False,
    ),
]
👤Marcus

Leave a comment