장고 모델을 다른 앱으로 옮겨보자

이승연·2021년 12월 16일
0

DJango

목록 보기
8/11

릴리프맘 시스템이 확장되며 새로운 테이블과 앱이 필요해졌다.
기존에 신속하게 개발하느라 하나의 앱으로 몰아넣은 테이블을 이 기회에 다른 앱으로 이관해보도록 하자.
생각만 해도 아찔하지만 다행히 장고에는 이 과정을 도와주는 여러 기능이 있다.

  • 방향성: 기존 테이블이 새로운 모델을 참조하도록 한다. 새로운 모델을 만든 후 AlterModelTable이라는 마이그레이션 옵션을 활용하여 기존 테이블의 이름을 새로운 모델의 이름으로 바꾼다. 이때, 새로운 모델로 새로운 테이블을 생성하지 않는다.

1. 새로운 모델을 만든다

  • 앱에서 옮기고자 하는 모델을 지운 후 새로운 앱에 새로운 모델을 만든다. 외래키 관계도 변경사항에 맞게 수정한다.
    • classes: Matching, Presession, SessionBatch, Session
    • core: Region
    • personnels: Staff, Counselor, counselor_available_region
    • services: Application
  • migration 파일을 만든다. 어떤 이름으로 각 모델이 생성되었는지 확인한다. 나의 경우에는 메타로 db_table을 설정해두었기 때문에 메타를 참조하면 된다.

2. 기존 테이블의 이름을 수정한다

  • 기존 앱에서 테이블을 삭제했기 때문에 migration 파일에는 외래키 관계를 삭제하는 removefield, 모델을 삭제하는 deletemodel, 새로운 모델을 생성하는 CreateModel, 새로운 외래키를 생성하는 alterfield 네가지의 오퍼레이션이 이루어진다.
  • 이 네가지 오퍼레이션에 대하여 수정을 해주어야 한다. 나는 staffs 앱에서 모델을 삭제하고 옮기고 있는 중이기 때문에 removefield, deletemodel, alterfield는 staffs의 마이그레이션 파일에 있고 나머지 앱에는 alterfield와 createmodel이 대다수를 차지하고 있을 것이다.

removefield:

  • Using SeparateDatabaseAndState with database_operations set to an empty list prevents Django from dropping the column.
operations = [
-        migrations.RemoveField(
-            model_name='product',
-            name='category',
+        migrations.SeparateDatabaseAndState(
+            state_operations=[
+                migrations.RemoveField(
+                    model_name='product',
+                    name='category',
+                ),
+            ],
+            database_operations=[],
                   ),
               ]

deletemodel:

  • Django provides a special migration operation, AlterModelTable, to rename a table for a model. Edit the migration that drops the old table, and instead rename the table to the name of the new table
operations = [
-        migrations.DeleteModel(
-            name='Product',
-        ),
+        migrations.SeparateDatabaseAndState(
+            state_operations=[
+                migrations.DeleteModel(
+                    name='Product',
+                ),
+            ],
+            database_operations=[
+                migrations.AlterModelTable(
+                    name='Product',
+                    table='product_product',
+                ),
+            ],
+        )
     ]

createmodel

  • Next, you need to prevent Django from creating a table for the new Product model. Instead, you want it to use the table you renamed. Due to the line database_operations=[], Django does not create the table.
operations = [
-        migrations.CreateModel(
-            name='Product',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('name', models.CharField(db_index=True, max_length=100)),
-                ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='catalog.Category')),
-            ],
+        migrations.SeparateDatabaseAndState(
+            state_operations=[
+                migrations.CreateModel(
+                    name='Product',
+                    fields=[
+                        ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                        ('name', models.CharField(db_index=True, max_length=100)),
+                        ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='catalog.Category')),
+                    ],
+                ),
+            ],
+            # Table already exists. See catalog/migrations/0003_delete_product.py
+            database_operations=[],
         ),
     ]

alterfield

operations = [
-        migrations.AlterField(
-            model_name='sale',
-            name='product',
-            field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='product.Product'),
-        ),
+        migrations.SeparateDatabaseAndState(
+            state_operations=[
+                migrations.AlterField(
+                    model_name='sale',
+                    name='product',
+                    field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='product.Product'),
+                ),
+            ],
+            # You're reusing an existing table, so do nothing
+            database_operations=[],
+        )
     ]
  • 주의: 기계적으로 코드 넣다가 새로 생성해야 하는 모델까지 separatedatabaseandstate처리하지 말자ㅠㅜ..
  • 새로 생성하는 필드나 모델은 다음 작업에 하자. 같이 하면 엉킴.

3. Migration한다

  • 나는 이런 에러가 계속 나왔다.
  • relation이 이미 존재한다는 것은 해당 이름을 가진 테이블이 이미 존재한다는 뜻인것 같아 데이터베이스를 보니 해당 테이블이 이미 생성되어있었다. 이 문제를 바로 캐치하지 못하고 롤백과 마이그레이션을 반복하다가 테이블이 생성되어버린 것이 문제였을까? 우선은 데이터베이스에서 바로 문제가 되는 테이블을 일괄 drop한 뒤에 다시 migration을 하니 정상적으로 작동되었다.
  • 이번엔 relation이 존재하지 않는다고 한다. 이런 경우 테이블이 가짜로 생성은 되었지만 다른 앱에 있는 모델 이름을 staffs_staff에서 personnels_staff로 바꿔주어야 하는데 그 부분이 안되어서 그런거다. 해당 마이그레이션 파일만 타겟해서 migrate해주자.
  • 이게 나오면 쉽게 해결할 수 있다. 해당 migration 파일에 가서 새로운 relation을 정의한 부분을 주석처리한다. 이게 나오는 이유는 기존에 있던 테이블에 이미 relation이 존재하고 테이블 이름만 수정하는 과정에서 장고가 새로 만들어졌다고 생각하는 테이블과 새로운 relation을 걸려고 시도하기 때문이다.
  • 일단 내가 제일 잘못한 부분은 새로 생성해야 하는 테이블들을 분리해서 작업하지 않은 것, 그리고 새로 생성해야 하는 테이블의 참조관계를 따로 정리해서 migration하지 않은 것. 다음부터는 혼동하지 말자.

Introspection

  • 장고 ORM은 파이썬으로 작성된 테이블 내용을 데이터베이스 테이블로 번역하는 역할을 하는데 이때 primary key나 auto-incrementing sequences 등도 생성한다. 이렇게 생성된 변수들은 해당 테이블의 이름 + 특정 이름 형태의 이름을 가지게 된다. 이때, 이 포스팅에서 한 것처럼 테이블의 이름을 바꿨을 때 이 변수들의 이름도 바뀔까? 대답은 바뀌지 않는다이다. 하지만 그럼에도 장고는 관계성을 찾을 수 있다. 장고는 데이터베이스에서 제공하는 메타데이터 테이블을 사용하여 관계성을 찾고 이것을 introspection이라고 한다. 다시 말해, naming convention에 의존하지 않고도 객체를 다룰 수 있다는 것이다.

출처: How to Move a Django Model to ANother App

1개의 댓글

comment-user-thumbnail
2022년 4월 28일

좋은 포스팅 잘 봤습니다. 감사합니다. :-)

답글 달기