【问题标题】:Revert Django 1.7 RemoveField migration恢复 Django 1.7 RemoveField 迁移
【发布时间】:2015-05-06 20:33:11
【问题描述】:

如果我有一个不可为空的模型字段,请将其删除并创建一个迁移,该迁移将变得不可逆:

考虑以下模型:

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

当然,这是预期的行为,它是 clearly documented,我不是在问为什么会这样:

请记住,当反转时,这实际上是将一个字段添加到 模型;如果该字段不可为空,则可能会执行此操作 不可逆的(除了任何数据丢失,这当然是 不可逆转)。

然而,我们都会犯错误,有时我们只是需要以某种方式撤消字段删除,即使这意味着手动为所有已撤消的非-空字段。例如,South 迁移可以选择允许此类操作的反转(通过询问开发人员是否为恢复的字段提供默认值,或禁止反向迁移),这似乎不是所有新的花哨的 Django 1.7 迁移的情况.

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

【问题讨论】:

    标签: django django-south django-1.7 django-migrations


    【解决方案1】:

    您可以手动编辑您的迁移并在 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,
        ),
    

    【讨论】:

    • AlterField 不接受 preserve_default 参数(但在这种情况下似乎不需要它,因为 default 无论如何都无法进入数据库模式)。除此之外,这似乎确实是一个正确和最佳的解决方案。谢谢!
    • 根据documentation,是的,但它是在 1.7.1 中添加的。在这种情况下, preserve_default 应该无关紧要。它确实会进入数据库,但只是很短的时间。
    • 我能够在 Django 1.8 中使用 preserve_default=True
    • @emyller 我添加了 RunPythons 以在相关模型表中创建和删除一个虚拟对象,以便默认值指向某个东西,它起作用了 :)
    • 我尝试使用AlterField 使该字段在删除之前可以为空(而不是提供默认值),但它不起作用。知道为什么吗?我的意思是,如果不是通过查看迁移文件中的修改,Django 如何确定数据库中不存在的字段不可为空?必须有一种方法可以在反向迁移期间执行 RemoveField 操作之前强制该字段为空。
    【解决方案2】:

    如果您想让未来的迁移可逆,您可以尝试将字段作为三个迁移删除。

    1. 使字段可以为空
    2. 数据迁移。前进,不要做任何事情。向后,将空值转换为存根值。
    3. 删除字段

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

    如果您已经运行迁移并需要反转它,您可以

    1. 手动添加字段,允许空值
    2. 将空值转换为存根值
    3. 手动添加非空约束
    4. 使用--fake 迁移到上一个迁移

    【讨论】:

    • 我不太确定第 1 步中的“手动添加字段”。我当然希望避免手动运行 ALTER TABLE .. ADD COLUMN,这就是 ORM 层一直在做的事情应该做。程序员不需要重复 ORM 所做的所有小技巧(命名字段、管理索引等)
    • 手动运行 SQL 并不理想,但有时您可能没有任何其他选择。其他人可能有更好的建议。您也许可以让 Django 使用./manage.py sqlmigrate --backwards 生成 SQL。
    • 我可以确认./manage.py sqlmigrate --backwards 提供了正确的 SQL 来执行此迁移,并且如果您使用像 PyCharm 这样的 IDE,运行它并不痛苦;迁移编辑方法不适用于向后迁移。
    【解决方案3】:

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

    您可以编辑迁移,使您的 operations 列表如下所示:

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

    这将是一个 hacky 解决方案,但可能是其他任何解决方案。

    【讨论】:

      【解决方案4】:

      只需在旧迁移中将 'default' 和 'preserve_default' 添加到 AddField 或 AddModel 中,Django 就会知道它必须使用提供的默认值重新创建列

      This Helped me

      【讨论】:

        猜你喜欢
        • 2014-12-13
        • 2015-10-31
        • 2014-05-28
        • 2015-02-09
        • 2014-12-21
        • 2014-10-28
        • 2015-06-27
        • 1970-01-01
        • 2018-02-22
        相关资源
        最近更新 更多