Contents

Django_09_CustomAuthUser_AbstractBaseUser

​

Django AbstractBaseUser

django는 기본적으로 username 과 password를 가지고 로그인합니다. 하지만 요즘에는 대부분의 웹사이트들이 email 과 password로 로그인(사용자를 인식 및 인증)을 합니다.

​

제가 진행할 것은 django의 AbstractBaseUser을 사용해 테이블을 만들고 사용자 인증하기 입니다.

  • 조건

    1. email, password, nickname 및 필수 요소(is_active, is_admin)로만 인증 테이블 구성.

    2. createsuperuser 명령어 입력시, 관리자 계정이 커스텀한 테이블에 생성.

      ​

Django 공식문서에는 models.py에서 기본적인 사용자모델을 설정하고 이를 활용해 admin.py에서 관리자모델을 만드는 예제가 있어 이를 따라하며 약간의 수정을 더해 진행해보려 합니다.

​

먼저 AbstractBaseUser를 상속받아 커스텀을 진행

django의 예제에는 AbstractBaseUser라는 클래스를 상속받은 모델을 제작하는데, Abstract(추상적인)Base(기초)User(사용자)의 틀을 가져와 이를 활용해 내 입맛대로 커스텀 하는 과정.

또한, 계정관련 모델을 만드는 경우에는 항상 두가지를 생각해야 합니다. 일반사용자, 관리자

AbstractBaseUser를 사용하면 로그인 방식도 변경할 수 있고(내마음대로 구성), 원하는 필드들로 유저 모델을 구성할 수 있습니다. 아래는 email, password를 활용하여 로그인하고 추가적으로 nickname을 가지는 유저 모델을 구현해보겠습니다.

이 때 BaseUserManager를 상속하는 UserManager를 함께 정의하여 일반 유저 및 슈퍼유저의 생성 방식을 정의해줘야 합니다. (또 PermissionsMixin 을 함께 상속하면 Django의 기본그룹, 허가권 관리 등을 사용할 수 있습니다. 하지만 여기서는 사용하지 않겠습니다.)

​

BaseUserManager를 상속받는 UserManager 정의

BaseUserManager 클래스는 유저를 생성할 때 사용하는 헬퍼(helper) 클래스이며, 실제 모델(model)에 반영되는 것은 AbstractBaseUser을 상속받아 생성하는 클래스가 될 것입니다.

아래의 코드는 크게 두가지 class를 선언해 사용하고 있고, 동작의 흐름은,
아래 class(MyUser) → 위 class(MyUserManager) 순서로 동작합니다.

소스 코드

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# accounts/models.py

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

class MyUserManager(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):
        user = self.create_user(
            email,
            password=password,
            nickname=nickname,
        )
        user.is_admin = True
        user.save(using=self._db)
        return user


class MyUser(AbstractBaseUser):
    email = models.EmailField(
        verbose_name='email addresss',
        max_length=255,
        unique=True,
    )
    nickname = models.CharField(max_length=100, default = None)

    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)

    objects = MyUserManager()

    USERNAME_FIELD = 'email'

    REQUIRED_FIELDS = ['nickname',]

    @property
    def is_staff(self):
        return self.is_admin

​

설명이 있는 소스코드(위와 동일한 코드이나 자세한 설명이 있습니다.)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# accounts/models.py

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

# 위에서 설명한 일반 유저 및 슈퍼유저의 생성 방식을 정의할 BaseUserManager를 상속받는
# 나만의 사용자 매니저 모델을 생성합니다. 필수입니다.
class MyUserManager(BaseUserManager):
    def create_user(self, email, nickname, password=None):
		# 만약 email을 입력하지 않으면 바로 에러를 뱉습니다.
        if not email:
            raise ValueError('Users must have an email address')
		
        user = self.model(
            email=self.normalize_email(email),
            nickname=nickname
        )
		# 비밀번호는 암호화(set_password)함수를 사용해 변수에 저장합니다.
        user.set_password(password)
        # 등록된 사용자 정보를 DB에 저장합니다.
        user.save(using=self._db)
        # 마지막으로 사용자 정보를 return합니다.
        return user

    # python manage.py createsuperuser 명령어 입력시 = 관리자 계정 생성시
    def create_superuser(self, email, nickname, password):
		
        user = self.create_user(
            email,
            password=password,
            nickname=nickname,
        )
        user.is_admin = True
        user.save(using=self._db)
        return user


