如何在两个Django应用程序之间移动模型(Django 1.7)

Sam*_*ham 112 python mysql database django schema-migration

所以大约一年前,我开始了一个项目,像所有新开发人员一样,我并没有真正关注结构,但是现在我与Django一起开始显示我的项目布局主要是我的模型结构很糟糕.

我的模型主要存放在一个应用程序中,并且这些模型中的大多数应该在他们自己的个人应用程序中,我确实尝试解决这个并将它们移动到南方然而我发现它很棘手并且由于外键等而非常困难.

但是由于Django 1.7和内置的迁移支持,现在有更好的方法吗?

oza*_*zan 316

这可以很容易地使用migrations.SeparateDatabaseAndState.基本上,我们使用数据库操作来同时使用两个状态操作重命名表,以从一个应用程序的历史记录中删除模型,并在另一个应用程序的历史记录中创建它.

从旧应用中删除

python manage.py makemigrations old_app --empty
Run Code Online (Sandbox Code Playgroud)

在迁移中:

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)
    ]
Run Code Online (Sandbox Code Playgroud)

添加到新应用

首先,将模型复制到新应用程序的model.py,然后:

python manage.py makemigrations new_app
Run Code Online (Sandbox Code Playgroud)

这将生成一个以天真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)
    ]
Run Code Online (Sandbox Code Playgroud)

  • 真的很好解释.这应该是答案,重命名表,避免丢失任何数据. (14认同)
  • 由于多个请求,我使用GitHub示例创建了有关FK模型迁移的详细答案.http://stackoverflow.com/questions/30601107/move-models-between-django-1-8-apps-with-required-foreignkey-references/30613732#30613732 (12认同)
  • 这是最好的方法,它比我的好得多.在我的回答顶部添加了注释. (11认同)
  • 我这样做了,但是当我在此之后在newapp上运行"makemigrations"时,它会生成一个AlterModelTable迁移,将其重命名为None. (3认同)
  • 找到了一种基于这些说明解决问题的方法.如果您具有必需字段的外键引用,则问题会更复杂.我不得不添加几个步骤来移动引用. (3认同)
  • @DiegoPonciano同样在这里.你有没有解决过这个问题? (2认同)
  • 为什么不简单地在应用程序之间移动模型定义类并保留“Meta.db_table = 'oldapp_modelname'”?这行得通吗?我不明白为什么不。如果您愿意,您还可以添加 RenameTable 迁移来重命名数据库表。 (2认同)
  • 一些问题:“管理迁移”是否需要在两个步骤之间运行,当您说“复制模式”时,您的意思是*复制*(即它现在存在于两个 models.py 文件中)还是您的意思是*移动*(即从oldapp/models.py中删除它)? (2认同)

小智 25

我遇到了同样的问题. Ozan的回答帮了我很多,但不幸的是还不够.事实上,我有几个ForeignKey链接到我想要移动的模型.经过一番头痛我找到了解决方案,所以决定发布它以解决人们的时间问题.

您还需要2个步骤:

  1. 在执行任何操作之前,请将所有ForeignKey链接更改TheModelIntegerfield.然后跑python manage.py makemigrations
  2. 做完Ozan的步骤后,重新转换你的外键:放回ForeignKey(TheModel)而不是IntegerField().然后再次进行迁移(python manage.py makemigrations).然后你可以迁移它应该工作(python manage.py migrate)

希望能帮助到你.当然在尝试生产之前在当地测试它以避免糟糕的惊喜:)

  • 那么ManyToManyField关系怎么样? (8认同)

Mic*_*ter 14

我是怎么做到的(在Django上测试== 1.8,有postgres,所以可能也是1.7)

情况

app1.YourModel

