web-dev-qa-db-ja.com

Django(1.8)アプリ間で必要なForeignKey参照を使用してモデルを移動する

これはこの質問の拡張です: 2つのモデル間でモデルを移動する方法Djangoアプリ(Django 1.7)

一連のモデルをold_appからnew_appに移動する必要があります。最良の答えは Ozan's のようですが、必要な外部キー参照があるため、少し複雑になります。 @halfnibbleはOzanの回答へのコメントで解決策を示していますが、ステップの正確な順序にまだ問題があります(たとえば、モデルをnew_appにコピーする場合、old_appからモデルを削除する場合、移行は待機しますold_app.migrationsnew_app.migrationsなど)

どんな助けでも大歓迎です!

30
Shreyas

アプリ間でのモデルの移行

短い答えは、しないでください!!

しかし、その答えが現実のプロジェクトやプロダクションデータベースで機能することはめったにありません。したがって、このかなり複雑なプロセスを示すために、 sample GitHub repo を作成しました。

MySQLを使用しています。(いいえ、それらは私の実際の資格情報ではありません)。

問題

私が使用している例は、最初にCarモデルとTiresモデルを持つcarsアプリを含むファクトリプロジェクトです。

factory
  |_ cars
    |_ Car
    |_ Tires

Carモデルには、TiresとForeignKeyの関係があります。 (同様に、車のモデルを介してタイヤを指定します)。

ただし、Tiresが独自のビューなどを備えた大きなモデルになることにすぐに気づきました。そのため、独自のアプリでそれを必要としています。したがって、望ましい構造は次のとおりです。

factory
  |_ cars
    |_ Car
  |_ tires
    |_ Tires

また、データの保存に依存しすぎているため、CarTiresのForeignKey関係を維持する必要があります。

ソリューション

ステップ1。設計が不適切な初期アプリをセットアップします。

step 1. のコードを参照します。

ステップ2。管理インターフェースを作成し、ForeignKey関係を含む一連のデータを追加します。

step 2. を表示します

ステップ3。Tiresモデルを独自のアプリに移動することを決定します。コードを細かくカットして新しいタイヤアプリに貼り付けます。新しいtires.Tiresモデルを指すようにCarモデルを更新してください。

次に、./manage.py makemigrationsを実行し、データベースをどこかにバックアップします(これがひどく失敗した場合に備えて)。

最後に、./manage.py migrateを実行して、Doomのエラーメッセージを確認します。

Django.db.utils.IntegrityError:(1217、 '親行を削除または更新できません:外部キー制約が失敗しました')

これまでのコードと移行を step 3. で表示します

ステップ4。トリッキーな部分。自動生成された移行では、モデルを別のアプリにコピーしただけであることがわかりません。したがって、これを修正するにはいくつかのことを行う必要があります。

ステップ4. にあるコメントを使用して、最終的な移行をたどって確認できます。テストして、動作することを確認しました。

まず、carsに取り組みます。新しい空の移行を行う必要があります。このマイグレーションは実際には、最後に作成されたマイグレーション(実行に失敗したマイグレーション)の前に実行する必要があります。そのため、作成した移行に番号を付け直し、依存関係を変更して、最初にカスタム移行を実行し、次にcarsアプリの最後に自動生成された移行を実行しました。

空のマイグレーションを作成するには:

./manage.py makemigrations --empty cars

ステップ4.a。カスタムold_appマイグレーションを作成します。

この最初のカスタム移行では、「database_operations」移行のみを実行します。 Djangoは、「状態」と「データベース」の操作を分割するオプションを提供します。これがどのように行われるかは、 code here で確認できます。

この最初のステップでの目標は、Djangoの状態をいじらずに、データベーステーブルの名前をoldapp_modelからnewapp_modelに変更することです。 Djangoがアプリ名とモデル名に基づいてデータベーステーブルに名前を付けるとしたらどうなるかを理解する必要があります。

これで、最初のtires移行を変更する準備ができました。

ステップ4.b。変更new_app初期移行

操作は問題ありませんが、データベースではなく「状態」のみを変更します。どうして? carsアプリのデータベーステーブルを保持しているためです。また、以前に行ったカスタム移行がこの移行の依存関係であることを確認する必要があります。タイヤ migrationファイル を参照してください。

