Django-DB-Migrations: impossible d'ALTER TABLE car il a des événements déclencheurs en attente

122

Je veux supprimer null = True d'un TextField:

-    footer=models.TextField(null=True, blank=True)
+    footer=models.TextField(blank=True, default='')

J'ai créé une migration de schéma:

manage.py schemamigration fooapp --auto

Étant donné que certaines colonnes de pied de page contiennent, NULLj'obtiens ceci errorsi j'exécute la migration:

django.db.utils.IntegrityError: la colonne "footer" contient des valeurs nulles

J'ai ajouté ceci à la migration de schéma:

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

Maintenant je reçois:

django.db.utils.DatabaseError: cannot ALTER TABLE "fooapp_emailsender" because it has pending trigger events

Qu'est-ce qui ne va pas?

guettli
la source
1
Cette question est similaire: stackoverflow.com/questions/28429933/… et avait des réponses qui m'ont été plus utiles.
SpoonMeiser

Réponses:

139

Une autre raison à cela peut-être parce que vous essayez de définir une colonne sur NOT NULLalors qu'elle contient déjà des NULLvaleurs.

maazza
la source
7
Pour résoudre ce problème, vous pouvez utiliser une migration de données ou manuellement (shell manage.py) entrer et mettre à jour les valeurs non conformes
mgojohn
@mgojohn Comment faites-vous cela?
pyramidface
1
@pyramidface Si vous n'êtes pas trop pointilleux, vous pouvez simplement mettre à jour les valeurs nulles au niveau du shell django. Si vous recherchez quelque chose de plus formel et testable, cela dépend des versions que vous utilisez. Si vous utilisez south, voir: south.readthedocs.org/en/latest/tutorial/part3.html et si vous utilisez les migrations de django, consultez la section "migrations de données" ici: docs.djangoproject.com/en/1.8/topics/ migrations
mgojohn
131

Chaque migration est à l'intérieur d'une transaction. Dans PostgreSQL, vous ne devez pas mettre à jour la table, puis modifier le schéma de la table en une seule transaction.

Vous devez séparer la migration des données et la migration du schéma. Créez d'abord la migration de données avec ce code:

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

Créez ensuite la migration de schéma:

manage.py schemamigration fooapp --auto

Vous avez maintenant deux transactions et la migration en deux étapes devrait fonctionner.

guettli
la source
8
PostgreSQL a probablement changé son comportement concernant de telles transactions, car j'ai réussi à exécuter une migration avec des modifications de données et de schéma sur ma machine de développement (PostgreSQL 9.4) alors qu'elle échouait sur le serveur (PostgreSQL 9.1).
Bertrand Bordage
1
Presque pareil pour moi. Il a parfaitement fonctionné pour plus de 100 migrations (y compris environ 20 migrations de données) jusqu'à aujourd'hui, tout en ajoutant une contrainte unique ensemble avec la migration de données en supprimant les doublons avant elle. PostgreSQL 10.0
Fan de LinPy
Si vous utilisez une opération RunPython dans la migration pour la migration de données, vous devez simplement vous assurer qu'il s'agit de la dernière opération. Django sait que si l'opération RunPython est la dernière, il faut ouvrir sa propre transaction.
Dougyfresh
1
@Dougyfresh est-ce une fonctionnalité documentée de django?
guettli le
En fait, je ne vois cela nulle part, c'était juste quelque chose que j'ai observé. docs.djangoproject.com/en/2.2/ref/migration-operations/…
Dougyfresh
9

Je viens de frapper ce problème. Vous pouvez également utiliser db.start_transaction () et db.commit_transaction () dans la migration de schéma pour séparer les modifications de données des modifications de schéma. Probablement pas assez propre pour avoir une migration de données séparée, mais dans mon cas, j'aurais besoin d'un schéma, de données, puis d'une autre migration de schéma, alors j'ai décidé de tout faire en même temps.

climat
la source
7
Le problème avec cette solution est le suivant: que se passe-t-il si votre migration échoue après db.commit_transaction ()? Je préfère utiliser trois migrations, si vous en avez besoin: schema-mig, data-mig, schema-mig.
guettli
5
Voir: django.readthedocs.io/en/latest/ref/migration-operations.html Sur les bases de données qui prennent en charge les transactions DDL (SQLite et PostgreSQL), les opérations RunPython n'ont pas de transactions ajoutées automatiquement en plus des transactions créées pour chaque migration. Ainsi, sur PostgreSQL, par exemple, vous devez éviter de combiner les changements de schéma et les opérations RunPython dans la même migration ou vous risquez de rencontrer des erreurs comme OperationalError: cannot ALTER TABLE "mytable" car il a des événements de déclenchement en attente.
Iasmini Gomes
6

Aux opérations je mets SET CONSTRAINTS:

operations = [
    migrations.RunSQL('SET CONSTRAINTS ALL IMMEDIATE;'),
    migrations.RunPython(migration_func),
    migrations.RunSQL('SET CONSTRAINTS ALL DEFERRED;'),
]
sluge
la source
Mieux vaut utiliser SeparateDatabaseAndState
bdoubleu
1

Vous modifiez le schéma de colonne. Cette colonne de pied de page ne peut plus contenir de valeur vide. Il y a probablement des valeurs vides déjà stockées dans la base de données pour cette colonne. Django va mettre à jour ces lignes vides dans votre base de données de vide à la valeur maintenant par défaut avec la commande migrate. Django essaie de mettre à jour les lignes où la colonne de pied de page a une valeur vide et de modifier le schéma en même temps qu'il semble (je ne suis pas sûr).

Le problème est que vous ne pouvez pas modifier le même schéma de colonne pour lequel vous essayez de mettre à jour les valeurs en même temps.

Une solution serait de supprimer le fichier de migrations mettant à jour le schéma. Ensuite, exécutez un script pour mettre à jour toutes ces valeurs avec votre valeur par défaut. Ensuite, réexécutez la migration pour mettre à jour le schéma. De cette façon, la mise à jour est déjà effectuée. La migration Django ne fait que modifier le schéma.

Uzzi Emuchay
la source
1
Lancer un script n'est pas vraiment une option pour moi. J'ai plusieurs instances de la base de données et le processus de déploiement continu appelle simplement "manage.py migrate". Cette question est déjà des réponses valides qui fonctionnent bien.
guettli