Перенос 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 связано транзакциями через контекстный менеджер, который вы должны использовать.