class MyUser(AbstractBaseUser):
    # DB에 저장할 column들을 설정합니다. 
	# 저는 email, nickname을 설정하겠습니다.
    # password는 필수값이기에 설정하지 않아도 입력받습니다.
    email = models.EmailField(
        verbose_name='email addresss',
        max_length=255,
        unique=True,
    )
    nickname = models.CharField(max_length=100, default = None)
    
    # 사용자 인증에 사용될 계정을 등록할 때 is_active와 is_admin도 필수값이기 때문에
    # 반드시 지정해주어야 합니다. (안하면 오류를 뱉습니다.)
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)
	# 테이블을 생성하면 last_login 컬럼도 생성되는데 models.~~을 설정해주지 않으면 NULL값으로 입력됩니다.
    
    
    # 위에서 생성한 일반 유저 및 슈퍼유저의 생성 방식을 정의할 나만의 사용자 매니저 모델을 가져옵니다.
    objects = MyUserManager()
	
    # unique한 값으로 설정할 것을 주로 USERNAME_FIELD로 설정합니다.
    # 저는 email의 중복을 허용하지 않고 이를 식별값(PK) 사용하겠습니다.(필수)
    USERNAME_FIELD = 'email'

    # createsuperuser 시 USERNAME_FIELD와 password 말고 추가로 받아야 하는 인자를 기재.
    # 상기 프로젝트의 경우 email, nickname, password를 받아야 하니
    # 기본으로 받는 인자인 USERNAME_FIELD = 'email'과 password 말고 나머지 요소를 기재
    REQUIRED_FIELDS = ['nickname',]

    # 이 아래는 선택사항입니다.
    def has_perm(self, perm, obj=None):
        "Does the user have a specific permission?"
        # Simplest possible answer: Yes, always
        return True

    def has_module_perms(self, app_label):
        "Does the user have permissions to view the app `app_label`?"
        # Simplest possible answer: Yes, always
        return True

    @property
    def is_staff(self):
        "Is the user a member of staff?"
        # Simplest possible answer: All admins are staff
        return self.is_admin

해당 사항을 정리하면서 필수라고 기재된 내용은 커스텀 사용자 인증 테이블을 만들 최소한의 필수 요소입니다.

​

이후 MyUser를 settings.py에 등록.

1
2
3
# setting.py 아무곳에나 ex) '앱이름.클래스명'
AUTH_USER_MODEL = 'accounts.MyUser'
...

​

이제 Terminal에 관리자 생성 명령어 입력..

잠깐! 제가 만든 코드들이 동작하는지 알기 위해서 의도한 실수 표기법인 “Email addresss"라는 input 메시지를 볼 수 있습니다.

​

이제 DB를 확인하는데 제가 만든 accounts앱의 myuser 테이블에 생성되었는지 확인합니다.

또 django 기본 사용자 인증 테이블인 auth_user에 생기지 않았는지도 확인합니다.

의도한대로 관리자 계정이 생긴것을 확인하였고 AbstractBaseUser의 기본적인 사용법을 알아보았습니다.

​

코드도 크게 복잡하지 않고 조금만 세세히 본다면 금방 이해할 수 있었고, 원하는 모습으로 설정하기에 쉬운 모습을 하고 있었습니다.

​

​

AbstractUser의 기본적인 사용법은 다음 글에 작성해보겠습니다.

​

​

​

🔧오류 모음

  1. Non-default argument follows default argument

    1
    2
    3
    4
    5
    
    from django.contrib.auth.models import BaseUserManager
    
    class MyUserManager(BaseUserManager):
    	def create_user(self, email, password, username)  
        # 이렇게 작성했을 때 username에 오류가 발생하였다. 
    

    원인은 username을 인자로 받는데 email이나 password보다 앞서 입력을 받야아 한다 왜냐하면, default(value parameter는 생략)는 non-default 앞에 오면 안된다.는 규칙이 있기 때문.

    ​

  2. Dependency on app with no migrations: accounts

    custom user의 모델을 만들고(models.py 수정 후) 마이그레이션 명령어를 입력하지 않아서 발생한 오류 같다.

    1
    2
    3
    
    > python manage.py makemigrations
    > python manage.py migrate
    # 명령어 입력 후 해결
    

    ​

  3. You are trying to add a non-nullable field ’nickname’ to myuser without a default; we can’t do that (the database needs something to populate existing rows). Please select a fix:

    1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
    2) Quit, and let me add a default in models.py
    

    이 오류는 DB, 모델을 수정할 경우에 자주 발생하는데, 기존에 makemigrations 를 통해 존재하는 정보들을 어떻게 수정할 지 정하지 않았기 때문.

    코드상으로 해결하는 방법과 물리적으로 해결하는 방법이 존재.

    코드로는 CharField(default = ‘’) or CharField(null = True) 같이 default값과 null 을 허용해서 기존의 데이터들을 null 값이나 default 값으로 설정하는 방법과
    migrations 폴더안의 요소를 지우는 방법이 있으나 이는 기존의 데이터를 모두 지우고 새롭게 시작하는 방법이 존재(비추..)

    ​

  4. django.db.migrations.exceptions.InconsistentMigrationHistory: Migration admin.0001_initial is applied before its dependency accounts.0001_initial on database ‘default’.

    ??????

    ​

    ​

    ​

    ​