33

这是对这个问题的扩展:如何在两个 Django 应用程序之间移动模型(Django 1.7)

我需要将一堆模型old_appnew_app. 最好的答案似乎是Ozan's,但需要外键引用,事情就有点棘手了。new_app@halfnibbleold_appold_app.migrations对Ozan 的回答的评论中提出了一个解决方案,但我仍然无法确定步骤的精确顺序(例如,我什么时候将模型复制到.new_app.migrations等)

任何帮助深表感谢!

4

10 回答 10

84

在应用程序之间迁移模型。

简短的回答是,不要这样做!

但这个答案在现实世界的生活项目和生产数据库中很少奏效。因此,我创建了一个示例 GitHub 存储库来演示这个相当复杂的过程。

我正在使用 MySQL。(不,这些不是我的真实凭据)。

问题

我正在使用的示例是一个带有汽车应用程序的工厂项目,该应用程序最初具有一个Car模型和一个Tires模型。

factory
  |_ cars
    |_ Car
    |_ Tires

Car模型与 具有 ForeignKey 关系Tires。(如,您通过汽车模型指定轮胎)。

但是,我们很快意识到这Tires将是一个拥有自己的视图等的大型模型,因此我们希望它在自己的应用程序中。因此,所需的结构是:

factory
  |_ cars
    |_ Car
  |_ tires
    |_ Tires

我们需要保持和之间的 ForeignKey 关系CarTires因为太多依赖于保存数据。

解决方案

步骤 1.设置设计不佳的初始应用程序。

浏览步骤 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_modelnewapp_model而不会弄乱 Django 的状态。你必须弄清楚 Django 会根据应用程序名称和模型名称来命名你的数据库表。

现在您已准备好修改初始tires迁移。

步骤 4.b。修改new_app初始迁移

操作很好,但我们只想修改“状态”而不是数据库。为什么?因为我们从cars应用程序中保留了数据库表。此外,您需要确保先前进行的自定义迁移是此迁移的依赖项。请参阅轮胎迁移文件

所以,现在我们在数据库中重命名cars.Tirestires.Tires,并改变 Django 的状态来识别tires.Tires表。

步骤 4.c。修改old_app上次自动生成的迁移。

回到汽车,我们需要修改最后一个自动生成的迁移它应该需要我们的第一次定制汽车迁移,以及最初的轮胎迁移(我们刚刚修改)。

在这里我们应该离开AlterField操作,因为Car模型指向不同的模型(即使它具有相同的数据)。但是,我们需要删除有关的迁移线,DeleteModel因为该cars.Tires模型不再存在。它已完全转换为tires.Tires. 查看此迁移

步骤 4.d。清理old_app中的陈旧模型。

最后但并非最不重要的一点是,您需要在汽车应用程序中进行最终的自定义迁移。在这里,我们将做一个“状态”操作,只是为了删除cars.Tires模型。它是仅状态的,因为数据库表cars.Tires已经被重命名。最后一次迁移清除了剩余的 Django 状态。

于 2015-06-03T07:38:44.567 回答
4

刚刚将两个模型 from 移动old_app到 to new_app,但 FK 引用在某些模型中是 fromapp_xapp_y,而不是模型 from old_app

在这种情况下,请按照 Nostalg.io 提供的步骤如下:

  • 将模型从old_app移至new_app,然后更新import代码库中的语句。
  • makemigrations.
  • 按照步骤 4.a。但AlterModelTable适用于所有移动的模型。两个给我。
  • 按照步骤 4.b。照原样。
  • 按照步骤 4.c。但是,对于每个具有新生成的迁移文件的应用程序,手动编辑它们,以便您迁移state_operations
  • 遵循步骤 4.d 但DeleteModel用于所有移动的模型。

笔记:

  • 所有从其他应用程序编辑的自动生成的迁移文件都依赖于用于重命名表的自定义迁移old_app文件AlterModelTable。(在步骤 4.a 中创建)
  • 就我而言,我不得不从中删除自动生成的迁移文件,old_app因为我没有任何AlterField操作,只有DeleteModelRemoveField操作。或者留空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

