Jak cofnąć ostatnią migrację?

446

Przeprowadziłem migrację, która dodała nową tabelę i chcę ją przywrócić i usunąć migrację bez tworzenia nowej migracji.

Jak mam to zrobić? Czy istnieje polecenie cofnięcia ostatniej migracji, a następnie mogę po prostu usunąć plik migracji?

Ronen Ness
źródło

Odpowiedzi:

795

Możesz przywrócić, migrując do poprzedniej migracji.

Na przykład, jeśli dwie ostatnie migracje to:

  • 0010_previous_migration
  • 0011_migration_to_revert

Następnie zrobiłbyś:

./manage.py migrate my_app 0010_previous_migration 

Następnie możesz usunąć migrację 0011_migration_to_revert.

Jeśli używasz Django 1.8+, możesz wyświetlać nazwy wszystkich migracji za pomocą

./manage.py showmigrations my_app

Aby cofnąć wszystkie migracje aplikacji, możesz uruchomić:

./manage.py migrate my_app zero
Alasdair
źródło
7
Widziałem wiele odpowiedzi na SO dla tego problemu, które są stare i po prostu już nie działają. +1, ponieważ działa z Django 1.8.
AlanSE
2
Jak, jeśli aplikacja ma tylko jeden plik migracji / migrację początkową. i muszę cofnąć tę początkową migrację?
Adiyat Mubarak,
37
migrateLet komenda jest użyć ./manage.py migrate my_app zerodo wycofania zastosowania wszystkie migracje dla aplikacji.
Alasdair,
4
Z jakiegoś powodu ./manage.py migracja moja_aplikacja 0010_poprzednia_migracja nie działała dla mnie. Próbowałem więc użyć ./manage.py migrować my_app 0010 i zadziałało. Jakieś powody, dla których Django 1.8 nie przyjmuje całej nazwy migracji?
Varun Verma
4
Tak długo, jak używasz swojej rzeczywistej nazwy migracji, a nie '0010_previous_migration', nie wiem, dlaczego widziałbyś to zachowanie.
Alasdair,
36

Odpowiedź Alasdair obejmuje podstawy

  • Zidentyfikuj migracje, według których chcesz ./manage.py showmigrations
  • migrate za pomocą nazwy aplikacji i nazwy migracji

Należy jednak zauważyć, że nie wszystkie migracje można odwrócić. Dzieje się tak, jeśli Django nie ma reguły umożliwiającej odwrócenie. W przypadku większości zmian, za pomocą których migracje były automatycznie wprowadzane ./manage.py makemigrations, cofnięcie będzie możliwe. Jednak skrypty niestandardowe będą musiały mieć napisane zarówno do przodu, jak i do tyłu, jak opisano w przykładzie tutaj:

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

Jak zrobić wycofanie bez operacji

Jeśli miałeś RunPythonoperację, być może chcesz po prostu wycofać migrację bez pisania logicznie rygorystycznego skryptu cofania. Pozwala na to szybki hack do przykładu z dokumentów (powyższy link), pozostawiając bazę danych w takim samym stanie, w jakim była po zastosowaniu migracji, nawet po jej cofnięciu.

# -*- 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),
    ]

Działa to dla Django 1.8, 1.9


Aktualizacja: Lepszym sposobem na piśmie, to byłoby zastąpienie lambda apps, schema_editor: Noneze migrations.RunPython.noopw powyższym fragmencie. Oba są funkcjonalnie tym samym. (podziękowania dla komentarzy)

AlanSE
źródło
5
Począwszy od Django 1.8, powinieneś używać RunPython.noopzamiast wbudowanej lambda lub ekwiwalentu: docs.djangoproject.com/en/1.8/ref/migration-operations/…
SpoonMeiser
@SpoonMeiser Wydaje mi się, że w składni tego przykładu migrations.RunPython(forwards_func, migrations.RunPython.noop). Musisz to sprawdzić funkcjonalnie. To powinno kiedyś zostać dodane jako odpowiedź lub edycja.
AlanSE,
13

Oto moje rozwiązanie, ponieważ powyższe rozwiązanie tak naprawdę nie obejmuje przypadku użycia, gdy używasz RunPython.

Dostęp do tabeli można uzyskać poprzez ORM za pomocą

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})

Możesz więc wyszukiwać w tabelach i usuwać te wpisy, które są dla Ciebie odpowiednie. W ten sposób możesz szczegółowo modyfikować. W przypadku RynPythonmigracji musisz także dbać o dane, które zostały dodane / zmienione / usunięte. Powyższy przykład pokazuje tylko, w jaki sposób uzyskujesz dostęp do tabeli za pomocą Djang ORM.

Özer S.
źródło
Podczas tworzenia nowych modeli za pomocą ForeignKeys, z wieloma migracjami, uświadomienie sobie wszystkiego jest złe i ponowne uruchomienie 2-3 migracji wstecz, z nowymi modelami, ale czasami tymi samymi nazwami modeli lub tymi samymi nazwami relacji ... to rozwiązanie jest zdecydowanie zwycięzcą. Miałem komunikat django.db.utils.ProgrammingError: relation "<relation name>" already existso migrate --fakebłędzie, więc zrobiłem błąd, więc próbowałem wrócić, ale psycopg2.ProgrammingError: relation "<other <relation name>" does not existDZIĘKI
onekiloparsec
10

Inną rzeczą, którą możesz zrobić, to usunąć tabelę utworzoną ręcznie.

Oprócz tego będziesz musiał usunąć ten konkretny plik migracji. Będziesz także musiał usunąć ten konkretny wpis z tabeli migracji django (prawdopodobnie ostatni w twoim przypadku), który koreluje z tą konkretną migracją.

