Bagaimana cara mengembalikan migrasi terakhir?

446

Saya telah membuat migrasi yang menambahkan tabel baru dan ingin mengembalikannya dan menghapus migrasi, tanpa membuat migrasi baru.

Bagaimana saya melakukannya? Apakah ada perintah untuk mengembalikan migrasi terakhir dan kemudian saya bisa menghapus file migrasi?

Ronen Ness
sumber

Jawaban:

794

Anda dapat kembali dengan bermigrasi ke migrasi sebelumnya.

Misalnya, jika dua migrasi terakhir Anda adalah:

  • 0010_previous_migration
  • 0011_migration_to_revert

Maka Anda akan melakukan:

./manage.py migrate my_app 0010_previous_migration 

Anda kemudian dapat menghapus migrasi 0011_migration_to_revert.

Jika Anda menggunakan Django 1.8+, Anda dapat menunjukkan nama semua migrasi dengan

./manage.py showmigrations my_app

Untuk membalikkan semua migrasi untuk suatu aplikasi, Anda dapat menjalankan:

./manage.py migrate my_app zero
Alasdair
sumber
7
Saya telah melihat banyak jawaban pada SO untuk masalah ini yang sudah lama dan tidak berfungsi lagi. +1 karena ini bekerja dengan Django 1.8.
AlanSE
2
Bagaimana jika aplikasi hanya memiliki satu file migrasi / migrasi awal. dan saya harus membatalkan migrasi awal itu?
Adiyat Mubarak
37
The migrateperintah membiarkan kau menggunakan ./manage.py migrate my_app zerountuk unapply semua migrasi untuk aplikasi.
Alasdair
4
Untuk beberapa alasan, ./manage.py migrasi my_app 0010_previous_migration tidak berfungsi untuk saya. Jadi saya mencoba menggunakan ./manage.py migrasi my_app 0010 dan berhasil. Adakah alasan mengapa Django 1.8 tidak akan mengambil seluruh nama migrasi?
Varun Verma
4
Selama Anda menggunakan nama migrasi Anda yang sebenarnya, dan tidak '0010_previous_migration', saya tidak tahu mengapa Anda akan melihat perilaku itu.
Alasdair
36

Jawaban oleh Alasdair mencakup dasar-dasarnya

  • Identifikasi migrasi yang Anda inginkan ./manage.py showmigrations
  • migrate menggunakan nama aplikasi dan nama migrasi

Tetapi harus ditunjukkan bahwa tidak semua migrasi dapat dibatalkan. Ini terjadi jika Django tidak memiliki aturan untuk melakukan pembalikan. Untuk sebagian besar perubahan yang Anda lakukan migrasi secara otomatis ./manage.py makemigrations, pembalikan akan dimungkinkan. Namun, skrip khusus harus memiliki maju dan mundur ditulis, seperti yang dijelaskan dalam contoh di sini:

https://docs.djangoproject.com/en/1.9/ref/migration-operations/

Cara melakukan pembalikan no-op

Jika Anda memiliki RunPythonoperasi, maka mungkin Anda hanya ingin mundur migrasi tanpa menulis skrip pembalikan yang ketat secara logis. Retas cepat berikut untuk contoh dari dokumen (tautan di atas) memungkinkan ini, meninggalkan database dalam keadaan yang sama setelah migrasi diterapkan, bahkan setelah membalikkannya.

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import migrations, models

def forwards_func(apps, schema_editor):
    # We get the model from the versioned app registry;
    # if we directly import it, it'll be the wrong version
    Country = apps.get_model("myapp", "Country")
    db_alias = schema_editor.connection.alias
    Country.objects.using(db_alias).bulk_create([
        Country(name="USA", code="us"),
        Country(name="France", code="fr"),
    ])

class Migration(migrations.Migration):

    dependencies = []

    operations = [
        migrations.RunPython(forwards_func, lambda apps, schema_editor: None),
    ]

Ini berfungsi untuk Django 1.8, 1.9


Update: Cara yang lebih baik dari menulis ini akan menggantikan lambda apps, schema_editor: Nonedengan migrations.RunPython.noopdi potongan di atas. Secara fungsional keduanya adalah hal yang sama. (kredit untuk komentar)

