web-dev-qa-db-ja.com

2つのDjangoアプリ(Django 1.7)の間でモデルを移動する方法

だから一年ほど前に私はプロジェクトを始め、すべての新しい開発者と同じように私は構造にあまり集中していませんでしたが、今ではさらにDjangoプロジェクトのレイアウトは、主に私のモデルの構造がひどいです。

私は主に単一のアプリで開催されたモデルを持っていますが、実際にはこれらのモデルのほとんどは独自のアプリにあるはずです、私はこれを解決して南に移動しようとしましたが、外部キーなどのためにトリッキーで本当に難しいことがわかりました.

ただし、Django 1.7と移行のサポートが組み込まれているため、これを行うより良い方法はありますか?

108
Sam Buckingham

データが失われる可能性があるため、古い回答を削除しています。 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)
    ]
12
ChillarAnand

これは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)
    ]
309
ozan

同じ問題が発生しました。 Ozanの答え 助けてくれましたが、残念ながら十分ではありませんでした。実際、移動したいモデルにリンクするForeignKeyがいくつかありました。いくつかの頭痛の種の後、私は解決策を見つけたので、人々の時間を解決するためにそれを投稿することにしました。

さらに2つのステップが必要です。

  1. 何かを行う前に、ForeignKeyへのすべてのTheModelリンクをIntegerfieldに変更します。次に、_python manage.py makemigrations_を実行します
  2. Ozanの手順を実行した後、外部キーを再変換します。ForeignKey(TheModel)の代わりにIntegerField()を戻します。次に、移行を再度行います(_python manage.py makemigrations_)。その後、移行でき、動作するはずです(_python manage.py migrate_)

それが役に立てば幸い。もちろん、悪いサプライズを避けるために、本番環境で試す前にローカルでテストしてください:)

23
otranzer

私がそれをやった方法(Django == 1.8で、postgresでテストしたので、おそらく1.7も)

状況

app1.YourModel

しかし、あなたはそれに行きたい:app2.YourModel

  1. YourModel(コード)をapp1からapp2にコピーします。
  2. これをapp2.YourModelに追加します。

    Class Meta:
        db_table = 'app1_yourmodel'
    
  3. $ python manage.py makemigrations app2

  4. 新しい移行(例:0009_auto_something.py)は、migrations.CreateModel()ステートメントを使用してapp2で作成され、このステートメントをapp2の初期移行(例:0001_initial.py)に移動します(常に存在するようになります)。そして、作成された移行= 0009_auto_something.pyを削除します

  5. あなたが行動するように、app2.YourModelのように常にそこにいたので、移行からapp1.YourModelの存在を削除します。意味:CreateModelステートメント、およびその後に使用したすべての調整またはデータ移行をコメント化します。

  6. そしてもちろん、プロジェクトを通じてapp1.YourModelへのすべての参照をapp2.YourModelに変更する必要があります。また、移行中のapp1.YourModelへのすべての可能な外部キーをapp2.YourModelに変更する必要があることを忘れないでください

  7. ここで$ python manage.py migrateを行っても、何も変更されていません。また、$ python manage.py makemigrationsを行っても、新しいものは何も検出されません。

  8. 最後の仕上げ:app2.YourModelからクラスメタを削除し、$ python manage.py makemigrations app2 && python manage.py migrate app2(この移行を調べると、次のようなものが表示されます。

        migrations.AlterModelTable(
        name='yourmodel',
        table=None,
    ),
    

table = None。デフォルトのテーブル名(この場合はapp2_yourmodel)を使用することを意味します。

  1. 完了、データが保存されました。

移行中のP.Sでは、content_type app1.yourmodelが削除され、削除できることがわかります。あなたはそれを「はい」と言うことができますが、それを使わない場合に限ります。そのコンテンツタイプへのFKをそのままにしておくことに大きく依存している場合は、yesまたはnoとは答えず、その時点でdbに手動で移動し、contentype app2.yourmodelを削除して、contenttype app1の名前を変更します。 yourmodelをapp2.yourmodelに変更し、いいえと答えて続行します。

14

Ozan's answerで必要とされるように、神経質なハンドコーディングの移行が発生するため、以下ではOzanと Michael's 戦略を組み合わせて、必要なハンドコーディングの量を最小限に抑えます。

  1. モデルを移動する前に、makemigrationsを実行して、クリーンなベースラインで作業していることを確認してください。
  2. モデルのコードを_app1_から_app2_に移動します
  3. @Michaelが推奨するように、「新しい」モデルで_db_table_ Metaオプションを使用して、新しいモデルが古いデータベーステーブルを指すようにします。

    _class Meta:
        db_table = 'app1_yourmodel'
    _
  4. makemigrationsを実行します。これにより、_app2_のCreateModelおよび_app1_のDeleteModelが生成されます。技術的には、これらの移行はまったく同じテーブルを参照し、テーブル(すべてのデータを含む)を削除して再作成します。

  5. 現実には、テーブルに対して何もしたくない(または必要としない)。変更が行われたと信じるには、Djangoが必要です。 @Ozanの答えによると、SeparateDatabaseAndStateの_state_operations_フラグがこれを行います。したがって、すべてのmigrationsエントリを両方の移行ファイルSeparateDatabaseAndState(state_operations=[...])でラップします。例えば、

    _operations = [
        ...
        migrations.DeleteModel(
            name='YourModel',
        ),
        ...
    ]
    _

    になる

    _operations = [
        migrations.SeparateDatabaseAndState(state_operations=[
            ...
            migrations.DeleteModel(
                name='YourModel',
            ),
            ...
        ])
    ]
    _
  6. [〜#〜] 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移行で機能するはずです。

  1. [〜#〜] copy [〜#〜]モデルを_app1_から_app2_に、セット_db_table_ 、ただし、FK参照を変更しないでください。
  2. makemigrationsを実行し、すべての_app2_移行を_state_operations_にラップします(上記を参照)
    • 上記のように、_app2_ CreateTableの依存関係を最新の_app1_移行に追加します
  3. すべてのFK参照が新しいモデルを指すようにします。文字列参照を使用していない場合は、インポートしたクラスと競合しないように、古いモデルを_models.py_の一番下に移動します(削除しないでください)。
  4. 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>'),
          ]  
      _
  5. _app1_からモデルを削除します

  6. makemigrationsを実行し、_app1_移行を_state_operations_。にラップします
    • 前のステップからのForeignKey移行(つまりAlterField)のすべてに依存関係を追加します(_app1_および_app2_の移行を含めることができます)。
    • これらの移行を作成したとき、DeleteTableは既にAlterField移行に依存していたため、手動で強制する必要はありませんでした(つまり、Alterの前にDelete)。

この時点で、Djangoは問題ありません。新しいモデルは古いテーブルを指しており、Djangoの移行により、すべてが適切に再配置されたと確信しました。大きな警告(@Michaelの答えから)は、新しいContentTypeが新しいモデル用に作成されることです。コンテンツタイプにリンクする場合(たとえば、ForeignKeyによって)、ContentTypeテーブルを更新するために移行を作成する必要があります。

自分(メタオプションとテーブル名)をクリーンアップしたかったので、次の手順を使用しました(@Michaelから)。

  1. _db_table_ Metaエントリを削除します
  2. makemigrationsを再度実行して、データベース名の変更を生成します
  3. この最後の移行を編集し、DeleteTable移行に依存することを確認してください。 Deleteは純粋に論理的である必要があるため、必要なようには見えませんが、そうでない場合はエラーになります(たとえば_app1_yourmodel_は存在しません)。
9
claytond

データが大きくない、または複雑すぎないが、維持することが依然として重要である場合の別のハッキング方法は、次のとおりです。

  • manage.py dumpdata を使用してデータフィクスチャを取得する
  • 変更を関連付けずに、モデルの変更と移行を適切に進めます
  • 古いモデルとアプリ名のフィクスチャーを新しいものにグローバルに置き換えます
  • manage.py loaddata を使用してデータをロードします
1
Wtower

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))
            ],
        ),
    ]
