Django: 기존 필드에 UUID 필드(or unique=True인 필드) 추가하기: RunPython 활용 방법

강지수·2023년 10월 11일
0

개발을 진행하다보면 unique=True인 필드를 추가해야할 때가 있다

예를들어 User model에 uuid필드를 추가한다고 해보자

  1. 필수로 존재해야하고

  2. 하나이상 존재해서는 안되는 값이다

먼저 해결방안부터 말하자면,

  1. 필드를 unique=True없이 추가해준다

  2. 빈 migration file을 생성시켜준다

  3. migration file RunPython을 이용하여 데이터를 채워준다

  4. unique=True인 속성으로 변경해준다


예시코드로 살펴보기

User 모델에 uuid필드를 추가해주려고 한다

class User(models.Model):
    email = models.EmailField(max_length=100, verbose_name="이메일", unique=True)
    name = models.CharField(max_length=15, verbose_name="이름")
    phonenumber = models.CharField(max_length=11, verbose_name="전화번호")

1. uuid필드를 추가해준다

class User(models.Model):
    uuid = models.UUIDField(null=True, default=uuid.uuid4, editable=False)
    email = models.EmailField(max_length=100, verbose_name="이메일", unique=True)
    name = models.CharField(max_length=15, verbose_name="이름")
    phonenumber = models.CharField(max_length=11, verbose_name="전화번호")

여기서 핵심은 unique=True속성을 빼고 추가해준다

기존의 데이터들은 uuid 필드가 존재하지 않기 때문에 null=True인 속성을 넣어주었다

default=uuid.uuid4는 필드가 null일 때 새로운 uuid를 생성해준다

migrate를 해보고 데이터를 확인해보면,

기존의 데이터의 uuid가 모두 같은 것을 확인할 수 있다

새로운 데이터를 생성하면 그때마다 새로운 uuid가 생성되지만,

이미 존재하는 데이터는 migration 과정에서 uuid.uuid4()가 한번만 호출되어 동일한 uuid값을 가지기 때문이다

그래서 수동으로 migration 으로 값을 할당해줘야한다


2. 빈 migration file을 생성시켜준다

python3 manage.py makemigrations <app_name> --empty

위 명령어를 통해 해당 앱의 migrations 폴더에 빈 migration 파일을 생성해줄 수 있다

앱의 이름이 accounts라면,

python3 manage.py makemigrations accounts --empty

이런식으로 명령하면된다

accounts/migrations 폴더를 보면 아래와 같은 파일을 볼 수 있다

# Generated by Django 4.1.6 on 2023-08-30 19:30

from django.db import migrations


class Migration(migrations.Migration):
    dependencies = [
        ("user", "0002_user_uuid"),
    ]

    operations = []

3. migration file RunPython을 이용하여 데이터를 채워준다

# Generated by Django 4.1.6 on 2023-08-30 14:12

import uuid

from django.db import migrations


def generate_uuid(apps, schema_editor):
    user = apps.get_model(
        "accounts", "User"
    )
    for i in user.objects.all():
        i.uuid = uuid.uuid4()
        i.save(update_fields=["uuid"])


class Migration(migrations.Migration):
    dependencies = [
        ("user", "0002_user_uuid"),
    ]

    operations = [
        migrations.RunPython(
            generate_uuid, reverse_code=migrations.RunPython.noop
        )
    ]

이렇게 migration 파일을 변경해준다

여기서 generate_uuid는 데이터를 채워주는 함수다

operations 에 있는 migrations.RunPython을 통해 migration과정에서 원하는 동작을 수행할 수 있다

user = apps.get_model("accounts", "User")

위는 내가 원하는 앱의 모델을 가져올 수 있다

앞에 앱이름을 선언해주고 뒤에 모델이름을 선언해줘서 User model을 user 변수에 담아주었다

reverse_code=migrations.RunPython.noop

reverse_code의 에서 noop을 지정하면 이전 migration 버전으로 되돌릴 수 있다

예를 들어, 내가 이미 migrate를 해버렸는데 데이터를 잘못 지정했으면

python3 manage.py migrate accounts 0001

이런식으로 되돌릴 수 있다

뒤의 숫자는 migrations 안에 있는 파일들의 내용을 보고 되돌리고 싶은 파일의 번호를 지정하면 된다


4. unique=True인 속성으로 변경해준다

class User(models.Model):
    uuid = models.UUIDField(unique=True, default=uuid.uuid4, editable=False)
    email = models.EmailField(max_length=100, verbose_name="이메일", unique=True)
    name = models.CharField(max_length=15, verbose_name="이름")
    phonenumber = models.CharField(max_length=11, verbose_name="전화번호")

이제 최종적으로 원하는 unique=True속성을 추가해준다

migration을 하고 데이터를 확인해보면,

각각 unique속성이 잘 적용되어 저장된 것을 볼 수 있다


정리

위의 방법들은 하나의 방법이다

속성이나 migration file에 실행하는 코드는 각자 편한방법대로, 더 효율적인 방법대로 실행하길바란다


참고

나는 data reverse를 계속해서 그런지,

Django자체에서는 migrate된것으로 뜨는데 DBMS에서는 적용이 안되는 문제가 생겼었다

그 정보는 아래와 같이 확인했다

python manage.py shell
>>> from accounts.models import User
>>> User._meta.get_fields()  # 모델의 필드 확인

그래서 야매로 DBMS자체에서 컬럼을 삭제하고

migrate를 진행했다

같은 문제가 생기는 분들은 이렇게 시도해보길바란다

당연히 로컬에서...!!!

로컬은 실서버와 다르게 반복적인 reverse를 했기에 실행한 방법이다

나는 실서버에 반영된 데이터에 대해 아직 reverse를 한적 없다


레퍼런스

https://docs.djangoproject.com/en/4.1/howto/writing-migrations/#migrations-that-add-unique-fields

profile
개발자

0개의 댓글