Comment déplacer un modèle Django vers une autre application – Real Python

By | mai 6, 2020

python pour débutant

Cette approche présente certains écueils dont vous devez être conscient. Vous les explorerez en détail dans les sections suivantes.

Créer le nouveau modèle

Commencez par créer un nouveau produit app. Depuis votre terminal, exécutez la commande suivante:

$ produit startapp python manage.py

Après avoir exécuté cette commande, vous remarquerez un nouveau répertoire appelé produit a été ajouté au projet.

Pour enregistrer la nouvelle application avec votre projet Django existant, ajoutez-la à la liste des INSTALLED_APPS dans Django settings.py:

--- un / store / store / settings.py
+++ b / store / store / settings.py
@@ -40,6 +40,7 @@ INSTALLED_APPS =[[[[

     'catalogue',
     'vente',
+ 'produit',
 ]MIDDLEWARE =[

Votre nouveau produit l'application est maintenant enregistrée auprès de Django. Ensuite, créez un Produit modèle dans le nouveau produit app. Vous pouvez copier le code depuis le catalogue application:

# product / models.py
de django.db importation des modèles

de catalog.models importation Catégorie

classe Produit(des modèles.Modèle):
    Nom = des modèles.CharField(longueur maximale=100, db_index=Vrai)
    Catégorie = des modèles.Clé étrangère(Catégorie, on_delete=des modèles.CASCADE)

Maintenant que vous avez défini le modèle, essayez de générer des migrations pour celui-ci:

$ python manage.py makemigrations produit
SystemCheckError: la vérification du système a identifié certains problèmes:

LES ERREURS:
catalog.Product.category: (fields.E304) L'accesseur inversé pour 'Product.category' se heurte à l'accesseur inversé pour 'Product.category'.
CONSEIL: ajoutez ou modifiez un argument nom_connecté à la définition de 'Product.category' ou 'Product.category'.
product.Product.category: (fields.E304) L'accesseur inverse pour 'Product.category' se heurte à l'accesseur inverse pour 'Product.category'.
CONSEIL: ajoutez ou modifiez un argument nom_connecté à la définition de 'Product.category' ou 'Product.category'.

L'erreur indique que Django a trouvé deux modèles avec le même accesseur inversé pour le champ Catégorie. En effet, il existe deux modèles nommés Produit qui font référence à la Catégorie modèle, créant un conflit.

Lorsque vous ajoutez des clés étrangères à votre modèle, Django crée un accesseur inversé dans le modèle associé. Dans ce cas, l'accesseur inversé est des produits. L'accesseur inverse vous permet d'accéder à des objets connexes comme celui-ci: category.products.

Le nouveau modèle est celui que vous souhaitez conserver. Pour résoudre ce conflit, supprimez l'accesseur inversé de l'ancien modèle dans catalogue / models.py:

--- un / magasin / catalogue / models.py
+++ b / magasin / catalogue / models.py
@@ -7,4 +7,4 @@ classe Catégorie (models.Model):

 Produit de classe (models.Model):
     name = models.CharField (max_length = 100, db_index = True)
- category = models.ForeignKey (Category, on_delete = models.CASCADE)
+ category = models.ForeignKey (Category, on_delete = models.CASCADE, related_name = '+')

L'attribut nom_relié peut être utilisé pour définir explicitement un nom associé pour un accesseur inversé. Ici, vous utilisez la valeur spéciale +, qui demande à Django de ne pas créer d'accesseur inversé.

Générez maintenant une migration pour le catalogue application:

$ python manage.py makemigrations catalogue
Migrations pour 'catalogue':
        catalogue / migrations / 0002_auto_20200124_1250.py
                - Modifier la catégorie de champ sur le produit

N'appliquez pas encore cette migration! Une fois cette modification effectuée, le code qui a utilisé l'accesseur inverse peut se casser.

Maintenant qu'il n'y a pas de conflit entre les accesseurs inversés, essayez de générer les migrations pour le nouveau produit application:

$ python manage.py makemigrations produit
Migrations pour «produit»:
        produit / migrations / 0001_initial.py
                - Créer un modèle de produit

Génial! Vous êtes prêt à passer à l'étape suivante.

Copiez les données dans le nouveau modèle

À l'étape précédente, vous avez créé un nouveau produit application avec un Produit modèle identique au modèle que vous souhaitez déplacer. L'étape suivante consiste à déplacer les données de l'ancien modèle vers le nouveau modèle.

Pour créer une migration de données, exécutez la commande suivante à partir de votre terminal:

$ produit python manage.py makemigrations --empty
Migrations pour «produit»:
        produit / migrations / 0002_auto_20200124_1300.py

Modifiez le nouveau fichier de migration et ajoutez l'opération pour copier les données de l'ancienne table:

de django.db importation migrations

classe Migration(migrations.Migration):

    dépendances = [[[[
        ('produit', '0001_initial'),
    ]

    opérations = [[[[
        migrations.RunSQL("" "
                                                INSÉRER DANS product_product (
                                                                id,
                                                                Nom,
                                                                category_id
                                                )
                                                SÉLECTIONNER
                                                                id,
                                                                Nom,
                                                                category_id
                                                DE
                                                                catalog_product;
                                "" ", reverse_sql="" "
                                                INSÉRER DANS catalog_product (
                                                                id,
                                                                Nom,
                                                                category_id
                                                )
                                                SÉLECTIONNER
                                                                id,
                                                                Nom,
                                                                category_id
                                                DE
                                                                product_product;
                                "" ")
    ]

Pour exécuter SQL dans une migration, vous utilisez le spécial RunSQL commande de migration. Le premier argument est le SQL à appliquer. Vous fournissez également une action pour inverser la migration à l'aide du reverse_sql argument.

Inverser une migration peut être utile lorsque vous découvrez une erreur et que vous souhaitez annuler la modification. La plupart des actions de migration intégrées peuvent être annulées. Par exemple, l'action inverse pour ajouter un champ supprime le champ. L'action inverse pour créer une nouvelle table consiste à supprimer la table. Il est généralement préférable de fournir reverse_SQL à RunSQL afin que vous puissiez revenir en arrière si quelque chose tourne mal.

Dans ce cas, l'opération de migration vers l'avant insère des données de produit_produit dans catalogue_produit. L'opération en arrière fera exactement le contraire, en insérant des données de catalogue_produit dans produit_produit. En fournissant à Django l'opération inverse, vous pourrez inverser la migration en cas de sinistre.

À ce stade, vous êtes encore à mi-chemin du processus de migration. Mais il y a une leçon à tirer ici, alors allez-y et appliquez les migrations:

$ produit de migration python manage.py
Opérations à effectuer:
        Appliquer toutes les migrations: produit
Exécution de migrations:
        Application du produit 0001_initial ... OK
        Application du produit 0002_auto_20200124_1300 ... OK

Avant de passer à l'étape suivante, essayez de créer un nouveau produit:

>>>

>>> de produits.modèles importation Produit
>>> Produit.objets.créer(Nom=«Bottes fantaisie», category_id=2)
Traceback (dernier appel le plus récent):
  Fichier "/venv/lib/python3.8/site-packages/django/db/backends/utils.py", ligne 86, dans _exécuter
    revenir soi.le curseur.exécuter(sql, params)
psycopg2.errors.UniqueViolation: la valeur de clé en double viole la contrainte unique "product_product_pkey"
DÉTAIL: La clé (id) = (1) existe déjà.

Lorsque vous utilisez un clé primaire à incrémentation automatique, Django crée une séquence dans la base de données pour attribuer des identifiants uniques aux nouveaux objets. Notez, par exemple, que vous n'avez pas fourni d'ID pour le nouveau produit. Normalement, vous ne voudriez pas fournir un ID, car vous souhaitez que la base de données vous attribue des clés primaires à l'aide d'une séquence. Cependant, dans ce cas, la nouvelle table a donné l'ID au nouveau produit 1 même si cet ID existait déjà dans la table.

Alors, qu'est-ce qui a mal tourné? Lorsque vous avez copié les données dans la nouvelle table, vous n'avez pas synchronisé la séquence. Pour synchroniser la séquence, vous pouvez utiliser une autre commande d'administration Django appelée sqlsequencereset. La commande produit un script pour définir la valeur actuelle de la séquence en fonction des données existantes dans la table. Cette commande est souvent utilisée pour remplir de nouveaux modèles avec des données préexistantes.

Utilisation sqlsequencereset pour produire un script pour synchroniser la séquence:

$ produit python manage.py sqlsequencereset
COMMENCER;
SELECT setval (pg_get_serial_sequence ('"product_product"', 'id'), coalesce (max ("id"), 1), max ("id") N'EST PAS nul)
FROM "product_product";
COMMETTRE;

Le script généré par la commande est spécifique à la base de données. Dans ce cas, la base de données est PostgreSQL. Le script définit la valeur actuelle de la séquence sur la valeur suivante que la séquence doit produire, qui est l'ID maximum dans le tableau plus un.

Pour terminer, ajoutez l'extrait de code à la migration des données:

--- un / magasin / produit / migrations / 0002_auto_20200124_1300.py
+++ b / magasin / produit / migrations / 0002_auto_20200124_1300.py
@@ -22,6 +22,8 @@ classe Migration (migrations.Migration):
                 category_id
             DE
                 catalog_product;
+
+ SELECT setval (pg_get_serial_sequence ('"product_product"', 'id'), coalesce (max ("id"), 1), max ("id") N'EST PAS nul) FROM "product_product";
         "" ", reverse_sql =" ""
             INSÉRER DANS catalog_product (
                 id,

L'extrait synchronisera la séquence lorsque vous appliquerez la migration, résolvant le problème de séquence que vous avez rencontré ci-dessus.

Ce détour pour en savoir plus sur la synchronisation des séquences a créé un petit gâchis dans votre code. Pour le nettoyer, supprimez les données du nouveau modèle du shell Django:

>>>

>>> de produits.modèles importation Produit
>>> Produit.objets.tout().supprimer()
(3, 'produit.Produit': 3)

Maintenant que les données que vous avez copiées sont supprimées, vous pouvez inverser la migration. Pour inverser une migration, vous migrez vers une migration précédente:

$ produit showmigrations python manage.py
produit
 [X]    0001_initial
 [X]    0002_auto_20200124_1300

$ python manage.py migrer le produit 0001_initial
Opérations à effectuer:
        Migration spécifique cible: 0001_initial, à partir du produit
Exécution de migrations:
        Le modèle de rendu indique ... TERMINÉ
        Produit non appliqué.0002_auto_20200124_1300 ... OK

Vous avez d'abord utilisé la commande showmigrations pour répertorier les migrations appliquées à l'application produit. La sortie montre que les deux migrations ont été appliquées. Vous avez ensuite inversé la migration 0002_auto_20200124_1300 en migrant vers la migration précédente, 0001_initial.

Si vous exécutez showmigrations encore une fois, vous verrez que la deuxième migration n'est plus marquée comme appliquée:

$ produit showmigrations python manage.py
produit
 [X]    0001_initial
 [ ]    0002_auto_20200124_1300

La case vide confirme que la deuxième migration a été inversée. Maintenant que vous avez une table rase, exécutez les migrations avec le nouveau code:

$ produit de migration python manage.py
Opérations à effectuer:
        Appliquer toutes les migrations: produit
Exécution de migrations:
        Application du produit 0002_auto_20200124_1300 ... OK

La migration a été appliquée avec succès. Assurez-vous que vous pouvez maintenant créer un nouveau Produit dans le shell Django:

>>>

>>> de produits.modèles importation Produit
>>> Produit.objets.créer(Nom=«Bottes fantaisie», category_id=2)

Incroyable! Votre travail acharné a porté ses fruits et vous êtes prêt pour la prochaine étape.

Mettre à jour les clés étrangères vers le nouveau modèle

L'ancienne table contient actuellement d'autres tables qui la référencent à l'aide d'un Clé étrangère champ. Avant de pouvoir supprimer l'ancien modèle, vous devez modifier les modèles référençant l'ancien modèle afin qu'ils référencent le nouveau modèle.

Un modèle qui fait toujours référence à l'ancien modèle est Vente dans le vente app. Modifiez la clé étrangère dans le Vente modèle pour référencer le nouveau Produit modèle:

--- un / magasin / vente / models.py
+++ b / magasin / vente / models.py
@@ -1,6 +1,6 @@
 à partir des modèles d'importation django.db

-de catalogue.models importer un produit
+ de product.models import Produit

 Vente de classe (models.Model):
     created = models.DateTimeField ()

Générez la migration et appliquez-la:

$ python manage.py makemigrations vente
Migrations pour la «vente»:
        vente / migrations / 0002_auto_20200124_1343.py
                - Alter field product en vente

$ python manage.py migrer la vente
Opérations à effectuer:
        Appliquer toutes les migrations: vente
Exécution de migrations:
        Appliquer sale.0002_auto_20200124_1343 ... OK

le Vente modèle fait désormais référence à la nouvelle Produit modèle dans le produit app. Étant donné que vous avez déjà copié toutes les données dans le nouveau modèle, il n'y a aucune violation de contrainte.

Supprimer l'ancien modèle

L'étape précédente a éliminé toutes les références à l'ancien Produit modèle. Il est désormais sûr de supprimer l’ancien modèle du catalogue application:

--- un / magasin / catalogue / models.py
+++ b / magasin / catalogue / models.py
@@ -3,8 +3,3 @@ à partir de modèles d'importation django.db

 Catégorie Catégorie (models.Model):
     name = models.CharField (max_length = 100)
-
-
-Produit de classe (modèles.Modèle):
- name = models.CharField (max_length = 100, db_index = True)
- category = models.ForeignKey (Category, on_delete = models.CASCADE, related_name = '+')

Générer une migration mais ne l'applique pas encore:

$ python manage.py makemigrations
Migrations pour 'catalogue':
        catalogue / migrations / 0003_delete_product.py
                - Supprimer le modèle de produit

Pour vous assurer que l'ancien modèle est supprimé uniquement après les données sont copiées, ajoutez la dépendance suivante:

--- un / magasin / catalogue / migrations / 0003_delete_product.py
+++ b / magasin / catalogue / migrations / 0003_delete_product.py
@@ -7,6 +7,7 @@ classe Migration (migrations.Migration):

     dépendances =[
         ('catalogue', '0002_auto_20200124_1250'),
+ ('vente', '0002_auto_20200124_1343'),
     ]opérations =[

L'ajout de cette dépendance est extrêmement important. Ignorer cette étape peut avoir des conséquences terribles, notamment la perte de données. Pour en savoir plus sur les fichiers de migration et les dépendances entre les migrations, consultez Digging Deeper Into Django Migrations.

Maintenant que vous avez ajouté la dépendance, appliquez la migration:

$ python manage.py migrer le catalogue
Opérations à effectuer:
        Appliquer toutes les migrations: catalogue
Exécution de migrations:
        Application du catalogue.0003_delete_product ... OK

Le transfert est maintenant terminé! Vous avez réussi à déplacer le Produit modèle de la catalogue app pour le nouveau produit en créant un nouveau modèle et en y copiant les données.

Bonus: inversez les migrations

L'un des avantages des migrations Django est qu'elles sont réversibles. Que signifie qu'une migration est réversible? Si vous faites une erreur, vous pouvez inverser la migration et la base de données reviendra à l'état d'avant l'application de la migration.

Rappelez-vous comment vous avez fourni précédemment reverse_sql à RunSQL? Eh bien, c'est là que ça paie.

Appliquez toutes les migrations sur une nouvelle base de données:

$ migration de python manage.py
Opérations à effectuer:
        Appliquer toutes les migrations: admin, auth, catalogue, contenttypes, produit, vente, sessions
Exécution de migrations:
        Application du produit 0001_initial ... OK
        Application du produit 0002_auto_20200124_1300 ... OK
        Appliquer sale.0002_auto_20200124_1343 ... OK
        Application du catalogue.0003_delete_product ... OK

Maintenant, inversez-les tous en utilisant le mot-clé spécial zéro:

$ python manage.py migrer le produit zéro
Opérations à effectuer:
        Désappliquer toutes les migrations: produit
Exécution de migrations:
        Le modèle de rendu indique ... TERMINÉ
        Désapplication du catalogue.0003_delete_product ... OK
        Vente sans application.0002_auto_20200124_1343 ... OK
        Produit non appliqué.0002_auto_20200124_1300 ... OK
        Désapplication du produit.0001_initial ... OK

La base de données est maintenant revenue à son état d'origine. Si vous déployez cette version et découvrez une erreur, vous pouvez l'inverser!

Gérer les cas spéciaux

Lorsque vous déplacez des modèles d'une application à une autre, certaines fonctionnalités de Django peuvent nécessiter une attention particulière. En particulier, ajouter ou modifier contraintes de base de données et en utilisant relations génériques les deux nécessitent des précautions supplémentaires.

Modification des contraintes

L'ajout de contraintes aux tables contenant des données peut être une opération dangereuse à effectuer sur un système en direct. Pour ajouter une contrainte, la base de données doit d'abord la vérifier. Pendant la vérification, la base de données acquiert un verrou sur la table, ce qui peut empêcher d'autres opérations jusqu'à la fin du processus.

Certaines contraintes, telles que PAS NUL et VÉRIFIER, peut nécessiter une analyse complète de la table pour vérifier que les nouvelles données sont valides. D'autres contraintes, telles que CLÉ ÉTRANGÈRE, nécessitent une validation avec une autre table, ce qui peut prendre un certain temps en fonction de la taille de la table référencée.

Gestion des relations génériques

Si vous utilisez des relations génériques, vous aurez peut-être besoin d'une étape supplémentaire. Les relations génériques utilisent à la fois la clé primaire et l'ID de type de contenu du modèle pour référencer une ligne dans tout table modèle. L'ancien modèle et le nouveau modèle n'ont pas le même ID de type de contenu, les connexions génériques peuvent donc se rompre. Cela peut parfois passer inaperçu car l'intégrité des clés étrangères génériques n'est pas appliquée par la base de données.

Il existe deux façons de gérer les clés étrangères génériques:

  1. Mettez à jour l'ID de type de contenu du nouveau modèle à celui de l'ancien modèle.
  2. Mettez à jour l'ID de type de contenu de toutes les tables de référence avec celui du nouveau modèle.

Dans les deux cas, assurez-vous de le tester correctement avant de le déployer en production.

Résumé: avantages et inconvénients de la copie des données

Déplacer un modèle Django vers une autre application en copiant les données a ses avantages et ses inconvénients. Voici quelques-uns des avantages associés à cette approche:

  • Il est soutenu par l'ORM: Effectuer cette transition à l'aide d'opérations de migration intégrées garantit une prise en charge appropriée de la base de données.
  • C'est réversible: Inverser cette migration est possible si nécessaire.

Voici quelques-uns des les inconvénients associés à cette approche:

  • C'est lent: La copie de grandes quantités de données peut prendre du temps.
  • Il nécessite des temps d'arrêt: La modification des données de l'ancienne table pendant sa copie dans la nouvelle table entraînera une perte de données pendant la transition. Pour éviter que cela ne se produise, un temps d'arrêt est nécessaire.
  • Il nécessite un travail manuel pour synchroniser la base de données: Le chargement des données dans des tables existantes nécessite des séquences de synchronisation et des clés étrangères génériques.

Comme vous le verrez dans les sections suivantes, l'utilisation de cette approche pour déplacer un modèle Django vers une autre application prend beaucoup plus de temps que les autres approches.