How to move a model between two Django apps (Django 1.7)
所以大约一年前我开始了一个项目,像所有的新开发人员一样,我并没有太多地关注结构,但是现在我和Django更进一步,我的项目布局主要是我的模型在结构上很糟糕。
我的模型主要放在一个应用程序中,实际上大多数模型都应该放在自己的应用程序中,我确实尝试解决了这个问题,并将它们向南移动,但是我发现这很棘手,而且由于使用了外置钥匙等原因,确实很难实现。
然而,由于django 1.7和内置的迁移支持,现在有更好的方法来实现这一点吗?
这可以很容易地使用
1 | python manage.py makemigrations old_app --empty |
在迁移过程中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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,然后:
1 | python manage.py makemigrations new_app |
这将生成一个以幼稚的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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) ] |
。
我也遇到了同样的问题。奥赞的回答帮助了我很多,但不幸的是还不够。事实上,我有几个外星键链接到我想要移动的模型。头痛过后,我找到了解决办法,所以决定把它贴出来解决人的时间。
您还需要两个步骤:
希望有帮助。当然,在试生产之前先在当地测试一下,以避免不好的惊吓。
我是怎么做到的(在Django上测试的==1.8,在Postgres上测试的,所以也可能是1.7)
情况
app1.您的模式
但你想让它转到:附录2.你的模型
将此添加到app2.yourmodel:
1 2 | Class Meta: db_table = 'app1_yourmodel' |
$python manage.py makemigrations应用程序2
在app2中使用migrations.createModel()语句进行新的迁移(例如,0009_auto_something.py),将此语句移动到app2的初始迁移(例如,0001_initial.py)(就像它一直存在一样)。现在移除创建的migration=0009_auto_something.py
正如您所做的,就像app2.yourmodel一直在那里一样,现在从迁移中删除app1.yourmodel的存在。意思是:注释掉createModel语句,以及之后使用的每个调整或数据迁移。
当然,每个对app1.yourmodel的引用都必须通过项目更改为app2.yourmodel。另外,不要忘记迁移中app1.yourmodel的所有可能的外键都必须更改为app2.yourmodel。
现在,如果您执行$python manage.py迁移,则不会发生任何更改,而且当您执行$python manage.py makemigrations时,也不会检测到任何新的更改。
现在,最后一步:从app2.yourmodel中移除类meta,并执行$python manage.py makemigrations app2&;python manage.py migrate app2(如果您查看此迁移,您将看到类似的情况:)
1 2 3 4 | migrations.AlterModelTable( name='yourmodel', table=None, ), |
号
table=none,表示它将采用默认的表名,在本例中是app2 yourmodel。
p.s在迁移过程中,它将看到内容"type app1.yourmodel"已被删除,可以删除。你可以这样说,但只有当你不使用它。如果您严重依赖于FK使该内容类型保持完整,请不要回答"是"或"否",而是手动进入数据库,删除ContentType App2.YourModel,并将ContentType App1.YourModel重命名为App2.YourModel,然后继续回答"否"。
我正在删除旧的答案,因为这可能会导致数据丢失。正如Ozan提到的,我们可以在每个应用程序中创建两个迁移。
第一次迁移以从第一个应用程序中删除模型。
1 | $ python manage.py makemigrations old_app --empty |
编辑迁移文件以包括这些操作。
1 2 3 4 5 6 7 8 9 10 11 | 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) ] |
号
第二次迁移依赖于第一次迁移并在第二个应用程序中创建新表。将模型代码移动到第二个应用程序后
1 | $ python manage.py makemigrations new_app |
把迁移文件编辑成这样的文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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) ] |
。
我感到紧张的手编码迁移(按照Ozan的回答要求),因此下面结合了Ozan和Michael的策略,以尽量减少所需的手编码量:好的。
按照@michael的建议,我们使用"新"模型上的
1 2 | class Meta: db_table = 'app1_yourmodel' |
运行
实际上,我们不想(或不需要)对桌子做任何事情。我们只需要Django相信改变已经发生。根据@ozan的回答,
1 2 3 4 5 6 7 | operations = [ ... migrations.DeleteModel( name='YourModel', ), ... ] |
号
变成好的。
1 2 3 4 5 6 7 8 9 | operations = [ migrations.SeparateDatabaseAndState(state_operations=[ ... migrations.DeleteModel( name='YourModel', ), ... ]) ] |
编辑:您还需要确保新的"虚拟"
- 打开
app1.migrations.0007_auto_ 并复制其app1 依赖项(如('app1', '0006...'), )。这是app1 中的"立即优先"迁移,应该包括对所有实际模型构建逻辑的依赖。 - 打开
app2.migrations.0004_auto_ 并将刚才复制的依赖项添加到其dependencies 列表中。
小精灵
编辑:如果您与要移动的模型有
- 不会为
ForeignKey 更改自动创建依赖项 - 我们不想包装
ForeignKey 中的state_operations 更改,因此我们需要确保它们与表操作分开。 - 将模型从
app1 复制到app2 ,设置db_table ,但不要更改任何fk引用。 - 运行
makemigrations 并在state_operations 中包装所有app2 迁移(见上文)- 如上所述,在
app2 CreateTable 中添加对最新app1 迁移的依赖项。
小精灵
- 如上所述,在
- 将所有FK引用指向新模型。如果不使用字符串引用,请将旧模型移动到
models.py 的底部(不要删除它),这样它就不会与导入的类竞争。 运行
makemigrations ,但不要在state_operations 中包装任何内容(实际上应该发生fk更改)。在所有ForeignKey 迁移(即AlterField 迁移)中添加一个依赖项到app2 中的CreateTable 迁移(下一步需要这个列表,所以要跟踪它们)。例如:好的。- 找到包含
CreateModel 的迁移,例如app2.migrations.0002_auto_ 并复制该迁移的名称。 查找具有该模型的foreignkey的所有迁移(例如,通过搜索
app2.YourModel 查找如下迁移:好的。1
2
3
4
5
6
7
8
9
10
11
12
13class Migration(migrations.Migration):
dependencies = [
('otherapp', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='relatedmodel',
name='fieldname',
field=models.ForeignKey(... to='app2.YourModel'),
),
]将
CreateModel 迁移作为依赖项添加:好的。1
2
3
4
5
6class Migration(migrations.Migration):
dependencies = [
('otherapp', '0001_initial'),
('app2', '0002_auto_<date>'),
]号
小精灵
- 找到包含
从
app1 中删除模型好的。- 运行
makemigrations 并在state_operations 中包装app1 迁移。- 从上一步(可能包括
app1 和app2 中的迁移)添加对所有ForeignKey 迁移(即AlterField 的依赖关系。 - 当我建立这些迁移时,
DeleteTable 已经依赖于AlterField 迁移,所以我不需要手动执行它(即Alter 比Delete 早)。
小精灵
- 从上一步(可能包括
- 删除
db_table 元条目 - 再次运行
makemigrations 生成数据库重命名 - 编辑上一次迁移,并确保它依赖于
DeleteTable 迁移。似乎没有必要这样做,因为Delete 应该是纯逻辑的,但是如果我不这样做,我会遇到错误(例如app1_yourmodel 不存在)。 - 使用manage.py dumpdata获取数据设备
- 正确进行模型更改和迁移,而不关联更改
- 全局将旧模型和应用程序名称中的设备替换为新的
- 使用manage.py load data加载数据
- 将旧型号的名称更改为"型号名称旧"
- 进行迁移
- 在相关模型上创建具有相同关系的名为"modelu nameu new"的新模型(例如,用户模型现在有user.blog旧的和user.blog新的)
- 进行迁移
- 编写将所有数据迁移到新模型表的自定义迁移
- 通过在运行迁移之前和之后将备份与新的DB拷贝进行比较,测试这些迁移的性能。
- 当一切都满意时,删除旧型号
- 进行迁移
- 将新型号更改为正确的名称'modelu nameu new'->'modelu name'
- 在临时服务器上测试整个迁移过程
- 将生产站点关闭几分钟,以便在不受用户干扰的情况下运行所有迁移。
- 将模型从
src_app 移到dest_app - 迁移
dest_app ;确保模式迁移依赖于最新的src_app 迁移(https://docs.djangoproject.com/en/dev/topics/migrations/迁移文件) - 添加一个到
dest_app 的数据迁移,复制src_app 的所有数据。 - 迁移
src_app ;确保模式迁移依赖于dest_app 的最新(数据)迁移,即步骤3的迁移
小精灵
"最小"操作集因情况而异,但以下步骤适用于大多数/所有
在这一点上,姜戈是好去。新模型指向旧表,Django的迁移使它确信所有的东西都被适当地重新定位了。(从@michael的回答中)最大的警告是,为新型号创建了一个新的
我想在自己之后进行清理(元选项和表名),因此使用了以下过程(来自@michael):好的。
好啊。
如果数据不太大或太复杂,但仍需维护,另一个黑客选择是:
小精灵
复制自我的答案:https://stackoverflow.com/a/47392970/8971048
如果您需要移动模型,并且您不再有访问应用程序的权限(或者您不希望访问),您可以创建一个新操作,并且仅当迁移的模型不存在时才考虑创建一个新模型。
在本例中,我将"mymodel"从旧的"app"传递到myapp。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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)) ], ), ] |
对需要移动的每个模型分别执行此操作。我不建议做另一个答案所说的,换成整数,再换回外键。在迁移之后,新的外键可能会不同,行可能具有不同的ID,我不想在切换回外键时冒任何ID不匹配的风险。
假设您正在将模型从app_a移动到app_b。
另一种解决方案是手动更改现有迁移。这个想法是,每次你看到一个操作改变了应用程序迁移中的模型,你就把这个操作复制到应用程序初始迁移的末尾。每次你在app-a的迁移中看到引用"app-a.themodel",你就会把它改为"app-b.themodel"。
我只是为一个现有的项目做了这个,我想从中提取一个特定的模型到一个可重用的应用程序。手术进行得很顺利。我想如果有从app_b到app_a的引用的话事情会更难一些。另外,我的模型有一个手工定义的meta.db_表,这可能会有所帮助。
值得注意的是,您最终会改变迁移历史。这并不重要,即使应用了原始迁移的数据库也是如此。如果原始迁移和重写迁移以相同的数据库模式结束,那么这样的重写应该是正常的。
您可以尝试以下操作(未测试):
请注意,您将复制整个表,而不是移动它,但这样两个应用程序就不必触摸属于另一个应用程序的表,我认为这更重要。
这是粗略的测试,所以不要忘记备份您的数据库!!!!
例如,有两个应用程序:
为两个应用程序创建空迁移:
1 2 | python manage.py makemigrations --empty src_app python manage.py makemigrations --empty dst_app |
假设新的迁移是
添加一个操作,重命名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | # -*- 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'), ] |
。
在
将
1 | python manage.py migrate |
。
这就是全部!