Creuser plus profondément dans les migrations – Real Python

By | juillet 24, 2019

python pour débutant

Voici le deuxième article de notre série sur les migrations Django:

Dans le précédent article de cette série, vous avez appris le but des migrations de 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 la question du système de migration et de jeter un coup d’œil sur certains de ses mécanismes sous-jacents.

À la fin de cet article, vous saurez:

  • Comment Django assure le suivi des 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 avez compris cette partie du système de migration Django, vous serez bien préparé pour créer vos propres migrations personnalisées. Passons directement là où nous nous sommes arrêtés!

Cet article utilise le bitcoin_tracker Projet Django intégré à Django Migrations: A Primer. Vous pouvez recréer ce projet en travaillant sur cet article ou télécharger le code source:

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 migrer.
Si cette commande a été exécutée avec succès, vos tables de base de données correspondent maintenant aux définitions de votre modèle.

Que se passe-t-il si vous exécutez à nouveau cette commande? Essayons-le:

$ python manage.py migrer
Opérations à effectuer:
        Appliquer toutes les migrations: admin, auth, contenttypes, historical_data, sessions
Migrations en cours:
        Aucune migration à appliquer.

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. S'assurer qu'une migration n'est appliquée qu'une seule fois nécessite de garder une trace des migrations 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 une migration. Pour chaque migration appliquée ou falsifiée, une nouvelle ligne est insérée dans le tableau.

Par exemple, voici à quoi ressemble ce tableau dans notre bitcoin_tracker projet:

ID App prénom Appliqué
1 types de contenu 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 types de contenu 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
dix 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 données historiques 0001_initial 2019-02-05 20: 23: 21.726000
16 sessions 0001_initial 2019-02-05 20: 23: 21.734611
19 données historiques 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. La table contient non seulement les migrations de notre données historiques app, mais aussi les migrations de toutes les autres applications installées.

Lors de la prochaine exécution des migrations, Django ignorera les migrations répertoriées dans la table de base de données. Cela signifie que, même si vous modifiez manuellement le fichier d'une migration déjà appliquée, Django ignorera ces modifications, à condition qu'il y ait déjà une entrée correspondante dans la base de données.

Vous pouvez amener Django à réexécuter une migration en supprimant la ligne correspondante de la table, mais ceci est rarement une bonne idée et peut vous laisser avec un système de migration endommagé.

Le fichier de migration

Qu'est-ce qui se passe quand tu cours python manage.py makemigrations ? Django recherche les modifications apportées aux modèles de votre application. . S'il en trouve, comme un modèle ajouté, il crée un fichier de migration dans le dossier migrations sous-répertoire. Ce fichier de migration contient une liste d'opérations permettant de synchroniser votre schéma de base de données avec la définition de votre modèle.

Les fichiers de migration ne sont que Python. Voyons donc le premier fichier de migration du prix_historique app. Vous pouvez le trouver à historical_prices / migrations / 0001_initial.py. Ça devrait ressembler a quelque chose comme ca:

de django.db importation des modèles, migrations

