Django 1.7による初期データの読み込みとデータの移行


95

最近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. への複数の呼び出しでPythonコードを記述しますmymodel.create(...)
  2. JSONフィクスチャファイルからデータをロードするには、Django関数(の呼び出しなどloaddata)を使用または記述します。

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

Djangoがネイティブで使用できるようになったため、私はSouthを使用したくありません。


3
また、OPの元の質問に別の質問を追加したいと思います。アプリケーションに属さないデータのデータ移行をどのように行うべきですか。たとえば、誰かがサイトフレームワークを使用している場合、サイトデータのフィクスチャを用意する必要があります。サイトのフレームワークはアプリケーションとは関係がないので、データの移行はどこに置けばよいでしょうか?よろしくお願いします!
セラフェイム2014

ここではまだ誰も取り上げていない重要な点は、データ移行で定義されたデータを、移行を偽装したデータベースに追加する必要がある場合に何が起こるかです。移行は偽装されているため、データ移行は実行されず、手動で実行する必要があります。この時点で、フィクスチャファイルに対してloaddataを呼び出すだけでもかまいません。
ヘケビントラン

別の興味深いシナリオは、たとえばauth.Groupインスタンスを作成するためのデータ移行があり、後でシードデータとして作成する新しいグループがある場合に何が起こるかです。新しいデータ移行を作成する必要があります。グループシードデータは複数のファイルに含まれるため、これは煩わしい場合があります。また、移行をリセットする必要がある場合は、シードデータを設定し、それらも移植するデータ移行を探す必要があります。
hekevintran

@Serafeimフィクスチャの代わりにデータ移行を使用しても、データのロード方法を変更するだけなので、「サードパーティアプリの初期データをどこに置くか」という質問は変わりません。私はこのようなことのために小さなカスタムアプリを使用しています。サードパーティアプリの名前が「foo」の場合、私はデータ移行/フィクスチャを含むシンプルなアプリを「foo_integration」と呼びます。
guettli 2015年

@guettliはい、おそらく追加のアプリケーションを使用するのが最善の方法です!
Serafeim、2015年

回答:


81

更新:このソリューションで発生する可能性のある問題については、以下の@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。最も簡単:loaddata with app_labelを呼び出すと、フィクスチャが<yourapp>fixturesdirから自動的に読み込まれます。

    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はすべてのアプリのフィクスチャディレクトリfixtureからファイル名をロードしようとします(おそらく必要ありません)。

  3. それを実行します

    python manage.py migrate <yourapp>

1
わかりました、そうです...また、呼び出しloaddata('loaddata', fixture_filename, app_label='<yourapp>')は、アプリのフィクスチャdirにも直接移動します(したがって、フィクスチャの完全パスを構築する必要はありません)
n__o

15
その方法を使用すると、シリアライザは現在のmodels.pyファイルからのモデルの状態を処理します。このファイルには、追加のフィールドやその他の変更が含まれている場合があります。移行の作成後に変更を加えた場合、失敗します(そのため、移行後にスキーマの移行を作成することもできません)。これを修正するには、シリアライザが作業しているアプリのレジストリを、最初のパラメータの移行機能に提供されているレジストリに一時的に変更できます。パスへのレジストリはにありdjango.core.serializers.python.appsます。
GwynBleidD 2015年

3
なぜこれを行うのですか?Djangoの実行と保守がますます難しくなるのはなぜですか?私はこれをやりたくありません、私はこの問題を解決する単純なコマンドラインインターフェイスを必要としています。Djangoはこれをより簡単にするためのもので、難しくはありません:(
CpILL

1
@GwynBleidDこれはあなたがしている非常に重要なポイントであり、私はそれがこの受け入れられた答えに現れるはずだと思います。これは、ドキュメントのデータ移行コードの例でコメントとして表示されるコメントと同じです。app registryグローバル変数を変更せずに、提供されているでシリアライザを使用する別の方法を知っていますか?
Ad N

3
この回答が受け入れとともにkazooに賛成されていることが、私がスタックオーバーフローを使用しないことをお勧めする理由です。今でもコメントと逸話がありますが、#djangoにこれについて言及している人がいます。
shangxiao 2017

50

短縮版

データ移行では、管理コマンドを直接使用しないでください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),
    ]

