短縮版
データ移行では、管理コマンドを直接使用しないでくださいloaddata
。
# Bad example for a data migration
from django.db import migrations
from django.core.management import call_command
def load_fixture(apps, schema_editor):
# No, it's wrong. DON'T DO THIS!
call_command('loaddata', 'your_data.json', app_label='yourapp')
class Migration(migrations.Migration):
dependencies = [
# Dependencies to other migrations
]
operations = [
migrations.RunPython(load_fixture),
]
ロングバージョン
loaddata
利用django.core.serializers.python.Deserializer
移行に履歴データをデシリアライズするために最新のモデルを使用しています。これは不正な動作です。
たとえば、loaddata
管理コマンドを使用してフィクスチャからデータをロードするデータ移行があり、それが開発環境にすでに適用されているとします。
後で、対応するモデルに新しい必須フィールドを追加することを決定したので、それを実行して、更新されたモデルに対して新しい移行を行います(./manage.py makemigrations
プロンプトが表示されたときに、新しいフィールドに1回限りの値を指定することもできます)。
次の移行を実行し、すべて順調です。
最後に、Djangoアプリケーションの開発が完了し、本番サーバーにデプロイします。さて、本番環境で移行全体を最初から実行する時が来ました。
ただし、データの移行は失敗します。loaddata
これは、現在のコードを表すコマンドの逆シリアル化モデルを、追加した新しい必須フィールドの空のデータと共に保存できないためです。元のフィクスチャには必要なデータがありません!
ただし、新しいフィールドに必要なデータでフィクスチャを更新した場合でも、データの移行は失敗します。データ移行の実行中は、対応する列をデータベースに追加する次の移行はまだ適用されていません。存在しない列にデータを保存することはできません!
結論:データの移行では、loaddata
コマンドによってモデルとデータベースの間に潜在的な不整合が生じます。データ移行で直接使用しないでください。
ソリューション
loaddata
コマンドは、django.core.serializers.python._get_model
関数に依存してフィクスチャから対応するモデルを取得します。これにより、モデルの最新バージョンが返されます。モンキーパッチを適用して、履歴モデルを取得する必要があります。
(次のコードはDjango 1.8.xで動作します)
# Good example for a data migration
from django.db import migrations
from django.core.serializers import base, python
from django.core.management import call_command
def load_fixture(apps, schema_editor):
# Save the old _get_model() function
old_get_model = python._get_model
# Define new _get_model() function here, which utilizes the apps argument to
# get the historical version of a model. This piece of code is directly stolen
# from django.core.serializers.python._get_model, unchanged. However, here it
# has a different context, specifically, the apps variable.
def _get_model(model_identifier):
try:
return apps.get_model(model_identifier)
except (LookupError, TypeError):
raise base.DeserializationError("Invalid model identifier: '%s'" % model_identifier)
# Replace the _get_model() function on the module, so loaddata can utilize it.
python._get_model = _get_model
try:
# Call loaddata command
call_command('loaddata', 'your_data.json', app_label='yourapp')
finally:
# Restore old _get_model() function
python._get_model = old_get_model
class Migration(migrations.Migration):
dependencies = [
# Dependencies to other migrations
]
operations = [
migrations.RunPython(load_fixture),
]