したがって、データベースのcars.Tiresの名前をtires.Tiresに変更し、Django状態を変更してtires.Tiresテーブルを認識できるようにしました。

ステップ4.c。変更old_app最後の自動生成マイグレーション。

車に戻る戻る、最後に自動生成された移行を変更する必要があります。最初のカスタムカーの移行と、最初のタイヤの移行(変更したばかり)が必要です。

AlterFieldモデルを別のモデルにポイントしているため(同じデータであっても)、ここではCarオペレーションをそのままにしておきます。ただし、cars.Tiresモデルが存在しないため、DeleteModelに関する移行の行を削除する必要があります。完全にtires.Tiresに変換されています。 this migration を表示します。

ステップ4.d。old_appの古いモデルをクリーンアップします。

最後に重要なことですが、自動車アプリで最終的なカスタム移行を行う必要があります。ここでは、cars.Tiresモデルを削除するためだけに「状態」操作を実行します。 cars.Tiresのデータベーステーブルの名前がす​​でに変更されているため、状態のみです。この last migration は、残りのDjango状態をクリーンアップします。

70
Nostalg.io

たった今、2つのモデルをold_appからnew_appに移動しましたが、FK参照は、app_xのモデルではなく、app_yold_appの一部のモデルにありました。

この場合、次のようにNostalg.ioが提供する手順に従います。

  • モデルをold_appからnew_appに移動し、コードベース全体でimportステートメントを更新します。
  • makemigrations
  • ステップ4.aに従います。ただし、移動したすべてのモデルにAlterModelTableを使用してください。私には2つ。
  • ステップ4.bに従います。そのまま。
  • ステップ4.cに従います。ただし、新しく生成された移行ファイルがあるアプリごとに手動で編集して、代わりにstate_operationsを移行してください。
  • 手順4.dに従いますが、移動したすべてのモデルにDeleteModelを使用します。

ノート:

  • 他のアプリから編集されたすべての自動生成移行ファイルは、old_appからのカスタム移行ファイルに依存しています。ここで、AlterModelTableはテーブルの名前を変更するために使用されます。 (ステップ4.aで作成されます。)
  • 私の場合、AlterField操作がなく、DeleteModelRemoveField操作しかなかったため、old_appから自動生成された移行ファイルを削除する必要がありました。 。または、空のままにしますoperations = []
  • 最初からテストDBを作成するときに移行の例外を回避するには、ステップ4.aで作成したold_appからのカスタム移行を確認してください。他のアプリからの以前のすべての移行依存関係があります。

    old_app
      0020_auto_others
      0021_custom_rename_models.py
        dependencies:
          ('old_app', '0020_auto_others'),
          ('app_x', '0002_auto_20170608_1452'),
          ('app_y', '0005_auto_20170608_1452'),
          ('new_app', '0001_initial'),
      0022_auto_maybe_empty_operations.py
        dependencies:
          ('old_app', '0021_custom_rename_models'),
      0023_custom_clean_models.py
        dependencies:
          ('old_app', '0022_auto_maybe_empty_operations'),
    app_x
      0001_initial.py
      0002_auto_20170608_1452.py
      0003_update_fk_state_operations.py
        dependencies
          ('app_x', '0002_auto_20170608_1452'),
          ('old_app', '0021_custom_rename_models'),
    app_y
      0004_auto_others_that_could_use_old_refs.py
      0005_auto_20170608_1452.py
      0006_update_fk_state_operations.py
        dependencies
          ('app_y', '0005_auto_20170608_1452'),
          ('old_app', '0021_custom_rename_models'),
    

ところで:これについてのオープンチケットがあります: https://code.djangoproject.com/ticket/24686

3
Lucianovici

仕事が終わった後、私は新しい移行を試みました。しかし、私は次のエラーに直面しています:ValueError: Unhandled pending operations for models: oldapp.modelname (referred to by fields: oldapp.HistoricalProductModelName.model_ref_obj)

Django HistoricalRecordsフィールドを使用したモデルの場合は、@ Nostalg.ioの回答に従って、追加のモデル/テーブルを追加することを忘れないでください。

最初のステップ(4.a)でdatabase_operationsに次の項目を追加します。

    migrations.AlterModelTable('historicalmodelname', 'newapp_historicalmodelname'),

