如何在两个Django应用程序之间移动模型(Django 1.7)
所以大约一年前,我开始了一个项目,就像所有新开发者一样,我并没有把注意力放在结构上,但是现在我和Django一起开始出现,我的项目布局主要是我的模型在结构上很糟糕。
我有一个模型主要是在一个单一的应用程序,实际上大多数这些模型应该在他们自己的个人应用程序,我试图解决这个问题,并把它们向南移动,但我发现它很棘手,真的很难由于外键等。
然而,由于Django 1.7和内置的迁移支持,现在有更好的方法来做到这一点吗?
预先感谢您的任何帮助。
注意: ozan答案比我的好。 请使用该方法
创build应用程序和模型
1)创build两个应用程序, app1
& app2
。
$ python manage.py startapp app1 $ python manage.py startapp app2
2)创build两个模型model1
和model2
。
APP1 / model1.py
from django.db import models class model1(models.Model): name = models.CharField(max_length=100)
APP 2 / model2.py
from django.db import models class model1(models.Model): name = models.CharField(max_length=100)
3)应用迁移。
$ python manage.py makemigrations app1 && python migrate app1 $ python manage.py makemigrations app2 && python migrate app2
Django将创build2个表app1_model
& app2_model2
。 您可以将数据添加到模型。
让我们将less量数据添加到我们的模型中,以确保我们在移动模型时保留数据。
In [1]: from apps.app1.models import model1 In [2]: model1.objects.create() Out[2]: <model1: model1 object> In [3]: model1.objects.create() Out[3]: <model1: model1 object>
迁移模型
让我们看看如何执行实际的迁移。
4)重命名表和移动源代码转到您的数据库,并将app1_model
重命名为app2_model1
。 将model1
源代码从app1
移动到app2
并添加元选项。
APP1 / model1.py
from django.db import models
APP 2 / model2.py
from django.db import models class model2(models.Model): name = models.CharField(max_length=100) class model1(models.Model): name = models.CharField(max_length=100) class Meta: managed = False db_table = 'app2_model1'
5)应用迁移。 为两个应用程序应用迁移。
$ python manage.py makemigrations app2 && python manage.py migrate app2 $ python manage.py makemigrations app1 && python manage.py migrate --fake app1
迁移完成后,您可以检查模型中的数据。
In [1]: from apps.app2.models import model1 In [2]: model1.objects.all() Out[2]: [<model1: model1 object>, <model1: model1 object>]
这可以使用migrations.SeparateDatabaseAndState
相当容易地完成。 基本上,我们使用数据库操作来同时为两个状态操作重命名表,以从一个应用程序的历史logging中删除模型,并在另一个应用程序的历史logging中创build它。
从旧应用中移除
python manage.py makemigrations old_app --empty
在迁移中:
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) ]
添加到新的应用程序
首先,将模型复制到新应用程序的model.py,然后:
python manage.py makemigrations new_app
这将作为唯一的操作生成一个天真的CreateModel
操作的迁移。 将其包装在SeparateDatabaseAndState
操作中,这样我们不会尝试重新创build表。 还包括以前的迁移作为依赖项:
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) ]
我遇到了同样的问题。 奥赞的回答帮了我很多,但不幸的是还不够。 事实上,我有几个ForeignKey链接到我想移动的模型。 经过一番头痛后,我发现解决scheme决定发布解决人们的时间。
你还需要2个步骤:
- 在做任何事之前,将所有的
ForeignKey
链接TheModel
到Integerfield
。 然后运行python manage.py makemigrations
- 在做Ozan的步骤后,重新转换你的外键:放回
ForeignKey(TheModel)
而不是IntegerField()
。 然后再次进行迁移(python manage.py makemigrations
)。 然后你可以迁移,它应该工作(python manage.py migrate
)
希望能帮助到你。 当然,在尝试在生产之前testing它在本地,以避免不好的惊喜:)
我是怎么做到的(在Django == 1.8上testing,使用postgres,所以也可能是1.7)
情况
app1.YourModel
但是你想要它去: app2.YourModel
- 将您的模型(代码)从app1复制到app2。
-
将此添加到app2.YourModel:
Class Meta: db_table = 'app1_yourmodel'
-
$ python manage.py makemigrations app2
-
使用migrations.CreateModel()语句在app2中创build新的迁移(例如0009_auto_something.py),将此语句移至app2的初始迁移(例如0001_initial.py)(就像它总是在那里一样)。 现在删除创build的迁移= 0009_auto_something.py
-
就像你的行为,像app2.YourModel总是在那里,现在从你的迁移中删除app1.YourModel的存在。 含义:注释掉CreateModel语句,以及之后使用的每个调整或数据迁移。
-
当然,对app1.YourModel的每个引用都必须通过您的项目更改为app2.YourModel。 另外,不要忘记,迁移中所有可能的app1.YourModel外键都必须更改为app2.YourModel
-
现在,如果你执行$ python manage.py migrate,没有任何改变,当你做$ python manage.py makemigrations,没有新的检测。
-
现在最后一步:从app2.YourModel中删除Class Meta,并执行$ python manage.py makemigrations app2 && python manage.py migrate app2(如果你看看这个迁移,你会看到类似这样的:)
migrations.AlterModelTable( name='yourmodel', table=None, ),
table = None意味着它将采用默认的表名,在这种情况下将是app2_yourmodel。
- 完成,保存数据。
PS在迁移过程中将会看到那个content_type app1.yourmodel已经被删除并且可以被删除。 你可以肯定地说,但只有当你不使用它。 如果你严重依赖它来使FK的内容types完整,不要回答是或否,但手动进入数据库,并删除内容typesapp2.yourmodel,并重命名内容typesapp1。 yourmodel到app2.yourmodel,然后继续回答no。
这是粗略的testing,所以不要忘记备份你的数据库!
例如,有两个应用程序: src_app
和dst_app
,我们希望将模型MoveMe
从src_app
移动到dst_app
。
为两个应用程序创build空迁移:
python manage.py makemigrations --empty src_app python manage.py makemigrations --empty dst_app
我们假设,新的迁移是XXX1_src_app_new
和XXX1_dst_app_new
, XXX0_src_app_old
迁移是XXX0_src_app_old
和XXX0_dst_app_old
。
添加一个为MoveMe
模型重命名表的操作,并将其在ProjectState中的XXX1_dst_app_new
重命名为XXX1_dst_app_new
。 不要忘记添加对XXX0_src_app_old
迁移的依赖。 生成的XXX1_dst_app_new
迁移是:
# -*- 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'), ]
将对XXX1_dst_app_new
依赖关系添加到XXX1_src_app_new
。 XXX1_src_app_new
是无操作迁移,需要确保未来的src_app
迁移将在XXX1_dst_app_new
之后执行。
将MoveMe
从src_app/models.py
移动到dst_app/models.py
。 然后运行:
python manage.py migrate
就这样!
您可以尝试以下(未经testing):
- 将模型从
src_app
移到dest_app
- 迁移
dest_app
; 确保模式迁移取决于最新的src_app
迁移( https://docs.djangoproject.com/en/dev/topics/migrations/#migration-files ) - 将数据迁移添加到
dest_app
,即从src_app
复制所有数据 - 迁移
src_app
; 确保模式迁移取决于dest_app
的最新(数据)迁移 – 即:迁移第3步
请注意,您将复制整个表格,而不是移动它,但这样两个应用程序都不必触摸属于另一个应用程序的表格,我认为这个表格更重要。
比方说你正在将模型TheModel从app_a移动到app_b。
另一种解决scheme是手动更改现有的迁移。 我们的想法是,每次在app_a的迁移中看到一个操作改变TheModel的操作时,都会将该操作复制到app_b的初始迁移结束。 每当你在app_a的迁移中看到一个引用“app_a.TheModel”时,你就把它改为“app_b.TheModel”。
我只是这样做了一个现有的项目,我想提取一个特定的模型到一个可重用的应用程序。 程序顺利进行。 我想如果有app_b到app_a的引用,事情会变得更加困难。 此外,我有一个手动定义的Meta.db_table为我的模型可能有所帮助。
值得注意的是,你将最终以改变的移民历史。 这并不重要,即使您拥有应用了原始迁移的数据库。 如果原来的和重写的迁移都以相同的数据库模式结束,那么这样的重写应该是可以的。
- 将旧模型的名称更改为“model_name_old”
- makemigrations
- 在相关模型上创build名为“model_name_new”的新模型具有相同的关系(例如,用户模型现在具有user.blog_old和user.blog_new)
- makemigrations
- 编写一个将所有数据迁移到新模型表的自定义迁移
- 通过在运行迁移之前和之后将备份与新数据库副本进行比较来testing这些迁移
- 当所有的都满意的时候,删掉旧的机型
- makemigrations
- 将新模型更改为正确的名称'model_name_new' – >'model_name'
- 在暂存服务器上testing整个迁移
- 将您的生产站点停下来几分钟,以便在没有用户干扰的情况下运行所有迁移
为需要移动的每个模型单独执行此操作。 我不会build议通过更改为整数和回到外键做其他答案所说的。在迁移后,有可能新的外键会有所不同,并且行可能有不同的ID,我不想冒任何风险切换回外键时不匹配的ID。
如果数据不是太大或太复杂,但仍然很重要的另一个方法是:
- 使用manage.py dumpdata获取数据装置
- 继续正确模型更改和迁移,而不涉及更改
- 全局将旧模型和应用程序名称中的灯具replace为新的
- 使用manage.py loaddata加载数据