program story

두 Django 앱간에 모델을 이동하는 방법 (Django 1.7)

inputbox 2020. 8. 2. 18:34
반응형

두 Django 앱간에 모델을 이동하는 방법 (Django 1.7)


약 1 년 전에 프로젝트를 시작했고 모든 새로운 개발자와 마찬가지로 구조에 너무 집중하지 않았지만 이제는 Django와 함께 내 프로젝트 레이아웃이 주로 내 모델의 구조가 끔찍한 것처럼 보이기 시작했습니다. .

나는 주로 단일 응용 프로그램에서 모델을 보유하고 있으며 실제로 대부분의 모델은 자체 응용 프로그램에 있어야합니다.이를 해결하고 남쪽으로 이동했지만 외래 키 요법으로 인해 까다 롭고 실제로 어렵다는 것을 알았습니다.

그러나 Django 1.7으로 인해 마이그레이션 지원 기능이 내장되어 있으므로 지금 더 좋은 방법이 있습니까?


데이터 손실이 발생할 수 있으므로 이전 답변을 제거하고 있습니다. 으로 오잔 언급 , 우리는 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)
    ]

첫 번째 마이그레이션에 의존하고 두 번째 앱에서 새 테이블을 생성하는 두 번째 마이그레이션. 모델 코드를 두 번째 앱으로 옮긴 후

$ 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)
    ]

이를 사용하면 상당히 쉽게 수행 할 수 있습니다 migrations.SeparateDatabaseAndState. 기본적으로 데이터베이스 작업을 사용하여 두 상태 작업과 동시에 테이블 이름을 바꾸어 한 앱의 기록에서 모델을 제거하고 다른 앱의 기록에서 모델을 만듭니다.

오래된 앱에서 제거

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

같은 문제가 발생했습니다. 오잔의 대답 은 많은 도움이되었지만 불행히도 충분하지 않았습니다. 실제로 이동하려는 모델에 몇 가지 ForeignKey 연결이있었습니다. 두통 후 나는 해결책을 찾았으므로 사람들의 시간을 해결하기 위해 게시하기로 결정했습니다.

2 단계가 더 필요합니다.

  1. 작업을 수행하기 전에 모든 ForeignKey연결을 TheModel변경하십시오 Integerfield. 그런 다음 실행python manage.py makemigrations
  2. Ozan의 단계를 수행 한 후 외래 키를 다시 변환하십시오 . ForeignKey(TheModel)대신에 다시 넣으십시오 IntegerField(). 그런 다음 마이그레이션을 다시 수행하십시오 ( python manage.py makemigrations). 그런 다음 마이그레이션 할 수 있으며 작동해야합니다 ( python manage.py migrate)

도움이 되길 바랍니다. 물론 나쁜 놀라움을 피하기 위해 프로덕션에서 시도하기 전에 로컬에서 테스트하십시오 :)


내가 한 방법 (Django = = 1.8에서 postgres로 테스트 했으므로 1.7)

상태

app1.YourModel

하지만 당신은 그것을 원합니다 : app2.YourModel

  1. YourModel (코드)을 app1에서 app2로 복사하십시오.
  2. 이것을 app2에 추가하십시오.

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

  4. 새로운 마이그레이션 (예 : 0009_auto_something.py)은 app2에서 migrations.CreateModel () 문을 사용하여 이루어지며이 문을 app2의 초기 마이그레이션 (예 : 0001_initial.py)으로 옮깁니다 (항상 존재했던 것처럼). 이제 생성 된 마이그레이션 = 0009_auto_something.py를 제거하십시오.

  5. app2.YourModel과 같이 항상 행동했던 것처럼 이제 마이그레이션에서 app1.YourModel의 존재를 제거하십시오. 의미 : CreateModel 문과 그 이후에 사용한 모든 조정 또는 데이터 마이그레이션을 주석 처리하십시오.

  6. 물론 app1.YourModel에 대한 모든 참조는 프로젝트를 통해 app2.YourModel로 변경해야합니다. 또한 app1에 가능한 모든 외래 키를 잊지 마십시오. 마이그레이션시 Model은 app2로 변경해야합니다.

  7. 이제 $ python manage.py migrate를 수행하면 아무것도 변경되지 않으며 $ python manage.py makemigrations를 수행해도 새로운 것이 감지되지 않습니다.

  8. 이제 마무리 손질 : app2.YourModel에서 Class Meta를 제거하고 $ python manage.py makemigrations app2 && python manage.py migrate app2를 수행하십시오 (이 마이그레이션을 살펴보면 다음과 같이 표시됩니다).

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

table = None은 기본 table-name을 사용하며이 경우 app2_yourmodel이됩니다.

  1. 완료, 데이터가 저장되었습니다.

