10 KiB
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 :
python ./manage.py migrate
Vous remarquerez peut-être que cette commande a été utilisée dans la section Installation. 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 :
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 :
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 :
python ./manage.py migrate <application> <numéro de la migration>
Par exemple, si vous voulez revenir à la migration 0001_initial
de l'application customer
, vous pouvez exécuter la commande suivante :
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 :
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
:
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 :
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 :
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 :
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
python ./manage.py squasmigrations <app> <migration de début (incluse)> <migration de fin (incluse)>
Par exemple, dans notre cas, ça donnera :
python ./manage.py squasmigrations pedagogy 0004 0005
La commande vous donnera ceci :
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 :
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.