マイグレーション・オペレーション

マイグレーションファイルは一つ以上の オペレーション、データベースに対してマイグレーションが行うべきオペレーションを宣言的に保持するオブジェクト、から構成されます。

Django はこれらの Operation オブジェクトも使用して、モデルの履歴を確認し、前回のマイグレーション以降にモデルに加えた変更を計算します。そのため、自動的にマイグレーションを書き出すことができます。マイグレーションの定義が宣言的なのはそのためであり、Django がそれらを簡単にメモリに読み込んで、データベースに触れることなくプロジェクトがどのようにあるべきかを把握するために実行することができます。

データのマイグレーション や高度なデータベースへの手動操作のような特別な オペレーション も存在します。もし頻繁に行われる独自の変更をカプセル化したければ独自に オペレーション を記述する事もできます。

もし独自の Operation オブジェクトを定義する空のマイグレーションファイルが必要であれば、python manage.py makemigrations --empty yourappname を実行してください。ただし、手動でのスキーマ変更オペレーションの追加はマイグレーションの自動検出機構を混乱させ、 makemigrations が正しい結果を出力しなくなる可能性があることに十分注意してください。

Django が提供するコア機能は全て django.db.migrations.operations モジュールから利用できます。

より入門的な内容に関しては、 トピックスのマイグレーション を参照ください。

スキーマ・オペレーション

CreateModel

class CreateModel(name, fields, options=None, bases=None, managers=None)

プロジェクトの履歴に新たなモデル、そしてデータベース上に対応するテーブルを作成します。

namemodels.py ファイルに定義されているであろうモデルの名称です。

fields(フィールド名, フィールドのインスタンス) という 2 値のタプルのリストです。フィールドのインスタンスは束縛されないフィールド(他のモデルから取得した物でなく、単に models.CharField(...) 等と記述する物)でなければいけません。

options は定義されるモデルの Meta クラスの値からなるオプションの辞書オブジェクトです。

bases はオプションで、このモデルに継承させる他のクラスのリストです。(すでに定義した他のモデルを継承する場合、)クラスオブジェクトまたは "appname.ModelName" という形式の文字列を含めることができます。このパラメータが空の場合、デフォルトでは標準の models.Model から継承します。

managers(マネージャ名, マネージャのインスタンス) という 2 値のタプルのリストを受け取ります。マイグレーションの間はリスト内で最初に定義されたマネージャがこのモデルの標準マネージャとして利用されます。

DeleteModel

class DeleteModel(name)

プロジェクトの履歴からモデルを、加えてデータベースから該当モデルを扱うテーブルを削除します。

RenameModel

class RenameModel(old_name, new_name)

モデルの名称を今までの名称から新しい名称に変更します。

一度に定義しているモデルの名前とかなり多くのフィールドの名前を変更した場合は手動でこの処理を追加する必要があるかもしれません;マイグレーションの自動検知機構が古い名称を持ったモデルを削除して異なった名称のモデルを追加したと認識してしまうため、そのマイグレーション処理は古いテーブルの全てのデータを消去してしまいます。

AlterModelTable

class AlterModelTable(name, table)

定義しているモデルのテーブル名を変更します(Meta サブクラスの db_table オプションを参照します)。

AlterModelTableComment

New in Django 4.2.
class AlterModelTableComment(name, table_comment)

モデルのテーブルコメント (Meta サブクラスの db_table_comment オプション) を変更します。

AlterUniqueTogether

class AlterUniqueTogether(name, unique_together)

モデルのユニーク制約のセットを変更します (Meta サブクラスの unique_together オプション)。

AlterIndexTogether

class AlterIndexTogether(name, index_together)

モデルのカスタムインデックスのセットを変更します (Meta サブクラスの index_together オプション)。

警告

AlterIndexTogether は公式には Django 4.2 以前のマイグレーションファイルにのみサポートされています。後方互換性の理由から、これはまだパブリック API の一部であり、非推奨または削除する予定はありませんが、新しいマイグレーションには使わないでください。代わりに AddIndexRemoveIndex オペレーションを使ってください。

AlterOrderWithRespectTo

class AlterOrderWithRespectTo(name, order_with_respect_to)

Meta サブクラスの order_with_respect_to オプションに必要な _order カラムを作成または削除します。

AlterModelOptions

class AlterModelOptions(name, options)

