回滚Django 1.7的RemoveField迁移

35
如果我有一个非空的模型字段,将其删除并创建一个迁移,那么该迁移将变为不可逆:
考虑以下模型:
class Foo(models.Model):
    bar = models.TextField()
    test = models.TextField()  # This field is to go away, bye-bye!

以及移民:

# app/migrations/003_remove_foo_test.py

class Migration(migrations.Migration):

    dependencies = [
        ('app', '0002_foo_test'),
    ]

    operations = [
        migrations.RemoveField(
            model_name='foo',
            name='test',
        ),
    ]

取消应用此迁移会抛出异常:

$ src/manage.py migrate app 0002
Operations to perform:
  Target specific migration: 0002_foo_test, from app
Running migrations:
  Unapplying app.0003_remove_foo_test...Traceback (most recent call last):
...
django.db.utils.IntegrityError: column "test" contains null values

当然,这是预期的行为,它被明确记录了,并且我不会问为什么会发生这种情况:

请记住,反向操作实际上是向模型添加字段;如果该字段不可为空,则可能使此操作不可逆转(除了任何数据损失,当然不可逆转)。

然而,我们都会犯错误,有时候我们只是需要一些办法来撤销字段删除,即使这意味着需要手动提供所有已撤销非空字段的临时存根值。例如,South迁移可选地允许撤销此类操作(通过询问开发人员是否为恢复的字段提供默认值或禁止反向迁移),但似乎在全新的Django 1.7迁移中不是这种情况。

问题:在Django 1.7+迁移中,撤消字段删除的最简单/最快方法是什么(假设已经发生)? 它不一定需要完全使用Python脚本编写,一组手动说明也可以。


为什么会引发 IntegrityError 而不是 django.db.migrations.exceptions.IrreversibleError?这个问题有没有讨论过? - Qback
4个回答

31

您可以手动编辑迁移并在RemoveField之前为字段添加具有默认值的AlterField。 即使应用迁移,这样做也是安全的。 这将使得之后发生的RemoveField可以被撤销。

例如,在模型summary中有一个名为profit的字段,在删除之前定义如下:

profit = models.PositiveIntegerField(verbose_name='profits')

你需要在RemoveField之前添加一个AlterField,就像这样:

migrations.AlterField(
    model_name='summary',
    name='profit',
    field=models.PositiveIntegerField(verbose_name='profits', default=0),
    preserve_default=False,
    ),

根据文档,是的,它确实支持,但这是在1.7.1版本中添加的。在这种情况下,preserve_default并不重要。它确实会传递到数据库,但只是短暂的。 - GwynBleidD
11
我可以在Django 1.8中使用 preserve_default=True 让它正常工作。 - Jess
@emyller 我添加了 RunPythons 来创建和删除相关模型表中的虚拟对象,以便默认值指向某个东西,它起作用了 :) - Ariel
我尝试使用AlterField在删除字段之前使其可为空(而不是提供默认值),但它没有起作用。有什么想法吗?我的意思是,如果不查看迁移文件中的修改,Django如何确定数据库中不存在的字段不可为空呢?在反向迁移期间执行RemoveField操作之前,一定有一种方法可以强制该字段为可为空。 - Ariel
1
没有使用 preserve_default=False 时无法工作,但不使用它时可以正常工作。 - ruohola
显示剩余3条评论

5

如果你想要未来的迁移可逆,可以尝试将该字段作为三个迁移移除。

  1. 将字段设置为可空
  2. 对数据进行迁移。前向迁移时不做任何操作,后向迁移时将 null 值转换为占位符值。
  3. 移除该字段

这三个步骤都应该是可逆的。

如果你已经运行了迁移并需要撤销它,可以:

  1. 手动添加该字段,并允许其为空
  2. 将 null 值转换为占位符值
  3. 手动添加非空约束
  4. 在上一个迁移中使用 --fake 进行迁移

我对第1步中的“手动添加字段”还不是很确定。我肯定想避免手动运行 ALTER TABLE .. ADD COLUMN,这正是 ORM 层始终执行并应该执行的操作。程序员不需要重复 ORM 所做的所有小技巧(命名字段、管理索引等)。 - Ilya Semenov
手动运行 SQL 并不理想,但有时可能没有其他选择。其他人可能会有更好的建议。您可以尝试使用 ./manage.py sqlmigrate --backwards 让 Django 生成 SQL。 - Alasdair
我可以确认 ./manage.py sqlmigrate --backwards 命令会提供正确的 SQL 语句来执行此迁移,如果你使用 PyCharm 这样的 IDE,运行该命令并不困难;对于反向迁移,迁移编辑方法对我来说并没有起作用。 - Symmetric

1
在旧版迁移中,只需在AddField或AddModel中添加“default”和“preserve_default”,Django就已经知道它必须使用提供的默认值重新创建列。 This Helped me">这对我很有帮助

1

最简单的方法可能是使用 migrations.RunSQL

您可以编辑迁移,使您的 operations 列表看起来像这样:

operations = [
    sql=[('alter table foo_test drop test)],
    reverse_sql=[('alter table foo_test add test varchar)]
]

那将是一个拙劣的解决方案,但可能也是其他任何解决方案。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接