## Qu'est-ce qu'une migration ? Une migration est un fichier Python qui contient des instructions pour modifier la base de données. Une base de données évolue au cours du temps, et les migrations permettent de garder une trace de ces modifications. Grâce à elles, on peut également apporter des modifications à la base de données sans être obligées de la recréer. On applique seulement les modifications nécessaires. ## Appliquer les migrations Pour appliquer les migrations, exécutez la commande suivante : ```bash python ./manage.py migrate ``` Vous remarquerez peut-être que cette commande a été utilisée dans la section [Installation](../tutorial/install.md). En effet, en partant d'une base de données vierge et en appliquant toutes les migrations, on arrive à l'état actuel de la base de données. Logique. Si vous utilisez cette commande sur une base de données sur laquelle toutes les migrations ont été appliquées, elle ne fera rien. Si vous utilisez cette commande sur une base de données sur laquelle seule une partie des migrations ont été appliquées, seules les migrations manquantes seront appliquées. ## Créer une migration Pour créer une migration, exécutez la commande suivante : ```bash python ./manage.py makemigrations ``` Cette commande comparera automatiquement le contenu des classes de modèles et le comparera avec les migrations déjà appliquées. A partir de cette comparaison, elle générera automatiquement une nouvelle migration. !!! note La commande `makemigrations` ne fait que générer les fichiers de migration. Elle ne modifie pas la base de données. Pour appliquer la migration, n'oubliez pas la commande `migrate`. Un fichier de migration ressemble à ça : ```python from django.db import migrations class Migration(migrations.Migration): dependencies = [ # liste des autres migrations à appliquer avant celle-ci ] operations = [ # liste des opérations à appliquer sur la db ] ``` Grâce à la liste des dépendances, Django sait dans quel ordre les migrations doivent être appliquées. Grâce à la liste des opérations, Django sait quelles sont les opérations à appliquer durant cette migration. ## Revenir à une migration antérieure Lorsque vous développez, il peut arriver que vous vouliez revenir à une migration antérieure. Pour cela, il suffit d'appliquer la commande `migrate` en spécifiant le nom de la migration à laquelle vous voulez revenir : ```bash python ./manage.py migrate ``` Par exemple, si vous voulez revenir à la migration `0001_initial` de l'application `customer`, vous pouvez exécuter la commande suivante : ```bash python ./manage.py migrate customer 0001 ``` ## Customiser une migration Il peut arriver que vous ayez besoin de modifier le fichier de migration généré par Django. Par exemple, si vous voulez exécuter un script Python lors de l'application de la migration. Dans ce cas, vous pouvez trouver les fichiers de migration dans le dossier `migrations` de chaque application. Vous pouvez modifier le fichier Python correspondant à la migration que vous voulez modifier. Ajoutez l'opération que vous voulez effectuer dans l'attribut `operations` de la classe `Migration`. Par exemple : ```python from django.db import migrations def forwards_func(apps, schema_editor): print("Appplication de la migration") def reverse_func(apps, schema_editor): print("Annulation de la migration") class Migration(migrations.Migration): dependencies = [] operations = [ migrations.RunPython(forwards_func, reverse_func), ] ``` !!! warning "Script d'annulation de la migration" Lorsque vous incluez un script Python dans une migration, incluez toujours aussi un script d'annulation, sinon Django ne pourra pas annuler la migration après son application. Vous ne pourrez donc pas revenir à un état antérieur de la db, à moins de la recréer de zéro. ## Fusionner des migrations Quand on travaille sur une fonctionnalité qui nécessite une modification de la base de données, les fichiers de migration sont comme toute chose : on peut se rendre compte que les changements apportés pourraient être meilleurs. Par exemple, supposons que nous voulons créer un modèle représentant une UE suivie par un étudiant (ne demandez pas pourquoi on voudrait faire ça, c'est juste pour l'exemple). Un tel modèle aurait besoin des informations suivantes : - l'utilisateur - le code de l'UE On écrirait donc, dans l'application `pedagogy` : ```python from django.db import models from core.models import User class UserUe(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) ue = models.CharField(max_length=10) ``` Et nous aurions le fichier de migration suivant : ```python from django.db import migrations, models from django.conf import settings class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ("pedagogy", "0003_alter_uv_language"), ] operations = [ migrations.CreateModel( name="UserUe", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("ue", models.CharField(max_length=10)), ( "user", models.ForeignKey( on_delete=models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, ), ), ], ), ] ``` On finit son travail, on soumet la PR. Mais là, quelqu'un fait remarquer qu'il existe déjà un modèle pour représenter une UE. On modifie donc le modèle : ```python from django.db import models from core.models import User from pedagogy.models import UV class UserUe(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) ue = models.ForeignKey(UV, on_delete=models.CASCADE) ``` On refait la commande `makemigrations` et on obtient : ```python from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ("pedagogy", "0004_userue"), ] operations = [ migrations.AlterField( model_name="userue", name="ue", field=models.ForeignKey( on_delete=models.deletion.CASCADE, to="pedagogy.uv" ), ), ] ``` Sauf que maintenant, nous avons deux fichiers de migration, alors qu'en réalité, on ne souhaite faire qu'une seule migration, une fois qu'on aura expédié le code en prod. Certes, ça fonctionnerait d'appliquer les deux, mais ça pose un problème d'encombrement. Plus il y a de fichiers de migrations, plus il y a de migrations à résoudre au moment de l'installation du projet chez quelqu'un, plus c'est embêtant à gérer et plus Django prendra du temps à résoudre les migrations. C'est pourquoi il est bon de respecter le principe : une PR = un fichier de migration maximum par application. Nous voulons donc fusionner les deux, pour n'en garder qu'une. Pour ça, deux manières de procéder : - le faire à la main - utiliser la commande squashmigrations Pour la méthode manuelle, on ne pourrait pas vous dire exhaustivement comment faire. Mais ne vous inquiétez pas, ce n'est pas très dur. Regardez bien quelles sont les instructions utilisées par django pour les opérations de migrations, et avec un peu d'astuce et quelques copier-coller, vous vous en sortirez comme des chefs. Pour la méthode `squashmigrations`, exécutez la commande ```bash python ./manage.py squasmigrations ``` Par exemple, dans notre cas, ça donnera : ```bash python ./manage.py squasmigrations pedagogy 0004 0005 ``` La commande vous donnera ceci : ```python from django.conf import settings from django.db import migrations, models class Migration(migrations.Migration): replaces = [("pedagogy", "0004_userue"), ("pedagogy", "0005_alter_userue_ue")] dependencies = [ ("pedagogy", "0003_alter_uv_language"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ migrations.CreateModel( name="UserUe", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ( "ue", models.ForeignKey( on_delete=models.deletion.CASCADE, to="pedagogy.uv" ), ), ( "user", models.ForeignKey( on_delete=models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, ), ), ], ), ] ``` Vous pouvez alors supprimer les deux autres fichiers. Vous remarquerez peut-être la présence de la ligne suivante : ```python replaces = [("pedagogy", "0004_userue"), ("pedagogy", "0005_alter_userue_ue")] ``` Cela sert à dire que cette migration doit être appliquée à la place des deux autres. Une fois que vous aurez supprimé les deux fichiers, supprimez également cette ligne. !!!warning Django sait quelles migrations ont été appliquées, en les stockant dans une table de la db. Si une migration est enregistrée en db, sans que le fichier de migration correspondant existe, la commande `migrate` échoue. Quand vous faites un `squashmigrations`, pensez donc à appliquer la commande `migrate` juste après (mais avant la suppression des anciens fichiers), pour que Django supprime de la base de données les migrations devenues inutiles.