2026/01/24~25

김기훈·2026년 1월 24일

TIL

목록 보기
120/194
# Keep (이번 주에 잘한 일)
- 1. 전날 구현내용 / 오늘 구현내용을 프론트와 매일 공유함
- 2. 구현중에 필요없다 생각드는 부분은 프론트와 협의하여 바로바로 제거 및 추가함
- 3. 멘토링 지시사항을 반영하여 최적화를 실시함

# Problem (고쳐야 할 점)
- 1. 이번주는 철저하게 잘했다고 생각함

# Try (Keep과 Problem을 기반으로 시도해볼 것)
- 1. 프론트와 대화를 좀 늘리니 충돌이 거의 없었음

구현 현황

  • 기획문서 작성 및 프로젝트 세팅
    • 내가 한 일
      • git organization 구성 / git webhook 연결 / ci 설정 / 도커세팅
      • README.md 작성 / EMS 구현 / swagger세팅 / ERD작성
    • 경과시간
      • 2026/01/06 ~ 2026/01/11
  • 각자 맡은 파트 구현
    • 나의 파트
      • 커뮤니티 리뷰CRUD
      • 댓글CRUD
      • "좋아요"기능
      • ai 리뷰요약 시스템(비동기/욕설1차필터)
    • 경과시간
      • 2026/01/12 ~ 2026/01/22 구현 끝
  • 결과적으로 2주정도 시간이 남았기 때문에 개인프로젝트를 진행함



user

AbstractBaseUser

  • "가장 기초적인 유저 모델의 뼈대"
    • Django가 기본적으로 제공하는 유저 모델(User)이 있지만
    • 이를 그대로 쓰기보다 현재 서비스에 맞게 커스터마이징하고 싶을 때 사용함
  • 특징

    • 정말 최소한의 기능만 들어 있음
      • 비밀번호 저장 기능, 마지막 로그인 시간 등
  • 사용 이유

    • Django 기본 유저는 username(아이디)이 필수임
      • 하지만 요즘 서비스는 email로 로그인하는 경우가 많음
    • 이때 AbstractBaseUser를 상속받아 유저 모델을 만들면
      • 아이디 필드를 없애고 이메일을 ID로 쓰도록 완전히 뜯어고칠 수 있음
  • 주의점

    • 권한 관리(Permissions) 기능이 없으므로
    • 보통 PermissionsMixin이라는 클래스와 함께 상속받아 사용함

TimeStampedModel

from django.db import models

class TimeStampedModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)  
    # auto_now_add=True는 처음 생성될 때만 현재 시간을 넣음
    updated_at = models.DateTimeField(auto_now=True) 
    # auto_now=True는 저장(save)될 때마다 현재 시간으로 갱신

    class Meta:
        abstract = True 
    """
    abstract=True로 설정하면 이 모델은 DB에 테이블을 만들지 않고,
    이를 상속받는 자식 모델들에게 필드만 물려주는 '추상 클래스'가 됨
    """
  • "생성 시간과 수정 시간을 자동으로 기록하는 틀"
    • 이건 Django 내장 기능이 아니라
      • 개발자들이 관례적으로 많이 만드는(혹은 라이브러리로 쓰는) 추상 모델
  • 기능

    • 데이터가 언제 생성되었는지(created_at)
      • 언제 마지막으로 수정되었는지(updated_at)를 자동으로 기록
  • 사용 이유

    • 회원가입 날짜, 글 쓴 날짜, 프로필 수정 날짜 등은 거의 모든 데이터에 필요
    • 모든 모델마다 이 두 필드를 일일이 적는 것은 비효율적(코드 중복)
    • 이 모델을 만들어두고 다른 모델들이 상속받게 하면, 자동으로 시간 기록 기능이 생김

UserManager (BaseUserManager)