AlanSE
sumber
5
Dari Django 1.8 dan seterusnya, Anda harus menggunakan RunPython.noopbukannya lambda inline atau equivelent
SpoonMeiser
@SpoonMeiser Dalam sintaks contoh, saya pikir itu terlihat seperti migrations.RunPython(forwards_func, migrations.RunPython.noop). Perlu memeriksa itu secara fungsional. Itu harus ditambahkan sebagai jawaban atau edit untuk yang satu ini suatu saat.
AlanSE
13

Inilah solusi saya, karena solusi di atas tidak benar-benar mencakup kasus penggunaan, ketika Anda menggunakannya RunPython.

Anda dapat mengakses tabel melalui ORM dengan

from django.db.migrations.recorder import MigrationRecorder

>>> MigrationRecorder.Migration.objects.all()
>>> MigrationRecorder.Migration.objects.latest('id')
Out[5]: <Migration: Migration 0050_auto_20170603_1814 for model>
>>> MigrationRecorder.Migration.objects.latest('id').delete()
Out[4]: (1, {u'migrations.Migration': 1})

Jadi, Anda dapat meminta tabel dan menghapus entri yang relevan untuk Anda. Dengan cara ini Anda dapat memodifikasi secara detail. Dengan RynPythonmigrasi Anda juga harus menjaga data yang ditambahkan / diubah / dihapus. Contoh di atas hanya menampilkan, bagaimana Anda mengakses tabel melalui Djang ORM.

Özer S.
sumber
Saat membuat model baru dengan ForeignKeys, dengan beberapa migrasi, kemudian menyadari bahwa semuanya salah, dan memulai kembali 2-3 migrasi ke belakang, dengan model baru tetapi terkadang nama model yang sama atau nama hubungan yang sama ... solusi ini jelas merupakan pemenangnya. Saya memiliki pesan kesalahan seperti django.db.utils.ProgrammingError: relation "<relation name>" already existssaya membuat migrate --fakeyang salah, jadi saya mencoba untuk kembali, lalu saya psycopg2.ProgrammingError: relation "<other <relation name>" does not exist
ucapkan
10

Hal lain yang dapat Anda lakukan adalah menghapus tabel yang dibuat secara manual.

Bersamaan dengan itu, Anda harus menghapus file migrasi tertentu. Juga, Anda harus menghapus entri tertentu itu di tabel migrasi-Django (mungkin yang terakhir dalam kasus Anda) yang berkorelasi dengan migrasi tertentu.

sprksh
sumber
hati-hati dalam hal ini - Anda diwajibkan untuk memverifikasi db memadai.
Sławomir Lenart
4
Saya akan menambahkan SANGAT hati-hati. Anda dapat merusak banyak hal di Postgres, misalnya kendala.
joedborg
9

Jangan hapus file migrasi sampai setelah pengembalian. Saya membuat kesalahan ini dan tanpa file migrasi, database tidak tahu apa yang harus dihapus.

