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

donc il y a environ un an j'ai commencé un projet et comme tous les nouveaux développeurs Je ne me suis pas trop concentré sur la structure, cependant maintenant je suis plus loin avec Django il a commencé à apparaître que ma mise en page de projet principalement mes modèles sont horribles dans la structure.

j'ai des modèles principalement lieu dans une application unique et vraiment la plupart de ces modèles devraient être dans leurs propres applications, j'ai essayer de résoudre ce et de les déplacer avec le sud, mais je l'ai trouvé délicate et vraiment difficile en raison de clés étrangères ect.

cependant grâce à Django 1.7 et construit pour soutenir les migrations, y a-t-il une meilleure façon de le faire maintenant?

93
demandé sur Francisco Couzo 2014-09-03 19:36:36

11 réponses

cela peut être fait assez facilement en utilisant migrations.SeparateDatabaseAndState . Fondamentalement, nous utilisons une opération de base de données pour renommer la table en même temps que deux opérations d'État pour supprimer le modèle de l'histoire 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

d'abord, Copiez le modèle à la nouvelle application model.py, puis:

python manage.py makemigrations new_app

cela générera une migration avec une opération naïve CreateModel comme seule opération. Envelopper cela dans une opération SeparateDatabaseAndState telle que nous n'essayons pas de recréer la table. Inclure également la migration antérieure comme une 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)
    ]
274
répondu ozan 2014-10-20 18:30:12

j'ai rencontré le même problème. la réponse D'Ozan m'a beaucoup aidé mais n'a malheureusement pas été suffisante. En effet, J'ai eu plusieurs ForeignKey lien vers le modèle que je voulais déplacer. Après quelques maux de tête j'ai trouvé la solution donc décidé de le poster pour résoudre le temps des gens.

vous avez besoin de 2 autres étapes:

  1. avant de faire quoi que ce soit, changez tous vos ForeignKey en Integerfield . Ensuite, exécutez python manage.py makemigrations
  2. après avoir fait les pas D'Ozan, reconvertissez vos clés étrangères: remettez ForeignKey(TheModel) au lieu de IntegerField() . Faites ensuite les migrations à nouveau ( python manage.py makemigrations ). Vous pouvez alors migrer et cela devrait fonctionner ( python manage.py migrate )

j'Espère que ça aide. Bien sûr, le tester en local avant d'essayer en production pour éviter de mauvaises surprises:)

19
répondu otranzer 2017-05-23 12:26:27

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

Situation

app1.Votremodèle