permissionsverbose_name のような雑多なモデルオプション(モデルの Meta の設定)の変更を保存します。データベースには影響を与えませんが、 RunPython インスタンスが使用するためにこれらの変更を保持します。 options はオプション名と値をマッピングした辞書である必要があります。

AlterModelManagers

class AlterModelManagers(name, managers)

マイグレーション中に利用可能なマネージャを変更します。

AddField

class AddField(model_name, name, field, preserve_default=True)

モデルにフィールドを追加します。 model_name はモデル名、 name はフィールド名、 field はバインドされていないフィールドのインスタンスです。これは models.py のフィールド宣言に記述します。たとえば、 models.IntegerField(null=True) のように書きます。

引数 preserve_default は、フィールドのデフォルト値が永続的で、プロジェクトの状態に焼き込むべきもの (True) か、それとも一時的で今回のマイグレーションのためのもの (False) かを示します。通常、マイグレーションはテーブルに NULL 値でないフィールドを追加するため、既存の行に入れるデフォルト値が必要なためです。Django がデータベースのデフォルトを設定することはなく、常に Django ORM コードの中でデフォルトを適用します。これは、データベースにデフォルトを直接設定する動作には影響しません。

警告

古いデータベースでは、デフォルト値を持つフィールドを追加すると、テーブルの完全な書き換えが発生することがあります。これはNULL可能なフィールドでも発生し、パフォーマンスに悪影響を及ぼす可能性があります。これを避けるには、以下の手順を実行する必要があります。

  • デフォルト値なしで nullable フィールドを追加し、 makemigrations コマンドを実行します。これで AddField オペレーションを持つマイグレーションが生成されるはずです。
  • デフォルト値をフィールドに追加し、 makemigrations コマンドを実行します。これで AlterField オペレーションを持つマイグレーションが生成されるはずです。

RemoveField

class RemoveField(model_name, name)

モデルからフィールドを削除します。

逆の場合、これは実際にはモデルにフィールドを追加していることに留意してください。フィールドが null 許容であるか、再作成されたカラムに入力するために使われるデフォルト値を持っている場合、この操作は可逆です(データの損失を除けば不可逆です)。フィールドが null 許容でなく、デフォルト値を持たない場合、この操作は不可逆です。

PostgreSQL

RemoveField will also delete any additional database objects that are related to the removed field (like views, for example). This is because the resulting DROP COLUMN statement will include the CASCADE clause to ensure dependent objects outside the table are also dropped.

AlterField

class AlterField(model_name, name, field, preserve_default=True)

フィールドの型を変更したり、 null, unique, db_column などのフィールド属性を変更したりします。

引数 preserve_default は、フィールドのデフォルト値が永続的で、プロジェクトの状態に保持されるべきもの (True) か、それとも一時的でこのマイグレーションのためだけのもの (False) かを指定します。通常マイグレーションは null 許容のフィールドを null 非許容のフィールドに変更し、既存の行に入れるデフォルト値が必要だからです。Django がデータベースのデフォルトを設定することはなく、常に Django ORM コードの中でデフォルトを適用します。

たとえば、 models.TextField() のようなテキスト型のフィールドを models.IntegerField() のような数値型のフィールドに変更することは、ほとんどのデータベースではできません。

RenameField

class RenameField(model_name, old_name, new_name)

フィールドの名前を変更します(また、 db_column が指定されていない限り、カラム名も変更します)。

AddIndex

class AddIndex(model_name, index)

データベーステーブルに model_name のモデルのインデックスを作成します。 indexIndex クラスのインスタンスです。

RemoveIndex

class RemoveIndex(model_name, name)

model_name のモデルから name という名前のインデックスを削除します。

RenameIndex

class RenameIndex(model_name, new_name, old_name=None, old_fields=None)

モデルのデータベーステーブルのインデックス名を model_name に変更します。 old_nameold_fields のどちらか一方を指定します。 old_fields は文字列のイテラブルで、多くの場合 index_together のフィールドに対応します。

インデックス名の変更文をサポートしていないデータベース(SQLiteとMariaDB < 10.5.2)では、この操作はインデックスを削除して再作成することになり、コストがかかります。

AddConstraint

class AddConstraint(model_name, constraint)

データベーステーブルの model_name のモデルに 制約 を作成します。

RemoveConstraint

class RemoveConstraint(model_name, name)

model_name のモデルから name という名前の制約を削除します。

特別なオペレーション

RunSQL

