モデルを1つのdjangoアプリから新しいアプリに移行するにはどうすればよいですか?


126

4つのモデルを含むdjangoアプリがあります。これらのモデルの1つが別のアプリにある必要があることに気づきました。私は移行用に南をインストールしていますが、これは自動的に処理できるものではないと思います。古いアプリから新しいアプリにモデルの1つを移行するにはどうすればよいですか?

また、本番システムなどを移行できるように、これを繰り返し可能なプロセスにする必要があることにも注意してください。


6
django 1.7以降については、stackoverflow.com
questions / 25648393 /…を

回答:


184

南を使用して移行する方法。

次の2つのアプリがあるとしましょう:共通と特定:

myproject/
|-- common
|   |-- migrations
|   |   |-- 0001_initial.py
|   |   `-- 0002_create_cat.py
|   `-- models.py
`-- specific
    |-- migrations
    |   |-- 0001_initial.py
    |   `-- 0002_create_dog.py
    `-- models.py

次に、モデルcommon.models.catを特定のアプリ(正確には、specific.models.cat)に移動します。最初にソースコードを変更してから、実行します。

$ python manage.py schemamigration specific create_cat --auto
 + Added model 'specific.cat'
$ python manage.py schemamigration common drop_cat --auto
 - Deleted model 'common.cat'

myproject/
|-- common
|   |-- migrations
|   |   |-- 0001_initial.py
|   |   |-- 0002_create_cat.py
|   |   `-- 0003_drop_cat.py
|   `-- models.py
`-- specific
    |-- migrations
    |   |-- 0001_initial.py
    |   |-- 0002_create_dog.py
    |   `-- 0003_create_cat.py
    `-- models.py

次に、両方の移行ファイルを編集する必要があります。

#0003_create_cat: replace existing forward and backward code
#to use just one sentence:

def forwards(self, orm):
    db.rename_table('common_cat', 'specific_cat') 

    if not db.dry_run:
        # For permissions to work properly after migrating
        orm['contenttypes.contenttype'].objects.filter(
            app_label='common',
            model='cat',
        ).update(app_label='specific')

def backwards(self, orm):
    db.rename_table('specific_cat', 'common_cat')

    if not db.dry_run:
        # For permissions to work properly after migrating
        orm['contenttypes.contenttype'].objects.filter(
            app_label='specific',
            model='cat',
        ).update(app_label='common')

#0003_drop_cat:replace existing forward and backward code
#to use just one sentence; add dependency:

depends_on = (
    ('specific', '0003_create_cat'),
)
def forwards(self, orm):
    pass
def backwards(self, orm):
    pass

現在、両方のアプリの移行は変更を認識しており、人生はほんの少し少なくなっています:-)移行間のこの関係を設定することが成功の鍵です。今あなたがするなら:

python manage.py migrate common
 > specific: 0003_create_cat
 > common: 0003_drop_cat

両方の移行を行い、

python manage.py migrate specific 0002_create_dog
 < common: 0003_drop_cat
 < specific: 0003_create_cat

ダウンマイグレーションします。

スキーマのアップグレードには一般的なアプリを使用し、ダウングレードには特定のアプリを使用したことに注意してください。これは、ここでの依存関係がどのように機能するかです。


1
わあ、ありがとう。この質問をして以来、私は自分で南を学びましたが、これは他の人を大いに助けると確信しています。
Apreche 2009年

11
django_content_typeテーブルのデータの移行も必要になる場合があります。
spookylukey

1
本当に素晴らしいガイド@Potr。気になるのですorm['contenttypes.contenttype'].objects.filter が、後ろの方にも線が入ってない 0003_create_catかな?また、ヒントを共有したいと思います。インデックスがある場合は、それらも変更する必要があります。:私の場合、彼らはこのように私の楽しみにしていますので、一意索引だった db.delete_unique('common_cat', ['col1']) db.rename_table('common_cat', 'specific_cat') db.delete_unique('specific_cat', ['col1'])
ブラッドピッチャー

2
にアクセスするには、コマンドにオプションをorm['contenttypes.contenttype']追加する必要もあります。--freeze contenttypesschemamigration
Gary、

1
私の場合(Django 1.5.7とSouth 1.0).. python manage.py schemamigration specific create_cat --auto --freeze common一般的なアプリから猫のモデルにアクセスするために入力する必要がありました。
geoom 2015

35

Potr Czachur答え基づいて構築するには、ForeignKeyが関係する状況はより複雑であり、少し異なる方法で処理する必要があります。