class UserManager(BaseUserManager):
    # 일반 유저를 생성하는 메서드입니다. email과 password는 필수입니다.
    def create_user(self, email, password=None, **extra_fields):
        # 이메일이 없으면 에러를 발생시킵니다. (방어 코드)
        if not email:
            # ValueError는 값이 부적절할 때 사용하는 파이썬 내장 에러입니다.
            raise ValueError('이메일은 필수입니다.')
        
        # 입력받은 이메일 주소를 정규화(소문자 변환 등) 하여 유저 객체를 메모리에 생성합니다.
        user = self.model(email=self.normalize_email(email), **extra_fields)
        # 비밀번호를 평문으로 저장하지 않고, 해시(암호화)하여 설정합니다.
        user.set_password(password)
        # 설정된 유저 객체를 실제 데이터베이스에 저장합니다. (using=self._db는 다중 DB 지원을 위함)
        user.save(using=self._db)
        # 생성된 유저 객체를 반환합니다.
        return user

    # 슈퍼유저(최고 관리자)를 생성하는 메서드입니다.
    def create_superuser(self, email, password):
        # 위의 create_user 메서드를 재활용하여 일단 유저를 만듭니다.
        user = self.create_user(email, password)
        # 관리자 사이트 접속 권한을 부여합니다. (PermissionsMixin에서 옴)
        user.is_staff = True
        # 모든 권한을 가진 슈퍼유저 권한을 부여합니다. (PermissionsMixin에서 옴)
        user.is_superuser = True
        # 변경된 권한 정보를 데이터베이스에 저장합니다.
        user.save(using=self._db)
        # 생성된 관리자 객체를 반환합니다.
        return user
  • "유저 생성(create_user)과 관리자 생성(create_superuser)을 담당합니다."
    • AbstractBaseUser로 유저 모델을 새로 정의했다면,
    • Django는 "그래서 유저는 어떻게 생성하는데?"라고 묻게 됨
      • 이 생성 방법을 정의하는 곳
  • 역할

    • create_user()
      • 일반 유저를 만드는 로직 (비밀번호 암호화 저장 등)
    • create_superuser()
      • 관리자(superuser)를 만드는 로직
  • 필요한 이유

    • 우리가 유저 모델에서 username을 없애고 email을 쓰기로 했다면
      • 유저를 생성할 때도 username 대신 email을 받아야함, 그 로직을 여기서 코딩해줘야 함

커스텀 유저 모델 정의

# AbstractBaseUser(인증기능), PermissionsMixin(권한기능), TimeStampedModel(시간기록)을 상속받습니다.
class User(AbstractBaseUser, PermissionsMixin, TimeStampedModel):
    # 이메일을 고유값(unique=True)으로 설정하여 ID처럼 사용합니다.
    email = models.EmailField(unique=True)
    # 사용자의 닉네임을 저장할 필드입니다.
    nickname = models.CharField(max_length=30)
    # 관리자 페이지 접속 가능 여부입니다. (기본값은 False)
    is_staff = models.BooleanField(default=False)
    # 계정 활성화 여부입니다. (이메일 인증 전에는 False로 두는 로직 등에 사용)
    is_active = models.BooleanField(default=True)

    # 위에서 만든 UserManager 클래스를 이 모델의 매니저로 연결합니다.
    # 이제 User.objects.create_user()를 호출하면 UserManager의 코드가 실행됩니다.
    objects = UserManager()

    # Django에게 'username' 필드 대신 'email' 필드를 식별자로 쓰겠다고 알립니다.
    USERNAME_FIELD = 'email'
    # 슈퍼유저 생성 시(createsuperuser 커맨드) 비밀번호 외에 추가로 물어볼 필드입니다.
    REQUIRED_FIELDS = ['nickname']

    # 객체를 문자열로 표현할 때 이메일을 리턴합니다. (관리자 페이지 등에서 보임)
    def __str__(self):
        return self.email

User

managers.py

# Django의 기본 유저 관리 기능을 상속받습니다.
# (비밀번호 정규화, 기본적인 유저 생성 로직 등의 뼈대가 들어있습니다.)
from django.contrib.auth.base_user import BaseUserManager


