マイグレーション操作¶
マイグレーションファイルは一つ以上の オペレーション、データベースに対してマイグレーションが行うべき操作を宣言的に保持するオブジェクト、から構成されます。
加えて Django はモデルが過去からどのように変遷したかを解明するため、またマイグレーションを自動的に記述できるように最後のマイグレーション以降モデルに対して行われた変更を導出するために、これらの オペレーション オブジェクトを利用します;マイグレーションの定義が宣言的であるのはそのためで、これは 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¶
モデルの名称を今までの名称から新しい名称に変更します。
一度に定義しているモデルの名前とかなり多くのフィールドの名前を変更した場合は手動でこの処理を追加する必要があるかもしれません;マイグレーションの自動検知機構が古い名称を持ったモデルを削除して異なった名称のモデルを追加したと認識してしまうため、そのマイグレーション処理は古いテーブルの全てのデータを消去してしまいます。
AlterUniqueTogether¶
Changes the model’s set of unique constraints (the
unique_together option on the Meta
subclass).
AlterIndexTogether¶
Changes the model’s set of custom indexes (the
index_together option on the Meta
subclass).
AlterOrderWithRespectTo¶
Makes or deletes the _order column needed for the
order_with_respect_to option on the Meta
subclass.
AlterModelOptions¶
Stores changes to miscellaneous model options (settings on a model’s Meta)
like permissions and verbose_name. Does not affect the database, but
persists these changes for RunPython instances to use. options
should be a dictionary mapping option names to values.
AlterModelManagers¶
Alters the managers that are available during migrations.
AddField¶
Adds a field to a model. model_name is the model’s name, name is
the field’s name, and field is an unbound Field instance (the thing
you would put in the field declaration in models.py - for example,
models.IntegerField(null=True).
The preserve_default argument indicates whether the field’s default
value is permanent and should be baked into the project state (True),
or if it is temporary and just for this migration (False) - usually
because the migration is adding a non-nullable field to a table and needs
a default value to put into existing rows. It does not affect the behavior
of setting defaults in the database directly - Django never sets database
defaults and always applies them in the Django ORM code.
RemoveField¶
Removes a field from a model.
Bear in mind that when reversed, this is actually adding a field to a model. The operation is reversible (apart from any data loss, which of course is irreversible) if the field is nullable or if it has a default value that can be used to populate the recreated column. If the field is not nullable and does not have a default value, the operation is irreversible.
AlterField¶
Alters a field’s definition, including changes to its type,
null, unique,
db_column and other field attributes.
The preserve_default argument indicates whether the field’s default
value is permanent and should be baked into the project state (True),
or if it is temporary and just for this migration (False) - usually
because the migration is altering a nullable field to a non-nullable one and
needs a default value to put into existing rows. It does not affect the
behavior of setting defaults in the database directly - Django never sets
database defaults and always applies them in the Django ORM code.
Note that not all changes are possible on all databases - for example, you
cannot change a text-type field like models.TextField() into a number-type
field like models.IntegerField() on most databases.
特別な操作¶
RunSQL¶
データベース上で任意の SQL を実行させる事ができます - データベースバックエンドで Django が直接サポートしていない先進機能、例えば部分インデックス等、を利用する上で有用です。
sql および、与えられれば reverse_sql もデータベース上で実行するための SQL 文字列である必要が有ります。多くの(PostgreSQL を除いた)データベースバックエンドにおいて、Django は与えられた SQL を実行前に独立した句で分割します。この処理は Python の sqlparse ライブラリを必要とします。
文字列もしくは 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 クエリはマイグレーションが非適用となった場合に実行され、前のクエリで行われた変更を取り消す事ができます:
migrations.RunSQL(
[("INSERT INTO musician (name) VALUES (%s);", ['Reinhardt'])],
[("DELETE FROM musician where name=%s;", ['Reinhardt'])],
)
引数 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 として渡されてルーティングの決定を補助します。データベースのヒントに関しての詳細は Hints を参照ください。
オプションの elidable 引数はこの処理を マイグレーションの統合 を行った際に消去する(省略する)か否かを決定します。
-
RunSQL.noop¶ 指定された順序での処理を行わない場合、
sqlもしくはreverse_sqlに対してRunSQL.noop要素を渡します。これは特に処理を逆の順序で行いたい場合に有用です。
elidable 引数が追加された。
RunPython¶
マイグレーション履歴を反映して独自の Python コードを実行します。code (与えられれば加えて reverse_code )は二つの引数を受け取る呼び出し可能オブジェクトでなければなりません;第一引数はプロジェクトの履歴において処理の場所が一致している履歴を反映したモデルを含んだ django.apps.registry.Apps のインスタンスであり、第二引数は SchemaEditor のインスタンスです。
reverse_code 引数はマイグレーションが非適用となった際に呼び出されます。この呼び出し可能オブジェクトはマイグレーションが逆順処理可能となるよう、先の呼び出し可能オブジェクト code で行われた処理を無効化しなければなりません。
オプションの hints 引数はルーティングの決定を補助するためにデータベースルータの allow_migrate() メソッドに **hints として渡されます。データベースのヒントに関しての詳細は Hints を参照してください。
オプションの elidable 引数はこの処理を マイグレーションの統合 を行った際に消去する(省略する)か否かを決定します。
上記マイグレーションファイル内の Migration クラスにおいてコードが分割された関数として記述され、単に RunPython に渡すだけで良い状態にすることが推奨されます。以下に RunPython を用いて Country モデル上の初期オブジェクトを追加する例を示します:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
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 のコードにアクセスを必要とするあらゆる操作のために行われる一般的な操作です。
もし South マイグレーションツールから移行した場合、これは基本的に処理として South の方式を用いています - 即ち順序通りおよび逆順のマイグレーションに対して 1 つもしくは 2 つのメソッドを用い、ORM とスキーマの操作が可能になるという方式です。多くの場合、South による orm.Model もしくは orm["appname", "Model"] に対する参照は、本マイグレーションツールによる apps.get_model("appname", "Model") に対する参照に直接置き換えて考える事ができ、残りのコードの大半はデータのマイグレーションに際して変更が不要です。しかしながら、他のアプリケーション内のマイグレーションが本アプリケーションの依存関係に組み込まれない限り apps は本アプリケーション内のモデルへの参照のみを持ちます。
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_codeにRunPython.noopメソッドを渡してください。この指定は処理を逆順で行いたい場合に特に有用です。
elidable 引数が追加された。
引数 atomic のデフォルト値が None に変更されました、これはマイグレーションのアトミック性がマイグレーションの属性値 atomic によって管理されるようになった事を示します。
SeparateDatabaseAndState¶
作業の観点からデータベース(スキーマ変更)と状態(自動検知機構による)を組み合わせて併用する、高度に特殊化された処理です。
2 つの行う処理のリストを受け取り、状態を適用する必要が有る場合は状態に関するリストを利用し、データベースの変更を行う必要が有る場合はデータベースのリストを利用します。あなたがこの操作によって何が起きるかを明確に認識していない場合はこの操作を行わないでください。
自分で操作を書く¶
オペレーションは比較的シンプルな 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"
上の例をテンプレートとして利用する事ができますが、django.db.migrations.operations 内の Django 内蔵のオペレーションを参考にする事もできます - これらは ProjectState のような半内部的側面を持つマイグレーションフレームワークとその履歴上のモデルを取得する方法、同様に ModelState が state_forwards() によってその履歴上のモデルを模倣する方法等の可読性が高く広範にわたる例を持っています。
いくつかの注意すべき点
単純なマイグレーションについて記述するだけであれば
ProjectStateについて多くを学ぶ必要は有りません; ただそれがアプリケーションの(それに対してget_modelを呼ぶことができる)登録情報へのアクセスを提供するプロパティappを持っている事だけ知っていて下さい。database_forwardsおよびdatabase_backwardsは共にパラメータとして 2 つの状態を受け取ります; これらは単に適用される事になるstate_forwardsメソッドの差異を表しているだけですが、利便性と実行速度のために渡されます。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