使用 RunPython 提交更改的 Django 迁移
Django migrations using RunPython to commit changes
我想更改我的一个模型中的外键,该模型当前可以具有 NULL 值以使其不可为空。
我从我的字段中删除了 null=True
并且 运行 makemigrations
因为我正在更改一个 table,它已经有在该字段中包含 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,
),
]
如果我理解正确,当一个字段被更改为不可为空但该字段包含空值时,可能会发生此错误。
在那种情况下,我能想到为什么会发生这种情况的唯一原因是因为 RunPython
操作 t运行saction 没有 "commit" 在 运行 之前数据库中的更改AlterField
.
如果这确实是原因 - 我如何确保更改反映在数据库中?
如果不是 - 错误的原因是什么?
谢谢!
是的,我想说是事务边界阻止了在 ALTER 为 运行 之前提交迁移中的数据更改。
我会像@danielcorreia 说的那样做,并将其实现为两次迁移,因为它看起来甚至 SchemaEditor 都受事务约束,通过您必须使用的上下文管理器。
发生这种情况是因为 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;
这告诉 PostgreSQL 不需要在每个命令后立即检查外键,而是可以推迟到事务结束。
因此,当事务修改内容和结构时,会在结构更改的同时检查约束,或者计划在更改结构后进行检查。这两种状态都是不好的,数据库将中止事务而不是做出任何假设。
您可以通过调用SET CONSTRAINTS ALL IMMEDIATE
指示PostgreSQL在当前事务中立即检查约束,因此结构更改不会成为问题(参考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'),
]
第一个操作用于应用(向前)迁移,最后一个操作用于取消应用(向后)迁移。
编辑:约束延迟对于避免插入排序很有用,特别是对于自引用表和具有循环依赖性的表。所以弯曲 Django 时要小心。
后期编辑: 在 Django 1.7 和更新版本上有一个特殊的 SeparateDatabaseAndState
操作允许在同一迁移中更改数据和结构。在求助于上面的“立即设置约束”方法之前尝试使用此操作。示例:
operations = [
migrations.SeparateDatabaseAndState(database_operations=[
# put your sql, python, whatever data migrations here
],
state_operations=[
# field/model changes goes here
]),
]
我想更改我的一个模型中的外键,该模型当前可以具有 NULL 值以使其不可为空。
我从我的字段中删除了 null=True
并且 运行 makemigrations
因为我正在更改一个 table,它已经有在该字段中包含 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,
),
]
如果我理解正确,当一个字段被更改为不可为空但该字段包含空值时,可能会发生此错误。
在那种情况下,我能想到为什么会发生这种情况的唯一原因是因为 RunPython
操作 t运行saction 没有 "commit" 在 运行 之前数据库中的更改AlterField
.
如果这确实是原因 - 我如何确保更改反映在数据库中? 如果不是 - 错误的原因是什么?
谢谢!
是的,我想说是事务边界阻止了在 ALTER 为 运行 之前提交迁移中的数据更改。
我会像@danielcorreia 说的那样做,并将其实现为两次迁移,因为它看起来甚至 SchemaEditor 都受事务约束,通过您必须使用的上下文管理器。
发生这种情况是因为 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;
这告诉 PostgreSQL 不需要在每个命令后立即检查外键,而是可以推迟到事务结束。
因此,当事务修改内容和结构时,会在结构更改的同时检查约束,或者计划在更改结构后进行检查。这两种状态都是不好的,数据库将中止事务而不是做出任何假设。
您可以通过调用SET CONSTRAINTS ALL IMMEDIATE
指示PostgreSQL在当前事务中立即检查约束,因此结构更改不会成为问题(参考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'),
]
第一个操作用于应用(向前)迁移,最后一个操作用于取消应用(向后)迁移。
编辑:约束延迟对于避免插入排序很有用,特别是对于自引用表和具有循环依赖性的表。所以弯曲 Django 时要小心。
后期编辑: 在 Django 1.7 和更新版本上有一个特殊的 SeparateDatabaseAndState
操作允许在同一迁移中更改数据和结构。在求助于上面的“立即设置约束”方法之前尝试使用此操作。示例:
operations = [
migrations.SeparateDatabaseAndState(database_operations=[
# put your sql, python, whatever data migrations here
],
state_operations=[
# field/model changes goes here
]),
]