mais vous voulez aller à: app2.Votremodèle

  1. Copiez votre modèle (le code) de app1 à app2.
  2. ajouter ceci à app2.Votremodèle:

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

  4. Une nouvelle migration (p. ex. 0009_auto_something.py se fait dans app2 avec les migrations.CreateModel (), déplacez cette déclaration à la migration initiale de app2 (par ex. 0001_initial.py) (ce sera juste comme il ont toujours été là). Et maintenant supprimer la migration créée = 0009_auto_something.py

  5. comme vous agissez, comme app2.YourModel toujours a été là, maintenant supprimer l'existence de app1.Votre modèle de vos migrations. Signification: commentez les instructions CreateModel, et chaque ajustement ou migration de données que vous avez utilisé après cela.

  6. et bien sûr, toute référence à app1.Votre modèle doit être changé en app2.YourModel par le biais de votre projet. Aussi, n'oubliez pas que toutes les clés étrangères pour app1.Votre modèle dans les migrations doit être changé en app2.Votremodèle

  7. Maintenant, si vous n' $ python manage.py migrer, rien n'a changé, aussi quand vous n' $ python manage.py makemigrations, rien de nouveau n'a été détecté.

  8. maintenant la touche finale: supprimer la classe Meta de app2.YourModel et n' $ python manage.py makemigrations app2 && python manage.py migrer app2 (si vous regardez dans cette migration, vous verrez quelque chose comme ceci:)

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

table=None, signifie qu'il prendra le nom de table par défaut, qui dans ce cas sera app2_yourmodel.

  1. fait, avec les données enregistrées.

P. S pendant la migration il verra que ce content_type app1.votre modèle a été retiré et peut être supprimé. Vous pouvez dire oui, mais seulement si vous ne l'utilisez pas. Dans le cas où vous dépendez fortement sur elle pour avoir FKs à ce type de contenu être intact, ne pas répondre oui ou non encore, mais aller dans le db que le temps manuellement, et supprimer le contentype app2.yourmodel, et renommer le contenttype app1.yourmodel à app2.votre modèle, et puis Continuer en répondant non.

11
répondu Michael van de Waeter 2015-06-11 15:06:51

je suis nerveuse de la main-codage des migrations (comme cela est requis par Ozan réponse) donc la suite combine Ozan et Michael les stratégies visant à réduire la quantité de main-codage:

  1. avant de déplacer tout modèle, assurez-vous de travailler avec une ligne de base propre en exécutant makemigrations .
  2. déplacer le code pour le modèle de app1 à app2
  3. comme recommandé par @Michael, nous pointons le nouveau modèle à l'ancienne table de base de données en utilisant l'option de méta db_table sur le" nouveau "modèle:

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

  5. en réalité, nous ne voulons pas (ou avons besoin) de faire quoi que ce soit à la table. Nous avons juste besoin que Django croie que le changement a été fait. Selon la réponse de @Ozan, le drapeau state_operations dans SeparateDatabaseAndState le fait. Nous enveloppons donc toutes les entrées migrations dans les deux fichiers de migration avec SeparateDatabaseAndState(state_operations=[...]) . Par exemple,

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

    devient

    operations = [
        migrations.SeparateDatabaseAndState(state_operations=[
            ...
            migrations.DeleteModel(
                name='YourModel',
            ),
            ...
        ])
    ]
    
  6. EDIT : vous devez également vous assurer que la nouvelle migration" virtuelle " CreateModel dépend de toute migration que réellement créé ou modifié la table originale . 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:

    • ouvrir app1.migrations.0007_auto_<date> et copier sa dépendance app1 (par exemple ('app1', '0006...'), ). Ce est la migration "immédiatement antérieure"dans app1 et devrait inclure des dépendances sur toute la logique de construction du modèle réel.
    • ouvrir app2.migrations.0004_auto_<date> et ajouter la dépendance que vous venez de copier sur sa liste dependencies .

EDIT : si vous avez ForeignKey relation(s) au 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 créées automatiquement pour les ForeignKey modifications
  • nous ne voulons pas envelopper les changements ForeignKey dans state_operations nous devons donc nous assurer qu'ils sont séparés des opérations de table.

l'ensemble" minimum "d'opérations diffère selon la situation, mais la procédure suivante devrait fonctionner pour la plupart /toutes ForeignKey migrations:

  1. copier le modèle de app1 à app2 , mettre db_table , mais ne changez aucune référence FK.
  2. exécuter makemigrations et envelopper tous app2 migration dans state_operations (voir ci-dessus))
    • comme ci-dessus, ajouter une dépendance dans le app2 CreateTable au dernier app1 migration
  3. Point toutes les références FK au nouveau modèle. Si vous ne sont pas en utilisant des références de chaîne, déplacer l'ancien modèle au bas de models.py (ne pas l'enlever) de sorte qu'il ne rivalise pas avec la classe importée.
  4. Exécuter makemigrations mais NE PAS envelopper tout en state_operations (FK changements devraient effectivement se produire)

    • ajouter une dépendance dans toutes les migrations ForeignKey (i.e. AlterField ) à la migration CreateTable dans app2 (vous aurez besoin de cette liste pour la prochaine étape afin de 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.
    • Trouvez toutes les migrations qui ont une clé étrangère à ce modèle (par exemple en cherchant app2.YourModel pour trouver des migrations comme:

      class Migration(migrations.Migration):
      
          dependencies = [
              ('otherapp', '0001_initial'),
          ]
      
          operations = [
              migrations.AlterField(
                  model_name='relatedmodel',
                  name='fieldname',
                  field=models.ForeignKey(... to='app2.YourModel'),
              ),
          ]
      
    • ajouter le CreateModel migration 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écuter makemigrations et envelopper la migration app1 dans state_operations .
    • ajouter une dépendance à toutes les migrations ForeignKey (i.e. AlterField ) de l'étape précédente (peut inclure les migrations dans app1 et app2 ).
    • quand j'ai construit ces migrations, le DeleteTable il dépendait déjà des migrations AlterField donc je n'ai pas eu besoin de l'appliquer manuellement (i.e. Alter avant Delete ).

à ce stade, Django est bon à emporter. Le nouveau modèle pointe vers l'ancienne table et les migrations de Django l'ont convaincue que tout a été déplacé de manière appropriée. La grande mise en garde (tirée de la réponse de @Michael) est qu'un nouveau ContentType est créé pour le nouveau modèle. Si vous avez un lien (par exemple, par ForeignKey ) aux types de contenu, vous aurez besoin de créer une migration pour mettre à jour la table ContentType .

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

  1. supprimer la db_table Meta entry
  2. Run makemigrations encore une fois pour générer la base de données rebaptisée
  3. Éditez cette dernière migration et assurez-vous qu'elle dépend de la DeleteTable la migration. Il ne semble pas que cela devrait être nécessaire car le Delete devrait être purement logique, mais j'ai rencontré des erreurs (par exemple app1_yourmodel n'existe pas) si je ne le fais pas.
5
répondu claytond 2018-05-17 18:42:05

cela a été testé rudement, donc n'oubliez pas de sauvegarder votre DB!!!

par exemple, il y a deux applications: src_app et dst_app , nous voulons déplacer le modèle MoveMe de src_app à dst_app .

créer 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 .

ajouter une opération qui renomme la table pour MoveMe modèle et renomme son app_label dans ProjectState à XXX1_dst_app_new . N'oubliez pas d'ajouter la dépendance à la migration XXX0_src_app_old . Le résultat XXX1_dst_app_new migration est:

# -*- 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'),
    ]

ajouter la dépendance à XXX1_dst_app_new à XXX1_src_app_new . XXX1_src_app_new est une migration non-op qui est nécessaire pour s'assurer que les futures migrations src_app seront effectuées après XXX1_dst_app_new .

déplacer MoveMe de src_app/models.py à dst_app/models.py . Puis exécuter:

python manage.py migrate

C'est tout!

0
répondu Sergey Fedoseev 2014-09-12 19:09:43

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

  1. déplacer le modèle de src_app à dest_app
  2. migrate dest_app ; assurez-vous que le schéma de migration 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. migrate src_app ; assurez-vous que la migration de schéma dépend de la dernière (données) migration de dest_app -- c'est-à-dire: la migration de l'étape 3

notez que vous serez copier la table entière, au lieu de déplacer il, mais de cette façon les deux applications ne doivent pas toucher une table qui appartient à l'autre application, qui je pense est plus important.

0
répondu Webthusiast 2014-10-13 13:18:41

disons que vous déplacez le model 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 le modèle 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.Le modèle 'dans les migrations d'app_a, vous le changez en' app_b.Le modèle.

je viens de le faire pour un projet existant, où je voulais extraire un certain modèle vers 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 d'app_b à app_a. Aussi, j'ai eu un méta défini manuellement.db_table pour mon modèle qui aurait pu aider.

notamment, vous finirez 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 originales appliquées. Si la migration originale et la migration réécrite se retrouvent avec la même schéma de base de données, alors une telle réécriture devrait être OK.

0
répondu akaariai 2015-10-13 07:11:10
  1. changer les noms des anciens modèles pour ‘model_name_old’
  2. makemigrations
  3. faire de nouveaux modèles nommés ' model_name_new’ avec des relations identiques sur les modèles liés (eg. modèle utilisateur a maintenant de l'utilisateur.blog_old et de l'utilisateur.blog_new)
  4. makemigrations
  5. écrire une coutume de la migration de migration de toutes les données vers le nouveau modèle de tables
  6. tester l'enfer hors de ces migrations en comparant les sauvegardes avec les nouvelles copies db avant et après l'exécution des migrations
  7. lorsque tout est satisfaisant, supprimer les anciens modèles
  8. makemigrations
  9. changer les nouveaux modèles au nom correct 'model_name_new' - > 'model_name’
  10. test de la flopée de migration sur un serveur de test
  11. prenez votre site de production pour quelques minutes afin d'exécuter toutes les migrations sans utilisateurs interférant

faites ceci individuellement pour chaque modèle qui doit être déplacé. Je ne suggérerais pas de faire ce que dit l'autre réponse en changeant les nombres entiers et les clés étrangères Il y a une chance que les nouvelles clés étrangères soient différentes et que les lignes puissent avoir des ID différents après les migrations et je ne voulais pas courir le risque d'avoir des ID inadéquats lors de la commutation vers des clés étrangères.

0
répondu tomcounsell 2017-04-04 05:25:20

une autre alternative hacky si les données ne sont pas grandes ou trop compliquées, mais encore important de maintenir, est de:

  • Obtenir des jeux de données de test à l'aide de manage.py dumpdata
  • procéder aux changements de modèle et aux migrations correctement, sans établir de relation entre les changements
  • Global remplacer les fixtures de l'ancien nom de modèle et d'application pour le nouveau
  • Charger des données à l'aide de manage.py loaddata
0
répondu Wtower 2017-06-20 06:45:07

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

dans le cas où vous devez déplacer le modèle et que vous n'avez plus accès à l'application (ou que vous ne voulez plus l'accès), vous pouvez créer une nouvelle opération et envisager de créer un nouveau modèle seulement si le modèle migré n'existe pas.

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
répondu Gal Singer 2017-11-20 13:23:03