使用RunPython进行更改的Django迁移

Gab*_*ram 20 django django-migrations

我想在我的一个模型中更改一个外键,该模型当前具有NULL值,不能为空.

null=True从我的字段中删除并运行makemigrations

因为我正在改变一个已经包含该字段中包含NULL值的行的表,所以我被要求立即提供一次性值或编辑迁移文件并添加RunPython操作.

我的RunPython操作在AlterField操作之前列出,并对此字段执行所需的更新,因此它不包含NULL值(仅包含已包含NULL值的行).

但是,迁移仍然失败并出现此错误: django.db.utils.OperationalError: cannot ALTER TABLE "my_app_site" because it has pending trigger events

这是我的代码:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations

def add_default_template(apps, schema_editor):
    Template = apps.get_model("my_app", "Template")
    Site = apps.get_model("my_app", "Site")

    accept_reject_template = Template.objects.get(name="Accept/Reject")
    Site.objects.filter(template=None).update(template=accept_reject_template)    

class Migration(migrations.Migration):

    dependencies = [
        ('my_app', '0021_auto_20150210_1008'),
    ]

    operations = [
        migrations.RunPython(add_default_template),
        migrations.AlterField(
            model_name='site',
            name='template',
            field=models.ForeignKey(to='my_app.Template'),
            preserve_default=False,
        ),
    ]
Run Code Online (Sandbox Code Playgroud)

如果我理解正确,当字段被更改为不可为空但字段包含空值时,可能会发生此错误.在这种情况下,我能想到为什么会发生这种情况的唯一原因是因为RunPython操作事务在运行之前没有"提交"数据库中的更改AlterField.

如果这确实是原因 - 我如何确保更改反映在数据库中?如果不是 - 错误的原因是什么?

谢谢!

eri*_*ric 33

发生这种情况是因为Django创建了约束DEFERRABLE INITIALLY DEFERRED:

ALTER TABLE my_app_site
ADD CONSTRAINT "[constraint_name]"
FOREIGN KEY (template_id)
REFERENCES my_app_template(id)
DEFERRABLE INITIALLY DEFERRED;
Run Code Online (Sandbox Code Playgroud)

这告诉PostgreSQL在每个命令之后不需要立即检查外键,但是可以推迟到事务结束.

因此,当事务修改内容和结构时,将在结构更改的同时检查约束,或者在更改结构后安排检查.这两种状态都很糟糕,数据库将中止事务而不是做出任何假设.

您可以通过调用指示PostgreSQL立即检查当前事务中的约束SET CONSTRAINTS ALL IMMEDIATE,因此结构更改不会成为问题(请参阅SET CONSTRAINTS文档).您的迁移应如下所示:

operations = [
    migrations.RunSQL('SET CONSTRAINTS ALL IMMEDIATE',
                      reverse_sql=migrations.RunSQL.noop),

    # ... the actual migration operations here ...

    migrations.RunSQL(migrations.RunSQL.noop,
                      reverse_sql='SET CONSTRAINTS ALL IMMEDIATE'),
]
Run Code Online (Sandbox Code Playgroud)

第一个操作是应用(转发)迁移,最后一个操作用于取消应用(向后)迁移.

编辑:约束延迟对于避免插入排序很有用,特别是对于具有循环依赖性的自引用表和表.弯曲Django时要小心.

LATE EDIT:在Django 1.7和更新的版本上,有一个特殊的SeparateDatabaseAndState操作,允许在同一个迁移中进行数据更改和结构更改.尝试使用此操作,然后重复上面的"立即设置约束"方法.例:

operations = [
    migrations.SeparateDatabaseAndState(database_operations=[
            # put your sql, python, whatever data migrations here
        ],
        state_operations=[
            # field/model changes goes here
        ]),
]
Run Code Online (Sandbox Code Playgroud)

  • 这需要重置回原来的样子吗? (2认同)
  • @shadow不,文档说"SET CONSTRAINTS设置当前事务中约束检查的行为." (2认同)

Ste*_*lim 19

是的,我会说这是在ALTER运行之前阻止迁移中的数据更改的事务边界.

我会像@danielcorreia那样说并将其实现为两次迁移,因为看起来甚至SchemaEditor都受到事务的约束,通过你必须使用的上下文管理器.

  • 拆分为2(一个使用--empty和手动处理)是解决方案! (3认同)