[Django 를 이용한 사이드 프로젝트 myBox] 002. User 모델 커스터마이징

계정관리용 accounts app

accounts app 생성 및 프로젝트 settings

python manage.py startapp accounts

 

위 명령어를 입력해 새로운 앱 accounts 를 만들면 아래와 같이 디렉토리로 묶여진 accounts app 이 생성된다.

accounts app 구조

만들어진 accounts app 을 myBox 가 사용하기 위해서는 settings.py INSTALLED_APPS 목록에 추가해주어야 한다.

User 모델

Django 에는 직접 제공하고 있는 User Model이 존재한다. 많은 기능을 포함하고 있기 때문에 직접 User 를 정의해주기보단, 해당 클래스를 상속받아 커스텀하는 방식을 많이 사용한다.


표준 User 모델

표준 User 모델은 AbstractUser 를 상속받는다.

class User(AbstractUser):
    class Meta(AbstractUser.Meta):
        swappable = "AUTH_USER_MODEL"

아래는 AbstractUser 에서 필요한 부분만 가져온 소스이다.

class AbstractUser(AbstractBaseUser, PermissionsMixin):
    username_validator = UnicodeUsernameValidator()

    username = models.CharField(_("username"), max_length=150, unique=True, validators=[username_validator])
    first_name = models.CharField(_("first name"), max_length=150, blank=True)
    last_name = models.CharField(_("last name"), max_length=150, blank=True)
    email = models.EmailField(_("email address"), blank=True)
    is_staff = models.BooleanField(_("staff status"), default=False)
    is_active = models.BooleanField(_("active"), default=True)
    date_joined = models.DateTimeField(_("date joined"), default=timezone.now)

    objects = UserManager()

    EMAIL_FIELD = "email"
    USERNAME_FIELD = "username"
    REQUIRED_FIELDS = ["email"]

    class Meta:
        verbose_name = _("user")
        verbose_name_plural = _("users")
        abstract = True

username, first_name 등 미리 지정된 속성(칼럼)들이 존재하며, UserManager() 를 호출하여 objects 속성을 정의한다.

 

Custom User 모델

myBox 서비스에서는

  • email, password 를 통해 회원가입
  • unique nickname

으로 진행하려 하기 때문에 first_name 과 같은 칼럼은 불필요하다.

아래는 서비스에 맞게 커스텀한 User 모델의 예시이다.

class User(AbstractBaseUser, PermissionsMixin):

    # email unique 추가
    email = models.EmailField(
        _("email address"),
        unique=True,
        error_messages={
            "unique": _("이미 등록된 이메일입니다."),
        },
    )
	
    # nickname 추가
    nickname = models.CharField(
        _("nickname"),
        max_length=6,
        unique=True,
        help_text=_(
            "6글자 이하의 한글, 영어, 숫자만 포함할 수 있습니다."
        ),
        validators=[validate_nickname],
    )

    # AbstractUser 와 동일
    is_staff = models.BooleanField(
        _("staff status"),
        default=False,
        help_text=_("Designates whether the user can log into this admin site."),
    )
    is_active = models.BooleanField(
        _("active"),
        default=True,
        help_text=_(
            "Designates whether this user should be treated as active. "
            "Unselect this instead of deleting accounts."
        ),
    )
    date_joined = models.DateTimeField(_("date joined"), default=timezone.now)

    # 커스텀한 UserManager 사용
    objects = CustomUserManager()

    EMAIL_FIELD = "email"
    # 회원가입 시 email 필드를 사용
    USERNAME_FIELD = "email"
    # 값을 필수로 받아야 하는 필드
    REQUIRED_FIELDS = ["nickname"]

    class Meta:
        verbose_name = _("user")
        verbose_name_plural = _("users")

    def __str__(self):
        return self.nickname

    def email_user(self, subject, message, from_email=None, **kwargs):
        send_mail(subject, message, from_email, [self.email], **kwargs)

AbstractUser 클래스를 참고해서 칼럼 정보를 변경해주었다.

 

