web-dev-qa-db-ja.com

Django 1.7を使用した初期データの読み込みとデータの移行

最近Django 1.6から1.7に切り替えて、移行の使用を開始しました(Southを使用したことはありません)。

1.7より前は、fixture/initial_data.jsonコマンドを使用してロードされたpython manage.py syncdbファイルで初期データをロードしていました(データベースの作成時)。

今、移行を使用し始めましたが、この動作は非推奨です:

アプリケーションが移行を使用する場合、フィクスチャの自動ロードはありません。 Django 2.0のアプリケーションでは移行が必要になるため、この動作は非推奨と見なされます。アプリの初期データをロードする場合は、データ移行で行うことを検討してください。 ( https://docs.djangoproject.com/en/1.7/howto/initial-data/#automatically-loading-initial-data-fixtures

公式ドキュメント には、それを行う方法に関する明確な例がないため、私の質問は次のとおりです。

データ移行を使用してこのような初期データをインポートする最良の方法は何ですか:

  1. mymodel.create(...)を複数回呼び出してPythonコードを記述し、
  2. JSONフィクスチャファイルからデータをロードするには、Django関数( loaddataの呼び出しのような )を使用または作成します。

私は2番目のオプションを好みます。

Djangoがネイティブでできるようになったので、Southは使いたくありません。

91
Mickaël

Update:このソリューションが引き起こす可能性のある問題については、以下の@GwynBleidDのコメントを参照してください。また、将来のモデル変更に対してより耐久性のあるアプローチについては、以下の@Rockalliteの回答を参照してください。


<yourapp>/fixtures/initial_data.jsonにフィクスチャファイルがあると仮定します

  1. 空の移行を作成します。

    Django 1.7の場合:

    python manage.py makemigrations --empty <yourapp>
    

    Django 1.8+では、名前を指定できます。

    python manage.py makemigrations --empty <yourapp> --name load_intial_data
    
  2. 移行ファイルを編集<yourapp>/migrations/0002_auto_xxx.py

    2.1。 Django 'loaddata(初期回答)に触発されたカスタム実装:

    import os
    from sys import path
    from Django.core import serializers
    
    fixture_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../fixtures'))
    fixture_filename = 'initial_data.json'
    
    def load_fixture(apps, schema_editor):
        fixture_file = os.path.join(fixture_dir, fixture_filename)
    
        fixture = open(fixture_file, 'rb')
        objects = serializers.deserialize('json', fixture, ignorenonexistent=True)
        for obj in objects:
            obj.save()
        fixture.close()
    
    def unload_fixture(apps, schema_editor):
        "Brutally deleting all entries for this model..."
    
        MyModel = apps.get_model("yourapp", "ModelName")
        MyModel.objects.all().delete()
    
    class Migration(migrations.Migration):  
    
        dependencies = [
            ('yourapp', '0001_initial'),
        ]
    
        operations = [
            migrations.RunPython(load_fixture, reverse_code=unload_fixture),
        ]
    

    2.2。 load_fixtureのより簡単なソリューション(@juliocesarの提案による):

    from Django.core.management import call_command
    
    fixture_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../fixtures'))
    fixture_filename = 'initial_data.json'
    
    def load_fixture(apps, schema_editor):
        fixture_file = os.path.join(fixture_dir, fixture_filename)
        call_command('loaddata', fixture_file) 
    

    カスタムディレクトリを使用する場合に便利です。

    2.3。 Simplest:app_labelloaddataを呼び出すと、<yourapp>fixtures dirからフィクスチャが自動的にロードされます。

    from Django.core.management import call_command
    
    fixture = 'initial_data'
    
    def load_fixture(apps, schema_editor):
        call_command('loaddata', fixture, app_label='yourapp') 
    

    app_labelを指定しない場合、loaddataはallアプリフィクスチャディレクトリからfixtureファイル名をロードしようとしますt want)。

  3. それを実行します

    python manage.py migrate <yourapp>
    
