web-dev-qa-db-ja.com

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

4つのモデルを含むDjangoアプリがあります。これらのモデルの1つは別のアプリにある必要があることに気付きました。移行のために南にインストールしましたが、古いアプリから新しいモデルにモデルを移行するにはどうすればよいですか?

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

123
Apreche

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

共通および特定の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

物事を移行します。

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

183
Potr Czachur

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

これまでにコードのどこかでContentTypesまたは汎用リレーションを使用したことがある場合は、おそらく、既存のリレーションが保持されるように、移動するモデルを指すcontenttypeのapp_labelの名前を変更する必要があります。

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

7
Daniel Roseman

何度かここに戻ってから現在定まっているプロセスで、それを正式にすることにしました。

これはもともと Potr Czachurの答え および MattBriançonの答え に基づいて構築され、South 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.動作するためにbackwards()が必要で、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>
4
pzrq

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

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

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

Southは、依存関係が明示的に設定されていない限り、移行を 辞書順 で実行します。 commonspecificの前に来るため、commonのすべての移行はテーブルの名前変更の前に実行されるため、Potrが示す元の例ではおそらく再現されません。ただし、commonapp2に、specificapp1に名前を変更すると、この問題が発生します。

4

そのため、上記の@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';")
3
Tim Sutton

ダニエル・ローズマンが彼の答えで示唆したことの一つのより明確なバージョンを提供するつもりです...

モデルのdb_table Meta属性を変更するだけで、既存のテーブル名を指すように移動した場合(新しい名前Djangoではなく、ドロップしてsyncdb)すると、複雑な南への移行を回避できます。例:

元の:

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

移動後:

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

app_labelテーブルのMyModelDjango_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()
1
Anentropic