2026/04/09 Promise - 5

김기훈·2026년 4월 9일

TIL

목록 보기
186/194

모델 작성

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" 처럼 개발자가 제약조건의 이름을 직접 명시할 수 있어서
    • 나중에 데이터베이스를 직접 조회하거나 에러 로그를 추적할 때
      • 어떤 부분에서 막혔는지 직관적으로 파악하기 좋음
      • 게다가 특정 조건일 때만 중복을 막는 등(예: 활성 상태인 유저만 중복 방지)
      • 훨씬 강력하고 유연한 확장이 가능

마이그레이션

커스텀 모델 등록

  • 장고는 특별한 말이 없으면 자신이 원래 가지고 있던 기본 유저 모델을 사용해서 데이터베이스를 만들어 버림
    • 만약 방금 만든 커스텀 유저 모델을 설정하지 않고 마이그레이션을 먼저 해버리면
    • 나중에 구조를 바꾸기가 매우 까다로워짐
# settings.py 아래 부분 
# 장고의 기본 인증 모델 대신 만든 User 앱의 User 모델을 사용하도록 지정
AUTH_USER_MODEL = 'User.User'

명령어

  • 변경된 모델 코드를 스캔하여 새로운 마이그레이션 파일들을 생성
    • python manage.py makemigrations
  • 생성된 마이그레이션 파일(설계도)을 바탕으로 실제 데이터베이스에 테이블을 만듬
    • python manage.py migrate

black 경고문

  • 해석

    • "파이썬 3.13 버전은 파이썬 3.14 버전용으로 정리된 코드를 해석할 수 없습니다"
    • 현재 Black 도구가 최신 문법인 Python 3.14를 기본 기준으로 삼고 코드를 정리해 버림
      • 하지만 내가 현재 사용하는 파이썬 버전은 3.13임 그래서 경고문 나옴

해결

  • pyproject.toml에 아래 내용 추가
[tool.black]
# Black이 코드를 포맷팅할 때 파이썬 3.13 버전을 기준으로 동작하도록 영구적으로 설정
target-version = ['py313']

profile
안녕하세요.

0개의 댓글