모델 작성
User
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
nickname = models.CharField(max_length=50, unique=True)
push_enabled = models.BooleanField(default=True)
def __str__(self):
return self.nickname
AbstractBaseUser vs AbstractUser
- 이론 바로가기
- username를 기반으로 회원을 식별할거임
- AbstractBaseUser를 사용하면 설계의 자유도가 높아지지만 관리해야 할 부분이 많아짐
고민
AbstractUser를 사용해도 소셜로그인을 구현할때 문제가 없을까?
- 검증된 라이브러리와의 호환성
- 장고에서 소셜 로그인을 구현할 때는 주로 django-allauth 라는 외부 라이브러리를 사용
- 이 라이브러리는 AbstractUser의 구조를 기본값으로 상정하고 만들어졌기 때문에
- 뼈대를 억지로 뜯어고칠 필요 없이 아주 매끄럽게 연동됨
- 필수 필드(username)의 스마트한 자동 처리
- AbstractUser는 가입 시 아이디(username)를 필수로 요구함
- 그런데 소셜 로그인은 보통 아이디 대신 이메일이나 긴 고유 식별 번호를 넘겨줌
- 충돌이 날 것 같지만, 이전에 이야기한 라이브러리들이 소셜에서 받아온 고유 정보를
- 조합하여 겹치지 않는 임시 아이디를 자동으로 만들어 username 필드에 채워줌
- 따라서 모델 구조를 전혀 바꿀 필요가 없음
- 비밀번호 없는 계정의 안전한 관리
- 소셜 로그인으로 가입한 유저는 데이터베이스에 비밀번호를 남기지 않음
- 장고의 AbstractUser는 이런 상황을 대비하여 사용할 수 없는
- 비밀번호(Unusable Password) 상태를 지정하는 강력한 보안 기능을 이미 내장하고 있음
- 덕분에 해킹 위험 없이 소셜 유저를 안전하게 관리 가능
Event
from django.db import models
from django.conf import settings
class Event(models.Model):
title = models.CharField(max_length=100)
is_business = models.BooleanField(default=False)
host = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='hosted_events')
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
EventMember
from django.db import models
from apps.Event.models import Event
from django.conf import settings
class EventMember(models.Model):
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='members')
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='participated_events')
can_invite = models.BooleanField(default=False)
agree_delete = models.BooleanField(default=False)
class Meta:
constraints = [
models.UniqueConstraint(
fields=['event', 'user'],
name='unique_event_member'
)
]
def __str__(self):
return f"{self.user.username} in {self.event.title}"
TimeSlot
from django.db import models
from apps.EventMember.models import EventMember
class TimeSlot(models.Model):
member = models.ForeignKey(EventMember, on_delete=models.CASCADE, related_name='time_slots')
start_time = models.DateTimeField()
end_time = models.DateTimeField()
def __str__(self):
return f"{self.member.user.username}: {self.start_time} ~ {self.end_time}"
PlaceCandidate
from django.db import models
from apps.Event.models import Event
class PlaceCandidate(models.Model):
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='place_candidates')
place_name = models.CharField(max_length=255)
category = models.CharField(max_length=50)
latitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)
longitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)
vote_count = models.IntegerField(default=0)
def __str__(self):
return self.place_name
인덱스
표기 방식
class Meta:
unique_together = ('event', 'user')
——————————————————————————————————————[비교]—————————————————————————————————————————
class Meta:
constraints = [
models.UniqueConstraint(
fields=['event', 'user'],
name='unique_event_member'
)
]
unique_together
- 장고의 초기 버전부터 오랫동안 쓰이던 전통적인 문법
- 코드가 짧고 직관적이라는 장점이 있음
- 하지만 데이터베이스에 생성되는 제약조건의 이름(name)을 개발자가 마음대로 정할 수 없고
- 현재 장고 공식 문서에서는 이 방식을 구형(Legacy)으로 분류하며
- 향후 지원 중단을 염두에 두고 점진적으로 사용을 줄이도록 권고하고 있음
constraints
- 장고 2.2 버전부터 도입된 최신 문법
- name="unique_event_member" 처럼 개발자가 제약조건의 이름을 직접 명시할 수 있어서
- 나중에 데이터베이스를 직접 조회하거나 에러 로그를 추적할 때
- 어떤 부분에서 막혔는지 직관적으로 파악하기 좋음
- 게다가 특정 조건일 때만 중복을 막는 등(예: 활성 상태인 유저만 중복 방지)
- 훨씬 강력하고 유연한 확장이 가능
마이그레이션
커스텀 모델 등록
- 장고는 특별한 말이 없으면 자신이 원래 가지고 있던 기본 유저 모델을 사용해서 데이터베이스를 만들어 버림
- 만약 방금 만든 커스텀 유저 모델을 설정하지 않고 마이그레이션을 먼저 해버리면
- 나중에 구조를 바꾸기가 매우 까다로워짐
AUTH_USER_MODEL = 'User.User'
명령어
- 변경된 모델 코드를 스캔하여 새로운 마이그레이션 파일들을 생성
- python manage.py makemigrations
- 생성된 마이그레이션 파일(설계도)을 바탕으로 실제 데이터베이스에 테이블을 만듬
black 경고문

해석
- "파이썬 3.13 버전은 파이썬 3.14 버전용으로 정리된 코드를 해석할 수 없습니다"
- 현재 Black 도구가 최신 문법인 Python 3.14를 기본 기준으로 삼고 코드를 정리해 버림
- 하지만 내가 현재 사용하는 파이썬 버전은 3.13임 그래서 경고문 나옴
해결
[tool.black]
# Black이 코드를 포맷팅할 때 파이썬 3.13 버전을 기준으로 동작하도록 영구적으로 설정
target-version = ['py313']