但你想要它去: app2.YourModel

  1. 将YourModel(代码)从app1复制到app2.
  2. 将此添加到app2.YourModel:

    Class Meta:
        db_table = 'app1_yourmodel'
    
    Run Code Online (Sandbox Code Playgroud)
  3. $ python manage.py makemigrations app2

  4. 在app2中使用migrations.CreateModel()语句进行新的迁移(例如0009_auto_something.py),将此语句移动到app2的初始迁移(例如0001_initial.py)(它就像它一直存在的那样).现在删除创建的migration = 0009_auto_something.py

  5. 就像你的行为一样,app2.YourModel总是在那里,现在从你的迁移中移除app1.YourModel的存在.含义:注释掉CreateModel语句,以及之后使用的每个调整或数据迁移.

  6. 当然,每个对app1.YourModel的引用都必须通过您的项目更改为app2.YourModel.另外,不要忘记迁移中app1.YourModel的所有可能外键都必须更改为app2.YourModel

  7. 现在,如果你进行$ python manage.py migrate,没有任何改变,当你执行$ python manage.py makemigrations时,没有检测到任何新内容.

  8. 现在最后一步:从app2.YourModel中删除Class Meta并执行$ python manage.py makemigrations app2 && python manage.py migrate app2(如果你看看这个迁移,你会看到这样的东西:)

        migrations.AlterModelTable(
        name='yourmodel',
        table=None,
    ),
    
    Run Code Online (Sandbox Code Playgroud)

table = None,表示它将采用默认的表名,在本例中为app2_yourmodel.

  1. 完成,保存数据.

PS在迁移过程中会看到content_type app1.yourmodel已被删除并可以删除.您可以对此说"是",但前提是您不使用它.如果您严重依赖它来使FK与该内容类型保持完整,请不要回答是或否,而是手动进入数据库,并删除内容类型app2.yourmodel,并重命名contenttype app1. yourmodel到app2.yourmodel,然后继续回答否.

  • 我按照您的步骤操作,但属于 app1 的某个模型中的字段包含一个外键,该外键与要移动的模型(myModel)具有递归关系。就像 ```field1 = models.ForeignKey('app1.myModel').``` 当我迁移时,我收到一个 ValueError ,指出 ```field1 是使用对 'app1.myModel' 的惰性引用声明的,但应用程序 'app1 ' 不提供模型 'MyModel'``` (4认同)
  • 虽然这个解决方案绝对比@ ozan更"hackier",但它肯定需要更多的编辑,它对我来说很有效(编辑迁移也没关系 - 根据文档,它们应该是可编辑的). (3认同)
  • 也可能使用 `app_label = 'app1'` 元选项。 (2认同)

Chi*_*and 13

我删除旧答案可能会导致数据丢失.正如ozan所说,我们可以在每个应用程序中创建一个迁移.

首次迁移从第一个应用程序中删除模型.

$ python manage.py makemigrations old_app --empty
Run Code Online (Sandbox Code Playgroud)

编辑迁移文件以包含这些操作.

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)
    ]
Run Code Online (Sandbox Code Playgroud)

第二次迁移取决于第一次迁移并在第二个应用程序中创建新表.将模型代码移动到第二个应用程序后

$ python manage.py makemigrations new_app 
Run Code Online (Sandbox Code Playgroud)

并将迁移文件编辑为类似的内容.

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)
    ]
Run Code Online (Sandbox Code Playgroud)

  • 这将导致删除任何现有数据.除非数据库是空的(如果OP已经工作了一年不太可能),这是一个危险的建议.更强大的方法:创建新的应用程序和模型; 进行旧数据的数据迁移; 只有这样才能删除旧模型. (30认同)
  • 我认为这将是最好的方式,非常感谢所有帮助人员. (2认同)

cla*_*ond 10