(次の例は、現在の回答で参照されているcommonおよびに基づいていspecificます)。

# common/models.py

class Cat(models.Model):
    # ...

class Toy(models.Model):
    belongs_to = models.ForeignKey(Cat)
    # ...

次に、

# common/models.py

from specific.models import Cat

class Toy(models.Model):
    belongs_to = models.ForeignKey(Cat)
    # ...

# specific/models.py

class Cat(models.Model):
    # ...

ランニング

./manage.py schemamigration common --auto
./manage.py schemamigration specific --auto # or --initial

次の移行が生成されます(私は意図的にDjango ContentTypeの変更を無視しています。その処理方法については、以前に参照した回答を参照してください)。

# common/migrations/0009_auto__del_cat.py

class Migration(SchemaMigration):
    def forwards(self, orm):
        db.delete_table('common_cat')
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['specific.Cat']))

    def backwards(self, orm):
        db.create_table('common_cat', (
            # ...
        ))
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['common.Cat']))

# specific/migrations/0004_auto__add_cat.py

class Migration(SchemaMigration):
    def forwards(self, orm):
        db.create_table('specific_cat', (
            # ...
        ))

    def backwards(self, orm):
        db.delete_table('specific_cat')

ご覧のとおり、新しいテーブルを参照するようにFKを変更する必要があります。ので、後方あまりにも動作します我々は(我々はそれにFKを追加しようとする前に、表が存在することがこれと)マイグレーションが適用される順序を知っているように、依存関係を追加する必要がありますが、我々はまた、必ず転がりようにする必要があり依存関係は逆方向に適用されます。

# common/migrations/0009_auto__del_cat.py

class Migration(SchemaMigration):

    depends_on = (
        ('specific', '0004_auto__add_cat'),
    )

    def forwards(self, orm):
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['specific.Cat']))

    def backwards(self, orm):
        db.rename_table('specific_cat', 'common_cat')
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['common.Cat']))

# specific/migrations/0004_auto__add_cat.py

class Migration(SchemaMigration):
    def forwards(self, orm):
        db.rename_table('common_cat', 'specific_cat')

    def backwards(self, orm):
        pass

パー南のドキュメントdepends_onその保証する0004_auto__add_cat前に実行を0009_auto__del_cat 転送移行するとき が、中に後方に移行反対の順序。ロールバックのままにdb.rename_table('specific_cat', 'common_cat')した場合、テーブル参照テーブルが存在しないため、ForeignKeyを移行しようとするとspecificcommonロールバックが失敗します。

うまくいけば、これは既存の解決策よりも「現実の世界」の状況に近く、誰かがこれが役立つと思うでしょう。乾杯!


この回答の固定ソースでは、Potr Czachurの回答にあるコンテンツタイプを更新するための行が省略されています。これは誤解を招く可能性があります。
Shai Berger 14

@ShaiBerger具体的には、「Django ContentTypeの変更を意図的に無視しています。これを処理する方法については、以前に参照した回答を参照してください。」
マット・ブリアンソン

9

モデルはアプリと密接に結びついていないため、移動はかなり簡単です。Djangoはデータベーステーブルの名前にアプリ名を使用するため、アプリを移動したい場合は、SQL ALTER TABLEステートメントを使用してデータベーステーブルの名前を変更するか、さらに簡単に、モデルのクラスのdb_tableパラメーターを使用しMetaて、古い名前。

これまでにコードのどこかでContentTypesまたは一般的な関係を使用したことがある場合app_labelは、移動するモデルを指すcontenttypeの名前を変更して、既存の関係が保持されるようにすることをお勧めします。

もちろん、保持するデータがまったくない場合は、データベーステーブルを完全に削除して./manage.py syncdb再実行するのが最も簡単な方法です。


2
サウスマイグレーションでどうすればいいですか?
Apreche 2009

4

Potrの優れたソリューションに対するもう1つの修正があります。specific / 0003_create_catに以下を追加します

depends_on = (
    ('common', '0002_create_cat'),
)

この依存関係が設定されていない限り、Southはcommon_catspecific / 0003_create_catの実行時にテーブルが存在することを保証せず、django.db.utils.OperationalError: no such table: common_catエラーがれます。

Southは、依存関係が明示的に設定されていない限り、辞書式順序で移行を実行します。以来common前に来てspecific、すべてcommonのそれはおそらくPotrで示す元の例では再現しないように、移行は、テーブルの名前を変更する前に実行になるだろう。しかし、名前commonを変更するapp2specificapp1この問題が発生します。


