유저 관련 API는 웹서비스에 가입하는 사용자 정보를 저장하고 이 정보를 사용하여 서비스를 이용할 수 있는 권한을 제어한다. 커스텀으로 만든 유저 모델과 매니저에 대해 정리하였다.


커스텀 유저 모델 - MyUser

유저 모델은 커스텀으로 AbstractBaseUser를 상속받아 구현하였다.

AbstractUser와 AbstractBaseUser의 차이

장고에서 유저 모델을 정의할 때 상속받을 수 있는 클래스는 AbstractUserAbstractBaseUser 두 가지가 있다.

AbstractUser는 장고가 제공하는 완벽한 추상 클래스로, 사용자 객체를 생성하는데 필요한 필드들을 대부분 갖추고 있다. 이를 상속받아 부가적으로 필요한 프로필 관련 필드와 메서드를 추가하여 사용한다.


AbstractBaseUser의 경우에는 인증 기능만을 가지고 있는 추상 클래스로 필드가 따로 정의되어 있지 않다. 가지고 있는 필드는 passwordlast_login 뿐이다. 따라서 해당 추상클래스를 상속받아 유저 서브클래스를 만들 경우에는 필드를 하나하나 정의해주어야 한다.

예를 들어 가장 기본적인 필드인 username,nickname 등의 필드도 정의되어 있지 않으며, 어떤 필드에 반드시 값을 적어주어야하는지(required 설정) 등을 따로 관리해주어야 한다. 또 유저를 어떻게 저장할 것인지를 설정해주는 모델 매니저도 존재하지 않기 때문에 해당 부분을 직접 만들어 커스터마이징할 수 있다.


언제 무엇을?

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


AbstractBaseUser를 상속받은 MyUser 모델

# 전체서비스 내 커스텀유저 모델
class MyUser(AbstractBaseUser):
    # 장고, 페이스북 로그인 타입 선택하는 필드.
    USER_TYPE_DJANGO = 'django'
    USER_TYPE_FACEBOOK = 'facebook'
    USER_TYPE_CHOICES = (
        ('django', 'Basic Login'),
        ('facebook', 'Facebook Login'),
    )

    # 장고, 페이스북 로그인 유저를 구분하는 필드
    user_type = models.CharField(
        max_length=20,
        choices=USER_TYPE_CHOICES,
        default=USER_TYPE_DJANGO,
    )

    # 회원가입시 입력한 사용자의 ID
    username = models.CharField(
        max_length=100,
        unique=True
    )

    # 사용자의 이름을 저장하는 필드. 회원가입시 등록
    name = models.CharField(
        max_length=100,
    )

    # 사용자의 이메일을 저장하는 필드. 페이스북 사용자용
    email = models.EmailField(default="")

    # 사용자 프로필이미지를 저장하는 필드.
    img_profile = CustomImageField(
        upload_to='member',
        default='member/basic_profile.png',
        blank=True
    )

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

    # custom manager 설정
    objects = MyUserManager()

    EMAIL_FIELD = 'username'
    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에 해당하는 권한이 있는지, has_perm과 비슷"""
        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_perm(self, perm, obj)

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

    # 장고 admin 이름출력시 필요한 메서드. AbstractBaseUser에는 없어서 따로 정의해줌.
    def get_full_name(self):
        return self.username

    def get_short_name(self):
        return self.username


get_full_name()get_short_name()

특히, get_full_name()메서드와 get_short_name() 메서드는 AbstractBaseUser를 사용할 때 쉽게 간과하는 부분이다. 위의 두 메서드는 AbstractBaseUser 클래스 내에 다음과 같이 정의되어 있다.

    def get_full_name(self):
        raise NotImplementedError('subclasses of AbstractBaseUser must provide a get_full_name() method')

    def get_short_name(self):
        raise NotImplementedError('subclasses of AbstractBaseUser must provide a get_short_name() method.')

즉, 따로 정의되어 있지 않고 없으면 에러메세지만 반환한다. 해당 메서드들은 관리자 페이지에서 로그인할 경우 이름을 렌더해줄 때 사용된다.

만약 두 메서드를 오버라이드 하지 않은 모델을 정의하고 관리자를 생성하여 관리자 페이지에 로그인할 경우 에러가 발생한다. 따라서 위의 MyUser 모델에 정의해주었다.


커스텀 유저모델 매니저 정의하기

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

# 커스텀 사용자 생성 매니저
class MyUserManager(BaseUserManager):
    def create_user(self, username, name, email=None, password=None, **extra_fields):
        """
        일반사용자 생성 메서드
        """
        try:
            user = self.model(
                user_type=User.USER_TYPE_DJANGO,
                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 proper Email Account'})

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



마치며

AbstractBaseUser를 사용하면 정의 및 오버라이드해줘야 하는 요소가 많지만 그만큼 자유도가 높은 사용자 모델을 만들 수 있다.

AbstractUser를 사용하면 장고에서 제공해주는 필드와 인증 관련 메서드들로 쉽게 유저를 생성할 수 있는 대신 자유도는 떨어진다.

유저를 생성, 관리하는 데 필요한 필드들이 무엇인지 구상해보고 필요에 따라 선택하여 사용하는 것이 좋겠다.



Julia Hwang

디발자를 꿈꾸는 웹개발자의 블로그입니다.