[Django] 2. User 모델 생성(1) - Model

김성산·2020년 11월 15일
7

안녕하세요! 레오입니다.🤗 날씨가 많이 추웠다가 최근에 많이 풀린 듯 하여 기분이 좋습니다 ㅎㅎ. 들뜬 기분과 함께 오늘부터 장고를 제대로 맛보려고 합니다. 모든 웹사이트의 기본이 되는 회원 모델을 만들어보려고 하는데요, 말씀드리고자 하는게 많아서 모델과 시리얼라이저, 뷰를 따로 분리해서 포스트하려고 합니다. 그럼 모델편을 시작하겠습니다.🏃🏃

이번 프로젝트에서는 장고의 기본 모델인 User 모델을 사용하지 않고 장고에서 제공하는 여러 클래스를 상속받아 커스터마이징 하려고 합니다. 이 모델은 🏅Django Rest API with JSON web token(JWT) authentication🏅의 코드를 참고하였으며🙏🙏, 저희 프로젝트에서 쓰고자하는 'nickname' 필드를 추가하여 작성하였습니다. 또한 'PermissionsMixin' 클래스를 상속받아 운영자 관리 권한까지 추가하였습니다.

커스터마이징을 하는 이유

팀원들과 회의를 한 결과, 이메일 인증을 통해 보안, 부정 사용자 검출 등을 이유로 id보다는 email을 기반으로 운영하자는 결론이 났습니다. email을 이용하여 회원가입 및 로그인을 하고 웹사이트에서 활동할 수 있도록 만들고자 하였습니다. 하지만 기본적으로 정의되어있는 장고의 기본 'User' 모델은 id를 기반으로 만들어지기때문에 이메일을 기반으로 할 수 있는 모델을 새로 커스터마이징 해야겠다고 판단하였습니다.

UserManager 모델

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

class UserManager(BaseUserManager):

    def create_user(self, email, nickname, password=None):
        if not email:
            raise ValueError("Users Must Have an email address")
        user = self.model(
            email=self.normalize_email(email),
            nickname=nickname,
        )
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, nickname, password):
        if password is None:
            raise TypeError("Superusers must have a password.")

        user = self.create_user(email, nickname, password)
        user.is_superuser = True
        user.is_staff = True
        user.is_active = True
        user.save()

        return user

User 모델

class User(AbstractBaseUser, PermissionsMixin):

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) 
    email = models.EmailField(
        verbose_name='email address',
        max_length=255,
        unique=True
    )
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    is_superuser = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    nickname = models.CharField(
        max_length=50,
        unique=True,
    )
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['nickname',] 

    objects = UserManager()

    def __str__(self):
        return self.email

    class Meta:
        db_table = "user"
  • UUIDField: uuid는 범용 고유 식별자입니다. uuid4는 version4를 의미합니다.
  • USERNAME_FIELD: username으로 무엇을 쓸 것인지 정하는 인스턴스입니다. 기본 User 모델의 경우 username으로 되어있지만 이 부분을 email로 바꿔주면서 email을 기본으로 세팅할 수 있습니다.
  • REQUIRED_FIELDS: 필요한 필드를 정의합니다. 'USERNAME_FIELD'와 password는 이미 required라서 넣을 필요가 없습니다. 오히려 넣으면 에러가 발생합니다.
    👉에러가 나는 과정 코드 보러가기
  • objects: 만들어진 쿼리셋이 UserManager를 상속받습니다.
  • __str__: 이 인스턴스를 조회할 때 보여지는 이름을 정의합니다. 정의하지 않으면 '<Queryset1>' 처럼 안이쁘게 보여지므로 보통 설정하는 편입니다.
  • class Meta: 데이터베이스에 담길 시 저장되는 이름을 정의합니다. 정의하지 않으면 '앱이름.클래스이름(user.User)'으로 저장됩니다.

평소 함수에 궁금한 것들이 많아 이리저리 찾아본다고 시간이 많이 소요되었던 모델이었습니다. 커뮤니티를 운영할 때 가장 기본이 되는 부분이므로 많은 신경을 써서 작성하였습니다. 만들면서 에러가 발생했던 부분, 궁금해서 찾아본 부분을 정리하고자 합니다.

에러가 발생했던 부분

  1. 필드 추가
    처음에는 'created_at'과 'updated_at' 필드를 정의하지 않았습니다. 추후에 수정하려고 했는데 이미 만들어진 인스턴스들이 있어서 추가하기 힘들었습니다. 그래서 먼저 default를 설정하여 모두 default값을 가지게 한 후 DateTimeField로 바꿨습니다. default는 DateTimeField의 규격?에 맞아야 했기 때문에 시간으로 설정하였습니다.

  2. admin 계정 로그인 불가
    createsuperuser로 생성을 하여도 /admin에 로그인이 되지 않았습니다. 문제는 두 부분이었습니다. 첫째로 해당 코드 create_superuser의 인자 부분에서 nickname과 password 순서가 달라서 적용이 되지 않았습니다. 그래서 이 순서를 정상적으로 바꿔주었습니다. 두번째로 Group과 User_permission을 요구하는데, 이 부분에서 어찌할 바를 몰라 애를 많이 먹었습니다. 이 에러는 PermissionsMixin을 상속받으면서 바로 해결되었습니다. 공들인 시간에 비해 다소 쉽게 처리하였습니다....

궁금해서 찾아본 부분

  1. if not email:
    저는 다음 포스트에서 설명드리겠지만 django rest framework를 이용하여 앱을 만들었습니다. DRF는 매우 강력한 프레임워크이고 이미 수많은 똑똑한 개발자들이 에러와 버그를 수정하였습니다. 그러므로 DRF를 사용하여 'create_user'를 호출할 시, 그 전에 email이 not이 되는 경우를 모두 걸러낼 수 있습니다. 즉 DRF덕분에 if not email 이 부분이 사실상 필요가 없다고 볼 수 있습니다.

  2. user.save:
    'objects.create' 와 차이를 알고 싶었습니다. 찾아보니 'objects.create'를 호출하면 create 함수 내부에서 받은 인자로 object를 정의하고 save를 실행합니다. 즉 'objects.create' 호출하는 것 대신에 ;user.save(using=self._db); 코드 위 부분에서 object를 정의하고(email, nickname, 해싱된 password) save를 실행한 것입니다.

  3. is_superuser, is_staff, is_active:
    구글링 해보았던 모든 블로그에서 이 부분에 대한 설명이 없었습니다. 제가 이 필드를 설정만 해놓는다고 어떻게 permission이 처리되는지 알고싶었습니다. 이 필드들 역시나 똑똑한 개발자들이 이미 모두 적용되도록 코딩을 해 놓았습니다. 추후에 알아보겠지만, 'admin' 'authenticated' 'allowany' 등으로 permission을 할 수 있는데 permission 제한을 걸어둔 부분에서 위의 세 필드가 lookup 되는 것을 확인하였습니다.

이상으로 가장 기본이 되는 User 모델의 첫 부분 모델편을 마치겠습니다! 다음 포스트에서는 시리얼라이저편을 포스트하겠습니다. 긴 글 읽어주셔서 감사드립니다!🙆‍♂️🙆‍♂️

profile
비단같은 마음씨

1개의 댓글

comment-user-thumbnail
2022년 1월 28일

도움이 많이 되었습니다!! 감사합니다!!

답글 달기