web-dev-qa-db-ja.com

Django RunPythonを使用して変更をコミットする移行

現在NULL値を持つ可能性のあるモデルの1つで、NULL可能ではない外部キーを変更したいと思います。

フィールドからnull=Trueを削除し、makemigrationsを実行しました

そのフィールドにNULL値を含む行がすでにあるテーブルを変更しているので、すぐに1回限りの値を指定するか、移行ファイルを編集して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,
        ),
    ]

私が正しく理解していれば、このエラーは、フィールドがnull不可に変更されたが、フィールドにnull値が含まれている場合に発生する可能性があります。その場合、これが発生する理由を私が考えることができる唯一の理由は、RunPython操作トランザクションがAlterFieldを実行する前にデータベースの変更を「コミット」しなかったためです。

これが本当に理由である場合-変更がデータベースに反映されていることを確認するにはどうすればよいですか?そうでない場合-エラーの理由は何でしょうか?

ありがとう!

21
Gabriel Amram

これは、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'),
]

最初の操作は(順方向)移行を適用するためのものであり、最後の操作は(逆方向)移行を適用しないためのものです。

EDIT:制約の延期は、挿入ソートを回避するのに役立ちます。特に、自己参照テーブルや循環依存のテーブルの場合に便利です。したがって、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
        ]),
]
35
eric

はい、ALTERが実行される前に、移行でのデータ変更がコミットされるのを妨げているのはトランザクションの境界だと思います。

@danielcorreiaが言うように、2つの移行として実装します。これは、 SchemaEditor でさえ、使用する必要のあるコンテキストマネージャーを介してトランザクションにバインドされているように見えるためです。

19
Steve Jalim