これは実際にはPotrの例では問題ではありません。特定の移行を使用して名前を変更し、一般的な移行を使用して特定の移行に依存します。特定が最初に実行される場合は、問題ありません。commonが最初に実行される場合、依存関係はその前に特定の実行を行います。そうは言っても、これを行うときに順序を入れ替えたため、名前の変更は共通に発生し、依存関係は具体的に発生したため、上記のように依存関係を変更する必要があります。
EmilStenström2014年

1
同意できません。私の見解では、ソリューションは堅牢であり、満足させることなく動作するはずです。元のソリューションは、新しいdbとsyncdb / migrateから開始すると機能しません。私の提案はそれを修正します。いずれにしても、ポートの答えは私に多くの時間を節約しました、彼への称賛:)
Ihor Kaharlichenko

これを行わないと、テストも失敗する可能性があります(テストデータベースを作成するときは、常に完全な南移行を実行しているように見えます)。以前に似たようなことをしたことがあります。グッドキャッチ
イホール

4

私が何度かここに戻って、それを形式化することを決めて以来、私が現在解決しているプロセス。

これは、もともとの上に建てられた Potr Czachurの答えマット・ブリアンソンの答え南の0.8.4を使用して、

ステップ1.子の外部キー関係を発見する

# Caution: This finds OneToOneField and ForeignKey.
# I don't know if this finds all the ways of specifying ManyToManyField.
# Hopefully Django or South throw errors if you have a situation like that.
>>> Cat._meta.get_all_related_objects()
[<RelatedObject: common:toy related to cat>,
 <RelatedObject: identity:microchip related to cat>]

したがって、この拡張ケースでは、次のような別の関連モデルを発見しました。

# Inside the "identity" app...
class Microchip(models.Model):

    # In reality we'd probably want a ForeignKey, but to show the OneToOneField
    identifies = models.OneToOneField(Cat)

    ...

ステップ2.マイグレーションを作成する

# Create the "new"-ly renamed model
# Yes I'm changing the model name in my refactoring too.
python manage.py schemamigration specific create_kittycat --auto

# Drop the old model
python manage.py schemamigration common drop_cat --auto

# Update downstream apps, so South thinks their ForeignKey(s) are correct.
# Can skip models like Toy if the app is already covered
python manage.py schemamigration identity update_microchip_fk --auto

ステップ3.ソース管理:これまでの変更をコミットします。

チームメイトが更新されたアプリでマイグレーションを作成するようなマージの競合に遭遇した場合、それをより反復可能なプロセスにします。

ステップ4.移行間の依存関係を追加します。

基本的create_kittycatにはすべての現在の状態に依存し、その後すべてがに依存しcreate_kittycatます。

# create_kittycat
class Migration(SchemaMigration):

    depends_on = (
        # Original model location
        ('common', 'the_one_before_drop_cat'),

        # Foreign keys to models not in original location
        ('identity', 'the_one_before_update_microchip_fk'),
    )
    ...


# drop_cat
class Migration(SchemaMigration):

    depends_on = (
        ('specific', 'create_kittycat'),
    )
    ...


# update_microchip_fk
class Migration(SchemaMigration):

    depends_on = (
        ('specific', 'create_kittycat'),
    )
    ...

ステップ5.テーブル名の変更を行います。