1
Rockallite、あなたは非常に強い主張をします。あなたの答えは私に疑問を残しました、@ n__o / @ mlissnerの答えからのソリューション2.1 objects = serializers.deserialize('json', fixture, ignorenonexistent=True)は同じ問題に苦しんでいloaddataますか?またはignorenonexistent=True、考えられるすべての問題をカバーしていますか?
ダリオ

7
ソースを見ると、ignorenonexistent=True引数には2つの効果があることがわかります。1)最新のモデル定義にないフィクスチャのモデルを無視します。2)ないフィクスチャのモデルのフィールドを無視します。最新の対応するモデル定義。それらのどれも、モデル新しい必須フィールドの状況を処理しません。だから、はい、私はそれが普通のものと同じ問題を抱えていると思いますloaddata
Rockallite 2016年

私の古いjsonがを使用して他のモデルを参照するモデルを持っていることがわかったら、これはうまくいきましたnatural_key()
dsummersl 2016

1
テストケースを実行すると新しいデータベースが作成され、すべての移行が最初から適用されるため、おそらくこの回答が受け入れられた回答としてより役立つでしょう。このソリューションは、データ移行で_get_modelを置き換えない場合に、ユニットテストを使用するプロジェクトが直面する問題を修正します。Tnx
Mohammad ali baghershemirani

アップデートと説明をありがとう、@ Rockallite。私の最初の回答は、Django 1.7で移行が導入されてから数週間後に投稿されたものであり、続行方法に関するドキュメントは不明確でした(そして、私が最後に確認したときも)。うまくいけば、Djangoは、いつかモデル履歴を考慮に入れるために、loaddata /移行メカニズムを更新するでしょう。
n__o

6

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

使用ジャンゴ移行-フィクスチャは、単に次の管理コマンドを実行することができますし、それはあなたのすべてを検索しますINSTALLED_APPSのためにinitial_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を参照してください。


2

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

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

データの移行は1回だけ実行されます。マイグレーションは、順序付けられた一連のマイグレーションです。003_xxxx.py移行が実行されると、django移行は、このアプリがこのアプリ(003)まで移行されることをデータベースに書き込み、次の移行のみを実行します。


myModel.create(...)RunPython関数で呼び出しを繰り返す(またはループを使用する)ことをお勧めしますか?
ミカエル2014年

そうだね。トランザクションデータベースはそれを完全に処理します:)
FlogFR '27 / 10/27

1

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

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


これをありがとう!Python 3で動作するバージョンを作成しました(厳密なPylintに合格しています)。でファクトリとして使用できますRunPython(load_fixture('badger', 'stoat'))gist.github.com/danni/1b2a0078e998ac080111
Danielle Madeley

1

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

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

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

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


1
...私は「頻繁に変更された場合維持するのは難しい」あなたの点については同意しますが、ここでの固定具は、プロジェクトだけをインストールする際に、最初の(そして最小限の)データを提供することを目指し
ミカエル・

1
これは、データの1回限りのロード用であり、移行のコンテキスト内で行われる場合に意味があります。移行内であれば、jsonデータに変更を加える必要はありません。今後のデータの変更を必要とするスキーマの変更は、別のマイグレーションを介して処理する必要があります(その時点で、データベース内の他のデータも変更する必要がある可能性があります)。
mtnpaul 2015

0

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

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

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

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

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

よりわかりやすくするために、sql/フォルダーを含むアプリは次のようになります。 ここに画像の説明を入力してください

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

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

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

ここに画像の説明を入力してください

各アプリケーション内で使用可能なrun_sql_scripts.pyすべてのsqlスクリプトを実行するように作成しました。これは、誰かが走ると発射されますpython manage.py migrate。このカスタムmigrationは、関連するアプリケーションを依存関係として追加します。これによりsql、必要なアプリケーションが0001_initial.py移行を実行した後にのみステートメントを実行しようとします(存在しないテーブルに対して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を使い始めたばかりなので、これは最善の解決策ではない可能性がありますが、これについてグーグルで情報を見つけられなかったため、この「ハウツー」を皆さんと共有したいと考えました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.