class RunSQL(sql, reverse_sql=None, state_operations=None, hints=None, elidable=False)

データベース上で任意の SQL を実行できるようにします。Django が直接サポートしていない、データベースバックエンドのより高度な機能に便利です。

sqlreverse_sql があれば、データベースで実行する SQL の文字列を指定します。ほとんどのデータベースバックエンド (PostgreSQL 以外) では、 Django は SQL を実行する前に個々の文に分割します。

警告

PostgreSQL と SQLite では、Django のトランザクション状態を壊さないように、 ノンアトミックなマイグレーション の SQL で BEGINCOMMIT だけを使ってください。

文字列もしくは 2 値のタプルのリストを渡すことができます。その内後者はクエリとパラメータを cursor.execute() において行うのと同じ形式で渡すために用いられます。以下の 3 つの処理は互いに等価です:

migrations.RunSQL("INSERT INTO musician (name) VALUES ('Reinhardt');")
migrations.RunSQL([("INSERT INTO musician (name) VALUES ('Reinhardt');", None)])
migrations.RunSQL([("INSERT INTO musician (name) VALUES (%s);", ["Reinhardt"])])

クエリにパーセント文字リテラルを含ませたい場合、パラメータを渡していればパーセント文字を二重に記述する必要があります。

reverse_sql クエリはマイグレーションを元に戻す際に実行されます。これらのクエリは sql クエリで実行された内容を元に戻します。たとえば、上記の挿入を削除で取り消す場合:

migrations.RunSQL(
    sql=[("INSERT INTO musician (name) VALUES (%s);", ["Reinhardt"])],
    reverse_sql=[("DELETE FROM musician where name=%s;", ["Reinhardt"])],
)

reverse_sqlNone (デフォルト) の場合、 RunSQL オペレーションは元に戻りません。

引数 state_operations にはプロジェクトのステートから見た SQL と等価となる処理を渡すことができます。たとえば、手動でカラムを作成した場合、マイグレーションの自動検知機構が最新のモデルのステートを保持できるように AddField のリストを渡す必要があります。そうしなければ、次に makemigrations を実行した際、フィールドを追加した処理を一切検知せずに同じ追加処理を再度適用してしまうでしょう。次に例を示します。

migrations.RunSQL(
    "ALTER TABLE musician ADD COLUMN name varchar(255) NOT NULL;",
    state_operations=[
        migrations.AddField(
            "musician",
            "name",
            models.CharField(max_length=255),
        ),
    ],
)

オプションの hints 引数はデータベースルータオブジェクトの allow_migrate() メソッドに **hints として渡されてルーティングの決定を補助します。データベースのヒントに関しての詳細は ヒント を参照ください。

オプションの elidable 引数はこの処理を マイグレーションのスカッシュ を行った際に消去する(省略する)か否かを決定します。

RunSQL.noop

指定された順序での処理を行わない場合、sql もしくは reverse_sql に対して RunSQL.noop 要素を渡します。これは特に処理を逆の順序で行いたい場合に有用です。

RunPython

class RunPython(code, reverse_code=None, atomic=None, hints=None, elidable=False)

マイグレーション履歴を反映して独自の Python コードを実行します。code (与えられれば加えて reverse_code )は二つの引数を受け取る呼び出し可能オブジェクトでなければなりません;第一引数はプロジェクトの履歴において処理の場所が一致している履歴を反映したモデルを含んだ django.apps.registry.Apps のインスタンスであり、第二引数は SchemaEditor のインスタンスです。

reverse_code 引数はマイグレーションを元に戻す際に呼び出されます。この呼び出し可能オブジェクトはマイグレーションが逆順処理可能となるよう、先の呼び出し可能オブジェクト code で行われた処理を無効化しなければなりません。 reverse_codeNone (デフォルト) の場合、 RunPython オペレーションは元に戻りません。

オプションの hints 引数はルーティングの決定を補助するためにデータベースルータの allow_migrate() メソッドに **hints として渡されます。データベースのヒントに関しての詳細は ヒント を参照してください。

オプションの elidable 引数はこの処理を マイグレーションのスカッシュ を行った際に消去する(省略する)か否かを決定します。

マイグレーションファイルの Migration クラスから独立した別の関数として記述し、 RunPython に渡すことを推奨します。以下に RunPython を用いて Country モデル上の初期オブジェクトを追加する例を示します:

from django.db import migrations


