web-dev-qa-db-fra.com

Comment déplacer un modèle entre deux Django apps (Django 1.7)

Il y a environ un an, j'ai donc démarré un projet et, comme tous les nouveaux développeurs, je ne me suis pas vraiment concentré sur la structure. Cependant, maintenant, je suis plus loin que Django projet mise en page principalement mes modèles sont horribles dans la structure.

J'ai des modèles principalement conservés dans une seule application et vraiment la plupart de ces modèles devraient être dans leurs propres applications individuelles, j'ai essayé de résoudre ce problème et de les déplacer avec le sud, mais je l'ai trouvé difficile et très difficile en raison de clés étrangères ect.

Cependant, en raison de Django 1.7 et du support intégré pour les migrations, existe-t-il un meilleur moyen de le faire maintenant?

108
Sam Buckingham

Je supprime l'ancienne réponse car cela pourrait entraîner une perte de données. Comme ozan mentionné , nous pouvons créer 2 migrations, une dans chaque application.

Première migration pour supprimer le modèle de la 1ère application.

$ python manage.py makemigrations old_app --empty

Editez le fichier de migration pour inclure ces opérations.

class Migration(migrations.Migration):

    database_operations = [migrations.AlterModelTable('TheModel', 'newapp_themodel')]

    state_operations = [migrations.DeleteModel('TheModel')]

    operations = [
      migrations.SeparateDatabaseAndState(
        database_operations=database_operations,
        state_operations=state_operations)
    ]

Deuxième migration qui dépend de la première migration et crée la nouvelle table dans la 2e application. Après avoir déplacé le code du modèle vers la 2e application

$ python manage.py makemigrations new_app 

et éditez le fichier de migration pour quelque chose comme ceci.

class Migration(migrations.Migration):

    dependencies = [
        ('old_app', 'above_migration')
    ]

    state_operations = [
        migrations.CreateModel(
            name='TheModel',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
            ],
            options={
                'db_table': 'newapp_themodel',
            },
            bases=(models.Model,),
        )
    ]

    operations = [
        migrations.SeparateDatabaseAndState(state_operations=state_operations)
    ]
12
ChillarAnand

Ceci peut être fait assez facilement en utilisant migrations.SeparateDatabaseAndState. Fondamentalement, nous utilisons une opération de base de données pour renommer la table simultanément avec deux opérations d'état afin de supprimer le modèle de l'historique d'une application et de le créer dans une autre.

Supprimer de l'ancienne application

python manage.py makemigrations old_app --empty

Dans la migration:

class Migration(migrations.Migration):

    dependencies = []

    database_operations = [
        migrations.AlterModelTable('TheModel', 'newapp_themodel')
    ]

    state_operations = [
        migrations.DeleteModel('TheModel')
    ]

    operations = [
        migrations.SeparateDatabaseAndState(
            database_operations=database_operations,
            state_operations=state_operations)
    ]

Ajouter à la nouvelle application

Commencez par copier le modèle dans le fichier model.py de la nouvelle application, puis:

python manage.py makemigrations new_app

Cela générera une migration avec une opération naïve CreateModel comme seule opération. Emballez cela dans une opération SeparateDatabaseAndState de sorte que nous n'essayions pas de recréer la table. Incluez également la migration antérieure en tant que dépendance:

class Migration(migrations.Migration):

    dependencies = [
        ('old_app', 'above_migration')
    ]

    state_operations = [
        migrations.CreateModel(
            name='TheModel',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
            ],
            options={
                'db_table': 'newapp_themodel',
            },
            bases=(models.Model,),
        )
    ]

    operations = [
        migrations.SeparateDatabaseAndState(state_operations=state_operations)
    ]
309
ozan

J'ai rencontré le même problème. réponse d'Ozan m'a beaucoup aidé, mais malheureusement, cela ne suffisait pas. En effet, plusieurs liens ForeignKey étaient liés au modèle que je souhaitais déplacer. Après avoir eu mal à la tête, j'ai trouvé la solution et j'ai donc décidé de l'afficher pour résoudre le problème.

