User

김기훈·2026년 2월 15일

이론

목록 보기
7/10
post-thumbnail

User model

from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.models import PermissionsMixin
from django.db import models

from apps.core.models import TimeStampedModel
from apps.user.managers import UserManager


class User(AbstractBaseUser, PermissionsMixin, TimeStampedModel):
    email = models.EmailField(
        max_length=255,
        unique=True,
    )

    nickname = models.CharField(
        max_length=50,
    )

    profile_img = models.CharField(max_length=255, null=True, blank=True)

    bio = models.CharField(max_length=150, null=True, blank=True)

    # 활성화 여부: True면 로그인 가능, False면 계정 정지 등의 상태(기본값은 True)
    is_active = models.BooleanField(default=True)
    # 관리자 사이트 접속 권한 여부: True면 admin 페이지 접속 가능
    is_staff = models.BooleanField(default=False)

    objects = UserManager()

    # 로그인 시 식별자로 사용할 필드를 지정합니다. 여기서는 'email'을 아이디로 씁니다.
    USERNAME_FIELD = "email"  # 로그인은 이메일로
    # createsuperuser 커맨드로 관리자 계정을 만들 때, 이메일/비번 외에 추가로 입력받을 필드입니다.
    REQUIRED_FIELDS = ["nickname"]  # createsuperuser 할 때 물어볼 필드

    class Meta:
        db_table = "users"
        verbose_name = "사용자"
        verbose_name_plural = "사용자 목록"

    def __str__(self) -> str:
        return f"{self.nickname} ({self.email})"

UserManager ✅

  • Django의 Manager는 데이터베이스 질의(Query) 작업이 인터페이스되는 통로

    • User.objects.create_user()와 같은 메서드를 호출할 때 실제 내부 로직을 담당하는 '유저 생성 대리인'
  • 특징

    • BaseUserManager 상속
      • 유저 생성에 필요한 헬퍼 메서드(예: 비밀번호 암호화)를 제공받음
    • 모델과의 연결
      • 이 Manager는 나중에 작성하실 AbstractBaseUser 모델 내부에서
        • objects = UserManager() 와 같이 연결되어 사용

create_user

  • 일반 사용자를 생성할 때 호출되는 메서드

    • normalize_email

      • BaseUserManager에서 제공하는 메서드
      • test@EXAMPLE.COMtest@example.com 으로 변환하여 동일성을 보장
    • set_password

      • 보안을 위해 평문 비밀번호를 PBKDF2 알고리즘 등으로 암호화(Hashing)
      • DB에 직접 비밀번호를 저장하지 않는 보안 표준을 따름
def create_user(self, email, nickname, password=None, **extra_fields):
    if not email:
        raise ValueError("이메일은 필수 입력값입니다.")

    # 1. 이메일 정규화 (Domain 부분을 소문자로 변환)
    email = self.normalize_email(email)
    
    # 2. 유저 객체 생성 (self.model은 이 Manager가 연결된 User 모델을 의미함)
    user = self.model(email=email, nickname=nickname, **extra_fields)

    # 3. 비밀번호 설정
    if password:
        user.set_password(password) # 해시(Hash)화하여 저장
    else:
        # 소셜 로그인 등 비밀번호가 없는 경우 로그인을 직접 못 하도록 설정
        user.set_unusable_password()

    # 4. 데이터베이스 저장
    user.save(using=self._db)
    return user

create_superuser

  • 관리자 페이지(admin/)에 접근 가능한 운영자를 생성할 때 호출
def create_superuser(self, email, nickname, password, **extra_fields):
    # 권한 관련 기본값 설정
    extra_fields.setdefault("is_staff", True)     # 관리자 페이지 접속 권한
    extra_fields.setdefault("is_superuser", True) # 모든 권한 허용
    extra_fields.setdefault("is_active", True)    # 계정 활성화 상태

    # 권한 검증 로직 (필수 사항)
    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를 재활용하여 실제 유저 생성
    return self.create_user(email, nickname, password, **extra_fields)

manager 필요 이유

  • Django의 기본 유저 시스템은 username을 식별자로 사용 하지만 현대적인 서비스는 대부분 email 을 아이디로 사용
    • 식별자 변경

      • username 대신 email을 필수값으로 받기 위해 로직 수정이 필요
    • 확장성

      • nickname 같은 커스텀 필드를 유저 생성 시점에 안전하게 주입하기 위함
    • 보안 유지

      • 관리자가 직접 유저를 생성하든, 사용자가 회원가입을 하든
      • 비밀번호 해싱 로직이 누락되지 않도록 캡슐화(Encapsulation)하는 과정

AbstractBaseUser vs AbstractUser ✅


AbstractUser

  • Django가 기본으로 제공하는 유저 모델(User)의 기능을 그대로 유지하면서, 새로운 필드만 추가하고 싶을 때 사용
    • 장점

      • username 기반 로그인을 그대로 쓰고, 추가 정보(전화번호, 주소 등)만 더하면 되므로 매우 간편
    • 단점

      • 사용하지 않는 기본 필드(first_name, last_name 등)가 DB에 그대로 생성
from django.contrib.auth.models import AbstractUser
from django.db import models