# create_kittycat
class Migration(SchemaMigration):

    ...

    # Hopefully for create_kittycat you only need to change the following
    # 4 strings to go forward cleanly... backwards will need a bit more work.
    old_app = 'common'
    old_model = 'cat'
    new_app = 'specific'
    new_model = 'kittycat'

    # You may also wish to update the ContentType.name,
    # personally, I don't know what its for and
    # haven't seen any side effects from skipping it.

    def forwards(self, orm):

        db.rename_table(
            '%s_%s' % (self.old_app, self.old_model),
            '%s_%s' % (self.new_app, self.new_model),
        )

        if not db.dry_run:
            # For permissions, GenericForeignKeys, etc to work properly after migrating.
            orm['contenttypes.contenttype'].objects.filter(
                app_label=self.old_app,
                model=self.old_model,
            ).update(
                app_label=self.new_app,
                model=self.new_model,
            )

        # Going forwards, should be no problem just updating child foreign keys
        # with the --auto in the other new South migrations

    def backwards(self, orm):

        db.rename_table(
            '%s_%s' % (self.new_app, self.new_model),
            '%s_%s' % (self.old_app, self.old_model),
        )

        if not db.dry_run:
            # For permissions, GenericForeignKeys, etc to work properly after migrating.
            orm['contenttypes.contenttype'].objects.filter(
                app_label=self.new_app,
                model=self.new_model,
            ).update(
                app_label=self.old_app,
                model=self.old_model,
            )

        # Going backwards, you probably should copy the ForeignKey
        # db.alter_column() changes from the other new migrations in here
        # so they run in the correct order.
        #
        # Test it! See Step 6 for more details if you need to go backwards.
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['common.Cat']))
        db.alter_column('identity_microchip', 'identifies_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['common.Cat']))


# drop_cat
class Migration(SchemaMigration):

    ...

    def forwards(self, orm):
        # Remove the db.delete_table(), if you don't at Step 7 you'll likely get
        # "django.db.utils.ProgrammingError: table "common_cat" does not exist"

        # Leave existing db.alter_column() statements here
        db.alter_column('common_toy', 'belongs_to_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['specific.KittyCat']))

    def backwards(self, orm):
        # Copy/paste the auto-generated db.alter_column()
        # into the create_kittycat migration if you need backwards to work.
        pass


# update_microchip_fk
class Migration(SchemaMigration):

    ...

    def forwards(self, orm):
        # Leave existing db.alter_column() statements here
        db.alter_column('identity_microchip', 'identifies_id', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['specific.KittyCat']))

    def backwards(self, orm):
        # Copy/paste the auto-generated db.alter_column()
        # into the create_kittycat migration if you need backwards to work.
        pass

ステップ6.逆方向に動作する必要があり、KeyErrorを逆方向に実行する必要がある場合のみ。

# the_one_before_create_kittycat
class Migration(SchemaMigration):

    # You many also need to add more models to South's FakeORM if you run into
    # more KeyErrors, the trade-off chosen was to make going forward as easy as
    # possible, as that's what you'll probably want to do once in QA and once in
    # production, rather than running the following many times:
    #
    # python manage.py migrate specific <the_one_before_create_kittycat>

    models = {
        ...
        # Copied from 'identity' app, 'update_microchip_fk' migration
        u'identity.microchip': {
            'Meta': {'object_name': 'Microchip'},
            u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
            'identifies': ('django.db.models.fields.related.OneToOneField', [], {to=orm['specific.KittyCat']})
        },
        ...
    }

ステップ7.それをテストします-私にとってうまくいくことはあなたの実際の状況には十分ではないかもしれません:)

python manage.py migrate

# If you need backwards to work
python manage.py migrate specific <the_one_before_create_kittycat>

3

したがって、上記の@Potrからの元の応答を使用しても、South 0.8.1およびDjango 1.5.1では機能しませんでした。他の人に役立つことを願って、私にとって何がうまくいったかを以下に投稿します。

from south.db import db
from south.v2 import SchemaMigration
from django.db import models

class Migration(SchemaMigration):

    def forwards(self, orm):
        db.rename_table('common_cat', 'specific_cat') 

        if not db.dry_run:
             db.execute(
                "update django_content_type set app_label = 'specific' where "
                " app_label = 'common' and model = 'cat';")

    def backwards(self, orm):
        db.rename_table('specific_cat', 'common_cat')
            db.execute(
                "update django_content_type set app_label = 'common' where "
                " app_label = 'specific' and model = 'cat';")

1

私はダニエル・ローズマンが彼の答えで提案したものの1つのより明確なバージョンを与えるつもりです...

db_tableモデルのメタ属性を変更するだけで、既存のテーブル名をポイントするように移動した場合(新しい名前ではなく、Djangoがドロップしてsyncdb)、複雑なSouthの移行を回避できます。例えば:

元の:

# app1/models.py
class MyModel(models.Model):
    ...

移動後:

# app2/models.py
class MyModel(models.Model):
    class Meta:
        db_table = "app1_mymodel"

これで、テーブルのapp_labelfor を更新するためMyModelにデータを移行する必要があるだけで、 django_content_type準備完了です...

実行して./manage.py datamigration django update_content_type、Southが作成するファイルを編集します。

def forwards(self, orm):
    moved = orm.ContentType.objects.get(app_label='app1', model='mymodel')
    moved.app_label = 'app2'
    moved.save()

def backwards(self, orm):
    moved = orm.ContentType.objects.get(app_label='app2', model='mymodel')
    moved.app_label = 'app1'
    moved.save()
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.