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


126

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

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


답변:


184

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

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

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

11
django_content_type 테이블에서 데이터 마이그레이션을 수행해야 할 수도 있습니다.
spookylukey 2016 년

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'])
Brad Pitcher

2
에 액세스하려면 명령에 옵션을 orm['contenttypes.contenttype']추가 --freeze contenttypes해야 schemamigration합니다.
게리

1
내 경우 (Django 1.5.7 및 South 1.0) .. python manage.py schemamigration specific create_cat --auto --freeze common일반적인 응용 프로그램에서 고양이 모델에 액세스하려면 입력 해야했습니다.
geoom

35

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

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


이 답변의 고정 소스는 Potr Czachur의 답변에있는 내용 유형을 업데이트하기위한 줄을 생략합니다. 오해의 소지가 있습니다.
Shai Berger

@ShaiBerger 나는 구체적으로 다음과 같이 언급했다. "고의적으로 Django ContentType 변경을 무시하고 있습니다.이를 처리하는 방법은 이전에 참조 된 답변을 참조하십시오."
Matt Briançon

9

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

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

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


2
남쪽으로 이주하면 어떻게합니까?
Apreche

4

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의 예제에서는 문제가되지 않습니다. 특정 마이그레이션을 사용하여 이름을 바꾸고 일반적인 마이그레이션을 사용하여 이름을 바꿉니다. 특정이 먼저 실행되면 괜찮습니다. common이 먼저 실행되면 종속성이 특정 run을 먼저 실행합니다. 즉,이 작업을 수행 할 때 순서를 바꿨으므로 이름 바꾸기가 공통적으로 발생하고 특정 종속성이 발생했으며 위에서 설명한 것처럼 종속성을 변경해야합니다.
Emil Stenström

1
나는 당신에게 동의 할 수 없습니다. 내 관점에서 볼 때 솔루션은 강력해야하며 만족시키지 않고 작동해야합니다. 새로운 db 및 syncdb / migrate에서 시작하면 원래 솔루션이 작동하지 않습니다. 내 제안으로 해결됩니다. 또는 다른 방법 중 하나는, 포트의 대답은 :) 그에게, 나에게 칭찬을 많은 시간을 저장
호르 Kaharlichenko에게

이렇게하지 않으면 테스트도 실패 할 수 있습니다 (테스트 데이터베이스를 만들 때 항상 전체 남쪽 마이그레이션을 실행하는 것처럼 보입니다). 나는 전에 비슷한 것을 해왔다. Good catch Ihor :)
odinho-Velmont

4

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

이것은 원래 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>

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

나는 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()
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.