116 lines
6.0 KiB
Plaintext
116 lines
6.0 KiB
Plaintext
|
== Migrations
|
||
|
|
||
|
L'intégration des migrations a été réalisée dans la version 1.7 de Django.
|
||
|
Avant cela, il convenait de passer par une librairie tierce intitulée https://south.readthedocs.io/en/latest[South].
|
||
|
|
||
|
Dans cette section, nous allons voir comment fonctionnent les migrations.
|
||
|
Lors d'une première approche, elles peuvent sembler un peu magiques, puisqu'elles centralisent un ensemble de modifications pouvant être répétées sur un schéma de données, en tenant compte de ce qui a déjà été appliqué et en vérifiant quelles migrations devaient encore l'être pour mettre l'application à niveau.
|
||
|
|
||
|
Une analyse en profondeur montrera qu'elles ne sont pas plus complexes à suivre et à comprendre qu'un ensemble de fonctions de gestion appliquées à notre application.
|
||
|
|
||
|
Prenons l'exemple de notre liste de souhaits; nous nous rendons (bêtement) compte que nous avons oublié d'ajouter un champ de `description` à une liste.
|
||
|
Historiquement, cette action nécessitait l'intervention d'un administrateur système ou d'une personne ayant accès au schéma de la base de données, à partir duquel ce-dit utilisateur pouvait jouer manuellement un script SQL.
|
||
|
Cet enchaînement d'étapes nécessitait une bonne coordination d'équipe, mais également une bonne confiance dans les scripts à exécuter.
|
||
|
Et souvenez-vous (cf. ref-à-insérer), que l'ensemble des actions doit être répétable et automatisable.
|
||
|
|
||
|
Bref, dans les années '80, il convenait de jouer ceci après s'être connecté à la base de données:
|
||
|
|
||
|
[source,sql]
|
||
|
----
|
||
|
ALTER TABLE WishList ADD COLUMN Description nvarchar(MAX);
|
||
|
----
|
||
|
|
||
|
Et là, nous nous rappelons qu'un utilisateur tourne sur Oracle et pas sur MySQL, et qu'il a donc besoin de son propre script d'exécution, parce que le type du nouveau champ n'est pas exactement le même entre les deux moteurs:
|
||
|
|
||
|
[source,sql]
|
||
|
----
|
||
|
|
||
|
----
|
||
|
|
||
|
Bref, vous voyez le(s) problème(s):
|
||
|
|
||
|
1. Aucune autonomie
|
||
|
2. Aucune automatisation possible (à moins d'écrire un programme, qu'il faudra également maintenir et intégrer au niveau des tests)
|
||
|
3. Nécessiter de maintenir autant de scripts différents qu'il y a de moteurs de base de données supportés
|
||
|
4. Aucune possibilité de vérifier si le script a déjà été exécuté ou non (à moins de maintenir un programme supplémentaire, à nouveau)
|
||
|
5. ...
|
||
|
|
||
|
Les migrations résolvent la plupart de ces soucis: le framework embarque ses propres applications, dont les migrations, qui gèrent elles-mêmes l'arbre de dépendances entre les modifications devant être appliquées.
|
||
|
Une migration consiste donc à appliquer un ensemble de modifications (ou **opérations**), qui exercent un ensemble de transformations, pour que le schéma de base de données corresponde au modèle de l'application sous-jacente.
|
||
|
Les migrations (comprendre les "_migrations du schéma de base de données_") sont intimement liées à la représentation d'un contexte fonctionnel.
|
||
|
L'ajout d'une nouvelle information, d'un nouveau champ ou d'une nouvelle fonction peut s'accompagner de tables de données à mettre à jour ou de champs à étendre.
|
||
|
|
||
|
Toujours dans une optique de centralisation, les migrations sont directement embarquées au niveau du code.
|
||
|
Le développeur s'occupe de créer les migrations en fonction des actions à entreprendre; ces migrations peuvent être retravaillées, _squashées_, ... et feront partie intégrante du processus de mise à jour de l'application.
|
||
|
|
||
|
A noter que les migrations n'appliqueront de modifications que si le schéma est impacté.
|
||
|
Ajouter une propriété `related_name` sur une ForeignKey n'engendrera aucune nouvelle action de migration, puisque ce type d'action ne s'applique que sur l'ORM, et pas directement sur la base de données: au niveau des tables, rien ne change.
|
||
|
Seul le code et le modèle sont impactés.
|
||
|
|
||
|
|
||
|
Une migration est donc une classe Python, présentant _a minima_ deux propriétés:
|
||
|
|
||
|
1. `dependencies`, qui décrit les opérations précédentes devant obligatoirement avoir été appliquées
|
||
|
2. `operations`, qui consiste à décrire précisément ce qui doit être exécuté.
|
||
|
|
||
|
Pour reprendre notre exemple d'ajout d'un champ `description` sur le modèle `WishList`, la migration ressemblera à ceci:
|
||
|
|
||
|
[source,python]
|
||
|
----
|
||
|
from django.db import migrations, models
|
||
|
import django.db.models.deletion
|
||
|
import django.utils.timezone
|
||
|
|
||
|
|
||
|
class Migration(migrations.Migration):
|
||
|
|
||
|
dependencies = [
|
||
|
('gwift', '0004_name_value'),
|
||
|
]
|
||
|
|
||
|
operations = [
|
||
|
migrations.AddField(
|
||
|
model_name='wishlist',
|
||
|
name='description',
|
||
|
field=models.TextField(default="", null=True)
|
||
|
preserve_default=False,
|
||
|
),
|
||
|
]
|
||
|
----
|
||
|
|
||
|
Nous avions un modèle reprenant quelques classes, elles-mêmes saupoudrées de quelques propriétés.
|
||
|
|
||
|
|
||
|
=== Réinitialisation d'une ou plusieurs migrations
|
||
|
|
||
|
https://simpleisbetterthancomplex.com/tutorial/2016/07/26/how-to-reset-migrations.html[reset migrations].
|
||
|
|
||
|
> En gros, soit on supprime toutes les migrations (en conservant le fichier __init__.py), soit on réinitialise proprement les migrations avec un --fake-initial (sous réserve que toutes les personnes qui utilisent déjà le projet s'y conforment... Ce qui n'est pas gagné.
|
||
|
Pour repartir de notre exemple ci-dessus, nous avions un modèle reprenant quelques classes, saupoudrées de propriétés décrivant nos différents champs. Pour être prise en compte par le moteur de base de données, chaque modification doit être
|
||
|
|
||
|
|
||
|
=== Description d'une migration
|
||
|
|
||
|
1. Décrite, grâce à la commande `makemigrations`
|
||
|
|
||
|
=== Application d'une ou plusieurs migrations
|
||
|
|
||
|
1. Appliquée, avec la commande `migrate`.
|
||
|
|
||
|
=== Analyse
|
||
|
|
||
|
Nous allons ci-dessous analyser exactement les modifications appliquées au schéma de la base de données, en fonction des différents cas, et comment ils sont gérés par les pilotes de Django.
|
||
|
Nous utiliserons https://sqlitebrowser.org/[Sqlite Browser] et la commande `sqldump`, qui nous présentera le schéma tel qu'il sera compris.
|
||
|
|
||
|
==== Création de nouveaux champs
|
||
|
|
||
|
|
||
|
|
||
|
==== Modification d'un champ existant
|
||
|
|
||
|
|
||
|
==== Suppression d'un champ existant
|
||
|
|
||
|
|
||
|
|