如何在两个Django应用之间移动模型(Django 1.7)
大约一年前,我开始了一个项目,像所有新手开发者一样,我并没有太关注项目的结构。不过现在我在使用Django的过程中,发现我的项目布局,特别是我的模型,结构实在是糟糕透了。
我的模型主要集中在一个应用里,实际上这些模型应该分散到各自的应用中。我曾经尝试用south来解决这个问题,把模型移动到不同的应用里,但因为涉及到外键等问题,我发现这非常棘手和困难。
不过现在因为Django 1.7引入了内置的迁移支持,难道现在有更好的方法来处理这个问题吗?
13 个回答
我是怎么做到的(在Django==1.8和Postgres上测试过,可能也适用于1.7)
情况
app1.YourModel
但你想把它改成:
app2.YourModel- 把app1里的YourModel(代码)复制到app2。
在app2.YourModel里添加以下内容:
Class Meta: db_table = 'app1_yourmodel'
运行命令:$ python manage.py makemigrations app2
这时会在app2里生成一个新的迁移文件(比如0009_auto_something.py),里面有一个migrations.CreateModel()的语句,把这个语句移动到app2的初始迁移文件里(比如0001_initial.py),就像它一直在那样。然后删除刚刚创建的迁移文件=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。
- 完成,数据已保存。
附注:在迁移过程中,它会发现app1.yourmodel的内容类型已经被删除,可以被删除。你可以选择“是”,但前提是你不再使用它。如果你非常依赖这个内容类型的外键保持完整,暂时不要回答“是”或“否”,而是手动进入数据库,删除app2.yourmodel的内容类型,并把app1.yourmodel的内容类型重命名为app2.yourmodel,然后再继续选择“否”。
我在手动编写迁移时总是很紧张(正如Ozan的回答所要求的),所以下面的方法结合了Ozan和Michael的策略,以尽量减少手动编码的工作量:
- 在移动任何模型之前,先运行
makemigrations
,确保你在一个干净的基础上工作。 - 把模型的代码从
app1
移动到app2
。 按照@Michael的建议,我们使用“新”模型上的
db_table
元数据选项,将新模型指向旧的数据库表:class Meta: db_table = 'app1_yourmodel'
运行
makemigrations
。这会在app2
中生成CreateModel
,在app1
中生成DeleteModel
。从技术上讲,这些迁移指的是完全相同的表,并会删除(包括所有数据)并重新创建该表。实际上,我们并不想(也不需要)对表做任何事情。我们只需要让Django相信这个变化已经发生。根据@Ozan的回答,
SeparateDatabaseAndState
中的state_operations
标志可以做到这一点。所以我们将所有的migrations
条目在两个迁移文件中都用SeparateDatabaseAndState(state_operations=[...])
包裹起来。例如:operations = [ ... migrations.DeleteModel( name='YourModel', ), ... ]
变成
operations = [ migrations.SeparateDatabaseAndState(state_operations=[ ... migrations.DeleteModel( name='YourModel', ), ... ]) ]
你还需要确保新的“虚拟”
CreateModel
迁移依赖于任何实际创建或更改原始表的迁移。例如,如果你的新迁移是app2.migrations.0004_auto_<date>
(用于Create
)和app1.migrations.0007_auto_<date>
(用于Delete
),最简单的做法是:- 打开
app1.migrations.0007_auto_<date>
,复制它的app1
依赖项(例如('app1', '0006...'),
)。这是app1
中“紧接着前”的迁移,应该包括所有实际模型构建逻辑的依赖。 - 打开
app2.migrations.0004_auto_<date>
,将你刚复制的依赖项添加到它的dependencies
列表中。
- 打开
如果你有 ForeignKey
关系指向你要移动的模型,上面的步骤可能不适用。这是因为:
- 对于
ForeignKey
的更改,依赖项不会自动创建 - 我们不想将
ForeignKey
的更改包裹在state_operations
中,所以我们需要确保它们与表操作分开。
注意:Django 2.2 添加了一个警告(models.E028
),这会破坏这种方法。你可以尝试使用 managed=False
来解决,但我没有测试过。
“最小”操作集根据情况不同,但以下步骤应该适用于大多数/所有 ForeignKey
迁移:
- 复制 模型从
app1
到app2
,设置db_table
,但不要更改任何 FK 引用。 - 运行
makemigrations
并将所有app2
的迁移包裹在state_operations
中(见上文)- 如上所述,在
app2
的CreateTable
中添加对最新app1
迁移的依赖
- 如上所述,在
- 将所有的 FK 引用指向新模型。如果你没有使用字符串引用,将旧模型移动到
models.py
的底部(不要删除它),这样它就不会与导入的类冲突。 运行
makemigrations
,但不要将任何内容包裹在state_operations
中(FK 更改应该实际发生)。在所有ForeignKey
迁移(即AlterField
)中添加对app2
中CreateTable
迁移的依赖(你需要这个列表用于下一步,所以要记住它们)。例如:- 找到包含
CreateModel
的迁移,例如app2.migrations.0002_auto_<date>
,并复制该迁移的名称。 找到所有对该模型有
ForeignKey
的迁移(例如,通过搜索app2.YourModel
找到类似的迁移:class Migration(migrations.Migration): dependencies = [ ('otherapp', '0001_initial'), ] operations = [ migrations.AlterField( model_name='relatedmodel', name='fieldname', field=models.ForeignKey(... to='app2.YourModel'), ), ]
将
CreateModel
迁移添加为依赖:class Migration(migrations.Migration): dependencies = [ ('otherapp', '0001_initial'), ('app2', '0002_auto_<date>'), ]
- 找到包含
从
app1
中移除模型。- 运行
makemigrations
,并将app1
的迁移包裹在state_operations
中。- 为上一步中的所有
ForeignKey
迁移(即AlterField
)添加依赖(可能包括app1
和app2
中的迁移)。 - 当我构建这些迁移时,
DeleteTable
已经依赖于AlterField
迁移,所以我不需要手动强制执行(即Alter
在Delete
之前)。
- 为上一步中的所有
到此为止,Django 就可以正常工作了。新模型指向旧表,Django 的迁移已经让它相信一切都已正确迁移。一个重要的注意事项(来自@Michael的回答)是,新的 ContentType
会为新模型创建。如果你通过 ForeignKey
链接到内容类型,你需要创建一个迁移来更新 ContentType
表。
我想在自己完成后清理一下(元数据选项和表名),所以我使用了以下步骤(来自@Michael):
- 移除
db_table
元数据条目。 - 再次运行
makemigrations
以生成数据库重命名。 - 编辑最后这个迁移,确保它依赖于
DeleteTable
迁移。虽然看起来不应该是必要的,因为Delete
应该是纯逻辑的,但如果我不这样做,就会遇到错误(例如app1_yourmodel
不存在)。
我遇到了同样的问题。Ozan的回答对我帮助很大,但不幸的是还不够。实际上,我有几个外键(ForeignKey)链接到我想移动的模型。经过一番折腾,我找到了解决办法,所以决定分享出来,帮大家节省时间。
你需要多做两个步骤:
- 在开始之前,把所有链接到
TheModel
的外键(ForeignKey
)改成整数字段(IntegerField
)。然后运行python manage.py makemigrations
。 - 完成Ozan的步骤后,再把你的外键改回来:把
IntegerField()
换成ForeignKey(TheModel)
。然后再次进行迁移(python manage.py makemigrations
)。接着你就可以进行迁移操作了,它应该能正常工作(python manage.py migrate
)。
希望这能帮到你。当然,记得在本地测试一下再在生产环境中使用,以免出现意外情况哦 :)
这件事可以通过使用 migrations.SeparateDatabaseAndState
来比较简单地完成。基本上,我们通过一个数据库操作来重命名表,同时进行两个状态操作,一个是把模型从旧应用的历史中移除,另一个是把它添加到新应用的历史中。
从旧应用中移除
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
操作里,这样我们就不会试图重新创建这个表。同时,还要把之前的迁移作为依赖项包含进来:
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提到的,我们可以在每个应用里创建两个迁移文件。下面的评论是针对我之前的回答。
第一个迁移文件是为了从第一个应用中移除模型。
$ python manage.py makemigrations old_app --empty
接着,编辑迁移文件,加入这些操作。
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)
]
第二个迁移文件依赖于第一个迁移文件,并在第二个应用中创建新的表。在把模型代码移动到第二个应用后,
$ python manage.py makemigrations new_app
再把迁移文件编辑成这样。
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)
]