기술정리/Django

Django 안정적인 마이그레이션 방법

bingual 2024. 4. 6. 18:27
반응형

 

models.py

class Post(models.Model):
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) # 추가
    ...

 

해당 코드와 같이 Post 모델에 author 칼럼을 추가하고 마이그레이션 생성 명령을 수행한다면 문제없이 동작할 거다.


게시글에서의 작성자는 default, null, blank 등등 지정 유효성 조건은 전부 맞지 않다. 게시글이 생성될 때 작성자가 등록되기 때문이다. 1번 선택지를 선택하고 일회성 기본값으로 1을 입력해 준다.

 

0002_post_author.py

class Migration(migrations.Migration):

    dependencies = [
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
        ("blog", "0001_initial"),
    ]

    operations = [
        migrations.AddField(
            model_name="post",
            name="author",
            field=models.ForeignKey(
                default=1,
                on_delete=django.db.models.deletion.CASCADE,
                to=settings.AUTH_USER_MODEL,
            ),
            preserve_default=False,
        ),
    ]
ALTER TABLE "blog_post" ADD COLUMN "author_id" bigint DEFAULT 1 NOT NULL CONSTRAINT "blog_post_author_id_dd7a8485_fk_accounts_user_id" REFERENCES "accounts_user"("id") DEFERRABLE INITIALLY DEFERRED; SET CONSTRAINTS "blog_post_author_id_dd7a8485_fk_accounts_user_id" IMMEDIATE;
ALTER TABLE "blog_post" ALTER COLUMN "author_id" DROP DEFAULT;
CREATE INDEX "blog_post_author_id_dd7a8485" ON "blog_post" ("author_id");
COMMIT;


author 필드의 default 값으로 1가 할당되어 있다. 이는 Post 테이블에서 새로운 레코드가 등록될 때 author 필드는 외래키로 연결한 부모 테이블의 레코드 중 기본 값으로 사용할 pk 값이 1인 레코드가 있기를 기대하고 있기 때문에 해당 레코드가 등록되어있어야 함을 뜻한다.

 

만약 User 테이블의 레코드가 등록되어있지 않은 상태에서 마이그레이트 명령을 수행할 시 Post 테이블의 레코드가 하나도 없는 상태라면 상관없지만 이미 레코드가 존재하고 있을 경우 마이그레이트 명령 수행 시 위와 같은 외래키 제약조건 에러가 발생한다.

 

수동적으로 부모 테이블에 레코드를 생성하고 마이그레이션을 수행하면 되겠지만 매번 그런 작업을 하게 된다면 번거롭기 마련이다.

 

마이그레이션시 유저 자동생성

 

0002_post_author.py

# Generated by Django 4.2.7 on 2024-04-06 18:01
import string

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
from django.utils.crypto import get_random_string


def create_user_if_empty(apps, schema_editor):
    User = apps.get_model(settings.AUTH_USER_MODEL)
    is_existed = User.objects.filter(pk=1).exists()
    if is_existed is False:
        random_username = "auto-" + get_random_string(
            length=8, allowed_chars=string.ascii_letters
        )
        user = User.objects.create_user(
            username=random_username, password=None, is_active=False, pk=1
        )
        print("auto_generated_user (pk=1):", user)


class Migration(migrations.Migration):

    dependencies = [
        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
        ("blog", "0001_initial"),
    ]

    operations = [
        migrations.RunPython(create_user_if_empty, migrations.RunPython.noop),
        migrations.AddField(
            model_name="post",
            name="author",
            field=models.ForeignKey(
                default=1,
                on_delete=django.db.models.deletion.CASCADE,
                to=settings.AUTH_USER_MODEL,
            ),
            preserve_default=False,
        ),
    ]

 

  • User모델의 username은 unique 하기 때문에 겹치지 않게끔 8자리의 아스키코드 문자열로 생성해 준다.
  • 로그인이 불가능하게 password는 None으로 설정해 주고 is_active도 False로 주어 계정도 비활성화시켜 준다.
  • RunPython 구문은 외래키 제약 조건이 추가되기 전 선언해 주도록 한다.

 

 

 

마이그레이트 명령 수행 시 pk=1인 유저 레코드가 생성되면서 마이그레이션이 성공한 것을 확인 가능하다.


마이그레이션시 카테고리 자동생성

 

models.py

class Category(models.Model):
    name = models.CharField(max_length=50)


class Post(models.Model):
    category = models.ForeignKey(Category, on_delete=models.CASCADE) # 추가
    ...

 

 

두 번째 예시이다. 

Post의 레코드가 이미 존재하는 상태에서 Category의 레코드가 없는 경우 외래키 제약조건 에러가 발생할 것을 예상할 수 있다.

 

 

0003_category_post_category.py

# Generated by Django 4.2.7 on 2024-04-06 18:34

from django.db import migrations, models
import django.db.models.deletion


def create_initial_category(apps, schema_editor):
    Category = apps.get_model("blog", "Category")
    is_existed = Category.objects.filter(pk=1).exists()
    if is_existed is False:
        Category.objects.create(name="initial", pk=1)


class Migration(migrations.Migration):

    dependencies = [
        ("blog", "0002_post_author"),
    ]

    operations = [
        migrations.CreateModel(
            name="Category",
            fields=[
                (
                    "id",
                    models.BigAutoField(
                        auto_created=True,
                        primary_key=True,
                        serialize=False,
                        verbose_name="ID",
                    ),
                ),
                ("name", models.CharField(max_length=50)),
            ],
        ),
        migrations.RunPython(create_initial_category, migrations.RunPython.noop),
        migrations.AddField(
            model_name="post",
            name="category",
            field=models.ForeignKey(
                default=1,
                on_delete=django.db.models.deletion.CASCADE,
                to="blog.category",
            ),
            preserve_default=False,
        ),
    ]

 

 

 

해당 코드를 적용하고 마이그레이트 명령 수행 시 마이그레이션이 성공하고 위와 같이 author_id, category_id에 pk=1인 레코드를 참조하는 것을 확인할 수 있다.

 

이렇듯 레코드가 존재하는 상황에서 외래키를 추가하여 마이그레이션을 해야 할 때 자동적으로 처리할 수 있게끔 진행할 수 있다.