マイグレーション・オペレーション¶
マイグレーションファイルは一つ以上の オペレーション
、データベースに対してマイグレーションが行うべきオペレーションを宣言的に保持するオブジェクト、から構成されます。
Django はこれらの Operation
オブジェクトも使用して、モデルの履歴を確認し、前回のマイグレーション以降にモデルに加えた変更を計算します。そのため、自動的にマイグレーションを書き出すことができます。マイグレーションの定義が宣言的なのはそのためであり、Django がそれらを簡単にメモリに読み込んで、データベースに触れることなくプロジェクトがどのようにあるべきかを把握するために実行することができます。
データのマイグレーション や高度なデータベースへの手動操作のような特別な オペレーション
も存在します。もし頻繁に行われる独自の変更をカプセル化したければ独自に オペレーション
を記述する事もできます。
もし独自の Operation
オブジェクトを定義する空のマイグレーションファイルが必要であれば、python manage.py makemigrations --empty yourappname
を実行してください。ただし、手動でのスキーマ変更オペレーションの追加はマイグレーションの自動検出機構を混乱させ、 makemigrations
が正しい結果を出力しなくなる可能性があることに十分注意してください。
Django が提供するコア機能は全て django.db.migrations.operations
モジュールから利用できます。
より入門的な内容に関しては、 トピックスのマイグレーション を参照ください。
スキーマ・オペレーション¶
CreateModel
¶
プロジェクトの履歴に新たなモデル、そしてデータベース上に対応するテーブルを作成します。
name
は models.py
ファイルに定義されているであろうモデルの名称です。
fields
は (フィールド名, フィールドのインスタンス)
という 2 値のタプルのリストです。フィールドのインスタンスは束縛されないフィールド(他のモデルから取得した物でなく、単に models.CharField(...)
等と記述する物)でなければいけません。
options
は定義されるモデルの Meta
クラスの値からなるオプションの辞書オブジェクトです。
bases
はオプションで、このモデルに継承させる他のクラスのリストです。(すでに定義した他のモデルを継承する場合、)クラスオブジェクトまたは "appname.ModelName"
という形式の文字列を含めることができます。このパラメータが空の場合、デフォルトでは標準の models.Model
から継承します。
managers
は (マネージャ名, マネージャのインスタンス)
という 2 値のタプルのリストを受け取ります。マイグレーションの間はリスト内で最初に定義されたマネージャがこのモデルの標準マネージャとして利用されます。
RenameModel
¶
モデルの名称を今までの名称から新しい名称に変更します。
一度に定義しているモデルの名前とかなり多くのフィールドの名前を変更した場合は手動でこの処理を追加する必要があるかもしれません;マイグレーションの自動検知機構が古い名称を持ったモデルを削除して異なった名称のモデルを追加したと認識してしまうため、そのマイグレーション処理は古いテーブルの全てのデータを消去してしまいます。
AlterModelTableComment
¶
モデルのテーブルコメント (Meta
サブクラスの db_table_comment
オプション) を変更します。
AlterUniqueTogether
¶
モデルのユニーク制約のセットを変更します (Meta
サブクラスの unique_together
オプション)。
AlterIndexTogether
¶
モデルのカスタムインデックスのセットを変更します (Meta
サブクラスの index_together
オプション)。
警告
AlterIndexTogether
は公式には Django 4.2 以前のマイグレーションファイルにのみサポートされています。後方互換性の理由から、これはまだパブリック API の一部であり、非推奨または削除する予定はありませんが、新しいマイグレーションには使わないでください。代わりに AddIndex
と RemoveIndex
オペレーションを使ってください。
AlterOrderWithRespectTo
¶
Meta
サブクラスの order_with_respect_to
オプションに必要な _order
カラムを作成または削除します。
AlterModelOptions
¶
permissions
や verbose_name
のような雑多なモデルオプション(モデルの Meta
の設定)の変更を保存します。データベースには影響を与えませんが、 RunPython
インスタンスが使用するためにこれらの変更を保持します。 options
はオプション名と値をマッピングした辞書である必要があります。
AddField
¶
モデルにフィールドを追加します。 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
¶
モデルからフィールドを削除します。
逆の場合、これは実際にはモデルにフィールドを追加していることに留意してください。フィールドが null 許容であるか、再作成されたカラムに入力するために使われるデフォルト値を持っている場合、この操作は可逆です(データの損失を除けば不可逆です)。フィールドが null 許容でなく、デフォルト値を持たない場合、この操作は不可逆です。
PostgreSQL
RemoveField
は、削除されたフィールドに関連する追加のデータベースオブジェクト(たとえばビューなど)も削除します。これは、テーブル外の依存オブジェクトも確実に削除されるようにするために(参照: dependent objects outside the table are also dropped )、生成される DROP COLUMN
ステートメントは CASCADE
句を含むからです。
AlterField
¶
フィールドの型を変更したり、 null
, unique
, db_column
などのフィールド属性を変更したりします。
引数 preserve_default
は、フィールドのデフォルト値が永続的で、プロジェクトの状態に保持されるべきもの (True
) か、それとも一時的でこのマイグレーションのためだけのもの (False
) かを指定します。通常マイグレーションは null 許容のフィールドを null 非許容のフィールドに変更し、既存の行に入れるデフォルト値が必要だからです。Django がデータベースのデフォルトを設定することはなく、常に Django ORM コードの中でデフォルトを適用します。
たとえば、 models.TextField()
のようなテキスト型のフィールドを models.IntegerField()
のような数値型のフィールドに変更することは、ほとんどのデータベースではできません。
RenameIndex
¶
Renames an index in the database table for the model with model_name
.
Exactly one of old_name
and old_fields
can be provided. old_fields
is an iterable of the strings, often corresponding to fields of
index_together
(pre-Django 5.1 option).
インデックス名の変更文をサポートしていないデータベース(SQLiteとMariaDB < 10.5.2)では、この操作はインデックスを削除して再作成することになり、コストがかかります。
特別なオペレーション¶
RunSQL
¶
データベース上で任意の SQL を実行できるようにします。Django が直接サポートしていない、データベースバックエンドのより高度な機能に便利です。
sql
と reverse_sql
があれば、データベースで実行する SQL の文字列を指定します。ほとんどのデータベースバックエンド (PostgreSQL 以外) では、 Django は SQL を実行する前に個々の文に分割します。
警告
PostgreSQL と SQLite では、Django のトランザクション状態を壊さないように、 ノンアトミックなマイグレーション の SQL で BEGIN
か COMMIT
だけを使ってください。
文字列もしくは 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_sql
が None
(デフォルト) の場合、 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
¶
マイグレーション履歴を反映して独自の Python コードを実行します。code
(与えられれば加えて reverse_code
)は二つの引数を受け取る呼び出し可能オブジェクトでなければなりません;第一引数はプロジェクトの履歴において処理の場所が一致している履歴を反映したモデルを含んだ django.apps.registry.Apps
のインスタンスであり、第二引数は SchemaEditor
のインスタンスです。
reverse_code
引数はマイグレーションを元に戻す際に呼び出されます。この呼び出し可能オブジェクトはマイグレーションが逆順処理可能となるよう、先の呼び出し可能オブジェクト code
で行われた処理を無効化しなければなりません。 reverse_code
が None
(デフォルト) の場合、 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
から利用可能)を指定していないモデルのメソッドはすべてデフォルトのデータベースを利用します。
SeparateDatabaseAndState
¶
データベース(スキーマ変更)と状態(自動検出機能)という2つの側面を混在させてマッチさせることができる、高度に専門化されたオペレーションです。
これは2つのオペレーションのリストを受け入れます。状態への適用を求められたときは、 state_operations
リストを使用します(これは RunSQL
の state_operations
引数の一般化されたバージョンです)。データベースへの変更の適用を求められたときは、 database_operations
リストを使用します。
データベースの実際の状態と Django の状態のビューが同期しなくなると、マイグレーションフレームワークが壊れてしまい、データが失われることさえあります。じっくりと、データベースと状態のオペレーションを慎重にチェックすべきです。データベースのオペレーションをチェックするために、 sqlmigrate
と dbshell
を使うことができます。状態のオペレーションをチェックするために makemigrations
、特に --dry-run
を使うことができます。
SeparateDatabaseAndState
の使用例は、 ManyToManyField を中間 (through) モデルを使うように変更する を参照してください。
Operation category¶
独自のオペレーションを書く¶
オペレーションは比較的シンプルな 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
# This categorizes the operation. The corresponding symbol will be
# displayed by the makemigrations command.
category = OperationCategory.ADDITION
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.
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
や履歴上のモデルを取得するために使用されるパターン、 ModelState
や state_forwards()
で履歴上のモデルを模倣するために使用されるパターンなど、マイグレーションフレームワークの半内部的な側面の使用例を多くカバーしています。
いくつかの注意すべき点
マイグレーションを記述するのに
ProjectState
について多くを学ぶ必要はありません。ただし、それがアプリケーションの登録情報へのアクセスを提供するプロパティapp
を持っていることだけは知っていて下さい。それに対してget_model
を呼ぶことで取得できます。database_forwards
とdatabase_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