78
n__o

短縮版

NOTデータ移行で直接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管理コマンドを使用してフィクスチャからデータをロードするデータ移行があり、開発環境にすでに適用されているとします。

後で、新しいrequiredフィールドを対応するモデルに追加することにしました。そのため、それを実行し、更新されたモデルに対して新しい移行を行います(場合によっては、 ./manage.py makemigrationsのプロンプトが表示されたときの新しいフィールドへの1回限りの値)。

次の移行を実行すると、すべて順調です。

最後に、Djangoアプリケーションの開発が完了し、運用サーバーに展開します。これで、実稼働環境で移行全体を最初から実行するときが来ました。

ただし、データの移行は失敗します。これは、現在のコードを表すloaddataコマンドからの逆シリアル化されたモデルを、新しいrequiredフィールドの空のデータと共に保存できないためです。追加しました。元のフィクスチャには、必要なデータがありません!

ただし、新しいフィールドに必要なデータでフィクスチャを更新しても、データの移行は失敗します。データ移行の実行中、対応する列をデータベースに追加するnext移行はまだ適用されていません。存在しない列にデータを保存することはできません!

結論:データ移行では、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),
    ]
43
Rockallite

いくつかのコメント(n__oのコメント)と、複数のアプリに多数のinitial_data.*ファイルがあるという事実に触発されて、これらの作成を容易にするDjangoアプリを作成することにしましたデータの移行。

Django-migration-fixture を使用すると、次の管理コマンドを実行するだけで、すべてのINSTALLED_APPSinitial_data.*ファイルを検索し、データ移行に変換できます。

./manage.py create_initial_data_fixtures
Migrations for 'eggs':
  0002_auto_20150107_0817.py:
Migrations for 'sausage':
  Ignoring 'initial_data.yaml' - migration already exists.
Migrations for 'foo':
  Ignoring 'initial_data.yaml' - not migrated.

インストール/使用方法については Django-migration-fixture をご覧ください。

6
alexhayes

移行されたアプリに初期データを読み込む最良の方法は、データの移行を使用することです(ドキュメントでも推奨されています)。利点は、テスト中と生産中の両方でフィクスチャがロードされることです。

@n__oは、移行でloaddataコマンドを再実装することを提案しました。ただし、私のテストでは、loaddataコマンドを直接呼び出すことも正常に機能します。したがって、プロセス全体は次のとおりです。

  1. <yourapp>/fixtures/initial_data.jsonにフィクスチャファイルを作成します

  2. 空の移行を作成します。

    python manage.py makemigrations --empty <yourapp>
    
  3. 移行ファイル/migrations/0002_auto_xxx.pyを編集します

    from Django.db import migrations
    from Django.core.management import call_command
    
    
    def loadfixture(apps, schema_editor):
        call_command('loaddata', 'initial_data.json')
    
    
    class Migration(migrations.Migration):
    
        dependencies = [
            ('<yourapp>', '0001_initial'),
        ]
    
        operations = [
            migrations.RunPython(loadfixture),
        ]
    
4
Pratyush

データベースに初期データを提供するには、 data migration。 を記述します。データの移行では、 RunPython 関数を使用してデータをロードします。

この方法は非推奨であるため、loaddataコマンドを記述しないでください。

データの移行は1回のみ実行されます。移行は、移行の順序付けられたシーケンスです。 003_xxxx.py移行が実行されると、Django移行により、このアプリがこれまで(003)まで移行されることがデータベースに書き込まれ、次の移行のみが実行されます。

2
FlogFR

残念ながら、上記のソリューションはうまくいきませんでした。モデルを変更するたびに、フィクスチャを更新する必要があることがわかりました。理想的には、代わりに作成されたデータとフィクスチャがロードされたデータを同様に変更するデータ移行を記述します。