class UserManager(BaseUserManager):
    # 일반 유저를 생성하는 메서드입니다.
    # **extra_fields는 이메일, 닉네임 외의 추가 정보(예: 나이, 주소)를 유연하게 받기 위해 사용합니다.
    def create_user(self, email, nickname, password=None, **extra_fields):
        
        # [Validation] 이메일이 없으면 에러를 발생시켜 DB 저장을 막습니다.
        if not email:
            raise ValueError("이메일은 필수 입력값입니다.")

        # [Preprocessing] 이메일의 도메인 부분(@뒤쪽)을 소문자로 변환하여 정리합니다.
        # 예: User@Example.com -> User@example.com (중복 가입 방지)
        email = self.normalize_email(email)

        # [Instantiation] 메모리 상에 유저 객체(인스턴스)를 생성합니다. 아직 DB에는 안 들어갔습니다.
        # self.model은 이 매니저가 연결된 모델 클래스(User)를 가리킵니다.
        user = self.model(email=email, nickname=nickname, **extra_fields)

        # [Password Logic] 비밀번호가 있는 경우 (일반 회원가입)
        if password:
            # ★ 핵심: 비밀번호를 평문으로 저장하지 않고, 암호화(Hashed)하여 저장합니다.
            user.set_password(password)
        else:
            # 비밀번호가 없는 경우 (소셜 로그인 등)
            # 사용 불가능한 비밀번호로 설정하여, 비밀번호 방식의 로그인을 막습니다.
            user.set_unusable_password()

        # [Execute Query] 실제 데이터베이스에 INSERT 쿼리를 날려 저장합니다.
        # using=self._db는 다중 DB 환경에서도 현재 매니저가 속한 DB에 저장하겠다는 안전장치입니다.
        user.save(using=self._db)
        
        # 생성된 유저 객체를 반환합니다.
        return user

    # 관리자(Superuser)를 생성하는 메서드입니다.
    # createsuperuser 커맨드를 실행할 때 호출됩니다.
    def create_superuser(self, email, nickname, password, **extra_fields):
        
        # 관리자는 스태프 권한(관리자 페이지 접속)이 있어야 합니다.
        extra_fields.setdefault("is_staff", True)
        
        # 관리자는 모든 권한(superuser)을 가져야 합니다.
        extra_fields.setdefault("is_superuser", True)
        
        # 관리자는 계정이 활성화되어 있어야 합니다.
        extra_fields.setdefault("is_active", True)

        # [Safety Check] 의도치 않게 권한이 False로 들어오는 것을 방지합니다.
        if extra_fields.get("is_staff") is not True:
            raise ValueError("Superuser must have is_staff=True.")
        if extra_fields.get("is_superuser") is not True:
            raise ValueError("Superuser must have is_superuser=True.")

        # 위에서 설정한 권한들을 가지고 create_user를 재사용하여 실제 생성을 진행합니다.
        # (DRY 원칙: 중복 코드 방지)
        return self.create_user(email, nickname, password, **extra_fields)
  • 데이터의 생성(Create) 로직을 담당하는 특수한 매니저
    • 이 코드의 존재이유는 "비밀번호 암호화"와 "필수 필드 전처리" 때문
  • Django 기본 User 모델을 쓰지 않고 커스텀 유저 모델을 만들 때는
    • "유저를 DB에 저장할 때 비밀번호를 그냥 텍스트로 넣으면 안 되고, 반드시 해시(암호화) 필요"
    • 라는 규칙을 이 매니저 파일(create_user 메서드)에 정의해야 함
  • 커스텀 유저 매니저의 표준 패턴

    • normalize_email
      • 이메일 주소의 대소문자 문제로 인한 중복 가입을 방지하는 표준 방식
    • set_password
      • 보안의 핵심, 절대 직접 user.password = password라고 쓰면 안 됨
    • set_unusable_password
      • 요즘 트렌드인 소셜 로그인(구글, 카카오 등)까지 고려하여 유연하게 작성되었습니다.
    • using=self._db
      • 나중에 데이터베이스가 여러 개로 늘어날 경우(Replication 등)를 대비한 안전한 코드

참조

import

  • Django에서는 모델 간에 관계(ForeignKey, ManyToManyField)를 맺을 때
    • 직접 클래스를 import 하지 않고 '앱이름.모델이름' 문자열로 적는 것을 강력히 권장
from django.db import models
from apps.post.models.post import Post 

class PostTag(models.Model):
	...
    # 직접 클래스를 넣음 ❌
    post = models.ForeignKey(Post, on_delete=models.CASCADE)
    
——————————————————————————————————————[비교]—————————————————————————————————————————
from django.db import models

class PostTag(models.Model):
	...
    # '앱이름.모델이름' 문자열로 변경
    post = models.ForeignKey('post.Post', on_delete=models.CASCADE)

login

view

profile
안녕하세요.

0개의 댓글