だから一年ほど前に私はプロジェクトを始め、すべての新しい開発者と同じように私は構造にあまり集中していませんでしたが、今ではさらにDjangoプロジェクトのレイアウトは、主に私のモデルの構造がひどいです。
私は主に単一のアプリで開催されたモデルを持っていますが、実際にはこれらのモデルのほとんどは独自のアプリにあるはずです、私はこれを解決して南に移動しようとしましたが、外部キーなどのためにトリッキーで本当に難しいことがわかりました.
ただし、Django 1.7と移行のサポートが組み込まれているため、これを行うより良い方法はありますか?
データが失われる可能性があるため、古い回答を削除しています。 ozan前述 のように、各アプリに2つの移行を作成できます。
最初のアプリからモデルを削除する最初の移行。
$ python manage.py makemigrations old_app --empty
移行ファイルを編集して、これらの操作を含めます。
class Migration(migrations.Migration):
database_operations = [migrations.AlterModelTable('TheModel', 'newapp_themodel')]
state_operations = [migrations.DeleteModel('TheModel')]
operations = [
migrations.SeparateDatabaseAndState(
database_operations=database_operations,
state_operations=state_operations)
]
最初の移行に依存し、2番目のアプリで新しいテーブルを作成する2番目の移行。モデルコードを2番目のアプリに移動した後
$ python manage.py makemigrations new_app
移行ファイルを次のように編集します。
class Migration(migrations.Migration):
dependencies = [
('old_app', 'above_migration')
]
state_operations = [
migrations.CreateModel(
name='TheModel',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
],
options={
'db_table': 'newapp_themodel',
},
bases=(models.Model,),
)
]
operations = [
migrations.SeparateDatabaseAndState(state_operations=state_operations)
]
これはmigrations.SeparateDatabaseAndState
を使用してかなり簡単に行うことができます。基本的に、データベース操作を使用して、2つの状態操作と同時にテーブルの名前を変更し、あるアプリの履歴からモデルを削除し、別のアプリでモデルを作成します。
python manage.py makemigrations old_app --empty
移行中:
class Migration(migrations.Migration):
dependencies = []
database_operations = [
migrations.AlterModelTable('TheModel', 'newapp_themodel')
]
state_operations = [
migrations.DeleteModel('TheModel')
]
operations = [
migrations.SeparateDatabaseAndState(
database_operations=database_operations,
state_operations=state_operations)
]
最初に、モデルを新しいアプリのmodel.pyにコピーしてから、
python manage.py makemigrations new_app
これは、単純なCreateModel
操作を唯一の操作として移行を生成します。テーブルを再作成しないように、SeparateDatabaseAndState
操作でラップします。また、以前の移行を依存関係として含めます。
class Migration(migrations.Migration):
dependencies = [
('old_app', 'above_migration')
]
state_operations = [
migrations.CreateModel(
name='TheModel',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
],
options={
'db_table': 'newapp_themodel',
},
bases=(models.Model,),
)
]
operations = [
migrations.SeparateDatabaseAndState(state_operations=state_operations)
]
同じ問題が発生しました。 Ozanの答え 助けてくれましたが、残念ながら十分ではありませんでした。実際、移動したいモデルにリンクするForeignKeyがいくつかありました。いくつかの頭痛の種の後、私は解決策を見つけたので、人々の時間を解決するためにそれを投稿することにしました。
さらに2つのステップが必要です。
ForeignKey
へのすべてのTheModel
リンクをIntegerfield
に変更します。次に、_python manage.py makemigrations
_を実行しますForeignKey(TheModel)
の代わりにIntegerField()
を戻します。次に、移行を再度行います(_python manage.py makemigrations
_)。その後、移行でき、動作するはずです(_python manage.py migrate
_)それが役に立てば幸い。もちろん、悪いサプライズを避けるために、本番環境で試す前にローカルでテストしてください:)
私がそれをやった方法(Django == 1.8で、postgresでテストしたので、おそらく1.7も)
状況
app1.YourModel
しかし、あなたはそれに行きたい:app2.YourModel
これをapp2.YourModelに追加します。
Class Meta:
db_table = 'app1_yourmodel'
$ python manage.py makemigrations app2
新しい移行(例:0009_auto_something.py)は、migrations.CreateModel()ステートメントを使用してapp2で作成され、このステートメントをapp2の初期移行(例:0001_initial.py)に移動します(常に存在するようになります)。そして、作成された移行= 0009_auto_something.pyを削除します
あなたが行動するように、app2.YourModelのように常にそこにいたので、移行からapp1.YourModelの存在を削除します。意味:CreateModelステートメント、およびその後に使用したすべての調整またはデータ移行をコメント化します。
そしてもちろん、プロジェクトを通じてapp1.YourModelへのすべての参照をapp2.YourModelに変更する必要があります。また、移行中のapp1.YourModelへのすべての可能な外部キーをapp2.YourModelに変更する必要があることを忘れないでください
ここで$ python manage.py migrateを行っても、何も変更されていません。また、$ python manage.py makemigrationsを行っても、新しいものは何も検出されません。
最後の仕上げ:app2.YourModelからクラスメタを削除し、$ python manage.py makemigrations app2 && python manage.py migrate app2(この移行を調べると、次のようなものが表示されます。
migrations.AlterModelTable(
name='yourmodel',
table=None,
),
table = None。デフォルトのテーブル名(この場合はapp2_yourmodel)を使用することを意味します。
移行中のP.Sでは、content_type app1.yourmodelが削除され、削除できることがわかります。あなたはそれを「はい」と言うことができますが、それを使わない場合に限ります。そのコンテンツタイプへのFKをそのままにしておくことに大きく依存している場合は、yesまたはnoとは答えず、その時点でdbに手動で移動し、contentype app2.yourmodelを削除して、contenttype app1の名前を変更します。 yourmodelをapp2.yourmodelに変更し、いいえと答えて続行します。
Ozan's answerで必要とされるように、神経質なハンドコーディングの移行が発生するため、以下ではOzanと Michael's 戦略を組み合わせて、必要なハンドコーディングの量を最小限に抑えます。
makemigrations
を実行して、クリーンなベースラインで作業していることを確認してください。app1
_から_app2
_に移動します@Michaelが推奨するように、「新しい」モデルで_db_table
_ Metaオプションを使用して、新しいモデルが古いデータベーステーブルを指すようにします。
_class Meta:
db_table = 'app1_yourmodel'
_
makemigrations
を実行します。これにより、_app2
_のCreateModel
および_app1
_のDeleteModel
が生成されます。技術的には、これらの移行はまったく同じテーブルを参照し、テーブル(すべてのデータを含む)を削除して再作成します。
現実には、テーブルに対して何もしたくない(または必要としない)。変更が行われたと信じるには、Djangoが必要です。 @Ozanの答えによると、SeparateDatabaseAndState
の_state_operations
_フラグがこれを行います。したがって、すべてのmigrations
エントリを両方の移行ファイルでSeparateDatabaseAndState(state_operations=[...])
でラップします。例えば、
_operations = [
...
migrations.DeleteModel(
name='YourModel',
),
...
]
_
になる
_operations = [
migrations.SeparateDatabaseAndState(state_operations=[
...
migrations.DeleteModel(
name='YourModel',
),
...
])
]
_
[〜#〜] edit [〜#〜]:また、新しい「仮想」CreateModel
移行が、元のテーブルを実際に作成または変更しました。たとえば、新しい移行が_app2.migrations.0004_auto_<date>
_(Create
の場合)および_app1.migrations.0007_auto_<date>
_(Delete
の場合)の場合、最も簡単なことは次のとおりです。
app1.migrations.0007_auto_<date>
_を開き、その_app1
_依存関係をコピーします(例:_('app1', '0006...'),
_)。これは_app1
_での「直前」の移行であり、実際のモデル構築ロジックすべてへの依存関係を含める必要があります。app2.migrations.0004_auto_<date>
_を開き、コピーした依存関係をそのdependencies
リストに追加します。[〜#〜] edit [〜#〜]:移動しているモデルとForeignKey
の関係がある場合、上記はそうではありません作業。これは次の理由によります。
ForeignKey
の変更に対する依存関係は自動的に作成されませんForeignKey
の変更を_state_operations
_でラップしたくないので、テーブル操作とは別にする必要があります。操作の「最小」セットは状況に応じて異なりますが、次の手順はほとんど/すべてのForeignKey
移行で機能するはずです。
app1
_から_app2
_に、セット_db_table
_ 、ただし、FK参照を変更しないでください。makemigrations
を実行し、すべての_app2
_移行を_state_operations
_にラップします(上記を参照)app2
_ CreateTable
の依存関係を最新の_app1
_移行に追加しますmodels.py
_の一番下に移動します(削除しないでください)。makemigrations
を実行しますが、_state_operations
_で何もラップしないでください(FKの変更は実際に発生するはずです)。すべてのForeignKey
移行(つまり、AlterField
)の依存関係を_app2
_のCreateTable
移行に追加します(次のステップでこのリストが必要になるので、それらを追跡してください)。例えば:
CreateModel
を含む移行を見つけます。 _app2.migrations.0002_auto_<date>
_およびその移行の名前をコピーします。そのモデルへのForeignKeyを持つすべての移行を検索します(たとえば、_app2.YourModel
_を検索して、次のような移行を見つけます)。
_class Migration(migrations.Migration):
dependencies = [
('otherapp', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='relatedmodel',
name='fieldname',
field=models.ForeignKey(... to='app2.YourModel'),
),
]
_
CreateModel
移行を依存関係として追加します。
_class Migration(migrations.Migration):
dependencies = [
('otherapp', '0001_initial'),
('app2', '0002_auto_<date>'),
]
_
_app1
_からモデルを削除します
makemigrations
を実行し、_app1
_移行を_state_operations
_。にラップしますForeignKey
移行(つまりAlterField
)のすべてに依存関係を追加します(_app1
_および_app2
_の移行を含めることができます)。DeleteTable
は既にAlterField
移行に依存していたため、手動で強制する必要はありませんでした(つまり、Alter
の前にDelete
)。この時点で、Djangoは問題ありません。新しいモデルは古いテーブルを指しており、Djangoの移行により、すべてが適切に再配置されたと確信しました。大きな警告(@Michaelの答えから)は、新しいContentType
が新しいモデル用に作成されることです。コンテンツタイプにリンクする場合(たとえば、ForeignKey
によって)、ContentType
テーブルを更新するために移行を作成する必要があります。
自分(メタオプションとテーブル名)をクリーンアップしたかったので、次の手順を使用しました(@Michaelから)。
db_table
_ Metaエントリを削除しますmakemigrations
を再度実行して、データベース名の変更を生成しますDeleteTable
移行に依存することを確認してください。 Delete
は純粋に論理的である必要があるため、必要なようには見えませんが、そうでない場合はエラーになります(たとえば_app1_yourmodel
_は存在しません)。データが大きくない、または複雑すぎないが、維持することが依然として重要である場合の別のハッキング方法は、次のとおりです。
https://stackoverflow.com/a/47392970/8971048 の回答からコピー
モデルを移動する必要があり、アプリにアクセスできない場合(またはアクセスが不要な場合)、新しい操作を作成し、移行したモデルがそうでない場合にのみ新しいモデルを作成することを検討できます存在します。
この例では、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))
],
),
]
以下を試すことができます(テストされていません):
src_app
からdest_app
に移動しますdest_app
;スキーマの移行が最新のsrc_app
移行に依存していることを確認してください( https://docs.djangoproject.com/en/dev/topics/migrations/#migration-files )dest_app
にデータ移行を追加し、src_app
からすべてのデータをコピーしますsrc_app
;スキーマの移行がdest_app
の最新の(データ)移行に依存していることを確認してください。つまり、ステップ3の移行です。movingではなく、テーブル全体をcopyingすることに注意してください、しかし、その方法は両方のアプリが他のアプリに属するテーブルに触れる必要はありません。それは私がより重要だと思います。
モデルTheModelをapp_aからapp_bに移動しているとしましょう。
別の解決策は、既存の移行を手動で変更することです。アイデアは、app_aの移行でTheModelを変更する操作を見るたびに、その操作をapp_bの最初の移行の最後にコピーするということです。また、app_aの移行で参照「app_a.TheModel」が表示されるたびに、「app_b.TheModel」に変更します。
特定のモデルを再利用可能なアプリに抽出したい既存のプロジェクトでこれを実行しました。手順はスムーズに進みました。 app_bからapp_aへの参照があると、事態はもっと難しくなると思います。また、手伝ってくれたかもしれないモデルのために、手動でMeta.db_tableを定義しました。
特に、移行履歴が変更されることになります。元の移行が適用されたデータベースがある場合でも、これは重要ではありません。元の移行と書き換えられた移行の両方が同じデータベーススキーマで終了する場合、そのような書き換えは問題ないはずです。
これは大まかにテストされているため、データベースのバックアップを忘れないでください!!!
たとえば、src_app
とdst_app
の2つのアプリがあります。モデルMoveMe
をsrc_app
からdst_app
に移動します。
両方のアプリの空の移行を作成します。
python manage.py makemigrations --empty src_app
python manage.py makemigrations --empty dst_app
新しい移行はXXX1_src_app_new
とXXX1_dst_app_new
であり、上位の移行はXXX0_src_app_old
とXXX0_dst_app_old
であると仮定しましょう。
MoveMe
モデルのテーブルの名前を変更し、ProjectStateのapp_labelの名前をXXX1_dst_app_new
に変更する操作を追加します。 XXX0_src_app_old
移行に依存関係を追加することを忘れないでください。結果のXXX1_dst_app_new
移行は次のとおりです。
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from Django.db import models, migrations
# this operations is almost the same as RenameModel
# https://github.com/Django/django/blob/1.7/Django/db/migrations/operations/models.py#L104
class MoveModelFromOtherApp(migrations.operations.base.Operation):
def __init__(self, name, old_app_label):
self.name = name
self.old_app_label = old_app_label
def state_forwards(self, app_label, state):
# Get all of the related objects we need to repoint
apps = state.render(skip_cache=True)
model = apps.get_model(self.old_app_label, self.name)
related_objects = model._meta.get_all_related_objects()
related_m2m_objects = model._meta.get_all_related_many_to_many_objects()
# Rename the model
state.models[app_label, self.name.lower()] = state.models.pop(
(self.old_app_label, self.name.lower())
)
state.models[app_label, self.name.lower()].app_label = app_label
for model_state in state.models.values():
try:
i = model_state.bases.index("%s.%s" % (self.old_app_label, self.name.lower()))
model_state.bases = model_state.bases[:i] + ("%s.%s" % (app_label, self.name.lower()),) + model_state.bases[i+1:]
except ValueError:
pass
# Repoint the FKs and M2Ms pointing to us
for related_object in (related_objects + related_m2m_objects):
# Use the new related key for self referential related objects.
if related_object.model == model:
related_key = (app_label, self.name.lower())
else:
related_key = (
related_object.model._meta.app_label,
related_object.model._meta.object_name.lower(),
)
new_fields = []
for name, field in state.models[related_key].fields:
if name == related_object.field.name:
field = field.clone()
field.rel.to = "%s.%s" % (app_label, self.name)
new_fields.append((name, field))
state.models[related_key].fields = new_fields
def database_forwards(self, app_label, schema_editor, from_state, to_state):
old_apps = from_state.render()
new_apps = to_state.render()
old_model = old_apps.get_model(self.old_app_label, self.name)
new_model = new_apps.get_model(app_label, self.name)
if self.allowed_to_migrate(schema_editor.connection.alias, new_model):
# Move the main table
schema_editor.alter_db_table(
new_model,
old_model._meta.db_table,
new_model._meta.db_table,
)
# Alter the fields pointing to us
related_objects = old_model._meta.get_all_related_objects()
related_m2m_objects = old_model._meta.get_all_related_many_to_many_objects()
for related_object in (related_objects + related_m2m_objects):
if related_object.model == old_model:
model = new_model
related_key = (app_label, self.name.lower())
else:
model = related_object.model
related_key = (
related_object.model._meta.app_label,
related_object.model._meta.object_name.lower(),
)
to_field = new_apps.get_model(
*related_key
)._meta.get_field_by_name(related_object.field.name)[0]
schema_editor.alter_field(
model,
related_object.field,
to_field,
)
def database_backwards(self, app_label, schema_editor, from_state, to_state):
self.old_app_label, app_label = app_label, self.old_app_label
self.database_forwards(app_label, schema_editor, from_state, to_state)
app_label, self.old_app_label = self.old_app_label, app_label
def describe(self):
return "Move %s from %s" % (self.name, self.old_app_label)
class Migration(migrations.Migration):
dependencies = [
('dst_app', 'XXX0_dst_app_old'),
('src_app', 'XXX0_src_app_old'),
]
operations = [
MoveModelFromOtherApp('MoveMe', 'src_app'),
]
XXX1_dst_app_new
への依存関係をXXX1_src_app_new
に追加します。 XXX1_src_app_new
は、将来のsrc_app
移行がXXX1_dst_app_new
の後に実行されるようにするために必要なノーオペレーション移行です。
MoveMe
をsrc_app/models.py
からdst_app/models.py
に移動します。次に実行します:
python manage.py migrate
それで全部です!
移動する必要がある各モデルに対して個別にこれを行います。整数に変更して外部キーに戻すことで、他の答えが言っていることを行うことをお勧めしません外部キーに切り替える際のIDの不一致の原因。