Ceci est le deuxième article de notre série sur les migrations Django :
- Partie 1 :Migrations Django :introduction
- Partie 2 :Approfondir les migrations vers Django (article actuel)
- Partie 3 :Migrations de données
- Vidéo :Migrations Django 1.7 – Introduction
Dans l'article précédent de cette série, vous avez découvert le but des migrations Django. Vous vous êtes familiarisé avec les modèles d'utilisation fondamentaux tels que la création et l'application de migrations. Il est maintenant temps d'approfondir le système de migration et de jeter un coup d'œil à certains de ses mécanismes sous-jacents.
À la fin de cet article, vous saurez :
- Comment Django suit les migrations
- Comment les migrations savent quelles opérations de base de données effectuer
- Comment les dépendances entre les migrations sont définies
Une fois que vous aurez compris cette partie du système de migration Django, vous serez bien préparé pour créer vos propres migrations personnalisées. Reprenons là où nous nous étions arrêtés !
Cet article utilise le bitcoin_tracker
Projet Django construit dans Django Migrations :A Primer. Vous pouvez soit recréer ce projet en travaillant sur cet article, soit télécharger le code source :
Télécharger le code source : Cliquez ici pour télécharger le code du projet de migration Django que vous utiliserez dans cet article.
Comment Django sait quelles migrations appliquer
Récapitulons la toute dernière étape de l'article précédent de la série. Vous avez créé une migration, puis appliqué toutes les migrations disponibles avec python manage.py migrate
.Si cette commande s'est exécutée avec succès, vos tables de base de données correspondent désormais aux définitions de votre modèle.
Que se passe-t-il si vous exécutez à nouveau cette commande ? Essayons :
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, historical_data, sessions
Running migrations:
No migrations to apply.
Rien ne s'est passé! Une fois qu'une migration a été appliquée à une base de données, Django n'appliquera plus cette migration à cette base de données particulière. Pour s'assurer qu'une migration n'est appliquée qu'une seule fois, il faut garder une trace des migrations qui ont été appliquées.
Django utilise une table de base de données appelée django_migrations
. Django crée automatiquement cette table dans votre base de données la première fois que vous appliquez des migrations. Pour chaque migration appliquée ou simulée, une nouvelle ligne est insérée dans le tableau.
Par exemple, voici à quoi ressemble ce tableau dans notre bitcoin_tracker
projet :
identifiant | Application | Nom | Appliqué |
---|---|---|---|
1 | contenttypes | 0001_initial | 2019-02-05 20:23:21.461496 |
2 | auth | 0001_initial | 2019-02-05 20:23:21.489948 |
3 | admin | 0001_initial | 2019-02-05 20:23:21.508742 |
4 | admin | 0002_logentry_remove... | 2019-02-05 20:23:21.531390 |
5 | admin | 0003_logentry_add_ac... | 2019-02-05 20:23:21.564834 |
6 | contenttypes | 0002_remove_content_... | 2019-02-05 20:23:21.597186 |
7 | auth | 0002_alter_permissio... | 2019-02-05 20:23:21.608705 |
8 | auth | 0003_alter_user_emai... | 2019-02-05 20:23:21.628441 |
9 | auth | 0004_alter_user_user... | 2019-02-05 20:23:21.646824 |
10 | auth | 0005_alter_user_last... | 2019-02-05 20:23:21.661182 |
11 | auth | 0006_require_content... | 2019-02-05 20:23:21.663664 |
12 | auth | 0007_alter_validator... | 2019-02-05 20:23:21.679482 |
13 | auth | 0008_alter_user_user... | 2019-02-05 20:23:21.699201 |
14 | auth | 0009_alter_user_last... | 2019-02-05 20:23:21.718652 |
15 | historical_data | 0001_initial | 2019-02-05 20:23:21.726000 |
16 | sessions | 0001_initial | 2019-02-05 20:23:21.734611 |
19 | historical_data | 0002_switch_to_decimals | 2019-02-05 20:30:11.337894 |
Comme vous pouvez le constater, il existe une entrée pour chaque migration appliquée. Le tableau contient non seulement les migrations de nos historical_data
app, mais aussi les migrations depuis toutes les autres apps installées.
Lors de la prochaine exécution des migrations, Django ignorera les migrations répertoriées dans la table de la base de données. Cela signifie que, même si vous modifiez manuellement le fichier d'une migration qui a déjà été appliquée, Django ignorera ces modifications, tant qu'il existe déjà une entrée pour celle-ci dans la base de données.
Vous pouvez inciter Django à relancer une migration en supprimant la ligne correspondante du tableau, mais c'est rarement une bonne idée et peut vous laisser avec un système de migration défectueux.
Le fichier de migration
Que se passe-t-il lorsque vous exécutez python manage.py makemigrations <appname>
? Django recherche les modifications apportées aux modèles de votre application <appname>
. S'il en trouve, comme un modèle qui a été ajouté, il crée alors un fichier de migration dans le dossier migrations
sous-répertoire. Ce fichier de migration contient une liste d'opérations pour synchroniser votre schéma de base de données avec votre définition de modèle.
Remarque : Votre application doit être répertoriée dans INSTALLED_APPS
paramètre, et il doit contenir un migrations
répertoire avec un __init__.py
dossier. Sinon, Django ne créera aucune migration pour lui.
Les migrations
Le répertoire est automatiquement créé lorsque vous créez une nouvelle application avec le startapp
commande de gestion, mais il est facile de l'oublier lors de la création manuelle d'une application.
Les fichiers de migration ne sont que Python, alors regardons le premier fichier de migration dans les historical_prices
application. Vous pouvez le trouver sur historical_prices/migrations/0001_initial.py
. Cela devrait ressembler à ceci :
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = []
operations = [
migrations.CreateModel(
name='PriceHistory',
fields=[
('id', models.AutoField(
verbose_name='ID',
serialize=False,
primary_key=True,
auto_created=True)),
('date', models.DateTimeField(auto_now_add=True)),
('price', models.DecimalField(decimal_places=2, max_digits=5)),
('volume', models.PositiveIntegerField()),
('total_btc', models.PositiveIntegerField()),
],
options={
},
bases=(models.Model,),
),
]
Comme vous pouvez le voir, il contient une seule classe appelée Migration
qui hérite de django.db.migrations.Migration
. Il s'agit de la classe que le framework de migration recherchera et exécutera lorsque vous lui demanderez d'appliquer des migrations.
La Migration
class contient deux listes principales :
dependencies
operations
Opérations migratoires
Regardons les operations
liste en premier. Ce tableau contient les opérations à effectuer dans le cadre de la migration. Les opérations sont des sous-classes de la classe django.db.migrations.operations.base.Operation
. Voici les opérations courantes intégrées à Django :
Classe d'opération | Description |
---|---|
CreateModel | Crée un nouveau modèle et la table de base de données correspondante |
DeleteModel | Supprime un modèle et supprime sa table de base de données |
RenameModel | Renommer un modèle et renommer sa table de base de données |
AlterModelTable | Renommer la table de base de données pour un modèle |
AlterUniqueTogether | Modifie les contraintes uniques d'un modèle |
AlterIndexTogether | Change les index d'un modèle |
AlterOrderWithRespectTo | Crée ou supprime le _order colonne pour un modèle |
AlterModelOptions | Modifie diverses options de modèle sans affecter la base de données |
AlterModelManagers | Modifie les gestionnaires disponibles lors des migrations |
AddField | Ajoute un champ à un modèle et la colonne correspondante dans la base de données |
RemoveField | Supprime un champ d'un modèle et supprime la colonne correspondante de la base de données |
AlterField | Modifie la définition d'un champ et modifie sa colonne de base de données si nécessaire |
RenameField | Renommer un champ et, si nécessaire, également sa colonne de base de données |
AddIndex | Crée un index dans la table de la base de données pour le modèle |
RemoveIndex | Supprime un index de la table de base de données pour le modèle |
Notez comment les opérations sont nommées d'après les modifications apportées aux définitions de modèle, et non les actions effectuées sur la base de données. Lorsque vous appliquez une migration, chaque opération est chargée de générer les instructions SQL nécessaires pour votre base de données spécifique. Par exemple, CreateModel
générerait un CREATE TABLE
Instruction SQL.
Par défaut, les migrations prennent en charge toutes les bases de données standard prises en charge par Django. Donc, si vous vous en tenez aux opérations répertoriées ici, vous pouvez apporter plus ou moins les modifications que vous souhaitez à vos modèles, sans avoir à vous soucier du SQL sous-jacent. Tout est fait pour vous.
Remarque : Dans certains cas, Django peut ne pas détecter correctement vos modifications. Si vous renommez un modèle et modifiez plusieurs de ses champs, Django risque de confondre cela avec un nouveau modèle.
Au lieu d'un RenameModel
et plusieurs AlterField
opérations, il créera un DeleteModel
et un CreateModel
opération. Au lieu de renommer la table de base de données pour le modèle, il la supprimera et créera une nouvelle table avec le nouveau nom, supprimant ainsi toutes vos données !
Prenez l'habitude de vérifier les migrations générées et de les tester sur une copie de votre base de données avant de les exécuter sur des données de production.
Django fournit trois classes d'opérations supplémentaires pour les cas d'utilisation avancés :
RunSQL
vous permet d'exécuter du SQL personnalisé dans la base de données.RunPython
vous permet d'exécuter n'importe quel code Python.SeparateDatabaseAndState
est une opération spécialisée pour des utilisations avancées.
Avec ces opérations, vous pouvez essentiellement apporter les modifications que vous souhaitez à votre base de données. Cependant, vous ne trouverez pas ces opérations dans une migration qui a été créée automatiquement avec le makemigrations
commande de gestion.
Depuis Django 2.0, il y a aussi quelques opérations spécifiques à PostgreSQL disponibles dans django.contrib.postgres.operations
que vous pouvez utiliser pour installer diverses extensions PostgreSQL :
BtreeGinExtension
BtreeGistExtension
CITextExtension
CryptoExtension
HStoreExtension
TrigramExtension
UnaccentExtension
Notez qu'une migration contenant l'une de ces opérations nécessite un utilisateur de base de données avec des privilèges de superutilisateur.
Enfin, vous pouvez également créer vos propres classes d'opérations. Si vous souhaitez en savoir plus, consultez la documentation de Django sur la création d'opérations de migration personnalisées.
Dépendances migratoires
Les dependencies
list dans une classe de migration contient toutes les migrations qui doivent être appliquées avant que cette migration puisse être appliquée.
Dans le 0001_initial.py
migration que vous avez vue ci-dessus, rien ne doit être appliqué au préalable, il n'y a donc pas de dépendances. Examinons la deuxième migration dans les historical_prices
application. Dans le fichier 0002_switch_to_decimals.py
, les dependencies
attribut de Migration
a une entrée :
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('historical_data', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='pricehistory',
name='volume',
field=models.DecimalField(decimal_places=3, max_digits=7),
),
]
La dépendance ci-dessus indique que la migration 0001_initial
de l'application historical_data
doit être exécuté en premier. C'est logique, car la migration 0001_initial
crée la table contenant le champ que la migration 0002_switch_to_decimals
veut changer.
Une migration peut également dépendre d'une migration depuis une autre application, comme ceci :
class Migration(migrations.Migration):
...
dependencies = [
('auth', '0009_alter_user_last_name_max_length'),
]
Cela est généralement nécessaire si un modèle a une clé étrangère pointant vers un modèle dans une autre application.
Vous pouvez également imposer qu'une migration soit exécutée avant une autre migration en utilisant l'attribut run_before
:
class Migration(migrations.Migration):
...
run_before = [
('third_party_app', '0001_initial'),
]
Les dépendances peuvent également être combinées afin que vous puissiez avoir plusieurs dépendances. Cette fonctionnalité offre une grande flexibilité, car vous pouvez accepter des clés étrangères qui dépendent de modèles de différentes applications.
L'option de définir explicitement les dépendances entre les migrations signifie également que la numérotation des migrations (généralement 0001
, 0002
, 0003
, …) ne représente pas strictement l'ordre dans lequel les migrations sont appliquées. Vous pouvez ajouter toutes les dépendances que vous souhaitez et ainsi contrôler l'ordre sans avoir à renuméroter toutes les migrations.
Affichage de la migration
Vous n'avez généralement pas à vous soucier du SQL généré par les migrations. Mais si vous voulez revérifier que le SQL généré a du sens ou si vous êtes simplement curieux de savoir à quoi il ressemble, alors Django vous couvre avec le sqlmigrate
commande de gestion :
$ python manage.py sqlmigrate historical_data 0001
BEGIN;
--
-- Create model PriceHistory
--
CREATE TABLE "historical_data_pricehistory" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"date" datetime NOT NULL,
"price" decimal NOT NULL,
"volume" integer unsigned NOT NULL
);
COMMIT;
Cela répertoriera les requêtes SQL sous-jacentes qui seront générées par la migration spécifiée, en fonction de la base de données dans votre settings.py
dossier. Lorsque vous passez le paramètre --backwards
, Django génère le SQL pour désappliquer la migration :
$ python manage.py sqlmigrate --backwards historical_data 0001
BEGIN;
--
-- Create model PriceHistory
--
DROP TABLE "historical_data_pricehistory";
COMMIT;
Une fois que vous voyez la sortie de sqlmigrate
pour une migration légèrement plus complexe, vous apprécierez peut-être que vous n'ayez pas à créer tout ce code SQL à la main !
Comment Django détecte les modifications apportées à vos modèles
Vous avez vu à quoi ressemble un fichier de migration et comment sa liste d'Operation
classes définit les modifications apportées à la base de données. Mais comment Django sait-il exactement quelles opérations doivent être effectuées dans un fichier de migration ? Vous pourriez vous attendre à ce que Django compare vos modèles au schéma de votre base de données, mais ce n'est pas le cas.
Lors de l'exécution de makemigrations
, Django ne le fait pas inspectez votre base de données. Il ne compare pas non plus votre fichier de modèle à une version antérieure. Au lieu de cela, Django parcourt toutes les migrations qui ont été appliquées et construit un état de projet de ce à quoi les modèles devraient ressembler. Cet état de projet est ensuite comparé à vos définitions de modèle actuelles, et une liste d'opérations est créée, qui, une fois appliquée, mettrait à jour l'état du projet avec les définitions de modèle.
Jouer aux échecs avec Django
Vous pouvez considérer vos modèles comme un échiquier, et Django est un grand maître des échecs qui vous regarde jouer contre vous-même. Mais le grand maître ne surveille pas chacun de vos mouvements. Le grand maître ne regarde le tableau que lorsque vous criez makemigrations
.
Parce qu'il n'y a qu'un ensemble limité de mouvements possibles (et que le grand maître est un grand maître), elle peut proposer les mouvements qui se sont produits depuis la dernière fois qu'elle a regardé le tableau. Elle prend des notes et vous laisse jouer jusqu'à ce que vous criiez makemigrations
à nouveau.
Lorsqu'il regarde l'échiquier la prochaine fois, le grand maître ne se souvient pas à quoi ressemblait l'échiquier la dernière fois, mais il peut parcourir ses notes des mouvements précédents et construire un modèle mental de ce à quoi ressemblait l'échiquier.
Maintenant, quand vous criez migrate
, le grand maître rejouera tous les coups enregistrés sur un autre échiquier et notera dans une feuille de calcul lesquels de ses records ont déjà été appliqués. Ce deuxième échiquier est votre base de données, et la feuille de calcul est le django_migrations
tableau.
Cette analogie est tout à fait appropriée, car elle illustre bien certains comportements des migrations Django :
-
Les migrations Django essaient d'être efficaces : Tout comme le grand maître suppose que vous avez effectué le moins de déplacements, Django essaiera de créer les migrations les plus efficaces. Si vous ajoutez un champ nommé
A
à un modèle, puis renommez-le enB
, puis exécutezmakemigrations
, alors Django créera une nouvelle migration pour ajouter un champ nomméB
. -
Les migrations Django ont leurs limites : Si vous faites beaucoup de mouvements avant de laisser le grand maître regarder l'échiquier, il se peut qu'il ne soit pas en mesure de retracer les mouvements exacts de chaque pièce. De même, Django peut ne pas proposer la bonne migration si vous apportez trop de modifications à la fois.
-
La migration Django attend de vous que vous respectiez les règles : Lorsque vous faites quelque chose d'inattendu, comme retirer une pièce au hasard du tableau ou jouer avec les notes, le grand maître peut ne pas le remarquer au début, mais tôt ou tard, il lèvera les mains et refusera de continuer. La même chose se produit lorsque vous jouez avec les
django_migrations
table ou modifiez votre schéma de base de données en dehors des migrations, par exemple en supprimant la table de base de données d'un modèle.
Comprendre SeparateDatabaseAndState
Maintenant que vous connaissez l'état du projet construit par Django, il est temps d'examiner de plus près l'opération SeparateDatabaseAndState
. Cette opération peut faire exactement ce que son nom implique :elle peut séparer l'état du projet (le modèle mental construit par Django) de votre base de données.
SeparateDatabaseAndState
est instancié avec deux listes d'opérations :
state_operations
contient des opérations qui ne s'appliquent qu'à l'état du projet.database_operations
contient des opérations qui ne s'appliquent qu'à la base de données.
Cette opération vous permet d'apporter n'importe quel type de modification à votre base de données, mais il est de votre responsabilité de vous assurer que l'état du projet correspond à la base de données par la suite. Exemples de cas d'utilisation pour SeparateDatabaseAndState
déplacez un modèle d'une application à une autre ou créez un index sur une énorme base de données sans temps d'arrêt.
SeparateDatabaseAndState
est une opération avancée et vous n'aurez pas besoin le premier jour de travailler avec les migrations et peut-être jamais du tout. SeparateDatabaseAndState
est similaire à la chirurgie cardiaque. Cela comporte pas mal de risques et ce n'est pas quelque chose que vous faites juste pour le plaisir, mais parfois c'est une procédure nécessaire pour maintenir le patient en vie.
Conclusion
Ceci conclut votre plongée en profondeur dans les migrations Django. Toutes nos félicitations! Vous avez couvert pas mal de sujets avancés et vous avez maintenant une solide compréhension de ce qui se passe sous le capot des migrations.
Vous avez appris que :
- Django assure le suivi des migrations appliquées dans le tableau des migrations Django.
- Les migrations Django consistent en des fichiers Python simples contenant une
Migration
classe. - Django sait quelles modifications effectuer à partir des
operations
liste dans laMigration
cours. - Django compare vos modèles à un état de projet qu'il construit à partir des migrations.
Avec ces connaissances, vous êtes maintenant prêt à aborder la troisième partie de la série sur les migrations Django, où vous apprendrez à utiliser les migrations de données pour apporter en toute sécurité des modifications ponctuelles à vos données. Restez à l'écoute !
Cet article a utilisé le bitcoin_tracker
Projet Django construit dans Django Migrations :A Primer. Vous pouvez soit recréer ce projet en travaillant sur cet article, soit télécharger le code source :
Télécharger le code source : Cliquez ici pour télécharger le code du projet de migration Django que vous utiliserez dans cet article.