장고에서 랜덤 문자열 저장하는 유니크 필드 추가하기
Django 프로젝트에서 slug 필드와 관련된 문제를 해결하려고 애를 먹었다.
slug 필드에 유니크한 값을 기본 값으로 지정하려다 보니 예상치 못한 문제가 발생했다.
문제
프로젝트에서 특정 모델에 slug 필드를 추가해야 했는데, 이를 유니크하게 생성하기 위해 함수를 default 값으로 사용했다. 하지만 테이블을 처음 생성할 때 다음과 같은 오류가 발생했다.
- 테이블이 없어서 쿼리 실패: 아직 테이블이 존재하지 않기 때문에 slug 생성을 위한 데이터베이스 쿼리가 실패하는 문제.
- 마이그레이션 순서 문제: 마이그레이션이 진행되면서 slug 필드의 default 함수가 실행되고, 이 과정에서 의도치 않게 문제가 발생했다.
정리해보면, 테이블이 없는 상태에서 무리하게 slug를 생성하려다 생긴 문제였다. 이런 문제를 해결하려면 어떻게 해야 할까? 조금 더 고민이 필요했다.
첫 번째 해결 방법: save 메서드 활용하기
default 값으로 직접 함수를 지정하는 대신, save 메서드에서 slug 값을 설정하는 방법을 떠올렸다. 테이블 생성 시점에는 이 메서드가 실행되지 않기 때문에 마이그레이션 시 발생할 수 있는 문제를 피할 수 있었다.
from django.utils.text import slugify
import uuid
from django.db import models
class MyModel(models.Model):
title = models.CharField(max_length=100)
slug = models.SlugField(unique=True, blank=True)
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.title) + '-' + str(uuid.uuid4())[:8]
super().save(*args, **kwargs)
이제 save 메서드가 객체 저장 시점에만 slug를 생성하게 되므로, 초기 테이블 생성 시에는 문제가 발생하지 않았다.
두 번째 해결 방법: 신규 객체에만 slug 생성하기
이제 조금 더 세부적으로 다듬어보고 싶었다. 기존에 존재하는 객체는 건드리지 않고, 새로운 객체가 생성될 때만 slug가 생성되도록 설정할 수 있을 것 같았다. 그래서 조건을 추가했다.
def save(self, *args, **kwargs):
if not self.pk and not self.slug:
self.slug = slugify(self.title) + '-' + str(uuid.uuid4())[:8]
super().save(*args, **kwargs)
이제 not self.pk 조건을 추가하여 새로운 객체에만 slug가 생성되도록 처리할 수 있었다. 덕분에 기존 객체가 불필요하게 수정되지 않는 것도 확인할 수 있었다.
세 번째 해결 방법: 마이그레이션 단계 나누기
마지막으로 생각해본 방법은 마이그레이션 시점에서 발생하는 문제를 단계적으로 해결하는 방법이었다.
- 첫 번째 단계: slug 필드를 null=True, blank=True로 설정해 테이블이 비워져 있을 때 문제가 생기지 않도록 했다.
- 두 번째 단계: 데이터가 채워진 후, slug 필드에 unique 제약 조건을 추가하고, blank=False로 변경했다.
이렇게 두 번에 걸쳐 마이그레이션을 수행하면, 초기 데이터베이스 상태와 이후 제약 조건을 안전하게 관리할 수 있었다.
마무리
slug 필드에 대한 문제를 여러 방법으로 해결하는 경험을 통해 많은 걸 배울 수 있었다.
때로는 코드 한 줄을 수정하는 것이 테이블 생성과 데이터베이스 접근에서 큰 차이를 만들기도 한다.