这是对这个问题的扩展:如何在两个 Django 应用程序之间移动模型(Django 1.7)
我需要将一堆模型old_app
从new_app
. 最好的答案似乎是Ozan's,但需要外键引用,事情就有点棘手了。new_app
@halfnibbleold_app
在old_app.migrations
对Ozan 的回答的评论中提出了一个解决方案,但我仍然无法确定步骤的精确顺序(例如,我什么时候将模型复制到.new_app.migrations
等)
任何帮助深表感谢!
这是对这个问题的扩展:如何在两个 Django 应用程序之间移动模型(Django 1.7)
我需要将一堆模型old_app
从new_app
. 最好的答案似乎是Ozan's,但需要外键引用,事情就有点棘手了。new_app
@halfnibbleold_app
在old_app.migrations
对Ozan 的回答的评论中提出了一个解决方案,但我仍然无法确定步骤的精确顺序(例如,我什么时候将模型复制到.new_app.migrations
等)
任何帮助深表感谢!
在应用程序之间迁移模型。
简短的回答是,不要这样做!
但这个答案在现实世界的生活项目和生产数据库中很少奏效。因此,我创建了一个示例 GitHub 存储库来演示这个相当复杂的过程。
我正在使用 MySQL。(不,这些不是我的真实凭据)。
问题
我正在使用的示例是一个带有汽车应用程序的工厂项目,该应用程序最初具有一个Car
模型和一个Tires
模型。
factory
|_ cars
|_ Car
|_ Tires
该Car
模型与 具有 ForeignKey 关系Tires
。(如,您通过汽车模型指定轮胎)。
但是,我们很快意识到这Tires
将是一个拥有自己的视图等的大型模型,因此我们希望它在自己的应用程序中。因此,所需的结构是:
factory
|_ cars
|_ Car
|_ tires
|_ Tires
我们需要保持和之间的 ForeignKey 关系Car
,Tires
因为太多依赖于保存数据。
解决方案
步骤 1.设置设计不佳的初始应用程序。
步骤 2.创建一个管理界面并添加一堆包含 ForeignKey 关系的数据。
查看步骤 2。
步骤 3.决定将Tires
模型移动到它自己的应用程序中。小心翼翼地将代码剪切并粘贴到新的轮胎应用程序中。确保更新Car
模型以指向新tires.Tires
模型。
然后在某处运行./manage.py makemigrations
并备份数据库(以防万一这严重失败)。
最后运行./manage.py migrate
看看doom的报错信息,
django.db.utils.IntegrityError: (1217, '无法删除或更新父行:外键约束失败')
在步骤 3中查看到目前为止的代码和迁移。
第 4 步。棘手的部分。自动生成的迁移看不到您只是将模型复制到不同的应用程序。所以,我们必须做一些事情来解决这个问题。
您可以在第 4 步中跟随并查看带有注释的最终迁移。我确实对此进行了测试以验证它是否有效。
首先,我们将致力于cars
. 您必须进行新的空迁移。此迁移实际上需要在最近创建的迁移(执行失败的迁移)之前运行。因此,我重新编号了我创建的迁移并更改了依赖项以首先运行我的自定义迁移,然后是cars
应用程序的最后一个自动生成的迁移。
您可以使用以下命令创建一个空迁移:
./manage.py makemigrations --empty cars
步骤 4.a。进行自定义old_app迁移。
在第一次自定义迁移中,我将只执行“database_operations”迁移。Django 为您提供了拆分“状态”和“数据库”操作的选项。您可以通过查看此处的代码来了解这是如何完成的。
我第一步的目标是将数据库表从 重命名为oldapp_model
,newapp_model
而不会弄乱 Django 的状态。你必须弄清楚 Django 会根据应用程序名称和模型名称来命名你的数据库表。
现在您已准备好修改初始tires
迁移。
步骤 4.b。修改new_app初始迁移
操作很好,但我们只想修改“状态”而不是数据库。为什么?因为我们从cars
应用程序中保留了数据库表。此外,您需要确保先前进行的自定义迁移是此迁移的依赖项。请参阅轮胎迁移文件。
所以,现在我们在数据库中重命名cars.Tires
为tires.Tires
,并改变 Django 的状态来识别tires.Tires
表。
步骤 4.c。修改old_app上次自动生成的迁移。
回到汽车,我们需要修改最后一个自动生成的迁移。它应该需要我们的第一次定制汽车迁移,以及最初的轮胎迁移(我们刚刚修改)。
在这里我们应该离开AlterField
操作,因为Car
模型指向不同的模型(即使它具有相同的数据)。但是,我们需要删除有关的迁移线,DeleteModel
因为该cars.Tires
模型不再存在。它已完全转换为tires.Tires
. 查看此迁移。
步骤 4.d。清理old_app中的陈旧模型。
最后但并非最不重要的一点是,您需要在汽车应用程序中进行最终的自定义迁移。在这里,我们将做一个“状态”操作,只是为了删除cars.Tires
模型。它是仅状态的,因为数据库表cars.Tires
已经被重命名。最后一次迁移清除了剩余的 Django 状态。
刚刚将两个模型 from 移动old_app
到 to new_app
,但 FK 引用在某些模型中是 fromapp_x
和app_y
,而不是模型 from old_app
。
在这种情况下,请按照 Nostalg.io 提供的步骤如下:
old_app
移至new_app
,然后更新import
代码库中的语句。makemigrations
.AlterModelTable
适用于所有移动的模型。两个给我。state_operations
。DeleteModel
用于所有移动的模型。笔记:
old_app
文件AlterModelTable
。(在步骤 4.a 中创建)old_app
因为我没有任何AlterField
操作,只有DeleteModel
和RemoveField
操作。或者留空operations = []
为避免在从头开始创建测试数据库时出现迁移异常,请确保old_app
在步骤 4.a 中创建自定义迁移。具有来自其他应用程序的所有先前迁移依赖项。
old_app
0020_auto_others
0021_custom_rename_models.py
dependencies:
('old_app', '0020_auto_others'),
('app_x', '0002_auto_20170608_1452'),
('app_y', '0005_auto_20170608_1452'),
('new_app', '0001_initial'),
0022_auto_maybe_empty_operations.py
dependencies:
('old_app', '0021_custom_rename_models'),
0023_custom_clean_models.py
dependencies:
('old_app', '0022_auto_maybe_empty_operations'),
app_x
0001_initial.py
0002_auto_20170608_1452.py
0003_update_fk_state_operations.py
dependencies
('app_x', '0002_auto_20170608_1452'),
('old_app', '0021_custom_rename_models'),
app_y
0004_auto_others_that_could_use_old_refs.py
0005_auto_20170608_1452.py
0006_update_fk_state_operations.py
dependencies
('app_y', '0005_auto_20170608_1452'),
('old_app', '0021_custom_rename_models'),
顺便说一句:有一张关于此的公开票:https ://code.djangoproject.com/ticket/24686
如果您需要移动模型并且您不再有权访问该应用程序(或者您不希望访问),您可以创建一个新的操作并考虑仅在迁移的模型没有的情况下创建一个新模型存在。
在此示例中,我将“MyModel”从 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))
],
),
]
工作完成后,我尝试进行新的迁移。但我面临以下错误:
ValueError: Unhandled pending operations for models:
oldapp.modelname (referred to by fields: oldapp.HistoricalProductModelName.model_ref_obj)
如果您的 Django 模型使用HistoricalRecords
字段,请不要忘记在遵循 @Nostalg.io 答案时添加附加模型/表。
database_operations
在第一步 (4.a) 中添加以下项目:
migrations.AlterModelTable('historicalmodelname', 'newapp_historicalmodelname'),
并state_operations
在最后一步(4.d)添加额外的删除:
migrations.DeleteModel(name='HistoricalModleName'),
Nostalg.io 的方式在前向工作(自动生成引用它的所有其他应用程序 FK)。但我也需要倒退。为此,向后的 AlterTable 必须在向后任何 FK 之前发生(最初它会在那之后发生)。因此,为此,我将 AlterTable 拆分为 2 个单独的 AlterTableF 和 AlterTableR,每个仅在一个方向上工作,然后在第一次自定义迁移中使用正向而不是原始方向,并在最后一次汽车迁移中反转一个(两者都发生在汽车应用程序中)。像这样的东西:
#cars/migrations/0002...py :
class AlterModelTableF( migrations.AlterModelTable):
def database_backwards(self, app_label, schema_editor, from_state, to_state):
print( 'nothing back on', app_label, self.name, self.table)
class Migration(migrations.Migration):
dependencies = [
('cars', '0001_initial'),
]
database_operations= [
AlterModelTableF( 'tires', 'tires_tires' ),
]
operations = [
migrations.SeparateDatabaseAndState( database_operations= database_operations)
]
#cars/migrations/0004...py :
class AlterModelTableR( migrations.AlterModelTable):
def database_forwards(self, app_label, schema_editor, from_state, to_state):
print( 'nothing forw on', app_label, self.name, self.table)
def database_backwards(self, app_label, schema_editor, from_state, to_state):
super().database_forwards( app_label, schema_editor, from_state, to_state)
class Migration(migrations.Migration):
dependencies = [
('cars', '0003_auto_20150603_0630'),
]
# This needs to be a state-only operation because the database model was renamed, and no longer exists according to Django.
state_operations = [
migrations.DeleteModel(
name='Tires',
),
]
database_operations= [
AlterModelTableR( 'tires', 'tires_tires' ),
]
operations = [
# After this state operation, the Django DB state should match the actual database structure.
migrations.SeparateDatabaseAndState( state_operations=state_operations,
database_operations=database_operations)
]
我已经建立了一个管理命令来做到这一点 - 将模型从一个 Django 应用程序移动到另一个 - 基于https://stackoverflow.com/a/30613732/1639699上 nostalgic.io 的建议
你可以在 GitHub 上的alexei/django-move-model上找到它
您可以相对简单地执行此操作,但您需要遵循这些步骤,这些步骤是从Django 用户组中的一个问题中总结的。
在将您的模型移动到我们将调用的新应用程序之前,new
将db_table
选项添加到当前模型的Meta
类中。我们将调用您要移动的模型M
。但是如果你愿意,你可以一次做多个模型。
class M(models.Model):
a = models.ForeignKey(B, on_delete=models.CASCADE)
b = models.IntegerField()
class Meta:
db_table = "new_M"
运行python manage.py makemigrations
。这会生成一个新的迁移文件,该文件会将数据库中的表从 重命名current_M
为new_M
。我们稍后会提到这个迁移文件x
。
现在将模型移动到您的new
应用程序中。删除对的引用,db_table
因为 Django 会自动将它放在名为new_M
.
进行新的迁移。运行python manage.py makemigrations
。这将在我们的示例中生成两个新的迁移文件。第一个将在new
应用程序中。验证在 dependencies 属性中,Djangox
已从之前的迁移文件中列出。第二个将在current
应用程序中。现在将操作列表包装在两个迁移文件中的调用中SeparateDatabaseAndState
,如下所示:
operations = [
SeparateDatabaseAndState([], [
migrations.CreateModel(...), ...
]),
]
运行python manage.py migrate
。你完成了。执行此操作的时间相对较快,因为与某些答案不同,您不会将记录从一个表复制到另一个表。您只是重命名表,这本身就是一项快速操作。
这对我有用,但我相信我会听到为什么这是一个糟糕的主意。将此函数和调用它的操作添加到您的 old_app 迁移:
def migrate_model(apps, schema_editor):
old_model = apps.get_model('old_app', 'MovingModel')
new_model = apps.get_model('new_app', 'MovingModel')
for mod in old_model.objects.all():
mod.__class__ = new_model
mod.save()
class Migration(migrations.Migration):
dependencies = [
('new_app', '0006_auto_20171027_0213'),
]
operations = [
migrations.RunPython(migrate_model),
migrations.DeleteModel(
name='MovingModel',
),
]
第一步:备份你的数据库!
确保首先运行 new_app 迁移,和/或 old_app 迁移的要求。在完成 old_app 迁移之前,请拒绝删除过时的内容类型。
在 Django 1.9 之后,您可能需要更仔细地逐步完成:
Migration1:创建新表
Migration2:填充表
Migration3:更改其他表上的字段
Migration4:删除旧表
几个月后回到这个问题(在成功实施 Lucianovici 的方法之后),在我看来,如果你注意指向旧表(如果你只关心代码组织并且不介意的话)它会变得更db_table
简单数据库中的过时名称)。
所以我所做的只是从 Django 中获取自动迁移并将它们包装到 migrations.SeparateDatabaseAndState 中。
请注意(再次),这只有在您注意将 db_table 指向每个模型的旧表时才有效。
我不确定这是否有问题,我还没有看到,但它似乎在我的开发系统上工作(当然,我注意备份)。所有数据看起来都完好无损。我会仔细看看是否有任何问题出现...
也许以后也可以在单独的步骤中重命名数据库表,从而使整个过程不那么复杂。
这有点晚了,但如果您想要最简单的路径并且不太关心保留您的迁移历史。简单的解决方案就是擦除迁移并刷新。
我有一个相当复杂的应用程序,在尝试了上述解决方案几个小时都没有成功后,我意识到我可以做到。
rm cars/migrations/*
./manage.py makemigrations
./manage.py migrate --fake-initial
快!如果需要,迁移历史记录仍在 Git 中。而且由于这本质上是无操作的,所以回滚不是问题。