mirror of
https://github.com/ae-utbm/sith.git
synced 2025-07-09 19:40:19 +00:00
Rewrite documentation with MkDocs
This commit is contained in:
354
docs/howto/migrations.md
Normal file
354
docs/howto/migrations.md
Normal file
@ -0,0 +1,354 @@
|
||||
## 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 <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 :
|
||||
|
||||
```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 <app> <migration de début (incluse)> <migration de fin (incluse)>
|
||||
```
|
||||
|
||||
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.
|
Reference in New Issue
Block a user