これを容易にするために クイック関数を書きました これは現在のアプリのfixturesディレクトリを見て、フィクスチャをロードします。移行のフィールドに一致するモデル履歴のポイントで、この関数を移行に配置します。

1
leifdenby

Django 2.1では、いくつかのモデル(国名など)に初期データをロードしたかった。

しかし、初期移行の実行直後にこれが自動的に行われるようにしたかったのです。

したがって、初期データのロードを必要とする各アプリケーション内にsql/フォルダーがあればいいと思いました。

次に、そのsql/フォルダー内に、対応するモデルに初期データをロードするために必要なDMLを含む.sqlファイルがあります。たとえば、

INSERT INTO appName_modelName(fieldName)
VALUES
    ("country 1"),
    ("country 2"),
    ("country 3"),
    ("country 4");

よりわかりやすくするために、これはsql/フォルダーを含むアプリの外観です。 enter image description here

また、特定の順序でsqlスクリプトを実行する必要がある場合もありました。そこで、上の画像に見られるように、ファイル名の前に連続番号を付けることにしました。

次に、python manage.py migrateを実行して、アプリケーションフォルダー内で使用可能なSQLsを自動的にロードする方法が必要でした。

そこで、initial_data_migrationsという名前の別のアプリケーションを作成し、INSTALLED_APPSファイルのsettings.pyのリストにこのアプリを追加しました。次に、migrationsフォルダーを内部に作成し、run_sql_scripts.pyというファイルを追加しました(これは実際にカスタム移行です)。以下の画像に見られるように:

enter image description here

各アプリケーション内で使用可能なすべてのsqlスクリプトを実行するように、run_sql_scripts.pyを作成しました。これは、誰かがpython manage.py migrateを実行したときに起動されます。このカスタムmigrationは、必要なアプリケーションが0001_initial.pyマイグレーションを実行した後にのみsqlステートメントの実行を試みるように、関係するアプリケーションも依存関係として追加します(試行したくない存在しないテーブルに対してSQLステートメントを実行する)。

そのスクリプトのソースは次のとおりです。

import os
import itertools

from Django.db import migrations
from YourDjangoProjectName.settings import BASE_DIR, INSTALLED_APPS

SQL_FOLDER = "/sql/"

APP_SQL_FOLDERS = [
    (os.path.join(BASE_DIR, app + SQL_FOLDER), app) for app in INSTALLED_APPS
    if os.path.isdir(os.path.join(BASE_DIR, app + SQL_FOLDER))
]

SQL_FILES = [
    sorted([path + file for file in os.listdir(path) if file.lower().endswith('.sql')])
    for path, app in APP_SQL_FOLDERS
]


def load_file(path):
    with open(path, 'r') as f:
        return f.read()


class Migration(migrations.Migration):

    dependencies = [
        (app, '__first__') for path, app in APP_SQL_FOLDERS
    ]

    operations = [
        migrations.RunSQL(load_file(f)) for f in list(itertools.chain.from_iterable(SQL_FILES))
    ]

誰かがこれが役立ってくれるといいのですが、私にとってはうまくいったと思います!ご質問がありましたらお知らせください。

注:私はDjangoを使い始めたばかりなので、これは最良の解決策ではないかもしれませんが、それでもこの "How-to"を皆さんと共有したかったのです。これについてグーグルで検索している間はあまり情報が見つかりませんでした。

私の意見では、フィクスチャは少し悪いです。データベースが頻繁に変更される場合、それらを最新の状態に保つのはすぐに悪夢になります。実際には、それは私の意見だけでなく、「Djangoの2つのスクープ」という本の中でずっとよく説明されています。

代わりに、Pythonファイルを作成して、初期セットアップを提供します。さらに何かが必要な場合は、 Factory boy をご覧になることをお勧めします。

一部のデータを移行する必要がある場合は、 data migrations を使用する必要があります。

「フィクスチャの書き込み、モデルファクトリの使用」 フィクスチャの使用についてもあります。

0
Griffosx