장고에서 제공되는 authentication은 대부분의 경우에 적용하기에 적합하지만, 실제로 우리가 필요로 하는 것과 맞지 않을 수도 있습니다. 그래서 이와 같은 상황을 해결하기위해 약간의 수정이 필요합니다.

유저 모델을 커스텀 마이징 하기 위해서는 AbstractBaseUserAbstractUser를 상속받아 구현하는 것이 가능하다. AbstractBaseUserAbstractUser의 차이점은 아래와 같다.

  • AbstractUser : 사용자 객체를 생성하는데 필요한 필드를 대부분 가지고 있다. 이를 상속받아 부가적으로 필요한 프로필 관련 필드와 메서드를 추가해 사용한다.

  • AbstractBaseUser : 인증 기능만을 가지고있는 추상클래스다. 그렇기에 필드는 따로 정의되어 있지 않아 직접 구현해줘야 하며, 기본적으로 가지고 있는 필드는 passwordlast_login뿐이다.

  • 만약 장고의 기존 인증 설정을 바꾸고 싶으면, AbstractBaseUser를 사용하면 된다. 그 외 기존의 유저 모델과 데이터가 존재하고 몇몇 기능만 상속받아 사용하고 싶으면, AbstractUser를 상속받아 사용하는 것이 편하다.

커스텀 유저 모델 매니저 - UserManager

AbstractBaseUser에서는 ORM을 사용할 수 있는 objects가 생략되어 있다. 따라서 유저를 생성할 수 있는 create_user()create_superuser()등을 따라 선언해주고 해당 매니저를 사용할 것임을 모델 안에 선언해줘야 한다.

class UserManager(BaseUserManager):
    def create_user(self, username, name, email=None, password=None, **extra_fields):
        """
        일반 사용자 생성 메서드
        """
        try:
            user = self.model(
                username=username,
                name=name,
                email=email if email else "",
            )
            extra_fields.setdefault('is_staff', False)
            extra_fields.setdefault('is_superuser', False)
            user.set_password(password)
            user.is_active = True
            user.save()
            return user
        except ValidationError:
            raise ValidationError({'detail': 'Enter a name proper'})

    def create_superuser(self, username, name, email=None, password=None, **extra_fields):
        """
        관리자 생성 메서드
        """
        try:
            superuser = self.create_user(
                username=username,
                name=name,
                password=password
            )
            superuser.is_admin = True
            superuser.is_superuser = True
            superuser.is_active = True
            superuser.save()
            return superuser
        except:
            raise ValidationError({'detail': 'Enter a proper email account'})

커스텀 유저 모델 - User

class User(AbstractBaseUser):
    # 회원가입시 입력한 사용자 ID
    username = models.CharField(max_length=30, unique=True)

    # 회원가입시 입력한 사용자 이름
    name = models.CharField(max_length=30, )

    # 회원가입시 입력한 사용자 이메일
    email = models.EmailField(defualt="")

    # AbstractBaseUser를 상속받음으로써 정의해줘야하는 필드
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    is_admin = models.BooleanField(default=False)
    is_superuser = models.BooleanField(default=False)

    # Custom Manager 설정
    objects = UserManager()

    EMAIL_FIELD = 'email'
    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['name', ]

    def __str__(self):
        return self.name if self.name else self.username

    @property
    def is_staff(self):
        """일반 사용자 or 스태프 권한"""
        return self.is_admin

    def has_module_perms(self, app_label):
        """user가 주어진 app_label에 해당 권한이 있는지 확인"""
        if self.is_active and self.is_superuser:
            return True
        return auth_models._user_has_module_perms(self, app_label)

    def has_perm(self, perm, obj=None):
        if self.is_active and self.is_superuser:
            return True
        return auth_models._user_has_module_perms(self, perm, obj)

    # AbstractBaseUser에는 존재하지 않으므로 따로 선언
    def user_permission(self):
        return self._user_permissions

    # 장고 admin 이름 출력시 필요한 메소드
    def get_full_name():
        return self.username

    def get_short_name():
        return self.username
  • get_full_name()get_short_name() : 두 메서드는 관리자 페이지에서 로그인 했을 경우 이름을 랜더해줄 때 사용한다. 만약 두 메서드를 오버라이드 하지 않은 모델을 정의하고 관리자를 생성하여 관리자 페이지에 로그인할 경우 에러가 발생한다.

마치며

다음 포스팅에는 이를 활용해 JWT를 구현하는 것을 포스팅할 계획이다.

reference : https://juliahwang.kr/%EB%AA%A8%EB%82%B4%EA%B8%B0%20%EC%8A%A4%ED%84%B0%EB%94%94/2017/09/22/2.0-%EC%9D%B8%EC%A6%9DAPI%EC%A0%9C%EC%9E%91%ED%95%98%EA%B8%B01.html