最後のステップで追加の削除をstate_operationsに追加します(4.d):

    migrations.DeleteModel(name='HistoricalModleName'),
1
Daniil Mashkin

モデルを移動する必要があり、アプリにアクセスできない場合(またはアクセスしたくない場合)は、新しいオペレーションを作成し、移行したモデルがそうでない場合にのみ新しいモデルを作成することを検討できます。存在します。

この例では、old_appからmyappに「MyModel」を渡しています。

class MigrateOrCreateTable(migrations.CreateModel):
    def __init__(self, source_table, dst_table, *args, **kwargs):
        super(MigrateOrCreateTable, self).__init__(*args, **kwargs)
        self.source_table = source_table
        self.dst_table = dst_table

    def database_forwards(self, app_label, schema_editor, from_state, to_state):
        table_exists = self.source_table in schema_editor.connection.introspection.table_names()
        if table_exists:
            with schema_editor.connection.cursor() as cursor:
                cursor.execute("RENAME TABLE {} TO {};".format(self.source_table, self.dst_table))
        else:
            return super(MigrateOrCreateTable, self).database_forwards(app_label, schema_editor, from_state, to_state)


class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0002_some_migration'),
    ]

    operations = [
        MigrateOrCreateTable(
            source_table='old_app_mymodel',
            dst_table='myapp_mymodel',
            name='MyModel',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('name', models.CharField(max_length=18))
            ],
        ),
    ]
1
Gal Singer

Nostalg.ioの方法は順方向に機能しました(それを参照する他のすべてのアプリのFKを自動生成します)。しかし、私は後方にも必要でした。このため、FKが後方に移動される前に後方のAlterTableが発生する必要があります(元の状態では、その後に発生します)。したがって、このために、AlterTableを2つの別々のAlterTableFとAlterTableRに分割し、それぞれが一方向でのみ機能するようにし、最初のカスタム移行で元の代わりに1つを使用し、最後の車の移行で1つを逆にします(どちらも車のアプリで発生します) )。このようなもの:

#cars/migrations/0002...py :

class AlterModelTableF( migrations.AlterModelTable):
    def database_backwards(self, app_label, schema_editor, from_state, to_state):
        print( 'nothing back on', app_label, self.name, self.table)

class Migration(migrations.Migration):                                                         
    dependencies = [
        ('cars', '0001_initial'),
    ]

    database_operations= [
        AlterModelTableF( 'tires', 'tires_tires' ),
        ]
    operations = [
        migrations.SeparateDatabaseAndState( database_operations= database_operations)         
    ]           


#cars/migrations/0004...py :

class AlterModelTableR( migrations.AlterModelTable):
    def database_forwards(self, app_label, schema_editor, from_state, to_state):
        print( 'nothing forw on', app_label, self.name, self.table)
    def database_backwards(self, app_label, schema_editor, from_state, to_state):
        super().database_forwards( app_label, schema_editor, from_state, to_state)

class Migration(migrations.Migration):
    dependencies = [
        ('cars', '0003_auto_20150603_0630'),
    ]

    # This needs to be a state-only operation because the database model was renamed, and no longer exists according to Django.
    state_operations = [
        migrations.DeleteModel(
            name='Tires',
        ),
    ]

    database_operations= [
        AlterModelTableR( 'tires', 'tires_tires' ),
        ]
    operations = [
        # After this state operation, the Django DB state should match the actual database structure.
       migrations.SeparateDatabaseAndState( state_operations=state_operations,
         database_operations=database_operations)
    ]   
0
svil

これは私にとってはうまくいきましたが、それがひどい考えである理由を私はきっと聞くでしょう。この関数とそれを呼び出す操作をold_appマイグレーションに追加します。

def migrate_model(apps, schema_editor):
    old_model = apps.get_model('old_app', 'MovingModel')
    new_model = apps.get_model('new_app', 'MovingModel')
    for mod in old_model.objects.all():
        mod.__class__ = new_model
        mod.save()


class Migration(migrations.Migration):

    dependencies = [
        ('new_app', '0006_auto_20171027_0213'),
    ]

    operations = [
        migrations.RunPython(migrate_model),
        migrations.DeleteModel(
            name='MovingModel',
        ),
    ]     