PS는 마이그레이션하는 동안 content_type app1.yourmodel이 제거되었으며 삭제할 수 있음을 알 수 있습니다. 당신이 그것을 사용하지 않는 경우에만 당신은 그것에 예라고 말할 수 있습니다. 해당 컨텐츠 유형에 대한 FK가 그대로 유지되도록 크게 의존하는 경우, 예 또는 아니오로 대답하지 말고 수동으로 db로 이동하여 contentype app2.yourmodel을 제거하고 컨텐츠 유형 app1의 이름을 바꾸십시오. yourmodel을 app2.yourmodel로 보낸 다음 no로 계속 응답하십시오.


나는 Ozan의 대답에 필요한대로 긴장된 핸드 코딩 마이그레이션을 얻으 므로 다음은 Ozan과 Michael의 전략을 결합하여 필요한 핸드 코딩 양을 최소화합니다.

  1. 모델을 이동하기 전에을 실행하여 깨끗한 기준선으로 작업하고 있는지 확인하십시오 makemigrations.
  2. 에서 모델에 대한 코드를 이동 app1하는app2
  3. @Michael이 권장하는대로 db_table"새"모델 에서 메타 옵션을 사용하여 새 모델을 이전 데이터베이스 테이블을 가리 킵니다 .

    class Meta:
        db_table = 'app1_yourmodel'
    
  4. 를 실행하십시오 makemigrations. CreateModelapp2DeleteModel안으로 생성 됩니다 app1. 기술적으로 이러한 마이그레이션은 정확히 동일한 테이블을 참조하며 모든 데이터를 포함하여 테이블을 제거하고 다시 만듭니다.

  5. 실제로, 우리는 테이블에 아무것도하지 않기를 원합니다. 우리는 변화가 이루어 졌다고 믿기 위해 장고가 필요합니다. @Ozan의 답변에 따라 state_operations플래그 SeparateDatabaseAndState가이 작업 수행합니다. 그래서 우리는 모든 랩 migrations항목을 BOTH 마이그레이션 파일SeparateDatabaseAndState(state_operations=[...]). 예를 들어

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

    된다

    operations = [
        migrations.SeparateDatabaseAndState(state_operations=[
            ...
            migrations.DeleteModel(
                name='YourModel',
            ),
            ...
        ])
    ]
    
  6. 편집 : 또한 새 "가상" 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목록에 열고 추가하십시오 .

편집 : ForeignKey이동하는 모델과 관계 가 있으면 위의 작동하지 않을 수 있습니다. 이것은 다음과 같은 이유로 발생합니다.

  • ForeignKey변경 사항에 대한 종속성이 자동으로 작성되지 않습니다
  • ForeignKey변경 사항 을 랩핑하고 싶지 않으므로 변경 사항 state_operations이 테이블 조작과 분리되어 있는지 확인해야합니다.

"최소"작업 집합은 상황에 따라 다르지만 다음 절차는 대부분 / 모든 ForeignKey마이그레이션에 적용됩니다.

  1. 모델을에서 app1복사하고을app2 설정 db_table하되 FK 참조를 변경하지 마십시오.
  2. makemigrations모든 app2마이그레이션을 실행 하고 래핑합니다 state_operations(위 참조).
    • 위와 같이 app2 CreateTable최신 app1마이그레이션에 종속성을 추가하십시오.
  3. 모든 FK 참조를 새 모델로 지정하십시오. 문자열 참조를 사용하지 않는 경우 이전 모델을 맨 아래로 이동 models.py(제거하지 마십시오)하여 가져온 클래스와 경쟁하지 않도록하십시오.
  4. 실행 makemigrations하지만 아무것도 포장하지 마십시오 state_operations(FK 변경 사항이 실제로 발생해야 함). 모두의 종속성을 추가 ForeignKey마이그레이션 (즉, AlterField받는 사람) CreateTable에서 마이그레이션 app2(그렇게 그들을 추적 다음 단계에 대한 목록이 필요합니다). 예를 들면 다음과 같습니다.

    • 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마이그레이션이 포함될 수 있음 ).app1app2
    • 이러한 마이그레이션을 구축 할 때 DeleteTable이미 마이그레이션에 의존 AlterField했으므로 수동으로 (즉, Alter이전 Delete) 시행 할 필요가 없었습니다 .

이 시점에서 Django가 좋습니다. 새로운 모델은 이전 테이블을 가리키고 Django의 마이그레이션은 모든 것이 적절하게 재배치되었다고 확신했습니다. @Michael의 답변에서 큰주의 사항 ContentType은 새 모델에 대해 것이 생성 된다는 것 입니다. ForeignKey콘텐츠 유형에 (예 :로 ) 연결하는 경우 ContentType테이블 을 업데이트하기 위해 마이그레이션을 만들어야 합니다.

