[Django]-Django-DB-Migrations: cannot ALTER TABLE because it has pending trigger events

197πŸ‘

βœ…

Every migration is inside a transaction. In PostgreSQL you must not update the table and then alter the table schema in one transaction.

You need to split the data migration and the schema migration. First create the data migration with this code:

 for sender in orm['fooapp.EmailSender'].objects.filter(footer=None):
    sender.footer=''
    sender.save()

Then create the schema migration:

manage.py schemamigration fooapp --auto

Now you have two transactions and the migration in two steps should work.

πŸ‘€guettli

204πŸ‘

Another reason for this maybe because you try to set a column to NOT NULL when it actually already has NULL values.

πŸ‘€maazza

37πŸ‘

At the operations I put SET CONSTRAINTS:

operations = [
    migrations.RunSQL('SET CONSTRAINTS ALL IMMEDIATE;'),
    migrations.RunPython(migration_func),
    migrations.RunSQL('SET CONSTRAINTS ALL DEFERRED;'),
]
πŸ‘€sluge

27πŸ‘

If you are adding a non-nullable field, you need to do it in two migrations:

  1. AddField and RunPython to populate it
  2. AlterField to change the field to be non-nullable

Explanation

On PostgreSQL and SQLite, this problem can occur if you have a sufficiently complex RunPython command combined with schema alterations in the same migration. For example, if you are adding a non-nullable field, the typical migration steps for this is:

  1. AddField to add the field as nullable
  2. RunRython to populate it
  3. AlterField to change the field to be non-nullable

On SQLite and Postgres, this can cause problems because the whole thing is being done in one transaction.
The Django docs have a specific warning about this:

On databases that do support DDL transactions (SQLite and PostgreSQL), RunPython operations do not have any transactions automatically added besides the transactions created for each migration. Thus, on PostgreSQL, for example, you should avoid combining schema changes and RunPython operations in the same migration or you may hit errors like OperationalError: cannot ALTER TABLE "mytable" because it has pending trigger events.

If this is the case, the solution is to separate your migration into multiple migrations. In general, the way to split is to have a first migration containing the steps up through the run_python command and the second migration containing all the ones after it. Thus, in the case described above, the pattern would be the AddField and RunPython in one migration, and the AlterField in a second.

πŸ‘€Zags

11πŸ‘

Have just hit this problem. You can also use db.start_transaction() and db.commit_transaction() in the schema migration to separate data changes from schema changes. Probably not so clean as to have a separate data migration but in my case I would need schema, data, and then another schema migration so I decided to do it all at once.

πŸ‘€clime

3πŸ‘

In my case I’ve got

  1. AddField
  2. RunPython
  3. RemoveField

Then I just moved the last RemoveFied to the new migration file, that fixed the problem

πŸ‘€Damir Nafikov

3πŸ‘

@Zags answer is correct, this error usually comes when you mix schema alteration and data manipulation in the same migration, which correspond to a unique transaction by default.

Another solution to solve that issue is to manually set atomic = False to the Migration class. As a result, no transaction is created to run operations, each one is run sequentially and immediatly applied to the database.

class Migration(migrations.Migration):
    atomic = False

    dependencies = [
        ('application', '00XX_previous_migration'),
    ]

    operations = [
        migrations.AddField(
            model_name='foo',
            name='bar',
            field=models.CharField(max_length=255),
        ),
        RunPython(migrate_data, reverse_code=reverse_migrate_data, atomic=True),
        migrations.RemoveField(
            model_name='foo',
            name='baz',
        ),
    ]

Tip: If the RunPython operation perform multiple save(), you can call it with atomic=True to create a transaction for the whole operation.

πŸ‘€Antwane

2πŸ‘

You are altering the column schema. That footer column can no longer contain a blank value. There are most likely blank values already stored in the DB for that column. Django is going to update those blank rows in your DB from blank to the now default value with the migrate command. Django tries to update the rows where footer column has a blank value and change the schema at the same time it seems (I’m not sure).

The problem is you can’t alter the same column schema you are trying to update the values for at the same time.

One solution would be to delete the migrations file updating the schema. Then, run a script to update all those values to your default value. Then re-run the migration to update the schema. This way, the update is already done. Django migration is only altering the schema.

πŸ‘€Uzzi Emuchay

0πŸ‘

step 1)the solution is to remove the latest migration from the migration folder and remove the latest added fields in models.

step 2)then again makemigration and migrate

step 3)At the last add the field again that has been removed in the first step

step 4)then again makemigration and migrate

Problem solved

πŸ‘€Shah Vipul

0πŸ‘

The likely cause of this is rooted in your model having check constraints on it via the Meta.constraints option. Check constraints (in Postgres) are implemented as triggers that execute at the end of a transaction. By default, a Django migration file encloses all migrations operations from a file in a single transaction. This means that other ALTER TABLE operations may happen before your triggers fire, a situation that Postgres can’t handle.

In order to get around this issue, you can do like another answer suggested:

operations = [
    migrations.RemoveField(...). # Do your normal table operations
    migrations.RunPython(migration_func),
    # Evaluate the check constraints
    migrations.RunSQL('SET CONSTRAINTS ALL IMMEDIATE;'),
    migrations.RemoveField(...). # Run the rest of your table operations
]

Running 'SET CONSTRAINTS ALL IMMEDIATE;' after the data migration ensures that all of those check constraints that normally wait until the end of the transaction will fire, meaning there are no more pending trigger events before the next ALTER TABLE statements.

FYI – This setting is effective for only the Django migration runner in that transaction. It won’t affect other database sessions or any subsequent migration files.

Unlike the other answer linked, I’d avoid running an additional migrations.RunSQL('SET CONSTRAINTS ALL DEFERRED;'), otherwise you may alter the behavior of other triggers in subsequent steps that should run immediately (i.e. application triggers that track history, etc, that aren’t check constraints).

Just wanted to add some more clarity here on exactly why this is happening since this is an older question with many different answers.

πŸ‘€Wes Kendall

Leave a comment