def forwards_func(apps, schema_editor):
    # We get the model from the versioned app registry;
    # if we directly import it, it'll be the wrong version
    Country = apps.get_model("myapp", "Country")
    db_alias = schema_editor.connection.alias
    Country.objects.using(db_alias).bulk_create(
        [
            Country(name="USA", code="us"),
            Country(name="France", code="fr"),
        ]
    )


def reverse_func(apps, schema_editor):
    # forwards_func() creates two Country instances,
    # so reverse_func() should delete them.
    Country = apps.get_model("myapp", "Country")
    db_alias = schema_editor.connection.alias
    Country.objects.using(db_alias).filter(name="USA", code="us").delete()
    Country.objects.using(db_alias).filter(name="France", code="fr").delete()


class Migration(migrations.Migration):
    dependencies = []

    operations = [
        migrations.RunPython(forwards_func, reverse_func),
    ]

これは データのマイグレーション の作成、独自のデータ更新と構成変更、そして ORM や Python のコードにアクセスを必要とするあらゆるオペレーションのために行われる一般的なオペレーションです。

RunSQL と同様、もし内部のスキーマを変更する場合は Django のモデル機構のスコープ範囲外 (たとえば triggers など) で行うか、もしくはモデルの状態に変更を反映する処理に SeparateDatabaseAndState を追加する事を心がけてください。そうでなければ、バージョン管理された ORM およびマイグレーションの自動検出機構が正常に動作しなくなってしまいます。

標準では RunPython は DDL トランザクションをサポートしないデータベース (たとえば MySQL と Oracle など) 上では通常のトランザクション内で記述された内容の処理を行います。この仕組みは安全ではありますが、これらのデータベースバックエンドを利用中に schema_editor を利用しようとするとクラッシュを引き起こす場合があります。このような場合は、RunPython に対して atomic=False を渡してください。

トランザクション中の DDL 使用をサポートしているデータベース(SQLite や PostgreSQL)においては、RunPython は各マイグレーションに対して作成されたトランザクション以外に自動的にトランザクションを保持しません。そのため、たとえば PostgreSQL では、スキーマ変更と RunPython を同一のマイグレーション内で結合させて用いるのは避けるべきであり、そうしなければ OperationalError: cannot ALTER TABLE "mytable" because it has pending trigger events のようなエラーに遭遇する可能性があります。

もしここまでに挙げられた物と異なるデータベースを利用しておりトランザクション中の DDL 事項をサポートしているか不明な場合、 django.db.connection.features.can_rollback_ddl 属性を確認してください。

もし RunPython オペレーションが 非アトミックなマイグレーション の一部である場合、そのオペレーションは RunPython オペレーションに対して atomic=True が渡されて生成されたトランザクション中においてのみ実行されます。

警告

RunPython はモデルのデータベース接続を魔法のように切り替える事はしません;データベースのエイリアス(作成した関数の第二引数 schema_editor 内、 schema_editor.connection.alias から利用可能)を指定していないモデルのメソッドはすべてデフォルトのデータベースを利用します。

static RunPython.noop()

指定された順序で処理を行いたくない場合は code もしくは reverse_codeRunPython.noop メソッドを渡してください。この指定は処理を逆順で行いたい場合に特に有用です。

SeparateDatabaseAndState

class SeparateDatabaseAndState(database_operations=None, state_operations=None)

データベース(スキーマ変更)と状態(自動検出機能)という2つの側面を混在させてマッチさせることができる、高度に専門化されたオペレーションです。

これは2つのオペレーションのリストを受け入れます。状態への適用を求められたときは、 state_operations リストを使用します(これは RunSQLstate_operations 引数の一般化されたバージョンです)。データベースへの変更の適用を求められたときは、 database_operations リストを使用します。

データベースの実際の状態と Django の状態のビューが同期しなくなると、マイグレーションフレームワークが壊れてしまい、データが失われることさえあります。じっくりと、データベースと状態のオペレーションを慎重にチェックすべきです。データベースのオペレーションをチェックするために、 sqlmigratedbshell を使うことができます。状態のオペレーションをチェックするために makemigrations 、特に --dry-run を使うことができます。

SeparateDatabaseAndState の使用例は、 ManyToManyField を中間 (through) モデルを使うように変更する を参照してください。

独自のオペレーションを書く

