hotamul의 개발 이야기

[Django] User Model 커스터마이징 #3 (TDD) 본문

Dev./Django

[Django] User Model 커스터마이징 #3 (TDD)

hotamul 2022. 7. 1. 00:36

[Django] User Model 커스터마이징 #2 (TDD)에서 test 코드를 작성했고 username이라는 필수 인자 (field)가 없기 때문에 user model 객체를 생성할 수 없음을 확인했다.

그럼 이제 test가 pass 할 수 있도록 custom User model, custom UserManager을 만들어 보자. (UserManager는 user model 객체를 생성, 저장, return 해주는 클래스라고 #1에서 설명했다)

User

User 클래스는 두 가지 클래스 상속이 필요하다. (python은 다중 상속을 지원한다.)

먼저 app에 models.py 파일을 열고 아래와 같이 필요한 클래스 및 models를 import 한다.

from django.db import models
from django.contrib.auth.models import (
    AbstractBaseUser,
    PermissionsMixin,
)


class User(AbstractBaseUser, PermissionsMixin):
    """User in the system."""

그리고 email, name, is_active, is_staff field를 아래와 같이 만든다.

class User(AbstractBaseUser, PermissionsMixin):
    """User in the system."""
    email = models.EmailField(max_length=255, unique=True)
    name = models.CharField(max_length=255)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)

여기서 max_length를 255로 설정한 이유는 EmailField,CharField가 최대로 가질 수 있는 글자 수가 255 이기 때문이다.
줄여도 상관은 없다.

그리고 email field가 user를 생성하기 위한 유일한 key이므로 unique=True를 생성자에 추가한다.

여기서 가장 중요한 것은 다음 attribute를 추가해야 한다는 것이다.

class User(AbstractBaseUser, PermissionsMixin):
    """User in the system."""
    email = models.EmailField(max_length=255, unique=True)
    name = models.CharField(max_length=255)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)

    USERNAME_FIELD = 'email'

USERNAME_FIELD = 'email'email field가 유일한 키이며 필수 인자임을 나타낸다.

이제 User model 클래스를 만들었으니 user model을 생성/관리 하는 UserManager를 커스터마이징 해보자.

UserManager

UserManagerBaseUserManager 클래스가 필요하므로 추가로 import 해준다.

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

그리고 User 클래스를 만들 때처럼 BaseUserManager를 상속하는 UserManager 클래스를 만들어 준다.

class UserManager(BaseUserManager):
    """Manager for users."""

    def create_user(self, email, password=None, **extra_fields):
        """Create, save and return a new user."""
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)

        return user

self.model(email=email, **extra_fields) 에서 password를 전달하지 않고 set_password를 사용하는 이유는 encyption 하기 위함이다. set_password를 사용하면 django framework이 직접 암호화 하여 hashing 하고 DB에서 직접 password를 조회 하면 암호화된 password를 확인할 수 있다. 이렇게 하면 user 들의 password를 안전한게 관리할 수 있다.

UserManager도 만들었으니 User에서 직접 만든 UserManager를 사용한다는 것을 알려주기 위해 objects = UserManager()User 클래스에 추가한다.

그러면 최종 완성본은 아래와 같다.

models.py 최종 코드

"""
Database models.
"""
from django.db import models
from django.contrib.auth.models import (
    AbstractBaseUser,
    BaseUserManager,
    PermissionsMixin,
)


class UserManager(BaseUserManager):
    """Manager for users."""

    def create_user(self, email, password=None, **extra_fields):
        """Create, save and return a new user."""
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)

        return user

class User(AbstractBaseUser, PermissionsMixin):
    """User in the system."""
    email = models.EmailField(max_length=255, unique=True)
    name = models.CharField(max_length=255)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)

    objects = UserManager()

    USERNAME_FIELD = 'email'

setting && migration

User, UserManager를 만들었다고 끝난것이 아니다. 먼저 이 app에서는 user는 내가 만든 User를 사용한다는 것을 settings.py에도 설정해줘야 한다.

settings.py 파일 아무 위치에 아래 코드를 추가한다.

AUTH_USER_MODEL = 'core.User'

여기서 core는 내가 추가한 app이다.

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'core',
]

이제 makemigrations CLI로 migration 파일을 만들어보자.

python manage.py makemigrations

위 명령어를 터미널에 입력하면 0001_initial.py이라는 파일이 migrations 디렉토리 아래에 생성됨을 확인할 수 있다.

그리고 python manage.py migrate 명령어를 입력하면 성공적으로 migration이 되었음을 확인 할 수 있다.

만약 docker를 사용하고 있다면 docker volume ls 명령어로 현재 사용중인 volume을 확인해서 해당 이름을 docker volume rm [사용중인 volume 이름]을 먼저 하고 python manage.py migrate를 입력하면 잘 실행됨을 알 수 있다. (docker rm ~을 하기전 docker-compose down을 해야 할 수도 있다)

자 이제 커스터마이징이 끝났으니 작성했던 test가 성공하는지 확인해보자

check test pass

python manage.py test 명령어를 실행하면 다음과 같이

...
Ran 5 tests in 0.102s

OK
...

모든 test가 성공함을 확인할 수 있다. (내가 작성한 test는 총 5개 였다 - 1개는 [Django] User Model 커스터마이징 #2 (TDD)서 작성했던 test)

다음은 email normalizing과 email을 빈칸으로 전달했을 때 발생하는 에러들을 잡아보자.

Comments