classe Migration(migrations.Migration):
    les dépendances = []
    opérations = [[[[
        migrations.CreateModel(
            prénom='PriceHistory',
            des champs=[[[[
                ('id', des modèles.AutoField(
                    nom verbeux='ID',
                    sérialiser=Faux,
                    clé primaire=Vrai,
                    auto_created=Vrai)),
                ('rendez-vous amoureux', des modèles.DateTimeField(auto_now_add=Vrai)),
                ('prix', des modèles.DecimalField(décimal_places=2, max_digits=5)),
                ('le volume', des modèles.PositiveIntegerField()),
                ('total_btc', des modèles.PositiveIntegerField()),
            ],
            options=
            ,
            des bases=(des modèles.Modèle,),
        ),
    ]

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 l'infrastructure de migration recherchera et exécutera lorsque vous lui demanderez d'appliquer des migrations.

le Migration La classe contient deux listes principales:

  1. les dépendances
  2. opérations

Opérations de migration

Regardons le opérations liste en premier. Cette table 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 La 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
RenommerModèle Renomme un modèle et renomme sa table de base de données
AlterModelTable Renomme la table de base de données pour un modèle
AlterUniqueTogether Change les contraintes uniques d'un modèle
AlterIndexTogether Change les index d'un modèle
AlterOrderWithRespectTo Crée ou supprime le _ordre 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
Ajouter le champ 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 la base de données si nécessaire
RenameField Renomme un champ et éventuellement sa colonne de base de données
AddIndex Crée un index dans la table de 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 que les opérations sont nommées en fonction des modifications apportées aux définitions de modèle, et non des 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 CREER LA TABLE Instruction SQL.

Les migrations prêtes à l'emploi prennent en charge toutes les bases de données standard prises en charge par Django. Ainsi, si vous vous en tenez aux opérations répertoriées ici, vous pourrez apporter plus ou moins de modifications à vos modèles, sans avoir à vous soucier du code SQL sous-jacent. Tout est fait pour vous.

Django fournit trois classes d'opération supplémentaires pour les cas d'utilisation avancés:

  1. RunSQL vous permet d'exécuter du code SQL personnalisé dans la base de données.
  2. RunPython vous permet d'exécuter n'importe quel code Python.
  3. SeparateDatabaseAndState est une opération spécialisée pour les utilisations avancées.

Avec ces opérations, vous pouvez essentiellement apporter les modifications souhaitées à votre base de données. Cependant, vous ne trouverez pas ces opérations dans une migration créée automatiquement avec le makemigrations commande de gestion.

Depuis Django 2.0, quelques opérations spécifiques à PostgreSQL sont 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 doté des privilèges de superutilisateur.

Enfin, vous pouvez également créer vos propres classes d'opération. Si vous souhaitez examiner cela, consultez la documentation de Django sur la création d'opérations de migration personnalisées.

Dépendances de la migration

le les dépendances La liste 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 la migration que vous avez vue ci-dessus, rien ne doit être appliqué auparavant, donc il n'y a pas de dépendances. Regardons la deuxième migration dans le prix_historique app. Dans le fichier 0002_switch_to_decimals.py, la les dépendances attribut de Migration a une entrée:

de django.db importation migrations, des modèles

classe Migration(migrations.Migration):
    les dépendances = [[[[
        ('données historiques', '0001_initial'),
    ]
    opérations = [[[[
        migrations.AlterField(
            nom du modèle='histoire de prix',
            prénom='le volume',
            champ=des modèles.DecimalField(décimal_places=3, max_digits=7),
        ),
    ]

La dépendance ci-dessus dit que la migration 0001_initial de l'application données historiques doit être exécuté en premier. Cela a du sens, 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 d'une autre application, comme ceci:

classe Migration(migrations.Migration):
    ...

    les dépendances = [[[[
        ('auth', '0009_alter_user_last_name_max_length'),
    ]

Cela est généralement nécessaire si un modèle a une clé étrangère qui pointe vers un modèle dans une autre application.

Alternativement, vous pouvez également exiger qu'une migration soit exécutée avant une autre migration utilisant l'attribut run_before:

classe 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 beaucoup de flexibilité, car vous pouvez gérer 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 n'importe quelle dépendance et contrôler ainsi l'ordre sans avoir à renuméroter toutes les migrations.

Affichage de la migration

En général, vous n'avez pas à vous soucier du SQL généré par les migrations. Mais si vous voulez vérifier que le SQL généré a du sens ou si vous voulez juste savoir de quoi il a l'air, alors Django vous en informera. sqlmigrate commande de gestion:

$ python manage.py sqlmigrate historical_data 0001
COMMENCER;
-
- Créer un modèle PriceHistory
-
CREATE TABLE "historique_données_prix_historique" (
                "id" entier NON NUL PRIMARY KEY AUTOINCREMENT,
                "date" datetime NON NULL,
                "prix" décimal NON NULL,
                "volume" entier non signé NON NUL
)
COMMETTRE;

Faire cela listera 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 de votre settings.py fichier. Quand vous passez le paramètre --en arrièreDjango génère le code SQL pour désappliquer la migration:

$ python manage.py sqlmigrate --backwards historical_data 0001
COMMENCER;
-
- Créer un modèle PriceHistory
-
DROP TABLE "historique_data_prix_historique";
COMMETTRE;

Une fois que vous voyez la sortie de sqlmigrate Pour une migration légèrement plus complexe, vous comprendrez que vous n’avez pas à concevoir tout ce 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 de Opération classes définit les modifications apportées à la base de données. Mais comment Django sait-il exactement quelles opérations doivent être insérées dans un fichier de migration? Vous pourriez vous attendre à ce que Django compare vos modèles à votre schéma de base de données, mais ce n'est pas le cas.

Lors de l'exécution makemigrationsDjango fait ne pas inspecter 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 crée 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. Elle permet, si elle est appliquée, de mettre l'état du projet à jour 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 surveille. 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.

Comme il n’ya qu’un nombre limité de mouvements possibles (et que le grand maître est un grand maître), elle peut imaginer les mouvements qui se sont produits depuis sa dernière visite au tableau. Elle prend des notes et vous laisse jouer jusqu'à ce que vous criiez makemigrations encore.

La prochaine fois que le grand maître regarde le tableau, il ne se souvient pas à quoi ressemblait l’échiquier la dernière fois, mais elle peut parcourir ses notes des mouvements précédents et construire un modèle mental de ce à quoi l’échiquier ressemblait.

Maintenant, quand vous criez émigrer, le grand maître rejouera tous les mouvements enregistrés sur un autre échiquier et notera dans un tableur quels de ses disques 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 table.

Cette analogie est tout à fait appropriée, car elle illustre bien certains comportements des migrations Django:

  • Les migrations Django tentent d’être efficaces: Tout comme le grand maître suppose que vous avez effectué le moins de mouvements possible, Django tentera de créer les migrations les plus efficaces. Si vous ajoutez un champ nommé UNE à un modèle, puis renommez-le en Bpuis courir makemigrationspuis 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 la grand-maîtresse regarder l'échiquier, elle risque alors de ne pas être en mesure de retracer les mouvements exacts de chaque pièce. De même, Django pourrait ne pas proposer la migration correcte si vous apportez trop de modifications en même temps.

  • La migration de Django attend de vous que vous respectiez les règles: Lorsque vous faites quelque chose d’imprévu, comme prendre un morceau au hasard sur le tableau ou jouer avec les notes, le grand maître ne le remarquera peut-être pas au début, mais tôt ou tard, elle lèvera les mains et refusera de continuer. La même chose arrive quand vous jouez avec le 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.

Compréhension SeparateDatabaseAndState

Maintenant que vous connaissez l’état du projet créé par Django, il est temps d’examiner de plus près le fonctionnement 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:

  1. state_operations contient des opérations qui ne sont appliquées qu'à l'état du projet.
  2. opérations_base_de_données contient des opérations qui ne sont appliquées qu'à la base de données.

Cette opération vous permet d’apporter tout type de modification à votre base de données, mais il vous incombe de vous assurer que l’état du projet correspond à la base de données par la suite. Exemple 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 de travailler le premier jour avec les migrations et peut-être jamais du tout. SeparateDatabaseAndState est similaire à la chirurgie cardiaque. Cela comporte pas mal de risques et n’est pas quelque chose que vous faites juste pour le plaisir, mais c’est parfois une procédure nécessaire pour garder le patient en vie.

Conclusion

Ceci conclut votre plongée dans les migrations Django. Toutes nos félicitations! Vous avez abordé un grand nombre de sujets avancés et vous comprenez maintenant mieux ce qui se passe sous le capot des migrations.

Vous avez appris que:

  • Django suit les migrations appliquées dans la table des migrations Django.
  • Les migrations Django se composent de fichiers Python simples contenant un Migration classe.
  • Django sait quels changements effectuer depuis le opérations liste dans le Migration Des classes.
  • Django compare vos modèles à l'état du projet qu'il a construit à partir des migrations.

Grâce à ces connaissances, vous êtes maintenant prêt à aborder la troisième partie de la série sur les migrations Django, dans laquelle vous apprendrez à utiliser les migrations de données pour apporter en toute sécurité des modifications uniques à vos données. Restez à l'écoute!

Cet article utilisait le bitcoin_tracker Projet Django intégré à Django Migrations: A Primer. Vous pouvez recréer ce projet en travaillant sur cet article ou télécharger le code source: