Comment annuler la dernière migration?

446

J'ai effectué une migration qui a ajouté une nouvelle table et je souhaite la rétablir et supprimer la migration, sans créer de nouvelle migration.

Comment fait-on ça? Existe-t-il une commande pour annuler la dernière migration et puis-je simplement supprimer le fichier de migration?

Ronen Ness
la source

Réponses:

794

Vous pouvez revenir en arrière en migrant vers la migration précédente.

Par exemple, si vos deux dernières migrations sont:

  • 0010_previous_migration
  • 0011_migration_to_revert

Ensuite, vous feriez:

./manage.py migrate my_app 0010_previous_migration 

Vous pouvez ensuite supprimer la migration 0011_migration_to_revert.

Si vous utilisez Django 1.8+, vous pouvez afficher les noms de toutes les migrations avec

./manage.py showmigrations my_app

Pour inverser toutes les migrations d'une application, vous pouvez exécuter:

./manage.py migrate my_app zero
Alasdair
la source
7
J'ai vu de nombreuses réponses sur SO à ce problème qui sont anciennes et ne fonctionnent tout simplement plus. +1 car cela fonctionne avec Django 1.8.
AlanSE
2
Comment faire si l'application n'a qu'un seul fichier de migration / migration initiale. et je dois annuler cette migration initiale?
Adiyat Mubarak
37
La migratecommande vous permet ./manage.py migrate my_app zerode désappliquer toutes les migrations de l'application.
Alasdair
4
Pour une raison quelconque, ./manage.py migrate my_app 0010_previous_migration n'a pas fonctionné pour moi. J'ai donc essayé d'utiliser ./manage.py migrate my_app 0010 et cela a fonctionné. Y a-t-il des raisons pour lesquelles Django 1.8 ne prendra pas l'intégralité du nom de la migration?
Varun Verma
4
Tant que vous utilisez votre nom de migration réel, et non '0010_previous_migration', je ne sais pas pourquoi vous verriez ce comportement.
Alasdair
36

La réponse d'Alasdair couvre les bases

  • Identifiez les migrations que vous souhaitez par ./manage.py showmigrations
  • migrate en utilisant le nom de l'application et le nom de la migration

Mais il convient de souligner que toutes les migrations ne peuvent pas être inversées. Cela se produit si Django n'a pas de règle pour effectuer l'inversion. Pour la plupart des modifications par lesquelles vous avez effectué automatiquement les migrations ./manage.py makemigrations, l'annulation sera possible. Cependant, les scripts personnalisés devront avoir à la fois un avant et un arrière écrit, comme décrit dans l'exemple ici:

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

Comment faire une inversion sans opération

Si vous avez eu une RunPythonopération, vous souhaiterez peut-être simplement annuler la migration sans écrire un script d'inversion logiquement rigoureux. Le hack rapide suivant de l'exemple de la documentation (lien ci-dessus) le permet, en laissant la base de données dans le même état qu'elle était après l'application de la migration, même après l'avoir inversée.

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

Cela fonctionne pour Django 1.8, 1.9


Mise à jour: Une meilleure façon d'écrire ceci serait de la remplacer lambda apps, schema_editor: Nonepar migrations.RunPython.noopdans l'extrait ci-dessus. Ce sont deux fonctionnellement la même chose. (crédit aux commentaires)

AlanSE
la source
5
À partir de Django 1.8, vous devez utiliser à la RunPython.noopplace d'un lambda en ligne ou d'un équivalent: docs.djangoproject.com/en/1.8/ref/migration-operations/…
SpoonMeiser
@SpoonMeiser Dans la syntaxe de l'exemple, je pense que cela ressemble migrations.RunPython(forwards_func, migrations.RunPython.noop). Besoin de vérifier cela fonctionnellement. Cela devrait être ajouté en tant que réponse ou modification à celle-ci un jour.
AlanSE
13

Voici ma solution, car la solution ci-dessus ne couvre pas vraiment le cas d'utilisation, lorsque vous utilisez RunPython.

Vous pouvez accéder à la table via l'ORM avec

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

Vous pouvez donc interroger les tables et supprimer les entrées qui vous concernent. De cette façon, vous pouvez modifier en détail. Avec les RynPythonmigrations, vous devez également prendre soin des données qui ont été ajoutées / modifiées / supprimées. L'exemple ci-dessus montre uniquement comment vous accédez à la table via Djang ORM.

Özer S.
la source
Lorsque vous créez de nouveaux modèles avec ForeignKeys, avec plusieurs migrations, puis réalisez que tout va mal, et redémarrez 2-3 migrations en arrière, avec de nouveaux modèles mais parfois les mêmes noms de modèle ou les mêmes noms de relation ... cette solution est clairement gagnante. J'ai eu un message d'erreur comme celui- django.db.utils.ProgrammingError: relation "<relation name>" already existsci, j'ai donc fait un migrate --fakequi est faux, alors j'ai essayé de revenir en arrière, puis j'ai eu un psycopg2.ProgrammingError: relation "<other <relation name>" does not existMERCI
onekiloparsec
10

L'autre chose que vous pouvez faire est de supprimer la table créée manuellement.