class User(AbstractUser):
    # 기본 필드(username, email 등)는 이미 존재함
    phone_number = models.CharField(max_length=15, blank=True) # 추가 필드만 정의

AbstractBaseUser

  • 사용자 식별 방식 자체를 바꾸고 싶을 때(예: ID 대신 Email로 로그인) 사용(UserManager와 함께 사용)
    • 장점

      • 불필요한 필드 없이 딱 필요한 데이터만 담을 수 있어 DB가 깔끔
      • 로그인 로직을 완전히 자유롭게 짤 수 있음
    • 단점

      • 관리자 페이지(Admin) 연동, 권한 체크 등을 위해 추가적인 코딩이 많이 필요
from django.db import models
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
# 앞에서 만든 UserManager를 가져옵니다.
# from .managers import UserManager 

class User(AbstractBaseUser, PermissionsMixin): 
    email = models.EmailField(unique=True) # 이메일을 식별자로 사용
    nickname = models.CharField(max_length=20)
    is_staff = models.BooleanField(default=False)
    
    # 이 모델에서 아이디로 사용할 필드 지정
    USERNAME_FIELD = 'email'
    # createsuperuser 실행 시 추가로 물어볼 필드
    REQUIRED_FIELDS = ['nickname']

    # 위에서 만든 UserManager 연결
    objects = UserManager() 

    def __str__(self):
        return self.email

요약

  • "나는 username이 필요 없고, email을 아이디로 쓰고 싶다"

    • AbstractBaseUser

  • "Django 기본 기능은 다 좋은데, 생년월일 필드 하나만 더 있으면 좋겠다"

    • AbstractUser


AUTH_USER_MODEL ✅

  • Django에게 "이제 네가 기본으로 제공하는 유저 말고 내가 만든 유저를 써!"

    • 라고 알려주는 설정
    • 이 설정을 안 하면 UserManager와 AbstractBaseUser가 무용지물
    • 이 설정은 프로젝트 첫 마이그레이션(migrate) 전에 하는 것이 가장 좋음

PermissionsMixin ✅

  • AbstractBaseUser를 사용하여 커스텀 유저 모델을 만들 때
    • 반드시 세트로 따라오는 단짝이 바로 PermissionsMixin

PermissionsMixin이란?

  • Django의 인증 시스템은 크게 두 가지로 나뉨

    • Authentication (인증)
      • 이 사용자가 누구인가? (ID/PW 확인) -> AbstractBaseUser 가 담당
    • Authorization (권한)
      • 이 사용자가 이 작업을 할 권한이 있는가? -> PermissionsMixin 이 담당
  • AbstractBaseUser 는 매우 가볍기 때문에 권한(Groups, Permissions)과 관련된 필드나 메서드가 전혀 없음
    • 그래서 Django의 기본 권한 시스템을 그대로 쓰고 싶다면 이 Mixin을 상속받아야 함

PermissionsMixin이 추가해주는 것들

  • 이 클래스를 상속받는 순간, 유저 모델에는 다음과 같은 필드와 메서드들이 자동으로 추가

    • 주요 필드 (Database Fields)

      • is_superuser
        • 모든 권한을 가진 사용자임을 나타내는 불리언 필드입니다.
      • groups
        • 사용자가 속한 그룹(Group 모델)과의 다대다(Many-to-Many) 관계 필드입니다.
      • user_permissions
        • 사용자에게 직접 부여된 개별 권한과의 다대다 관계 필드입니다.
    • 주요 메서드 (Logic Methods)

      • has_perm(perm)
        • 특정 권한이 있는지 확인합니다.
      • has_module_perms(app_label)
        • 특정 앱의 모델에 접근할 권한이 있는지 확인합니다.
      • get_all_permissions()
        • 사용자가 가진 모든 권한 리스트를 반환합니다.

코드 예시

from django.db import models
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin

class User(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(unique=True, verbose_name="이메일 주소")
    nickname = models.CharField(max_length=20, verbose_name="닉네임")
    
    # 관리자 페이지 접근 여부 (PermissionsMixin과 별개로 관리자가 되기 위한 필수 필드)
    is_staff = models.BooleanField(default=False)
    # 계정 활성화 여부
    is_active = models.BooleanField(default=True)

    # 1. 헬퍼 클래스 연결
    objects = UserManager()

    # 2. 로그인에 사용할 필드 지정
    USERNAME_FIELD = 'email'
    
    # 3. 필수 입력 필드 (createsuperuser 실행 시 물어볼 항목)
    REQUIRED_FIELDS = ['nickname']

    def __str__(self):
        return self.email

    class Meta:
        verbose_name = "사용자"
        verbose_name_plural = "사용자들"

상속받지 않을 경우

  • Django Admin 사용 불가

    • 관리자 페이지에 로그인하려고 해도 "권한이 없습니다"라는 메시지를 보게 됨
    • Admin 내부에서 has_perm 같은 메서드를 호출하는데, 이 메서드가 유저 모델에 없기 때문
  • 권한 데코레이터 사용 불가

    • @permission_required 같은 편리한 기능을 쓸 수 없음
  • 직접 구현의 늪

    • 그룹 관리, 개별 권한 할당 로직을 처음부터 끝까지 SQL이나 ORM으로 직접 짜야 함

profile
안녕하세요.

0개의 댓글