これはこの質問の拡張です: 2つのモデル間でモデルを移動する方法Djangoアプリ(Django 1.7)
一連のモデルをold_app
からnew_app
に移動する必要があります。最良の答えは Ozan's のようですが、必要な外部キー参照があるため、少し複雑になります。 @halfnibbleはOzanの回答へのコメントで解決策を示していますが、ステップの正確な順序にまだ問題があります(たとえば、モデルをnew_app
にコピーする場合、old_app
からモデルを削除する場合、移行は待機しますold_app.migrations
対new_app.migrations
など)
どんな助けでも大歓迎です!
アプリ間でのモデルの移行
短い答えは、しないでください!!
しかし、その答えが現実のプロジェクトやプロダクションデータベースで機能することはめったにありません。したがって、このかなり複雑なプロセスを示すために、 sample GitHub repo を作成しました。
MySQLを使用しています。(いいえ、それらは私の実際の資格情報ではありません)。
問題
私が使用している例は、最初にCar
モデルとTires
モデルを持つcarsアプリを含むファクトリプロジェクトです。
factory
|_ cars
|_ Car
|_ Tires
Car
モデルには、Tires
とForeignKeyの関係があります。 (同様に、車のモデルを介してタイヤを指定します)。
ただし、Tires
が独自のビューなどを備えた大きなモデルになることにすぐに気づきました。そのため、独自のアプリでそれを必要としています。したがって、望ましい構造は次のとおりです。
factory
|_ cars
|_ Car
|_ tires
|_ Tires
また、データの保存に依存しすぎているため、Car
とTires
の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状態をクリーンアップします。
たった今、2つのモデルをold_app
からnew_app
に移動しましたが、FK参照は、app_x
のモデルではなく、app_y
とold_app
の一部のモデルにありました。
この場合、次のようにNostalg.ioが提供する手順に従います。
old_app
からnew_app
に移動し、コードベース全体でimport
ステートメントを更新します。makemigrations
。AlterModelTable
を使用してください。私には2つ。state_operations
を移行してください。DeleteModel
を使用します。ノート:
old_app
からのカスタム移行ファイルに依存しています。ここで、AlterModelTable
はテーブルの名前を変更するために使用されます。 (ステップ4.aで作成されます。)AlterField
操作がなく、DeleteModel
とRemoveField
操作しかなかったため、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
仕事が終わった後、私は新しい移行を試みました。しかし、私は次のエラーに直面しています: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'),
モデルを移動する必要があり、アプリにアクセスできない場合(またはアクセスしたくない場合)は、新しいオペレーションを作成し、移行したモデルがそうでない場合にのみ新しいモデルを作成することを検討できます。存在します。
この例では、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))
],
),
]
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)
]
これは私にとってはうまくいきましたが、それがひどい考えである理由を私はきっと聞くでしょう。この関数とそれを呼び出す操作を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:古いテーブルを削除します
これは比較的簡単に行うことができますが、 Django Users 'Group の質問から要約した以下の手順に従う必要があります。
モデルを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"
python manage.py makemigrations
を実行します。これにより、データベースのテーブルの名前をcurrent_M
からnew_M
に変更する新しい移行ファイルが生成されます。この移行ファイルを後でx
と呼びます。
次に、モデルをnew
アプリに移動します。 Djangoは自動的にdb_table
と呼ばれるテーブルに配置するため、new_M
への参照を削除します。
新しい移行を行います。 python manage.py makemigrations
を実行します。これにより、この例ではtwo新しいマイグレーションファイルが生成されます。最初のものはnew
アプリにあります。依存関係プロパティでDjangoが前の移行ファイルのx
をリストしていることを確認します。2番目のファイルはcurrent
アプリにあります。ここで操作をラップしますSeparateDatabaseAndState
の呼び出しで両方のマイグレーションファイルにリストすると、次のようになります。
operations = [
SeparateDatabaseAndState([], [
migrations.CreateModel(...), ...
]),
]
python manage.py migrate
を実行します。完了です。一部の回答とは異なり、1つのテーブルから別のテーブルにレコードをコピーしないため、これを実行する時間は比較的高速です。あなたはそれだけで高速な操作であるテーブルの名前を変更しているだけです。
これが来るのは少し遅れますが、最も簡単なパスが必要な場合は、移行履歴の保持についてあまり気にしないでください。単純な解決策は、移行をワイプして更新するだけです。
私はかなり複雑なアプリを持っていて、何時間も成功せずに上記の解決策を試した後、私は自分でできることがわかりました。
rm cars/migrations/*
./manage.py makemigrations
./manage.py migrate --fake-initial
プレスト!必要に応じて、移行履歴はまだGitにあります。また、これは基本的に何もしないので、ロールバックは問題になりませんでした。
私はそれを行うための管理コマンドを作成しました-モデルをあるDjangoアプリから別のアプリに移動します- https://stackoverflow.com/でのnostalgic.ioの提案に基づいてa/30613732/1639699
GitHubの alexei/Django-move-model にあります。
(Lucianoviciのアプローチを正常に実装した後)数か月後にこれに戻ると、ポイントすることに注意すると、much単純になるように思えますdb_table
を古いテーブルに追加します(コード構成のみを気にし、データベース内の古い名前を気にしない場合)。
したがって、私がしたことは、Djangoからの自動移行を取得し、それらをmigrations.SeparateDatabaseAndStateにラップすることだけでした。
(再度)これは、db_tableが各モデルのoldテーブルを指すように注意した場合にのみ機能することに注意してください。
まだ見ていませんが、これに何か問題があるかどうかはわかりませんが、それは私の開発システムで動作しているようです(もちろん、バックアップには注意を払いました)。すべてのデータは損なわれていないように見えます。よく調べて、問題が発生していないか確認します...
後でデータベーステーブルの名前を別の手順で変更することもでき、このプロセス全体の複雑さが軽減されます。