개발을 진행하다보면 unique=True인 필드를 추가해야할 때가 있다
예를들어 User model에 uuid필드를 추가한다고 해보자
필수로 존재해야하고
하나이상 존재해서는 안되는 값이다
먼저 해결방안부터 말하자면,
필드를 unique=True없이 추가해준다
빈 migration file을 생성시켜준다
migration file RunPython을 이용하여 데이터를 채워준다
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="전화번호")
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 으로 값을 할당해줘야한다
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 = []
# 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 안에 있는 파일들의 내용을 보고 되돌리고 싶은 파일의 번호를 지정하면 된다
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