Python/Django

Django 2.2 | 유저 모델 커스텀하기

코닝구 2020. 9. 29. 17:43

Custom User Model

장고의 공식 문서에서는 user model을 프로젝트에 맞게 커스텀하기를 권장하고 있다. 이유는 user model의 내장된 필드들이 필요하지 않을 수도 있고, 내장되지 않는 필드들이 필요할 수도 있기 때문이다. 프로젝트 개발 중간 단계에 user model을 커스텀하려고 하면 프로젝트가 장고와 내부적으로 연결되어 있기 때문에 전환이 어려울 수 있다. 그렇기에 프로젝트 시작 단계에서 user model을 커스텀하여 개발하는게 좋다. user model 커스텀은 장고 v1.5에 큰 변화를 이루었다. v1.5 이전의 권장 방법은 Profile model에 OneToOne 으로 관계를 맺는 것이었고, 이는 헷갈리기 쉬운 구조였다. 레거시한 장고 프로젝트를 보면 이런 코드를 볼 수 있을 것이다. 요즘은 위에 말한 Profile model을 생성하는게 아니고, user model을 커스텀하는 것이 일반적인 방법이다. 장고에서는 user model을 커스텀하는 방법으로 두 가지의 슈퍼 클래스를 제공하고 있다.


AbstractUser

장고의 user model의 내장된 필드와 권한 시스템을 그대로 유지하면서 필요한 필드만 추가할 때 이용하는 방법이다. user model을 커스텀할 수 있는 가장 간단하면서도 빠른 방법이다. AbstractUser 슈퍼클래스로 서브클래스를 생성해보자.

#coningguapp/models/user.py
from django.contrib.auth.models import AbstractUser
from django.db import models


class User(AbstractUser):
    tel = models.CharField("연락처", max_length=20, null=True, blank=True)
    created_on = models.DateTimeField("등록일자", auto_now_add=True)
    updated_on = models.DateTimeField("수정일자", auto_now=True)

    class Meta:
        db_table = 'user'
				
#coningguapp/models/__init__.py
from .user import *

#coninggu/settings/base.py
...
AUTH_USER_MODEL = 'coningguapp.User'  # 추가

User 클래스는 AbstractUser 슈퍼클래스를 상속받는 서브클래스이다. 장고 user model에서 제공하는 필드외에 tel, created_on, updated_on 필드를 추가하였다. db_table을 정의하지 않으면 장고는 자동적으로 ‘appname_classname’ 명으로 테이블을 생성할 것이다. 자동적으로 정의해주는 네이밍이 마음에 들지 않아 db_table을 정의한 것임으로 꼭 정의하지 않아도 된다. 새롭게 정의한 user model을 장고에게 알려주어 default user model 대신에 커스텀한 user model을 사용하게 해야 한다. 그 코드가 base.py 파일의 AUTH_USER_MODEL=’coningguapp.User’ 이다.


AbstractBaseUser

AbstractBaseUser 슈퍼클래스는 password, last_login, is_active 필드만 제공한다. 그리고 패스워드와 관련된 함수를 제공해준다. AbstractBaseUser로 user model을 커스텀하면 필요한 필드만 있기에 깨끗한 상태로 커스텀할 수 있다. 다만, 그로 인해 AbstractUser를 사용할 때보다 몇 가지를 더 정의해 주어야 한다.

#coningguapp/models/user.py
from django.contrib.auth.models import AbstractBaseUser, UserManager
from django.db import models


class CustomUserManager(BaseUserManager):
    def create_user(self, email, name, password=None):
        if not email:
            raise ValueError("Users must have an email address")

        user = self.model(
            email=self.normalize_email(email),
            name=name,
        )

        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, name, password=None):
        user = self.create_user(
            email,
            password=password,
            name=name
        )
        user.is_admin = True
        user.save(using=self._db)
        return user
        
        
class User(AbstractBaseUser):
    email = models.EmailField(max_length=255, unique=True)
    name = models.CharField(max_length=10)
    is_admin = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)
    created_on = models.DateTimeField(auto_now_add=True)
    updated_on = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'user'

    objects = CustomUserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['name']

USERNAME_FIELD은 user model에서 사용할 고유 식별자로 필수로 입력해 주어야 한다. REQUIRED_FIELDS 는 createsuperuser 커맨드를 실행하여 관리자를 생성할 때 입력받을 필드를 정의해주면 된다. 그리고, 커스텀한 user model에 manager를 정의해야 한다. 만약 커스텀한 user model이 장고의 기본 user model과 같다면 UserManager를 정의하면 된다. 같지 않다면 create_user() 및 create_superuser() 메소드를 제공하는 BaseUserManager를 상속받아 create_user()와 create_superuser()를 정의해야 한다.

이후부턴 AbstractBaseUser를 상속받아 커스텀한 user model을 가지고 프로젝트를 진행할 것이다. 모델링을 하였음으로 makemigrations와 migrate 커맨드를 실행하여 반영하자.

(boot) $ ./manage.py makemigrations
(boot) $ ./manage.py migrate

Unit Tests

장고에서 제공하는 TestCase를 사용하여 단위 테스트를 작성해보자.

#coningguapp/tests/test_user.py
from django.contrib.auth import get_user_model
from django.test import TestCase


class CustomUserTest(TestCase):
    def test_create_user(self):
        user = get_user_model().objects.create_user(
            email='coninggu@example.com',
            name='coninggu',
            password='testpassword12!@#',
        )
        self.assertEqual(user.email, 'coninggu@example.com')
        self.assertEqual(user.name, 'coninggu')
        self.assertFalse(user.is_admin)
        self.assertTrue(user.is_active)

    def test_crete_superuser(self):
        user = get_user_model().objects.create_superuser(
            email='superuser@example.com',
            name='superuser',
            password='testpassword12!@#',
        )
        self.assertEqual(user.email, 'superuser@example.com')
        self.assertEqual(user.name, 'superuser')
        self.assertTrue(user.is_admin)
        self.assertTrue(user.is_active)

커스텀 user model의 TestCase를 작성하였다. 장고에서 제공하는 test 커맨드로 위에 작성한 테스트케이스의 결과를 검증하자.

(boot) $ ./manage.py test

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 0.406s

OK
Destroying test database for alias 'default'...

2개의 테스트가 실행되었고 성공적으로 검증되었다.