[django] 하나의 장고 앱에서 새 모델로 모델을 어떻게 마이그레이션합니까?

네 가지 모델의 장고 앱이 있습니다. 이제이 모델 중 하나가 별도의 앱에 있어야한다는 것을 알고 있습니다. 마이그레이션을 위해 남쪽을 설치했지만 이것이 자동으로 처리 할 수 ​​있다고 생각하지 않습니다. 이전 앱에서 모델 중 하나를 새 모델로 마이그레이션하려면 어떻게해야합니까?

또한 프로덕션 시스템 등을 마이그레이션 할 수 있도록 반복 가능한 프로세스가 필요하다는 점에 유의하십시오.



답변

남쪽을 사용하여 마이그레이션하는 방법

공통 및 특정의 두 가지 앱이 있다고 가정 해 보겠습니다.

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

사물을 마이그레이션합니다.

스키마 업그레이드에는 공통 앱을 사용하고 다운 그레이드에는 특정 앱을 사용했습니다. 종속성이 어떻게 작동하는지 때문입니다.


답변

Potr Czachur답변바탕으로 ForeignKeys가 관련된 상황은 더 복잡하고 약간 다르게 처리해야합니다.

(다음 예제 는 현재 답변에서 참조 된 commonspecific앱을 기반으로합니다 ).

# 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')에서 specific롤백의 common표 참조 된 테이블이 존재하지 것이기 때문에 외래 키를 마이그레이션 할 때 롤백이 실패합니다.

이 방법이 기존 솔루션보다 “실제”상황에 더 가깝기를 바랍니다. 누군가 도움이 될 것입니다. 건배!


답변

모델은 앱과 밀접하게 연결되어 있지 않으므로 이동이 매우 간단합니다. Django는 데이터베이스 테이블 이름에 앱 이름을 사용하므로 앱을 이동하려면 SQL ALTER TABLE문을 통해 데이터베이스 테이블의 이름을 바꾸 거나 더 간단하게 모델 클래스 의 db_table매개 변수 를 사용 Meta하여 고명.

코드에서 지금까지 ContentTypes 또는 일반 관계를 사용한 경우 app_label기존 관계가 유지되도록 이동중인 모델을 가리키는 contenttype의 이름을 바꾸고 싶을 것입니다 .

물론 보존 할 데이터가없는 경우 가장 쉬운 방법은 데이터베이스 테이블을 완전히 삭제하고 ./manage.py syncdb다시 실행하는 것입니다.


답변

Potr의 탁월한 솔루션에 대한 또 하나의 수정 사항이 있습니다. 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는 사전 순서대로 마이그레이션을 실행합니다 . 때문에 common앞에 오는 specific모든 common아마 Potr으로 표시 원래의 예에서 재현 할 것이다, 그래서의 마이그레이션, 테이블 이름을 바꾸기 전에 실행 얻을 것입니다. 이름을 변경하지만 만약 commonapp2specificapp1이 문제로 실행됩니다.


답변

내가 여기에 몇 번 돌아와서 공식화하기로 결정한 이후로 현재 정착 한 프로세스.

이것은 원래
Potr Czachur의 대답
Matt Briançon의 대답에 따라 남쪽 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. 작동하기 위해 reverses ()가 필요하고 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>


답변

따라서 위의 @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';")


답변

나는 Daniel Roseman이 그의 대답에서 제안한 것 중 하나의 더 명확한 버전을 제공 할 것입니다 …

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에 대한 MyModel에서 django_content_type테이블을 당신은 좋은 갈 수 있어야 …

./manage.py datamigration django update_content_typeSouth가 생성 한 파일을 실행 한 후 편집하십시오.

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()