Parallèlement à cela, vous devrez supprimer ce fichier de migration particulier. De plus, vous devrez supprimer cette entrée particulière dans la table django-migrations (probablement la dernière dans votre cas) qui correspond à cette migration particulière.

sprksh
la source
soyez prudent dans ce cas - vous êtes obligé de vérifier que db est adéquat.
Sławomir Lenart
4
J'ajouterais TRÈS prudent. Vous pourriez casser beaucoup de choses dans Postgres, par exemple des contraintes.
joedborg
9

Ne supprimez le fichier de migration qu'après la réversion. J'ai fait cette erreur et sans le fichier de migration, la base de données ne savait pas quoi supprimer.

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

Supprimez le fichier de migration. Une fois la migration souhaitée dans vos modèles ...

python manage.py makemigrations
python manage.py migrate
DanGoodrick
la source
8

Je l'ai fait dans 1.9.1 (pour supprimer la dernière ou la dernière migration créée):

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

    exemple: rm myapp/migrations/0011*

  2. connecté à la base de données et exécuté ce SQL (postgres dans cet exemple)

    delete from django_migrations where name like '0011%';

J'ai ensuite pu créer de nouvelles migrations qui ont commencé avec le numéro de migration que je venais de supprimer (dans ce cas, 11).

MIkee
la source
1
+1 Bien que cela fonctionne, vous devez enregistrer cette méthode en dernier recours. Vous devez également vous rappeler de modifier / supprimer les colonnes / tableaux dont la migration problématique a contribué.
nehem
bon point - j'ai utilisé ceci quand j'ai créé une migration mais je n'ai pas encore exécuté "./manage.py migrate"
MIkee
3

Cette réponse est pour des cas similaires si la première réponse d'Alasdair n'aide pas . (Par exemple, si la migration indésirable est créée à nouveau à chaque nouvelle migration ou si elle se trouve dans une migration plus importante qui ne peut pas être annulée ou si la table a été supprimée manuellement.)

... supprimer la migration, sans créer de nouvelle migration?

TL; DR : Vous pouvez supprimer quelques dernières migrations annulées (confuses) et en faire une nouvelle après avoir corrigé les modèles . Vous pouvez également utiliser d'autres méthodes pour le configurer afin de ne pas créer de table par commande migrate. La dernière migration doit être créée pour correspondre aux modèles actuels .


Cas où quelqu'un ne veut pas créer une table pour un modèle qui doit exister:

A) Aucune telle table ne devrait exister dans aucune base de données sur aucune machine et aucune condition

  • Quand: il s'agit d'un modèle de base créé uniquement pour l'héritage de modèle d'un autre modèle.
  • Solution: définirclass Meta: abstract = True

B) La table est créée rarement, par autre chose ou manuellement d'une manière spéciale.

  • Solution: Utilisation class Meta: managed = False
    La migration est créée, mais jamais utilisée, uniquement dans les tests. Le fichier de migration est important, sinon les tests de base de données ne peuvent pas s'exécuter, à partir d'un état initial reproductible.

C) La table est utilisée uniquement sur certaines machines (par exemple en développement).

  • Solution: déplacez le modèle vers une nouvelle application ajoutée à INSTALLED_APPS uniquement dans des conditions spéciales ou utilisez un conditionnel class Meta: managed = some_switch.

D) Le projet utilise plusieurs bases de donnéessettings.DATABASES

  • Solution: Écrivez un routeur de base de données avec une méthode allow_migrateafin de différencier les bases de données où la table doit être créée et où pas.

La migration est créée dans tous les cas A), B), C), D) avec Django 1.9+ (et seulement dans les cas B, C, D avec Django 1.8), mais appliquée à la base de données uniquement dans les cas appropriés ou peut-être jamais si nécessaire. Les migrations ont été nécessaires pour exécuter des tests depuis Django 1.8. L'état actuel complet pertinent est enregistré par les migrations même pour les modèles avec managed = False dans Django 1.9+ pour être possible de créer une clé étrangère entre les modèles gérés / non gérés ou pour rendre le modèle géré = True plus tard. (Cette question a été écrite à l'époque de Django 1.8. Tout ici devrait être valable pour les versions entre 1.8 et la 2.2 actuelle.)

Si la dernière migration n'est pas (ou n'est pas) facilement réversible, il est possible de faire prudemment (après la sauvegarde de la base de données) un faux rétablissement ./manage.py migrate --fake my_app 0010_previous_migration , supprimez la table manuellement.

Si nécessaire, créez une migration fixe à partir du modèle fixe et appliquez-la sans modifier la structure de la base de données ./manage.py migrate --fake my_app 0011_fixed_migration.

hynekcer
la source
3

Si vous rencontrez des problèmes lors de l'annulation de la migration et que vous l'avez en quelque sorte gâché, vous pouvez effectuer des fakemigrations.

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

Pour la version django <1.7, cela créera une entrée dans le south_migrationhistorytableau, vous devez supprimer cette entrée.

Vous pourrez désormais revenir facilement à la migration.

PS: J'ai été coincé pendant beaucoup de temps et j'ai effectué une fausse migration, puis revenir en arrière m'a aidé.

Pransh Tiwari
la source
1
Cette réponse est pour Django <1.7.
hynekcer