Django-DB-Migrationen: TABELLE NICHT ÄNDERN, da ausstehende Triggerereignisse vorliegen

121

Ich möchte null = True aus einem TextField entfernen:

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

Ich habe eine Schemamigration erstellt:

manage.py schemamigration fooapp --auto

Da einige Fußzeilenspalten enthalten, NULLerhalte ich Folgendes, errorwenn ich die Migration ausführe:

django.db.utils.IntegrityError: Spalte "Fußzeile" enthält Nullwerte

Ich habe dies zur Schemamigration hinzugefügt:

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

Jetzt bekomme ich:

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

Was ist falsch?

guettli
quelle
1
Diese Frage ist ähnlich: stackoverflow.com/questions/28429933/… und hatte Antworten, die für mich nützlicher waren.
SpoonMeiser

Antworten:

138

Ein weiterer Grund dafür ist möglicherweise, dass Sie versuchen, eine Spalte so einzustellen, NOT NULLdass sie tatsächlich bereits NULLWerte enthält.

Maazza
quelle
7
Um dies zu beheben, können Sie entweder eine Datenmigration verwenden oder manuell (manage.py-Shell) nicht konforme Werte aktualisieren
mgojohn
@mgojohn Wie machst du das?
Pyramidengesicht
1
@pyramidface Wenn Sie nicht zu wählerisch sind, können Sie einfach die Nullwerte in der Django-Shell aktualisieren. Wenn Sie etwas Formaleres und Testbareres suchen, hängt es davon ab, welche Versionen Sie verwenden. Wenn Sie South verwenden, lesen Sie : south.readthedocs.org/en/latest/tutorial/part3.html. Wenn Sie Djangos Migrationen verwenden, lesen Sie den Abschnitt "Datenmigrationen" hier: docs.djangoproject.com/de/1.8/topics/ Migrationen
mgojohn
131

Jede Migration erfolgt innerhalb einer Transaktion. In PostgreSQL dürfen Sie die Tabelle nicht aktualisieren und dann das Tabellenschema in einer Transaktion ändern.

Sie müssen die Datenmigration und die Schemamigration aufteilen. Erstellen Sie zunächst die Datenmigration mit folgendem Code:

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

Erstellen Sie dann die Schemamigration:

manage.py schemamigration fooapp --auto

Jetzt haben Sie zwei Transaktionen und die Migration in zwei Schritten sollte funktionieren.

guettli
quelle
8
PostgreSQL hat wahrscheinlich sein Verhalten in Bezug auf solche Transaktionen geändert, da ich es geschafft habe, eine Migration mit Daten- und Schemaänderungen auf meinem Entwicklungscomputer (PostgreSQL 9.4) durchzuführen, während sie auf dem Server fehlgeschlagen ist (PostgreSQL 9.1).
Bertrand Bordage
1
Fast das gleiche für mich. Es funktionierte bis heute einwandfrei für mehr als 100 Migrationen (einschließlich ~ 20 Datenmigrationen), während es zusammen mit der Datenmigration eine eindeutige Einschränkung für das Zusammenfügen von Duplikaten hinzufügte. PostgreSQL 10.0
LinPy Fan
Wenn Sie bei der Migration einen RunPython-Vorgang für die Datenmigration verwenden, müssen Sie nur sicherstellen, dass es sich um den letzten Vorgang handelt. Django weiß, dass, wenn der RunPython-Vorgang der letzte ist, eine eigene Transaktion geöffnet werden muss.
Dougyfresh
1
@Dougyfresh ist dies eine dokumentierte Funktion von Django?
Guettli
Ich sehe das eigentlich nirgendwo, war nur etwas, was ich beobachtet habe. docs.djangoproject.com/de/2.2/ref/migration-operations/…
Dougyfresh
9

Habe gerade dieses Problem getroffen. Sie können auch db.start_transaction () und db.commit_transaction () in der Schemamigration verwenden, um Datenänderungen von Schemaänderungen zu trennen. Wahrscheinlich nicht so sauber, dass es eine separate Datenmigration gibt, aber in meinem Fall würde ich Schema, Daten und dann eine weitere Schemamigration benötigen, also habe ich beschlossen, alles auf einmal zu tun.

Klima
quelle
7
Das Problem bei dieser Lösung ist folgendes: Was passiert, wenn Ihre Migration nach db.commit_transaction () fehlschlägt? Ich bevorzuge die Verwendung von drei Migrationen, wenn Sie dies benötigen: Schema-Mig, Daten-Mig, Schema-Mig.
Guettli
5
Siehe: django.readthedocs.io/en/latest/ref/migration-operations.html In Datenbanken, die DDL-Transaktionen unterstützen (SQLite und PostgreSQL), werden bei RunPython-Vorgängen neben den für jede Migration erstellten Transaktionen keine Transaktionen automatisch hinzugefügt. Unter PostgreSQL sollten Sie beispielsweise vermeiden, Schemaänderungen und RunPython-Operationen in derselben Migration zu kombinieren, da sonst Fehler wie OperationalError auftreten können: TABELLE "mytable" kann nicht geändert werden, da ausstehende Triggerereignisse vorliegen.
Iasmini Gomes
5

Bei den Operationen habe ich SET CONSTRAINTS gesetzt:

operations = [
    migrations.RunSQL('SET CONSTRAINTS ALL IMMEDIATE;'),
    migrations.RunPython(migration_func),
    migrations.RunSQL('SET CONSTRAINTS ALL DEFERRED;'),
]
Sluge
quelle
Besser SeparateDatabaseAndState
bdoubleu
0

Sie ändern das Spaltenschema. Diese Fußzeilenspalte darf keinen leeren Wert mehr enthalten. Für diese Spalte sind höchstwahrscheinlich bereits leere Werte in der Datenbank gespeichert. Django aktualisiert diese leeren Zeilen in Ihrer Datenbank mit dem Befehl migrate von leer auf den jetzt Standardwert. Django versucht, die Zeilen zu aktualisieren, in denen die Fußzeilenspalte einen leeren Wert hat, und das Schema zur gleichen Zeit zu ändern, wie es scheint (ich bin nicht sicher).

Das Problem ist, dass Sie nicht dasselbe Spaltenschema ändern können, für das Sie die Werte gleichzeitig aktualisieren möchten.

Eine Lösung wäre, die Migrationsdatei zu löschen, die das Schema aktualisiert. Führen Sie dann ein Skript aus, um alle diese Werte auf Ihren Standardwert zu aktualisieren. Führen Sie dann die Migration erneut aus, um das Schema zu aktualisieren. Auf diese Weise ist das Update bereits abgeschlossen. Die Django-Migration ändert nur das Schema.

Uzzi Emuchay
quelle
1
Das Ausführen eines Skripts ist für mich keine Option. Ich habe mehrere Instanzen der Datenbank und der kontinuierliche Bereitstellungsprozess ruft einfach "manage.py migrate" auf. Diese Frage ist bereits gültige Antworten, die gut funktionieren.
Guettli