나는 나 자신 (메타 옵션 및 테이블 이름)을 정리하고 싶었으므로 다음 절차 (@Michael)를 사용했습니다.

  1. db_table메타 항목 제거
  2. makemigrations데이터베이스 이름 바꾸기를 생성하기 위해 다시 실행
  3. 이 마지막 마이그레이션을 편집하여 DeleteTable마이그레이션에 의존하는지 확인하십시오 . Delete논리적 이어야하기 때문에 필요하지 않은 것처럼 보이지만 그렇지 않으면 오류가 발생합니다 (예 : app1_yourmodel존재하지 않음).

데이터가 크거나 복잡하지 않지만 여전히 유지 관리해야 할 중요한 또 다른 해킹 대안은 다음과 같습니다.

  • manage.py dumpdata를 사용하여 데이터 픽스쳐 가져 오기
  • 변경 사항을 관련시키지 않고 변경 사항 및 마이그레이션을 올바르게 모델링합니다.
  • 글로벌은 이전 모델 및 앱 이름에서 새로운 조명기로 조명기를 대체합니다.
  • manage.py loaddata를 사용하여 데이터로드

이것은 대략적으로 테스트되었으므로 DB를 백업하는 것을 잊지 마십시오!

예를 들어, 두 개의 앱이 있습니다. src_appdst_app모델을 MoveMe에서 src_app이동하려고 합니다 dst_app.

두 앱 모두에 대해 빈 마이그레이션을 만듭니다.

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

의 새로운 마이그레이션이라는 것을, 가정하자 XXX1_src_app_new하고 XXX1_dst_app_new, previuos 위로 마이그레이션이 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_newXXX1_src_app_new. XXX1_src_app_new이후 src_app마이그레이션이 실행되도록 하는 데 필요한 no-op 마이그레이션입니다 XXX1_dst_app_new.

Move MoveMe from src_app/models.py to dst_app/models.py. Then run:

python manage.py migrate

That's all!


You can try the following (untested):

  1. move the model from src_app to dest_app
  2. migrate dest_app; make sure the schema migration depends on the latest src_app migration (https://docs.djangoproject.com/en/dev/topics/migrations/#migration-files)
  3. add a data migration to dest_app, that copies all data from src_app
  4. migrate src_app; make sure the schema migration depends on the latest (data) migration of dest_app -- that is: the migration of step 3

Note that you will be copying the whole table, instead of moving it, but that way both apps don't have to touch a table that belongs to the other app, which I think is more important.


Lets say you are moving model TheModel from app_a to app_b.

An alternate solution is to alter the existing migrations by hand. The idea is that each time you see an operation altering TheModel in app_a's migrations, you copy that operation to the end of app_b's initial migration. And each time you see a reference 'app_a.TheModel' in app_a's migrations, you change it to 'app_b.TheModel'.

I just did this for an existing project, where I wanted to extract a certain model to an reusable app. The procedure went smoothly. I guess things would be much harder if there were references from app_b to app_a. Also, I had a manually defined Meta.db_table for my model which might have helped.

Notably you will end up with altered migration history. This doesn't matter, even if you have a database with the original migrations applied. If both the original and the rewritten migrations end up with the same database schema, then such rewrite should be OK.


  1. change the names of old models to ‘model_name_old’
  2. makemigrations
  3. make new models named ‘model_name_new’ with identical relationships on the related models (eg. user model now has user.blog_old and user.blog_new)
  4. makemigrations
  5. write a custom migration that migrates all the data to the new model tables
  6. test the hell out of these migrations by comparing backups with new db copies before and after running the migrations
  7. when all is satisfactory, delete the old models
  8. makemigrations
  9. change the new models to the correct name ‘model_name_new’ -> ‘model_name’
  10. test the whole slew of migrations on a staging server
  11. take your production site down for a few minutes in order to run all migrations without users interfering

Do this individually for each model that needs to be moved. I wouldn’t suggest doing what the other answer says by changing to integers and back to foreign keys There is a chance that new foreign keys will be different and rows may have different IDs after the migrations and I didn’t want to run any risk of mismatching ids when switching back to foreign keys.


Copied from my answer at https://stackoverflow.com/a/47392970/8971048

In case you need to move the model and you don't have access to the app anymore (or you don't want the access), you can create a new Operation and consider to create a new model only if the migrated model does not exist.

In this example I am passing 'MyModel' from old_app to myapp.

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))
            ],
        ),
    ]

참고URL : https://stackoverflow.com/questions/25648393/how-to-move-a-model-between-two-django-apps-django-1-7

반응형