オペレーションは比較的シンプルな API を持っており、Django に内蔵された機能を独自に補助する処理を簡単に記述できるよう設計されています。Operation の基本構造は以下のようなものです:

from django.db.migrations.operations.base import Operation


class MyCustomOperation(Operation):
    # If this is False, it means that this operation will be ignored by
    # sqlmigrate; if true, it will be run and the SQL collected for its output.
    reduces_to_sql = False

    # If this is False, Django will refuse to reverse past this operation.
    reversible = False

    def __init__(self, arg1, arg2):
        # Operations are usually instantiated with arguments in migration
        # files. Store the values of them on self for later use.
        pass

    def state_forwards(self, app_label, state):
        # The Operation should take the 'state' parameter (an instance of
        # django.db.migrations.state.ProjectState) and mutate it to match
        # any schema changes that have occurred.
        pass

    def database_forwards(self, app_label, schema_editor, from_state, to_state):
        # The Operation should use schema_editor to apply any changes it
        # wants to make to the database.
        pass

    def database_backwards(self, app_label, schema_editor, from_state, to_state):
        # If reversible is True, this is called when the operation is reversed.
        pass

    def describe(self):
        # This is used to describe what the operation does in console output.
        return "Custom Operation"

    @property
    def migration_name_fragment(self):
        # Optional. A filename part suitable for automatically naming a
        # migration containing this operation, or None if not applicable.
        return "custom_operation_%s_%s" % (self.arg1, self.arg2)

上の例もテンプレートとして利用できますが、 django.db.migrations.operations 内の Django 組み込みのオペレーションを参考にすることをお勧めします。これらは ProjectState や履歴上のモデルを取得するために使用されるパターン、 ModelStatestate_forwards() で履歴上のモデルを模倣するために使用されるパターンなど、マイグレーションフレームワークの半内部的な側面の使用例を多くカバーしています。

いくつかの注意すべき点

  • マイグレーションを記述するのに ProjectState について多くを学ぶ必要はありません。ただし、それがアプリケーションの登録情報へのアクセスを提供するプロパティ app を持っていることだけは知っていて下さい。それに対して get_model を呼ぶことで取得できます。

  • database_forwardsdatabase_backwards はどちらもパラメータとして 2 つの状態を受け取ります。これらは state_forwards メソッドが適用するであろう差分を表していますが、利便性と実行速度のために渡されます。

  • もし database_forwards()database_backwards()from_state 引数からモデルクラスやモデルインスタンスを操作したい場合は、 clear_delayed_apps_cache() メソッドを使ってモデルの状態をレンダリングし、リレーション先モデルを利用できるようにする必要があります:

    def database_forwards(self, app_label, schema_editor, from_state, to_state):
        # This operation should have access to all models. Ensure that all models are
        # reloaded in case any are delayed.
        from_state.clear_delayed_apps_cache()
        ...
    
  • database_backwards メソッドにおける to_state古い 状態を示します;そのため、その値はマイグレーションが状態を戻した場合は現在の状態となります。

  • 内蔵されたオペレーションにおいて references_model が実装されているのを見つけるかもしれません;これは独自のオペレーションのためでなく自動検知機構のコードの一部として存在しています。

警告

パフォーマンス上の理由から、ModelState.fields 内の Field のインスタンスはマイグレーション間で再利用されます。これらのインスタンスの属性を決して変更してはいけません。state_forwards() 内のフィールドを変更したい場合は、ModelState.fields から古いインスタンスを削除して新たなインスタンスと置き換える必要があります。ModelState.managers 内の Manager のインスタンスについても同様です。

簡単な例として、(PostgreSQL のよりエキサイティングな機能を含んだ) PostgreSQL 拡張をロードするオペレーションを作成してみましょう。これはモデルの状態を変えず、1 つのコマンドを実行します:

from django.db.migrations.operations.base import Operation


class LoadExtension(Operation):
    reversible = True

    def __init__(self, name):
        self.name = name

    def state_forwards(self, app_label, state):
        pass

    def database_forwards(self, app_label, schema_editor, from_state, to_state):
        schema_editor.execute("CREATE EXTENSION IF NOT EXISTS %s" % self.name)

    def database_backwards(self, app_label, schema_editor, from_state, to_state):
        schema_editor.execute("DROP EXTENSION %s" % self.name)

    def describe(self):
        return "Creates extension %s" % self.name

    @property
    def migration_name_fragment(self):
        return "create_extension_%s" % self.name
Back to Top