于 2017-06-12T20:52:19.570 回答
2

如果您需要移动模型并且您不再有权访问该应用程序(或者您不希望访问),您可以创建一个新的操作并考虑仅在迁移的模型没有的情况下创建一个新模型存在。

在此示例中,我将“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))
            ],
        ),
    ]
于 2017-11-20T13:17:12.877 回答
1

工作完成后,我尝试进行新的迁移。但我面临以下错误: 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'),
于 2017-11-28T17:41:38.743 回答
1

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)
    ]   
于 2018-01-25T13:23:16.747 回答
1

我已经建立了一个管理命令来做到这一点 - 将模型从一个 Django 应用程序移动到另一个 - 基于https://stackoverflow.com/a/30613732/1639699上 nostalgic.io 的建议

你可以在 GitHub 上的alexei/django-move-model上找到它

于 2018-01-25T16:41:56.720 回答
1

您可以相对简单地执行此操作,但您需要遵循这些步骤,这些步骤是从Django 用户组中的一个问题中总结的。

  1. 在将您的模型移动到我们将调用的新应用程序之前,newdb_table选项添加到当前模型的Meta类中。我们将调用您要移动的模型M。但是如果你愿意,你可以一次做多个模型。

    class M(models.Model):
        a = models.ForeignKey(B, on_delete=models.CASCADE)
        b = models.IntegerField()
    
        class Meta:
            db_table = "new_M"
    
  2. 运行python manage.py makemigrations。这会生成一个新的迁移文件,该文件会将数据库中的表从 重命名current_Mnew_M。我们稍后会提到这个迁移文件x

  3. 现在将模型移动到您的new应用程序中。删除对的引用,db_table因为 Django 会自动将它放在名为new_M.

  4. 进行新的迁移。运行python manage.py makemigrations。这将在我们的示例中生成两个新的迁移文件。第一个将在new应用程序中。验证在 dependencies 属性中,Djangox已从之前的迁移文件中列出。第二个将在current应用程序中。现在将操作列表包装在两个迁移文件中的调用中SeparateDatabaseAndState,如下所示:

    operations = [
        SeparateDatabaseAndState([], [
            migrations.CreateModel(...), ...
        ]),
    ]
    
  5. 运行python manage.py migrate。你完成了。执行此操作的时间相对较快,因为与某些答案不同,您不会将记录从一个表复制到另一个表。您只是重命名表,这本身就是一项快速操作。

于 2019-09-18T17:21:56.010 回答
0

这对我有用,但我相信我会听到为什么这是一个糟糕的主意。将此函数和调用它的操作添加到您的 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:删除旧表

于 2017-10-27T04:00:46.550 回答
0

几个月后回到这个问题(在成功实施 Lucianovici 的方法之后),在我看来,如果你注意指向旧表(如果你只关心代码组织并且不介意的话)它会变得更db_table简单数据库中的过时名称)。

  • 您不需要 AlterModelTable 迁移,因此不需要自定义第一步。
  • 您仍然需要在不接触数据库的情况下更改模型和关系。

所以我所做的只是从 Django 中获取自动迁移并将它们包装到 migrations.SeparateDatabaseAndState 中。

请注意(再次),这只有在您注意将 db_table 指向每个模型的旧表时才有效。

我不确定这是否有问题,我还没有看到,但它似乎在我的开发系统上工作(当然,我注意备份)。所有数据看起来都完好无损。我会仔细看看是否有任何问题出现...

也许以后也可以在单独的步骤中重命名数据库表,从而使整个过程不那么复杂。

于 2019-03-18T11:33:57.107 回答
0

这有点晚了,但如果您想要最简单的路径并且不太关心保留您的迁移历史。简单的解决方案就是擦除迁移并刷新。

我有一个相当复杂的应用程序,在尝试了上述解决方案几个小时都没有成功后,我意识到我可以做到。

rm cars/migrations/*
./manage.py makemigrations
./manage.py migrate --fake-initial

快!如果需要,迁移历史记录仍在 Git 中。而且由于这本质上是无操作的,所以回滚不是问题。

于 2019-08-09T19:00:42.743 回答