email 의 경우 EmailField 에서 자동으로 이메일 포맷에 대한 유효성 검사를 해주지만, nickname 에 설정한 CharField 는 따로 지정해주어야 한다.

line20을 보면 validators=[validate_nickname], 이 있는데 validate_nickname 은 직접 정의한 validator이다.

# accounts/validators.py
import re

from django.core.exceptions import ValidationError


def validate_nickname(value):
	if not re.match(r'^[가-힣a-zA-Z0-9]+$', value):
		raise ValidationError("닉네임은 한글, 영어, 숫자만 포함할 수 있습니다.", params={'value': value})

표준 UserManager

Custom한 User 모델의 line 40을 보면  objects = CustomUserManager() 이 있다.

AbstractUser 클래스를 보면 objects = UserManager() 로 정의되어 있는데 Manager 는 장고 모델의 헬퍼클래스로 ORM 의 기능을 담당한다. (User.objects.all() User.objects.filter(~~))

UserManager 는 BaseUserManager 를 상속받아 유저 생성, 슈퍼유저 생성에 대한 메서드가 정의되어 있다.

class UserManager(BaseUserManager):
    use_in_migrations = True

    def _create_user(self, username, email, password, **extra_fields):
        if not username:
            raise ValueError("The given username must be set")
        email = self.normalize_email(email)
        username = GlobalUserModel.normalize_username(username)
        user = self.model(username=username, email=email, **extra_fields)
        user.password = make_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, username, email=None, password=None, **extra_fields):
        extra_fields.setdefault("is_staff", False)
        extra_fields.setdefault("is_superuser", False)
        return self._create_user(username, email, password, **extra_fields)

    def create_superuser(self, username, email=None, password=None, **extra_fields):
        extra_fields.setdefault("is_staff", True)
        extra_fields.setdefault("is_superuser", 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.")

        return self._create_user(username, email, password, **extra_fields)

 

CustomUserManager

myBox 에서는 username 이 아닌 email, nickname 을 사용하기에 이 역시 커스텀해주었고 그것이 바로 CustomUserManager 이다.

class CustomUserManager(BaseUserManager):
    use_in_migrations = True

    def _create_user(self, nickname, email, password, **extra_fields):
        # nickname, email 입력 확인
        if not nickname:
            raise ValueError("nickname을 입력해주세요.")
        if not email:
            raise ValueError("email을 입력해주세요.")
        email = self.normalize_email(email)
        user = self.model(email=email, nickname=nickname, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, nickname, email, password=None, **extra_fields):
        extra_fields.setdefault("is_staff", False)
        extra_fields.setdefault("is_superuser", False)
        return self._create_user(nickname, email, password, **extra_fields)

    def create_superuser(self, nickname, email, password=None, **extra_fields):
        extra_fields.setdefault("is_staff", True)
        extra_fields.setdefault("is_superuser", 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.")

        return self._create_user(nickname, email, password, **extra_fields)

Migrate

이제 만들어진 User 모델을 DB에 migrate 하자

makemigrations 명령어를 이용해 변경된 모델정보를 migration file 로 생성한다.

python manage.py makemigrations accounts

 

 

migration 에 성공하면 파일이 생성된다.

sqlmigrate 명령어를 이용해 SQL 을 확인할 수 있고, migrate 를 이용해 DB에 변경사항을 적용할 수 있다.

# DB 에 실행하는 SQL 확인
python manage.py sqlmigrate accounts 0001

# DB에 SQL 실행
python manage.py migrate accounts

만들어진 sqlite3 파일을 이용해 DB 연결 후 조회해보면, accounts_user 테이블이 생성됐음을 확인할 수 있다.

{app}_{model} 이 default 로 설정된 테이블이름이며, 이는 모델정의 시 Meta 를 통해 변경할 수 있으나, 여기에서는 변경하지 않도록 하겠다.

그 외 테이블은 상속받은 표준 모델들로 인해 만들어지는 것이며, 이를 이용해 장고에서 사용자를 관리한다.

 

다음 포스팅은 User 모델을 이용한 회원가입, 로그인, 로그아웃 구현이다.

top
bottom