0
Gal Singer

以下を試すことができます(テストされていません):

  1. モデルをsrc_appからdest_appに移動します
  2. migrate dest_app;スキーマの移行が最新のsrc_app移行に依存していることを確認してください( https://docs.djangoproject.com/en/dev/topics/migrations/#migration-files
  3. dest_appにデータ移行を追加し、src_appからすべてのデータをコピーします
  4. migrate src_app;スキーマの移行がdest_appの最新の(データ)移行に依存していることを確認してください。つまり、ステップ3の移行です。

movingではなく、テーブル全体をcopyingすることに注意してください、しかし、その方法は両方のアプリが他のアプリに属する​​テーブルに触れる必要はありません。それは私がより重要だと思います。

0
Webthusiast

モデル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を定義しました。

特に、移行履歴が変更されることになります。元の移行が適用されたデータベースがある場合でも、これは重要ではありません。元の移行と書き換えられた移行の両方が同じデータベーススキーマで終了する場合、そのような書き換えは問題ないはずです。

0
akaariai

これは大まかにテストされているため、データベースのバックアップを忘れないでください!!!

たとえば、src_appdst_appの2つのアプリがあります。モデルMoveMesrc_appからdst_appに移動します。

両方のアプリの空の移行を作成します。

python manage.py makemigrations --empty src_app
python manage.py makemigrations --empty dst_app

新しい移行はXXX1_src_app_newXXX1_dst_app_newであり、上位の移行はXXX0_src_app_oldXXX0_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の後に実行されるようにするために必要なノーオペレーション移行です。

MoveMesrc_app/models.pyからdst_app/models.pyに移動します。次に実行します:

python manage.py migrate

それで全部です!

0
Sergey Fedoseev
  1. 古いモデルの名前を「model_name_old」に変更します
  2. 移行
  3. 関連モデルと同じ関係を持つ「model_name_new」という名前の新しいモデルを作成します(たとえば、ユーザーモデルにuser.blog_oldとuser.blog_newが追加されました)
  4. 移行
  5. すべてのデータを新しいモデルテーブルに移行するカスタム移行を作成する
  6. 移行を実行する前後にバックアップと新しいdbコピーを比較することにより、これらの移行を徹底的にテストする
  7. すべてが満足できる場合、古いモデルを削除します
  8. 移行
  9. 新しいモデルを正しい名前「model_name_new」に変更します->「model_name」
  10. ステージングサーバーで多数の移行全体をテストする
  11. ユーザーが干渉することなくすべての移行を実行するために、数分間本番サイトを停止します

移動する必要がある各モデルに対して個別にこれを行います。整数に変更して外部キーに戻すことで、他の答えが言っていることを行うことをお勧めしません外部キーに切り替える際のIDの不一致の原因。

0
tomcounsell