Parfois, lorsqu’on crée une migration, on fait des erreurs. Ne vous inquiétez pas ! Il n'y a pas lieu de paniquer.
Il y a deux approches possibles lorsqu'une migration doit être retirée.
Annulez une migration non désirée
Tout d'abord, créons notre migration indésirable. Nous voulons ajouter un champ like_new
au modèle Listing
mais « Zut ! », nous l'avons ajouté au modèle Band
à la place et nous avons exécuté notre migration.
Dans models.py, ajoutez :
class Band(models.Model):
…
like_new = models.fields.BooleanField(default=False)
puis exécutez dans le terminal :
(env) ~/projects/django-web-app/merchex
→ python manage.py makemigrations
Migrations for 'listings':
listings/migrations/0006_band_like_new.py
- Add field like_new to band
(env) ~/projects/django-web-app/merchex (master)
→ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, listings, sessions
Running migrations:
Applying listings.0006_band_like_new... OK
Nous avons donc maintenant une migration non désirée. Dans ce scénario, l'erreur n'a été commise que sur notre propre machine, elle n'a pas été partagée avec d'autres développeurs, c'est-à-dire qu'elle n'a pas été téléchargée sur GitHub et récupérée par qui que ce soit, ni exécutée ailleurs, par exemple sur notre base de données de production.
Si c'est le cas, nous pouvons simplement annuler la migration localement et la supprimer.
Pour ce faire, commencez par lister toutes les migrations en utilisant python manage.py showmigrations
.
(env) ~/projects/django-web-app/merchex
→ python manage.py showmigrations
admin
[X] 0001_initial
[X] 0002_logentry_remove_auto_add
[X] 0003_logentry_add_action_flag_choices
auth
[X] 0001_initial
[X] 0002_alter_permission_name_max_length
[X] 0003_alter_user_email_max_length
[X] 0004_alter_user_username_opts
[X] 0005_alter_user_last_login_null
[X] 0006_require_contenttypes_0002
[X] 0007_alter_validators_add_error_messages
[X] 0008_alter_user_username_max_length
[X] 0009_alter_user_last_name_max_length
[X] 0010_alter_group_name_max_length
[X] 0011_update_proxy_permissions
[X] 0012_alter_user_first_name_max_length
contenttypes
[X] 0001_initial
[X] 0002_remove_content_type_name
listings
[X] 0001_initial
[X] 0002_listing
[X] 0003_auto_20210329_2350
[X] 0004_auto_20210524_2030
[X] 0005_listing_band ← Il s’agit de la migration précédente
[X] 0006_band_like_new ← C’est notre migration indésirable
sessions
[X] 0001_initial
Identifiez ensuite la migration qui doit être annulée, dans notre cas il s'agit de 0006_band_like_new
. Ensuite, récupérez le nom de la migration précédente vers celle-ci : 0005_listing_band
. Vous devrez également noter le nom de l'application, qui est ici listings
.
Maintenant, pour annuler la migration, utilisez la commande python manage.py migrate
en spécifiant le nom de l'application et la migration précédente. Cela ramènera toutes les migrations ultérieures à celle que nous avons ciblée.
(env) ~/projects/django-web-app/merchex
→ python manage.py migrate listings 0005_listing_band
Operations to perform:
Target specific migration: 0005_listing_band, from listings
Running migrations:
Rendering model states... DONE
Unapplying listings.0006_band_like_new... OK
Nous pouvons voir que la migration listings.0006_band_like_new
n'a pas été appliquée ; si nous affichons à nouveau les migrations, nous pouvons voir que la migration n'a plus de X
à côté d'elle, ce qui indique qu'elle n'a pas été exécutée.
→ python manage.py showmigrations listings # ne liste que les migrations de l’application listings
listings
[X] 0001_initial
[X] 0002_listing
[X] 0003_auto_20210329_2350
[X] 0004_auto_20210524_2030
[X] 0005_listing_band
[ ] 0006_band_like_new
Maintenant que la migration a été annulée localement, nous sommes libres de la supprimer. Mais nous allons utiliser cette migration pour montrer une autre stratégie de retour en arrière après des changements, alors laissons-la ici pour l'instant.
(env) ~/projects/django-web-app/merchex
→ rm listings/migrations/0006_band_like_new.py # nous pouvons ainsi la supprimer, mais ne le faisons pas pour le moment !
Rétablissez les changements avec une nouvelle migration
L'approche exposée dans la section précédente n'est valable que si la migration indésirable n'a pas été partagée. Cependant, si c'est le cas, cette méthode ne doit pas être utilisée !
Si les modifications non souhaitées ont été appliquées sur une autre machine, vous devrez créer une nouvelle migration pour annuler les modifications.
Pour cela, il suffit de modifier votre fichier models,py jusqu'à ce qu'il soit au format souhaité.
class Band(models.Model):
…
# like_new = models.fields.BooleanField(default=False) <-- SUPPRIMER CETTE LIGNE
et ensuite créer et exécuter vos migrations.
(env) ~/projects/django-web-app/merchex
→ python manage.py makemigrations
Migrations for 'listings':
listings/migrations/0007_remove_band_like_new.py
- Remove field like_new from band
(env) ~/projects/django-web-app/merchex
→ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, listings, sessions
Running migrations:
Applying listings.0007_remove_band_like_new... OK
La base de données est maintenant revenue à son état antérieur et si quelqu'un a exécuté la migration indésirable sur sa machine, une fois qu'il aura exécuté cette migration, sa machine le fera aussi !
Si vous souhaitez revoir l'une des stratégies précédentes de retour en arrière des migrations, suivez les étapes du screencast pour vous rafraîchir la mémoire.
C'est à vous ! Créez et annulez une migration
Maintenant, c'est votre tour ! Ajoutez un champ indésirable au modèle Listing
, puis créez et exécutez la migration.
Maintenant vous devez annuler les changements. C'est à vous de choisir la stratégie que vous voulez utiliser, mais assurez-vous qu'une fois que vous avez terminé, la base de données est dans le même état que lorsque vous aviez commencé.
Fusionnez les migrations pour résoudre des conflits
Le dernier piège des migrations que nous allons aborder est celui des conflits de migration. Il s'agit d'un scénario peu fréquent si vous travaillez seul sur un projet sur une seule branche Git, mais qui se présente fréquemment lorsque vous travaillez sur des projets avec d'autres développeurs, ou lorsque vous utilisez différentes branches pour ajouter des fonctionnalités à votre projet.
Dans ce scénario, nous ajoutons deux champs,hometown
et record_company
, à Band
.
Disons que deux développeurs différents ont travaillé sur ces fonctionnalités. Le premier a ajouté le champ ville natale (hometown) sur une branche de fonctionnalité FEATURE-add-hometown-to-band
. Il a ensuite apporté les modifications au fichier models.py
et créé les migrations correspondantes.
(env) ~/projects/django-web-app/merchex (master)
→ git checkout -b FEATURE-add-hometown-to-band
Switched to a new branch 'FEATURE-add-hometown-to-band'
(env) ~/projects/django-web-app/merchex (FEATURE-add-hometown-to-band)
→ python manage.py makemigrations
Migrations for 'listings':
listings/migrations/0006_band_hometown.py
- Add field hometown to band
(env) ~/projects/django-web-app/merchex (FEATURE-add-hometown-to-band)
→ git add listings/
(env) ~/projects/django-web-app/merchex (FEATURE-add-hometown-to-band)
→ git commit -m 'added hometown field to band'
[FEATURE-add-hometown-to-band c577b20] added hometown field to band
2 files changed, 19 insertions(+)
create mode 100644 merchex/listings/migrations/0006_band_hometown.py
Au même moment, un autre développeur a ajouté le champ record_company
à Band
sur sa propre branche de fonctionnalités.
(env) ~/projects/django-web-app/merchex (master)
→ git checkout -b FEATURE-add-record-company-to-band
Switched to a new branch 'FEATURE-add-record-company-to-band'
(env) ~/projects/django-web-app/merchex (FEATURE-add-record-company-to-band)
→ python manage.py makemigrations
Migrations for 'listings':
listings/migrations/0006_band_record_company.py
- Add field record_company to band
(env) ~/projects/django-web-app/merchex (FEATURE-add-record-company-to-band)
→ git add listings/
(env) ~/projects/django-web-app/merchex (FEATURE-add-record-company-to-band)
→ git commit -m 'added record_company field to Band'
[FEATURE-add-record-company-to-band 406ccdf] added record_company field to Band
2 files changed, 19 insertions(+)
create mode 100644 merchex/listings/migrations/0006_band_record_company.py
Ensuite, ces deux branches de fonctionnalités ont été fusionnées dans la branche master
.
→ git merge FEATURE-add-record-company-to-band
Updating eef83c9..406ccdf
Fast-forward
merchex/listings/migrations/0006_band_record_company.py | 18 ++++++++++++++++++
merchex/listings/models.py | 1 +
2 files changed, 19 insertions(+)
create mode 100644 merchex/listings/migrations/0006_band_record_company.py
→ git merge FEATURE-add-hometown-to-band
Updating eef83c9..c577b20
Fast-forward
merchex/listings/migrations/0006_band_hometown.py | 18 ++++++++++++++++++
merchex/listings/models.py | 1 +
2 files changed, 19 insertions(+)
create mode 100644 merchex/listings/migrations/0006_band_hometown.py
Mais maintenant, si nous inspectons nos migrations pour l'application listings
, nous pouvons voir que nous avons à présent deux migrations préfixées avec 0006
.
(env) ~/projects/django-web-app/merchex (master)
→ pm showmigrations listings
listings
[X] 0001_initial
[X] 0002_listing
[X] 0003_auto_20210329_2350
[X] 0004_auto_20210524_2030
[X] 0005_listing_band
[ ] 0006_band_hometown
[ ] 0006_band_record_company
En plus, si nous essayons de les migrer, nous obtenons un message d'erreur.
(env) ~/projects/django-web-app/merchex (master)
→ python manage.py migrate
CommandError: Conflicting migrations detected; multiple leaf nodes in the migration graph: (0006_band_record_company, 0006_band_hometown in listings).
To fix them run 'python manage.py makemigrations --merge'
Comme ces deux migrations ont été créées sans que l'autre ne soit connue, Django n'a aucune idée de la migration à exécuter en premier.
Heureusement, nous pouvons fusionner ces migrations pour qu'elles fonctionnent correctement, en utilisant le flag --merge
avec makemigrations
.
→ python manage.py makemigrations --merge
Merging listings
Branch 0006_band_hometown
- Add field hometown to band
Branch 0006_band_record_company
- Add field record_company to band
Merging will only work if the operations printed above do not conflict
with each other (working on different fields or models)
Do you want to merge these migration branches? [y/N] y
Created new merge migration ~/projects/django-web-app/merchex/listings/migrations/0007_merge_20210701_1911.py
Il ne nous reste plus qu'à migrer !
(env) ~/projects/django-web-app/merchex (master)
→ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, listings, sessions
Running migrations:
Applying listings.0006_band_record_company... OK
Applying listings.0006_band_hometown... OK
Applying listings.0007_merge_20210701_1911... OK
En résumé
Lorsqu'il s'agit de rétablir les changements issus des migrations, il existe deux stratégies principales.
Si les modifications non souhaitées n'ont pas été partagées avec d'autres utilisateurs, vous pouvez annuler la migration localement, puis la supprimer.
Si les changements ont été partagés, il est préférable de créer une nouvelle migration qui annule les changements de la migration non désirée.
Parfois, lorsque vous travaillez sur un projet avec d'autres développeurs, vous risquez d’être confronté à des migrations conflictuelles. Si ces migrations concernent des champs ou des modèles différents, vous pouvez les fusionner ; sinon, supprimez-les et créez de nouvelles migrations à la place.
Dans la troisième partie, nous avons complété nos modèles avec d'autres champs, notamment en reliant nos modèles entre eux dans une relation de type « one-to-many ». Nous avons également examiné certaines stratégies de migration qui nous aideront à modifier les champs des modèles. Nous avons pu définir des valeurs pour ces champs dans les nouveaux objets que nous créons via l'administration Django, ainsi que dans les objets existants.
Maintenant, nous devons construire une interface CRUD dans notre frontend : une interface que nos utilisateurs peuvent utiliser, et c'est justement ce que nous allons faire dans la partie 4. Mais avant ça, le quiz !