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?
11 réponses
je supprime l'ancienne réponse qui peut entraîner une perte de données. ozan "mentionné 151960920" , nous pouvons créer 2 migrations un dans chaque application.
première migration pour supprimer le modèle de la 1ère application.
$ python manage.py makemigrations old_app --empty
édite 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 de créer le nouveau tableau dans la 2ème application. Après avoir déplacé le code du modèle vers la 2e application
$ python manage.py makemigrations new_app
et éditer le fichier de migration vers quelque chose comme ça.
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)
]
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)
]
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:
- avant de faire quoi que ce soit, changez tous vos
ForeignKey
enIntegerfield
. Ensuite, exécutezpython manage.py makemigrations
- après avoir fait les pas D'Ozan, reconvertissez vos clés étrangères: remettez
ForeignKey(TheModel)
au lieu deIntegerField()
. 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:)
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
- Copiez votre modèle (le code) de app1 à app2.
-
ajouter ceci à app2.Votremodèle:
Class Meta: db_table = 'app1_yourmodel'
-
$ python manage.py mouvements migratoires app2
-
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
-
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.
-
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
-
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é.
-
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.
- 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.
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:
- avant de déplacer tout modèle, assurez-vous de travailler avec une ligne de base propre en exécutant
makemigrations
. - déplacer le code pour le modèle de
app1
àapp2
-
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'
-
Exécuter
makemigrations
. Cela généreraCreateModel
dansapp2
etDeleteModel
dansapp1
. 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. -
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
dansSeparateDatabaseAndState
le fait. Nous enveloppons donc toutes les entréesmigrations
dans les deux fichiers de migration avecSeparateDatabaseAndState(state_operations=[...])
. Par exemple,operations = [ ... migrations.DeleteModel( name='YourModel', ), ... ]
devient
operations = [ migrations.SeparateDatabaseAndState(state_operations=[ ... migrations.DeleteModel( name='YourModel', ), ... ]) ]
-
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 sontapp2.migrations.0004_auto_<date>
(pour leCreate
) etapp1.migrations.0007_auto_<date>
(pour leDelete
), la chose la plus simple à faire est:- ouvrir
app1.migrations.0007_auto_<date>
et copier sa dépendanceapp1
(par exemple('app1', '0006...'),
). Ce est la migration "immédiatement antérieure"dansapp1
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 listedependencies
.
- ouvrir
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
dansstate_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:
- copier le modèle de
app1
àapp2
, mettredb_table
, mais ne changez aucune référence FK. - exécuter
makemigrations
et envelopper tousapp2
migration dansstate_operations
(voir ci-dessus))- comme ci-dessus, ajouter une dépendance dans le
app2
CreateTable
au dernierapp1
migration
- comme ci-dessus, ajouter une dépendance dans le
- 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. -
Exécuter
makemigrations
mais NE PAS envelopper tout enstate_operations
(FK changements devraient effectivement se produire)- ajouter une dépendance dans toutes les migrations
ForeignKey
(i.e.AlterField
) à la migrationCreateTable
dansapp2
(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 exempleapp2.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>'), ]
- ajouter une dépendance dans toutes les migrations
-
supprimer les modèles de
app1
- exécuter
makemigrations
et envelopper la migrationapp1
dansstate_operations
.- ajouter une dépendance à toutes les migrations
ForeignKey
(i.e.AlterField
) de l'étape précédente (peut inclure les migrations dansapp1
etapp2
). - quand j'ai construit ces migrations, le
DeleteTable
il dépendait déjà des migrationsAlterField
donc je n'ai pas eu besoin de l'appliquer manuellement (i.e.Alter
avantDelete
).
- ajouter une dépendance à toutes les migrations
à 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):
- supprimer la
db_table
Meta entry - Run
makemigrations
encore une fois pour générer la base de données rebaptisée - É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 leDelete
devrait être purement logique, mais j'ai rencontré des erreurs (par exempleapp1_yourmodel
n'existe pas) si je ne le fais pas.
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!
vous pouvez essayer ce qui suit (non testé):
- déplacer le modèle de
src_app
àdest_app
- migrate
dest_app
; assurez-vous que le schéma de migration dépend de la dernièresrc_app
migration ( https://docs.djangoproject.com/en/dev/topics/migrations/#migration-files ) - ajouter une migration de données à
dest_app
, qui copie toutes les données desrc_app
- migrate
src_app
; assurez-vous que la migration de schéma dépend de la dernière (données) migration dedest_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.
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.
- changer les noms des anciens modèles pour ‘model_name_old’
- makemigrations
- 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)
- makemigrations
- écrire une coutume de la migration de migration de toutes les données vers le nouveau modèle de tables
- 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
- lorsque tout est satisfaisant, supprimer les anciens modèles
- makemigrations
- changer les nouveaux modèles au nom correct 'model_name_new' - > 'model_name’
- test de la flopée de migration sur un serveur de test
- 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.
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
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))
],
),
]