ステップ1:データベースをバックアップします!
new_app移行が最初に実行されていること、および/またはold_app移行の要件が実行されていることを確認してください。 old_appの移行が完了するまで、古いコンテンツタイプの削除を拒否します。

Django 1.9の後、もう少し慎重にステップ実行することをお勧めします。
移行1:新しいテーブルを作成
移行2:テーブルに入力
Migration3:他のテーブルのフィールドを変更
Migration4:古いテーブルを削除します

0
Renoc

これは比較的簡単に行うことができますが、 Django Users 'Group の質問から要約した以下の手順に従う必要があります。

  1. モデルをnewと呼ばれる新しいアプリに移動する前に、db_tableオプションを現在のモデルのMetaクラスに追加します。移動したいモデルをMと呼びます。ただし、必要に応じて、一度に複数のモデルを実行できます。

    class M(models.Model):
        a = models.ForeignKey(B, on_delete=models.CASCADE)
        b = models.IntegerField()
    
        class Meta:
            db_table = "new_M"
    
  2. python manage.py makemigrationsを実行します。これにより、データベースのテーブルの名前をcurrent_Mからnew_Mに変更する新しい移行ファイルが生成されます。この移行ファイルを後でxと呼びます。

  3. 次に、モデルをnewアプリに移動します。 Djangoは自動的にdb_tableと呼ばれるテーブルに配置するため、new_Mへの参照を削除します。

  4. 新しい移行を行います。 python manage.py makemigrationsを実行します。これにより、この例ではtwo新しいマイグレーションファイルが生成されます。最初のものはnewアプリにあります。依存関係プロパティでDjangoが前の移行ファイルのxをリストしていることを確認します。2番目のファイルはcurrentアプリにあります。ここで操作をラップしますSeparateDatabaseAndStateの呼び出しで両方のマイグレーションファイルにリストすると、次のようになります。

    operations = [
        SeparateDatabaseAndState([], [
            migrations.CreateModel(...), ...
        ]),
    ]
    
  5. python manage.py migrateを実行します。完了です。一部の回答とは異なり、1つのテーブルから別のテーブルにレコードをコピーしないため、これを実行する時間は比較的高速です。あなたはそれだけで高速な操作であるテーブルの名前を変更しているだけです。

0
Bobort

これが来るのは少し遅れますが、最も簡単なパスが必要な場合は、移行履歴の保持についてあまり気にしないでください。単純な解決策は、移行をワイプして更新するだけです。

私はかなり複雑なアプリを持っていて、何時間も成功せずに上記の解決策を試した後、私は自分でできることがわかりました。

rm cars/migrations/*
./manage.py makemigrations
./manage.py migrate --fake-initial

プレスト!必要に応じて、移行履歴はまだGitにあります。また、これは基本的に何もしないので、ロールバックは問題になりませんでした。

0
keithhackbarth

私はそれを行うための管理コマンドを作成しました-モデルをあるDjangoアプリから別のアプリに移動します- https://stackoverflow.com/でのnostalgic.ioの提案に基づいてa/30613732/1639699

GitHubの alexei/Django-move-model にあります。

0
Alexei

(Lucianoviciのアプローチを正常に実装した後)数か月後にこれに戻ると、ポイントすることに注意すると、much単純になるように思えますdb_tableを古いテーブルに追加します(コード構成のみを気にし、データベース内の古い名前を気にしない場合)。

  • AlterModelTableの移行は必要ないため、カスタムの最初のステップは必要ありません。
  • データベースに触れずに、モデルとリレーションを変更する必要があります。

したがって、私がしたことは、Djangoからの自動移行を取得し、それらをmigrations.SeparateDatabaseAndStateにラップすることだけでした。

(再度)これは、db_tableが各モデルのoldテーブルを指すように注意した場合にのみ機能することに注意してください。

まだ見ていませんが、これに何か問題があるかどうかはわかりませんが、それは私の開発システムで動作しているようです(もちろん、バックアップには注意を払いました)。すべてのデータは損なわれていないように見えます。よく調べて、問題が発生していないか確認します...

後でデータベーステーブルの名前を別の手順で変更することもでき、このプロセス全体の複雑さが軽減されます。

0
TauPan