Перенос Django с использованием RunPython для фиксации изменений
Я хочу изменить внешний ключ в одной из моих моделей, у которых в настоящее время значения 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,
),
]
Если я правильно понимаю, эта ошибка может возникнуть, когда поле изменено, чтобы оно не было нулевым, но поле содержит нулевые значения.
В этом случае единственная причина, по которой я могу понять, почему это происходит, заключается в том, что транзакция операции RunPython
не "фиксировала" изменения в базе данных до запуска AlterField
.
Если это действительно причина - как я могу убедиться, что изменения отражены в базе данных?
Если нет - что может быть причиной ошибки?
Спасибо!
Ответы
Ответ 1
Это происходит потому, что 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, что внешний ключ не нужно проверять сразу после каждой команды, но может быть отложен до конца транзакций.
Итак, когда транзакция изменяет контент и структуру, ограничения проверяются параллельно с изменениями структуры, или проверки запланированы после изменения структуры. Оба этих состояния плохие, и база данных прервет транзакцию, а не делает какие-либо предположения.
Вы можете проинструктировать PostgreSQL немедленно проверить ограничения в текущей транзакции, вызвав SET CONSTRAINTS ALL IMMEDIATE
, поэтому изменения структуры не будут проблемой (см. SET КОНТРАКТЫ). Ваша миграция должна выглядеть так:
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.
ПОСЛЕДНИЙ EDIT: на Django 1.7 и более поздних версиях существует специальная SeparateDatabaseAndState
операция, которая позволяет данные изменения и изменения структуры при одной и той же миграции. Попробуйте использовать эту операцию, прежде чем переходить к методу "set constraints all all" выше. Пример:
operations = [
migrations.SeparateDatabaseAndState(database_operations=[
# put your sql, python, whatever data migrations here
],
state_operations=[
# field/model changes goes here
]),
]
Ответ 2
Да, я бы сказал, что это границы транзакций, которые препятствуют переносу данных в вашей миграции перед запуском ALTER.
Я бы сделал так, как говорит @danielcorreia и реализует его как две миграции, поскольку он выглядит как SchemaEditor связано транзакциями через контекстный менеджер, который вы должны использовать.