Django-DB-Migrations: tidak dapat MENGUBAH TABEL karena memiliki kejadian pemicu yang tertunda

122

Saya ingin menghapus null = True dari TextField:

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

Saya membuat migrasi skema:

manage.py schemamigration fooapp --auto

Karena beberapa kolom footer berisi, NULLsaya mendapatkan ini errorjika saya menjalankan migrasi:

django.db.utils.IntegrityError: kolom "footer" berisi nilai null

Saya menambahkan ini ke migrasi skema:

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

Sekarang saya mendapatkan:

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

Apa yang salah?

guettli
sumber
1
Pertanyaan ini serupa: stackoverflow.com/questions/28429933/… dan memiliki jawaban yang lebih berguna bagi saya.
SpoonMeiser

Jawaban:

139

Alasan lain untuk ini mungkin karena Anda mencoba untuk mengatur kolom NOT NULLketika sebenarnya sudah memiliki NULLnilai.

maazza
sumber
7
Untuk mengatasi ini, Anda dapat menggunakan migrasi data atau secara manual (manage.py shell) masuk dan memperbarui nilai yang tidak sesuai
mgojohn
@mgojohn Bagaimana Anda melakukannya?
muka piramida
1
@pyramidface Jika Anda tidak terlalu pilih-pilih, Anda bisa memperbarui nilai null pada shell django. Jika Anda mencari sesuatu yang lebih formal dan dapat diuji, itu tergantung pada versi yang Anda gunakan. Jika Anda menggunakan selatan, lihat: south.readthedocs.org/en/latest/tutorial/part3.html dan jika Anda menggunakan migrasi django, lihat bagian "migrasi data" di sini: docs.djangoproject.com/en/1.8/topics/ migrasi
mgojohn
131

Setiap migrasi ada di dalam transaksi. Di PostgreSQL Anda tidak boleh memperbarui tabel dan kemudian mengubah skema tabel dalam satu transaksi.

Anda perlu memisahkan migrasi data dan migrasi skema. Pertama buat migrasi data dengan kode ini:

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

Kemudian buat migrasi skema:

manage.py schemamigration fooapp --auto

Sekarang Anda memiliki dua transaksi dan migrasi dalam dua langkah seharusnya berfungsi.

guettli
sumber
8
PostgreSQL mungkin mengubah perilakunya terkait transaksi semacam itu, karena saya berhasil menjalankan migrasi dengan perubahan data dan skema pada mesin dev saya (PostgreSQL 9.4) ketika gagal di server (PostgreSQL 9.1).
Perbatasan Bertrand
1
Hampir sama bagiku. Ini bekerja dengan sempurna untuk 100+ migrasi (termasuk ~ 20 migrasi data) hingga hari ini, sambil menambahkan batasan bersama yang unik bersama dengan migrasi data yang menghapus duplikat sebelumnya. PostgreSQL 10.0
LinPy fan
Jika menggunakan operasi RunPython dalam migrasi untuk migrasi data, Anda hanya perlu memastikan ini adalah operasi terakhir. Django mengetahui bahwa jika operasi RunPython adalah yang terakhir, untuk membuka transaksinya sendiri.
Dougyfresh
1
@Dougyfresh Apakah ini fitur terdokumentasi dari django?
guettli
Saya sebenarnya tidak melihat ini di mana pun, hanya sesuatu yang saya amati. docs.djangoproject.com/en/2.2/ref/migration-operations/…
Dougyfresh
9

Baru saja mengalami masalah ini. Anda juga dapat menggunakan db.start_transaction () dan db.commit_transaction () dalam migrasi skema untuk memisahkan perubahan data dari perubahan skema. Mungkin tidak begitu bersih untuk memiliki migrasi data terpisah tetapi dalam kasus saya, saya memerlukan skema, data, dan kemudian migrasi skema lain jadi saya memutuskan untuk melakukan semuanya sekaligus.

iklim
sumber
7
Masalah dengan solusi ini adalah: Apa yang terjadi jika migrasi Anda gagal setelah db.commit_transaction ()? Saya lebih suka menggunakan tiga migrasi, jika Anda membutuhkan ini: schema-mig, data-mig, schema-mig.
guettli
5
Lihat: django.readthedocs.io/en/latest/ref/migration-operations.html Pada database yang mendukung transaksi DDL (SQLite dan PostgreSQL), operasi RunPython tidak memiliki transaksi apa pun yang ditambahkan secara otomatis selain transaksi yang dibuat untuk setiap migrasi. Jadi, di PostgreSQL, misalnya, Anda harus menghindari penggabungan perubahan skema dan operasi RunPython dalam migrasi yang sama atau Anda mungkin mengalami kesalahan seperti OperationalError: tidak dapat MENGUBAH TABEL "mytable" karena memiliki peristiwa pemicu tertunda.
Iasmini Gomes
6

Pada operasi saya meletakkan BATAS SET:

operations = [
    migrations.RunSQL('SET CONSTRAINTS ALL IMMEDIATE;'),
    migrations.RunPython(migration_func),
    migrations.RunSQL('SET CONSTRAINTS ALL DEFERRED;'),
]
sluge
sumber
Lebih baik gunakan SeparateDatabaseAndState
bdoubleu
1

Anda mengubah skema kolom. Kolom footer tersebut tidak lagi berisi nilai kosong. Ada kemungkinan besar nilai kosong sudah disimpan di DB untuk kolom itu. Django akan memperbarui baris kosong itu dalam DB Anda dari kosong ke nilai default sekarang dengan perintah migrate. Django mencoba memutakhirkan baris di mana kolom footer mempunyai nilai kosong dan mengubah skema pada saat yang sama kelihatannya (saya tidak yakin).

Masalahnya adalah Anda tidak dapat mengubah skema kolom yang sama dengan yang Anda coba perbarui nilainya pada saat yang sama.

Salah satu solusinya adalah menghapus file migrasi yang memperbarui skema. Kemudian, jalankan skrip untuk memperbarui semua nilai tersebut ke nilai default Anda. Kemudian jalankan kembali migrasi untuk memperbarui skema. Dengan cara ini, pembaruan sudah selesai. Migrasi Django hanya mengubah skema.

Uzzi Emuchay
sumber
1
Menjalankan beberapa skrip sebenarnya bukan pilihan bagi saya. Saya memiliki beberapa contoh database dan proses penerapan berkelanjutan hanya memanggil "manage.py migrate". Pertanyaan ini sudah merupakan jawaban yang valid yang berfungsi dengan baik.
guettli