我感到紧张的手动编码迁移(按照Ozan的回答所要求),因此以下结合Ozan和Michael的策略来最小化所需的手动编码量:

  1. 在移动任何模型之前,请确保通过运行来处理干净的基线makemigrations.
  2. 将模型的代码从中移动app1app2
  3. 根据@Michael的建议,我们使用db_table"new"模型上的Meta选项将新模型指向旧数据库表:

    class Meta:
        db_table = 'app1_yourmodel'
    
    Run Code Online (Sandbox Code Playgroud)
  4. makemigrations.这将产生CreateModelin app2DeleteModelin app1.从技术上讲,这些迁移引用完全相同的表,并将删除(包括所有数据)并重新创建表.

  5. 实际上,我们不希望(或需要)对表做任何事情.我们只需要让Django相信已经做出了改变.按照@ Ozan的回答,state_operations旗帜就是SeparateDatabaseAndState这样做的.因此,我们将所有migrations条目包装在两个MIGRATIONS FILESSeparateDatabaseAndState(state_operations=[...]).例如,

    operations = [
        ...
        migrations.DeleteModel(
            name='YourModel',
        ),
        ...
    ]
    
    Run Code Online (Sandbox Code Playgroud)

    operations = [
        migrations.SeparateDatabaseAndState(state_operations=[
            ...
            migrations.DeleteModel(
                name='YourModel',
            ),
            ...
        ])
    ]
    
    Run Code Online (Sandbox Code Playgroud)
  6. 编辑:您还需要确保新的"虚拟" CreateModel迁移依赖于实际创建或更改原始表的任何迁移.例如,如果您的新迁移是app2.migrations.0004_auto_<date>(for Create)和app1.migrations.0007_auto_<date>(for Delete),最简单的事情是:

    • 打开app1.migrations.0007_auto_<date>并复制其app1依赖关系(例如 ('app1', '0006...'),).这是"紧接在先"的迁移,app1并且应该包括对所有实际模型构建逻辑的依赖性.
    • 打开 app2.migrations.0004_auto_<date>并添加刚刚复制到其dependencies列表中的依赖项.

编辑:如果您ForeignKey与正在移动的模型有关系,则上述操作可能无效.这是因为:

  • 不会自动为ForeignKey更改创建依赖关系
  • 我们不希望包含ForeignKey更改,state_operations因此我们需要确保它们与表操作分开.

"最小"操作集取决于具体情况,但以下过程应适用于大多数/所有ForeignKey迁移:

  1. 将模型从app1to 复制app2,设置db_table,但不要更改任何FK参考.
  2. 运行makemigrations并包装所有app2迁移state_operations(参见上文)
    • 如上所述,app2 CreateTable在最新的app1迁移中添加依赖项
  3. 将所有FK引用指向新模型.如果您没有使用字符串引用,请将旧模型移动到底部models.py(不要删除它),这样它就不会与导入的类竞争.
  4. 运行makemigrations但不要包装任何东西state_operations(FK更改应该实际发生).在所有ForeignKey迁移中添加依赖项(即AlterField)到CreateTable迁移中app2(下一步需要此列表,以便跟踪它们).例如:

    • 查找包含CreateModeleg 的迁移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'),
              ),
          ]
      
      Run Code Online (Sandbox Code Playgroud)
    • CreateModel迁移添加为依赖项:

      class Migration(migrations.Migration):
      
          dependencies = [
              ('otherapp', '0001_initial'),
              ('app2', '0002_auto_<date>'),
          ]  
      
      Run Code Online (Sandbox Code Playgroud)
  5. 从中删除模型 app1

  6. 运行makemigrations并包装app1迁移state_operations.
    • 向上一步中的所有ForeignKey迁移(即AlterField)添加依赖项(可能包括迁移app1app2).
    • 当我构建这些迁移时,DeleteTable已经依赖于AlterField迁移,因此我不需要手动强制执行(即Alter之前Delete).

在这一点上,Django很高兴.新模型指向旧表,Django的迁移使其确信所有内容都已正确重新定位.最重要的警告(来自@ Michael的回答)是ContentType为新模型创建了一个新的.如果您链接(例如,通过ForeignKey)内容类型,则需要创建迁移以更新ContentType表.

我想在自己之后进行清理(元选项和表名),所以我使用了以下程序(来自@Michael):

  1. 删除db_tableMeta条目
  2. makemigrations再次运行以生成数据库重命名
  3. 编辑上次迁移并确保它取决于DeleteTable迁移.它似乎不应该是必要的,因为它Delete应该是纯逻辑的,但app1_yourmodel如果我不这样做,我会遇到错误(例如,不存在).