Database
 sql >> Base de données >  >> RDS >> Database

Approfondir les migrations Django

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 :

  1. dependencies
  2. 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 :

  1. RunSQL vous permet d'exécuter du 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 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 en B , puis exécutez makemigrations , 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 :

  1. state_operations contient des opérations qui ne s'appliquent qu'à l'état du projet.
  2. 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 la Migration 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.