python manage.py showmigrations
python manage.py migrate {app name from show migrations} {00##_migration file.py}

Hapus file migrasi. Setelah migrasi yang diinginkan ada di model Anda ...

python manage.py makemigrations
python manage.py migrate
DanGoodrick
sumber
8

Saya melakukan ini di 1.9.1 (untuk menghapus migrasi terakhir atau terbaru yang dibuat):

  1. rm <appname>/migrations/<migration #>*

    contoh: rm myapp/migrations/0011*

  2. masuk ke database dan menjalankan SQL ini (postgres dalam contoh ini)

    delete from django_migrations where name like '0011%';

Saya kemudian dapat membuat migrasi baru yang dimulai dengan nomor migrasi yang baru saja saya hapus (dalam hal ini, 11).

MIkee
sumber
1
+1 Meskipun ini akan berhasil, Anda harus menyimpan cara ini sebagai pilihan terakhir. Anda juga harus ingat untuk mengedit / menjatuhkan kolom / tabel yang berkontribusi terhadap migrasi yang bermasalah.
nehem
poin bagus - saya menggunakan ini ketika saya membuat migrasi tetapi belum menjalankan "./manage.py migrasi"
MIkee
3

Jawaban ini untuk kasus serupa jika jawaban teratas oleh Alasdair tidak membantu . (Misalnya jika migrasi yang tidak diinginkan segera dibuat lagi dengan setiap migrasi baru atau jika itu dalam migrasi yang lebih besar yang tidak dapat dikembalikan atau tabel telah dihapus secara manual.)

... hapus migrasi, tanpa membuat migrasi baru?

TL; DR : Anda dapat menghapus beberapa migrasi yang terakhir dikembalikan (bingung) dan membuat yang baru setelah memperbaiki model . Anda juga dapat menggunakan metode lain untuk mengonfigurasinya agar tidak membuat tabel dengan perintah migrasi. Migrasi terakhir harus dibuat agar sesuai dengan model saat ini .


Kasus mengapa ada orang yang tidak ingin membuat tabel untuk Model yang harus ada:

A) Tidak ada tabel seperti itu harus ada di tidak ada database di mesin tidak dan tidak ada kondisi

  • Kapan: Ini adalah model dasar yang dibuat hanya untuk pewarisan model dari model lain.
  • Solusi: Tetapkanclass Meta: abstract = True

B) Tabel jarang dibuat, oleh sesuatu yang lain atau secara manual dengan cara khusus.

  • Solusi: Gunakan class Meta: managed = False
    Migrasi dibuat, tetapi tidak pernah digunakan, hanya dalam tes. File migrasi penting, jika tidak, tes database tidak dapat berjalan, mulai dari keadaan awal yang dapat direproduksi.

C) Tabel ini hanya digunakan pada beberapa mesin (misalnya dalam pengembangan).

  • Solusi: Pindahkan model ke aplikasi baru yang ditambahkan ke INSTALLED_APPS hanya dalam kondisi khusus atau gunakan kondisional class Meta: managed = some_switch.

D) Proyek ini menggunakan banyak basis data disettings.DATABASES

  • Solusi: Tulis router Database dengan metode allow_migrateuntuk membedakan database di mana tabel harus dibuat dan di mana tidak.

Migrasi dibuat dalam semua kasus A), B), C), D) dengan Django 1.9+ (dan hanya dalam kasus B, C, D dengan Django 1.8), tetapi diterapkan ke database hanya dalam kasus yang sesuai atau mungkin tidak pernah jika diperlukan begitu. Migrasi diperlukan untuk menjalankan tes sejak Django 1.8. Keadaan saat ini yang relevan lengkap dicatat oleh migrasi bahkan untuk model dengan managed = False di Django 1.9+ agar dimungkinkan untuk membuat ForeignKey antara model yang dikelola / tidak dikelola atau untuk dapat membuat model dikelola = Benar nanti. (Pertanyaan ini telah ditulis pada saat Django 1.8. Semuanya di sini harus valid untuk versi antara 1,8 hingga saat ini 2.2.)

Jika migrasi terakhir tidak mudah dikembalikan maka dimungkinkan untuk berhati-hati (setelah cadangan basis data) melakukan pengembalian palsu ./manage.py migrate --fake my_app 0010_previous_migration , hapus tabel secara manual.

Jika perlu, buat migrasi tetap dari model tetap dan terapkan tanpa mengubah struktur basis data ./manage.py migrate --fake my_app 0011_fixed_migration.

hynekcer
sumber
3

Jika Anda menghadapi masalah saat mengembalikan migrasi, dan entah bagaimana telah mengacaukannya, Anda dapat melakukan fakemigrasi.

./manage.py migrate <name> --ignore-ghost-migrations --merge --fake

Untuk versi Django <1,7 ini akan membuat entri dalam south_migrationhistorytabel, Anda perlu menghapus entri itu.

Sekarang Anda dapat mengembalikan migrasi dengan mudah.

PS: Saya macet selama banyak waktu dan melakukan migrasi palsu dan kemudian kembali membantu saya.

Pransh Tiwari
sumber
1
Jawaban ini untuk Django <1.7.
hynekcer