sprksh
źródło
bądź ostrożny w tym przypadku - musisz zweryfikować db, aby był odpowiedni.
Sławomir Lenart
4
Dodałbym BARDZO ostrożnie. Możesz złamać wiele rzeczy w Postgres, na przykład ograniczenia.
joedborg
9

Nie usuwaj pliku migracji, dopóki nie zostanie przywrócone. Popełniłem ten błąd i bez pliku migracji baza danych nie wiedziała, co należy usunąć.

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

Usuń plik migracji. Gdy pożądana migracja znajdzie się w Twoich modelach ...

python manage.py makemigrations
python manage.py migrate
DanGoodrick
źródło
8

Zrobiłem to w wersji 1.9.1 (aby usunąć ostatnią lub najnowszą utworzoną migrację):

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

    przykład: rm myapp/migrations/0011*

  2. zalogowałem się do bazy danych i uruchomiłem ten SQL (postgres w tym przykładzie)

    delete from django_migrations where name like '0011%';

Byłem wtedy w stanie utworzyć nowe migracje, które zaczęły się od właśnie usuniętego numeru migracji (w tym przypadku 11).

Meeee
źródło
1
+1 Chociaż to zadziała, musisz zapisać ten sposób w ostateczności. Musisz także pamiętać o edycji / upuszczaniu kolumn / tabel, w których udział miała problematyczna migracja.
nehem
dobra uwaga - korzystałem z tego podczas tworzenia migracji, ale nie uruchomiłem jeszcze „./manage.py migrate”
MIkee
3

Ta odpowiedź dotyczy podobnych przypadków, jeśli najlepsza odpowiedź Alasdair nie pomaga . (Na przykład, jeśli niechciana migracja jest tworzona wkrótce przy każdej nowej migracji lub jeśli jest w większej migracji, której nie można przywrócić lub tabela została usunięta ręcznie.)

... usunąć migrację bez tworzenia nowej migracji?

TL; DR : Możesz usunąć kilka ostatnio cofniętych (zdezorientowanych) migracji i utworzyć nową po naprawieniu modeli . Możesz także użyć innych metod, aby skonfigurować go tak, aby nie tworzył tabeli za pomocą polecenia migrowania. Ostatnia migracja musi zostać utworzona, aby pasowała do aktualnych modeli .


Przypadki, dla których nikt nie chce tworzyć tabeli dla modelu, który musi istnieć:

A) Żadna taka tabela nie powinna istnieć w żadnej bazie danych na żadnym komputerze i żadnych warunkach

  • Kiedy: Jest to model podstawowy utworzony tylko dla dziedziczenia modelu innego modelu.
  • Rozwiązanie: Ustawclass Meta: abstract = True

B) Tabela jest tworzona rzadko, przez coś innego lub ręcznie w specjalny sposób.

  • Rozwiązanie: Zastosowanie class Meta: managed = False
    Migracja jest tworzona, ale nigdy nie używana, tylko w testach. Plik migracji jest ważny, w przeciwnym razie testy bazy danych nie będą mogły zostać uruchomione, począwszy od odtwarzalnego stanu początkowego.

C) Tabela jest używana tylko na niektórych komputerach (np. W fazie rozwoju).

  • Rozwiązanie: przenieś model do nowej aplikacji, która jest dodawana do INSTALLED_APPS tylko w specjalnych warunkach lub użyj warunkowego class Meta: managed = some_switch.

D) Projekt wykorzystuje wiele baz danych wsettings.DATABASES

  • Rozwiązanie: Napisz router bazy danych metodą allow_migrate, aby rozróżnić bazy danych, w których należy utworzyć tabelę, a gdzie nie.

Migracja jest tworzona we wszystkich przypadkach A), B), C), D) za pomocą Django 1.9+ (i tylko w przypadkach B, C, D z Django 1.8), ale jest stosowana do bazy danych tylko w odpowiednich przypadkach, a może nigdy, jeśli wymagane tak. Migracje były niezbędne do uruchomienia testów od wersji Django 1.8. Całkowity odpowiedni aktualny stan jest rejestrowany przez migracje, nawet dla modeli z parametrem manage = False w Django 1.9+, aby możliwe było utworzenie klucza obcego między modelami zarządzanymi / niezarządzanymi lub sprawienie, by model zarządzany = True później. (To pytanie zostało napisane w czasie Django 1.8. Wszystko tutaj powinno być ważne dla wersji od 1.8 do obecnej wersji 2.2.)

Jeśli ostatniej migracji nie da się łatwo przywrócić, możliwe jest ostrożne (po utworzeniu kopii zapasowej bazy danych) wykonanie fałszywego przywrócenia ./manage.py migrate --fake my_app 0010_previous_migration , ręczne usunięcie tabeli.

W razie potrzeby utwórz stałą migrację ze stałego modelu i zastosuj ją bez zmiany struktury bazy danych ./manage.py migrate --fake my_app 0011_fixed_migration.

hynekcer
źródło
3

Jeśli masz problemy z przywracaniem migracji i w jakiś sposób ją pomieszałeś, możesz przeprowadzić fakemigrację.

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

W przypadku wersji django <1.7 spowoduje to utworzenie wpisu w south_migrationhistorytabeli, musisz go usunąć.

Teraz możesz łatwo cofnąć migrację.

PS: Utknąłem przez długi czas i przeprowadzanie fałszywej migracji, a następnie cofanie się pomogło mi.

Pransh Tiwari
źródło
1
Ta odpowiedź dotyczy Django <1.7.
hynekcer