Vous avez besoin de 2 autres étapes:

  1. Avant de faire quoi que ce soit, changez tous vos liens ForeignKey vers TheModel en Integerfield. Puis lancez python manage.py makemigrations
  2. Après avoir suivi les étapes d'Ozan, reconvertissez vos clés étrangères: remettez ForeignKey(TheModel) au lieu de IntegerField(). Puis refaites les migrations (python manage.py makemigrations). Vous pouvez ensuite migrer et cela devrait fonctionner (python manage.py migrate)

J'espère que ça aide. Bien sûr, testez-le en local avant d'essayer en production pour éviter les mauvaises surprises :)

23
otranzer

Comment je l'ai fait (testé sur Django == 1.8, avec postgres, donc probablement aussi 1,7)

Situation

app1.YourModel

mais vous voulez qu'il aille à: app2.YourModel

  1. Copiez YourModel (le code) d’app1 à app2.
  2. ajoutez ceci à app2.YourModel:

    Class Meta:
        db_table = 'app1_yourmodel'
    
  3. $ python manage.py makemigrations app2

  4. Une nouvelle migration (par exemple, 0009_auto_something.py) est effectuée dans app2 avec une instruction migrations.CreateModel (), déplacez cette instruction vers la migration initiale de app2 (par exemple, 0001_initial.py) (elle sera comme si elle l'avait toujours été). Et maintenant, supprimez la migration créée = 0009_auto_something.py

  5. Tout comme vous agissez, comme app2.YourModel a toujours été là, supprimez maintenant app1.YourModel de vos migrations. Signification: commentez les instructions CreateModel et tous les ajustements ou migrations de données que vous avez utilisés par la suite.

  6. Et bien sûr, chaque référence à app1.YourModel doit être remplacée par app2.YourModel dans votre projet. N'oubliez pas non plus que toutes les clés étrangères possibles pour app1.YourModel dans les migrations doivent être remplacées par app2.YourModel.

  7. Maintenant, si vous faites $ python manage.py migrer, rien n'a changé, même lorsque vous faites $ python manage.py makemigrations, rien de nouveau n'a été détecté.

  8. La touche finale: supprimez la classe Meta de app2.YourModel et faites $ python manage.py makemigrations app2 && python manage.py migrate app2 (si vous Regardez dans cette migration, vous verrez quelque chose comme ça :)

        migrations.AlterModelTable(
        name='yourmodel',
        table=None,
    ),
    

table = None, cela signifie qu'il utilisera le nom de table par défaut, qui dans ce cas sera app2_votremodèle.

  1. FAIT, avec les données sauvegardées.

Pendant la migration, P.S verra que le type de contenu app1.votremodèle a été supprimé et peut être supprimé. Vous pouvez dire oui à cela, mais seulement si vous ne l'utilisez pas. Si vous dépendez beaucoup de la capacité des FK à conserver ce type de contenu, ne répondez pas encore oui ou non, mais entrez la base de données manuellement, supprimez le contentype app2.yourmodel et renommez le contenttype app1. votre modèle à app2.votre modèle, puis continuez en répondant non.

14

Les migrations avec codage manuel nerveuses (comme requis par Ozan's answer) combinent donc les stratégies suivantes: Ozan et les stratégies (Michael) visant à réduire le plus possible le codage manuel requis:

  1. Avant de déplacer des modèles, assurez-vous de travailler avec une ligne de base vierge en exécutant makemigrations.
  2. Déplacez le code pour le modèle de app1 À app2
  3. Comme recommandé par @Michael, nous pointons le nouveau modèle sur l'ancienne table de la base de données à l'aide de l'option db_table Meta sur le "nouveau" modèle:

    class Meta:
        db_table = 'app1_yourmodel'
    
  4. Exécutez makemigrations. Cela générera CreateModel dans app2 Et DeleteModel dans app1. Techniquement, ces migrations se réfèrent à la même table et supprimeraient (y compris toutes les données) et recréeraient la table.

  5. En réalité, nous ne voulons (ou n'avons pas besoin) de faire quoi que ce soit à la table. Nous avons juste besoin de Django pour croire que le changement a été effectué. La réponse de @ Ozan, l'indicateur state_operations Dans SeparateDatabaseAndState le fait. Ainsi, nous enveloppons tous les migrations entrées DANS LES DEUX FICHIERS DE MIGRATIONS avec SeparateDatabaseAndState(state_operations=[...]). Par exemple,

    operations = [
        ...
        migrations.DeleteModel(
            name='YourModel',
        ),
        ...
    ]
    

    devient

    operations = [
        migrations.SeparateDatabaseAndState(state_operations=[
            ...
            migrations.DeleteModel(
                name='YourModel',
            ),
            ...
        ])
    ]
    
  6. [~ # ~] modifier [~ # ~] : Vous devez également vous assurer que la nouvelle migration "virtuelle" CreateModel dépend de toute migration qui a réellement créé ou modifié la table d'origine . Par exemple, si vos nouvelles migrations sont app2.migrations.0004_auto_<date> (Pour le Create) et app1.migrations.0007_auto_<date> (Pour le Delete), la chose la plus simple à faire est la suivante:

    • Ouvrez app1.migrations.0007_auto_<date> Et copiez sa dépendance app1 (Par exemple ('app1', '0006...'),). Il s'agit de la migration "immédiatement antérieure" dans app1 Et doit inclure des dépendances sur l'ensemble de la logique de construction du modèle.
    • Ouvrez app2.migrations.0004_auto_<date> Et ajoutez la dépendance que vous venez de copier à sa liste dependencies.

[~ # ~] edit [~ # ~] : Si vous avez ForeignKey relation (s) avec le modèle que vous déplacez , ce qui précède peut ne pas fonctionner. Cela se produit parce que:

  • Les dépendances ne sont pas automatiquement créées pour les modifications ForeignKey
  • Nous ne souhaitons pas inclure les modifications ForeignKey dans state_operations, Nous devons donc nous assurer qu'elles sont distinctes des opérations de la table.

L'ensemble "minimal" d'opérations diffère en fonction de la situation, mais la procédure suivante devrait fonctionner pour la plupart/toutes les ForeignKey migrations:

  1. [~ # ~] copiez [~ # ~] le modèle de app1 à app2, définissez db_table, Mais NE changez PAS les références FK.
  2. Exécutez makemigrations et enveloppez toute la migration app2 Dans state_operations (Voir ci-dessus)
    • Comme ci-dessus, ajoutez une dépendance dans le fichier app2CreateTable à la dernière migration app1.
  3. Pointez toutes les références FK vers le nouveau modèle. Si vous n'utilisez pas de références de chaîne, déplacez l'ancien modèle vers le bas de models.py (NE LE RETIREZ PAS) afin qu'il ne fasse pas concurrence à la classe importée.
  4. Exécutez makemigrations mais n'emballez rien dans state_operations (Les modifications de FK devraient réellement se produire). Ajoutez une dépendance dans toutes les migrations ForeignKey (c'est-à-dire AlterField) à la migration CreateTable dans app2 (Vous aurez besoin de cette liste pour la prochaine étape afin garder une trace d'eux). Par exemple:

    • Trouvez la migration qui inclut le CreateModel par exemple. app2.migrations.0002_auto_<date> Et copiez le nom de cette migration.
    • Recherchez toutes les migrations qui ont une ForeignKey vers ce modèle (par exemple, en recherchant app2.YourModel Pour trouver des migrations telles que:

      class Migration(migrations.Migration):
      
          dependencies = [
              ('otherapp', '0001_initial'),
          ]
      
          operations = [
              migrations.AlterField(
                  model_name='relatedmodel',
                  name='fieldname',
                  field=models.ForeignKey(... to='app2.YourModel'),
              ),
          ]
      
    • Ajoutez la migration CreateModel en tant que dépendance:

      class Migration(migrations.Migration):
      
          dependencies = [
              ('otherapp', '0001_initial'),
              ('app2', '0002_auto_<date>'),
          ]  
      
  5. Supprimer les modèles de app1

  6. Exécutez makemigrations et encapsulez la migration app1 Dans state_operations.
    • Ajoutez une dépendance à toutes les ForeignKey migrations (c'est-à-dire AlterField) de l'étape précédente (peut inclure les migrations dans app1 Et app2).
    • Quand j'ai construit ces migrations, le DeleteTable dépendait déjà des migrations AlterField et je n'avais donc pas besoin de l'appliquer manuellement (c.-à-d. Alter avant Delete.) .

À ce stade, Django) convient. Le nouveau modèle pointe vers l’ancienne table et les migrations de Django l’ont convaincu que tout a été déplacé de manière appropriée. qu’un nouveau ContentType soit créé pour le nouveau modèle. Si vous créez un lien (par exemple, par ForeignKey) avec des types de contenu, vous devrez créer une migration pour mettre à jour le ContentType table.

Je voulais nettoyer après moi-même (options de méta et noms de table) alors j'ai utilisé la procédure suivante (de @Michael):

  1. Supprimer la méta entrée db_table
  2. Exécutez à nouveau makemigrations pour générer le renommage de la base de données.
  3. Éditez cette dernière migration et assurez-vous qu'elle dépend de la migration DeleteTable. Il ne semble pas que cela devrait être nécessaire, car Delete devrait être purement logique, mais j'ai rencontré des erreurs (par exemple, app1_yourmodel N'existe pas) si ce n'est pas le cas.
9
claytond

Une autre solution de rechange si les données ne sont pas volumineuses ou trop compliquées, mais qu'il est toujours important de conserver, consiste à:

  • Obtenir les fixtures de données en utilisant manage.py dumpdata
  • Modélisez correctement les modifications et les migrations sans les associer.
  • Global remplace les appareils de l’ancien modèle et les noms des applications par le nouveau
  • Charger des données en utilisant manage.py loaddata
1
Wtower

Copié de ma réponse à https://stackoverflow.com/a/47392970/8971048

Si vous devez déplacer le modèle et que vous n'avez plus accès à l'application (ou si vous ne souhaitez pas accéder à celui-ci), vous pouvez créer une nouvelle opération et envisager de créer un nouveau modèle uniquement si le modèle migré ne le fait pas. exister.

Dans cet exemple, je passe "MyModel" de old_app à myapp.

class MigrateOrCreateTable(migrations.CreateModel):
    def __init__(self, source_table, dst_table, *args, **kwargs):
        super(MigrateOrCreateTable, self).__init__(*args, **kwargs)
        self.source_table = source_table
        self.dst_table = dst_table

    def database_forwards(self, app_label, schema_editor, from_state, to_state):
        table_exists = self.source_table in schema_editor.connection.introspection.table_names()
        if table_exists:
            with schema_editor.connection.cursor() as cursor:
                cursor.execute("RENAME TABLE {} TO {};".format(self.source_table, self.dst_table))
        else:
            return super(MigrateOrCreateTable, self).database_forwards(app_label, schema_editor, from_state, to_state)


class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0002_some_migration'),
    ]

    operations = [
        MigrateOrCreateTable(
            source_table='old_app_mymodel',
            dst_table='myapp_mymodel',
            name='MyModel',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('name', models.CharField(max_length=18))
            ],
        ),
    ]
0
Gal Singer

Vous pouvez essayer ce qui suit (non testé):

  1. déplacer le modèle de src_app à dest_app
  2. migrer dest_app; assurez-vous que la migration du schéma dépend de la dernière src_app migration ( https://docs.djangoproject.com/en/dev/topics/migrations/#migration-files )
  3. ajouter une migration de données à dest_app, qui copie toutes les données de src_app
  4. migrer src_app; assurez-vous que la migration du schéma dépend de la dernière migration (de données) de dest_app - c'est-à-dire: la migration de l'étape 3

Notez que vous allez copier tout le tableau au lieu de le déplacer , mais de cette façon, les deux applications ne doivent pas toucher une table appartenant à l’autre application, ce qui, à mon avis, est plus important.

0
Webthusiast

Disons que vous déplacez le modèle TheModel d’app_a à app_b.

Une autre solution consiste à modifier manuellement les migrations existantes. L'idée est que chaque fois que vous voyez une opération modifier TheModel dans les migrations d'app_a, vous copiez cette opération à la fin de la migration initiale d'app_b. Et chaque fois que vous voyez une référence 'app_a.TheModel' dans les migrations d'app_a, vous la changez en 'app_b.TheModel'.

Je viens de faire cela pour un projet existant, où je voulais extraire un certain modèle pour une application réutilisable. La procédure s'est bien déroulée. Je suppose que les choses seraient beaucoup plus difficiles s'il y avait des références de app_b à app_a. En outre, j'avais un Meta.db_table défini manuellement pour mon modèle qui aurait pu aider.

En particulier, vous allez vous retrouver avec une histoire de migration modifiée. Cela n'a pas d'importance, même si vous avez une base de données avec les migrations d'origine appliquées. Si les migrations originale et réécrite se retrouvent avec le même schéma de base de données, cette réécriture devrait être OK.

0
akaariai

Ceci est testé approximativement, alors n'oubliez pas de sauvegarder votre base de données !!!

Par exemple, il existe deux applications: src_app Et dst_app, Nous souhaitons déplacer le modèle MoveMe de src_app Vers dst_app.

Créez des migrations vides pour les deux applications:

python manage.py makemigrations --empty src_app
python manage.py makemigrations --empty dst_app

Supposons que les nouvelles migrations sont XXX1_src_app_new Et XXX1_dst_app_new, Les migrations précédentes sont XXX0_src_app_old Et XXX0_dst_app_old.

Ajoutez une opération qui renomme la table pour MoveMe model et renomme son app_label dans ProjectState en XXX1_dst_app_new. N'oubliez pas d'ajouter une dépendance à la migration XXX0_src_app_old. La migration XXX1_dst_app_new Résultante est la suivante:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from Django.db import models, migrations

# this operations is almost the same as RenameModel
# https://github.com/Django/django/blob/1.7/Django/db/migrations/operations/models.py#L104
class MoveModelFromOtherApp(migrations.operations.base.Operation):

    def __init__(self, name, old_app_label):
        self.name = name
        self.old_app_label = old_app_label

    def state_forwards(self, app_label, state):

        # Get all of the related objects we need to repoint
        apps = state.render(skip_cache=True)
        model = apps.get_model(self.old_app_label, self.name)
        related_objects = model._meta.get_all_related_objects()
        related_m2m_objects = model._meta.get_all_related_many_to_many_objects()
        # Rename the model
        state.models[app_label, self.name.lower()] = state.models.pop(
            (self.old_app_label, self.name.lower())
        )
        state.models[app_label, self.name.lower()].app_label = app_label
        for model_state in state.models.values():
            try:
                i = model_state.bases.index("%s.%s" % (self.old_app_label, self.name.lower()))
                model_state.bases = model_state.bases[:i] + ("%s.%s" % (app_label, self.name.lower()),) + model_state.bases[i+1:]
            except ValueError:
                pass
        # Repoint the FKs and M2Ms pointing to us
        for related_object in (related_objects + related_m2m_objects):
            # Use the new related key for self referential related objects.
            if related_object.model == model:
                related_key = (app_label, self.name.lower())
            else:
                related_key = (
                    related_object.model._meta.app_label,
                    related_object.model._meta.object_name.lower(),
                )
            new_fields = []
            for name, field in state.models[related_key].fields:
                if name == related_object.field.name:
                    field = field.clone()
                    field.rel.to = "%s.%s" % (app_label, self.name)
                new_fields.append((name, field))
            state.models[related_key].fields = new_fields

    def database_forwards(self, app_label, schema_editor, from_state, to_state):
        old_apps = from_state.render()
        new_apps = to_state.render()
        old_model = old_apps.get_model(self.old_app_label, self.name)
        new_model = new_apps.get_model(app_label, self.name)
        if self.allowed_to_migrate(schema_editor.connection.alias, new_model):
            # Move the main table
            schema_editor.alter_db_table(
                new_model,
                old_model._meta.db_table,
                new_model._meta.db_table,
            )
            # Alter the fields pointing to us
            related_objects = old_model._meta.get_all_related_objects()
            related_m2m_objects = old_model._meta.get_all_related_many_to_many_objects()
            for related_object in (related_objects + related_m2m_objects):
                if related_object.model == old_model:
                    model = new_model
                    related_key = (app_label, self.name.lower())
                else:
                    model = related_object.model
                    related_key = (
                        related_object.model._meta.app_label,
                        related_object.model._meta.object_name.lower(),
                    )
                to_field = new_apps.get_model(
                    *related_key
                )._meta.get_field_by_name(related_object.field.name)[0]
                schema_editor.alter_field(
                    model,
                    related_object.field,
                    to_field,
                )

    def database_backwards(self, app_label, schema_editor, from_state, to_state):
        self.old_app_label, app_label = app_label, self.old_app_label
        self.database_forwards(app_label, schema_editor, from_state, to_state)
        app_label, self.old_app_label = self.old_app_label, app_label

    def describe(self):
        return "Move %s from %s" % (self.name, self.old_app_label)


class Migration(migrations.Migration):

    dependencies = [
       ('dst_app', 'XXX0_dst_app_old'),
       ('src_app', 'XXX0_src_app_old'),
    ]

    operations = [
        MoveModelFromOtherApp('MoveMe', 'src_app'),
    ]

Ajoutez la dépendance sur XXX1_dst_app_new À XXX1_src_app_new. XXX1_src_app_new Est une migration non opérationnelle nécessaire pour garantir que les futures migrations src_app Seront exécutées après XXX1_dst_app_new.

Déplacez MoveMe de src_app/models.py Vers dst_app/models.py. Puis lancez:

python manage.py migrate

C'est tout!

0
Sergey Fedoseev
  1. remplacez les noms des anciens modèles par ‘nom_modèle_old '
  2. makemigrations
  3. créer de nouveaux modèles nommés 'nom_modèle_nouveau' avec des relations identiques sur les modèles associés (par exemple, le modèle utilisateur a maintenant user.blog_old et user.blog_new)
  4. makemigrations
  5. écrire une migration personnalisée qui migre toutes les données vers les nouvelles tables de modèle
  6. testez ces migrations en comparant les sauvegardes avec les nouvelles copies de la base de données avant et après l'exécution des migrations
  7. lorsque tout est satisfaisant, supprimez les anciens modèles
  8. makemigrations
  9. remplacez les nouveaux modèles par le nom correct "nom_modèle_nouveau" -> "nom_modèle"
  10. tester l'ensemble des migrations sur un serveur de transfert
  11. arrêtez votre site de production pendant quelques minutes afin d'exécuter toutes les migrations sans interférence des utilisateurs

Faites-le individuellement pour chaque modèle à déplacer. Je ne recommanderais pas de faire ce que l'autre réponse dit en changeant en entiers et en clés étrangères. Il est possible que les nouvelles clés étrangères soient différentes et que les lignes aient des identifiants différents après les migrations et je ne voulais courir aucun risque. d’identités incompatibles lors du retour aux clés étrangères.

0
tomcounsell