IT/WEB

[Django] User모델 커스터마이즈하기 (커스텀 User 모델)

개발자 두더지 2022. 6. 29. 22:36
728x90

일본의 한 블로그 글을 번역한 포스트입니다. 의역 및 직역, 오역이 있을 수 있으며 틀린 내용은 지적해주시면 감사하겠습니다.

 

 Django에서는 표준 User 모델이 정의되어 있다. 그러나 대부분은 만드는 Web 어플리케이션에 맞게 User 모델을 커스터마이즈할 필요가 있다. 

 User 모델을 커스터마이즈하는 방법은 몇 가지 있으나 여기서는 가장 자유도가 높은 AbstractBaseUser을 상속받는 방법에 대해서 구체적으로 설명하도록 하겠다.

주의점 : 커스텀 User 모델을 이용할 경우 Django의 프로젝트 생성 후 바로 설정할 필요가 있다. 이미 migrate한 후에 User을 변경하려고하는 것은 매우 어렵다.

 

 

표준(디폴트) User모델


 앞서 말했듯 Django에서는 기본적으로 User 모델이 존재한다. 이 User 모델은 django.contrib.auth.models.User에 다음과 같이 정의되어 있다. 

class User(AbstractUser):
    class Meta(AbstractUser.Meta):
        swappable = 'AUTH_USER_MODEL'

 표준 User 모델은 AbstractUser를 상속받고 있는 것을 알 수 있다. 또한 표준 User 모델에서는 다음과 같은 칼럼이 정의되어 있다.

username = models.CharField()
first_name = models.CharField()
last_name = models.CharField()
email = models.EmailField()
is_staff = models.BooleanField()
is_active = models.BooleanField()
date_joined = models.DateTimeField()
칼럼명 설명
username 유저이름
first_name 이름
last_name
email 이메일
is_staff 관리화면의 액세스 허가 여부
is_active 로그인 가능 여부
date_joined 계정 생성일시

 Django 튜토리얼에서는 이 표준 User모델을 이용해서 개발 설명을 하는 경우가 많다.

 

 

커스터마이즈 User 모델


 Django의 표준 User 모델은 Django 설치 후에 바로 이용할 수 있으므로, Django 초심자에게 굉장히 편리하다. 그러나 Web 어플리케이션을 만들 때 유저 정보에 필요한 항목은 User 모델과 다른 경우가 일반적이다.

 그리고 로그인할 때 username이 아닌 email을 이용하고 싶은 경우가 있다. 따라서 User 모델을 Web 어플리케이션에 맞춰서 커스터마이즈해야할 필요가 있다. Django 공식 사이트에서도 User 모델을 커스텀해서 이용하는 것을 강력히 추천하고 있다.

 

커스텀 User 모델을 작성하는 세 가지 방법

 맨 처음 언급했듯 유저 모델을 커스터마이즈하는 방법은 여러 가지 있다.

  •  표준 User 모델과 1대 1 관계를 가지는 모델을 만드는 방법
  • AbstractUser을 상속받는 모델을 만드는 방법
  • AbstractBaseUser을 상속받는 모델을 만드는 방법

각 방법에 대해서 우선 간략하게 설명하도록 하겠다.

표준 User 모델과 1대 1관계를 가지는 모델을 만드는 방법

예를 들어 다음과 같이 표준 User 모델과 1대1 관계를 가진 모델을 만들어 유저 정보를 추가하고 싶은 정보를 쓴다.

class UserAddInfo(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL)
    department = models.CharField()
    image = models.ImageField()

이렇게 표준 User 모델을 그대로 활용하며 새로운 정보를 User에 추가할 수 있다. 그러나 이 방법에서는 기존의 칼럼(first_name이나 last_name등) 을 반드시 이용해야하며, email로 로그인할 수 있도록 변경할 수 없다.

 또한, 테이블가 여러개 나뉘므로 Web 어플리케이션의 포퍼먼스가 나빠지는 단점이 있다. 이러한 이유들도 표준 User모델과 1대1 관계를 가진 모델을 생성하는 것은 추천하지 않는다.

AbstaractUser을 상속한 모델을 만드는 방법

 AbstaractUser은 표준 User 모델이 상속하여 이용하는 방법이다. AbstractUser모델은 django.contrib.auth.models에 다음과 같이 정의되어 있다.

class AbstractUser(AbstractBaseUser, PermissionsMixin):
    username_validator = UnicodeUsernameValidator()
    username = models.CharField(_('username'), max_length=150, unique=True,…)
    first_name = models.CharField(_('first name'), max_length=30, blank=True)
    last_name = models.CharField(_('last name'), max_length=150, blank=True)
    email = models.EmailField(_('email address'), blank=True)
    is_staff = models.BooleanField(_('staff status'), default=False,…)
    is_active = models.BooleanField(_('active'), default=True,…)
    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)

    objects = UserManager()

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

    class Meta:
        verbose_name = _('user')
        verbose_name_plural = _('users')
        abstract = True

 AbstaractUser은 AbstaractUser과 PermissionMixin을 상속받고 있다. 위의 AbstaractUser모델을 상속받는 커스텀 User 모델을 생성하기 위한 방법은  "표준 User모델과 1대1의 관계를 가진 모델을 만들기"와 커다란 차이점이 없다.

 테이블이 여러 개로 나뉘어 퍼포먼스가 나빠지는 등의 불편함은 없지만, 칼럼(속성) 추가만 가능하므로 모델의 유연성이 낮다. 표준 User 모델에 칼럼만을 추가하고 싶은 경우에는 이 방법을 이용하면 편리하다.

AbstractBaseUser을 상속받는 모델을 만드는 방법

AbstractBaseUser은 django.contrib.auth.base_user에 다음과 같이 정의되어 있다.

class AbstractBaseUser(models.Model):
    password = models.CharField(_('password'), max_length=128)
    last_login = models.DateTimeField(_('last login'), blank=True, null=True)

    is_active = True

    REQUIRED_FIELDS = []

    _password = None

    class Meta:
        abstract = True

 AbstractBaseUser은 django.db.models.Model 상속 받고 있다. AbstractBaseUser에는 인증 기능이 포함되어 있으므로 독자적인 필드를 설정하는 것이 가능하다.

 즉, 인증 이외의 부분은 어떤 것도 구현되어 있지않은 클래스로, first_name이나 last_name이라는 필요없는 필드를 User 모델에 쓰지 않아도된다. AbstractBaseUser을 상속받는 User 모델을 만드는 것은 귀찮지만, 커스터마이즈의 유연성이 방법 중이 가장 높다.  

 

 지금까지 설명한 모델의 상속 관계를 그림으로 나타내면 다음과 같다.

 

 

AbstractBaseUser을 상속받는 User 모델 만들기


 이제부터는 가장 자유도가 높은 AbstractBaseUser을 상속받는 User 모델을 만드는 방법에 대해서 설명하도록 하겠다.커스터마이즈 설명은 다음의 흐름으로 전개된다.

  • 어플리케이션(app)의 생성
  • 커스텀 User 모델의 작성
  • 커스텀 UserManager의 작성
  • 인증 모델의 갱신
  • migration의 실행
  • 관리화면의 설정

 앞서 말했듯, 커스텀 User 모델을 이용할 경우 Django의 프로젝트 생성 후 바로 설정해야한다.이미 migrate한 후에 User을 변경하려고하는 것은 힘들다.

 또한 여기서 작성하는 User 모델은 로그인할 때 유저명이 아닌 메일을 사용하도록 변경할 것이다.

 

1. 어플리케이션(app)의 생성

 먼저 터미널에서 유저 관리용 어플리케이션을 생성한다. manage.py startapp 커맨드로 users 이라는 어플리케이션을 생성했다. 원하는 어플리케이션의 이름으로 생성해도 문제없다.

python manage.py startapp users

 그 다음 프로젝트 폴더의 settings.py의 INSTALLED_APPS란에 생성한 어플리케이션을 추가한다.

INSTALLED_APPS = [
    '…',
    'users.apps.UsersConfig',
]

 

2. 커스텀 User 모델의 작성

 다음은 커스텀 User을 만든다. 만든 어플리케이션의 안의 models.py를 다음과 같이 변경한다.  django.contrib.auth.models.AbstractUser를 바탕으로 작성했다.

from django.db import models
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
from django.core.mail import send_mail
from django.utils import timezone
from django.contrib.auth.validators import UnicodeUsernameValidator
from django.utils.translation import gettext_lazy as _

class User(AbstractBaseUser, PermissionsMixin):
    username_validator = UnicodeUsernameValidator()

    username = models.CharField(_("username"), max_length=50, validators=[username_validator], blank=True)
    email = models.EmailField(_("email_address"), unique=True)
    is_staff = models.BooleanField(_("staff status"), default=False)
    is_active = models.BooleanField(_("active"), default=True)
    date_joined = models.DateTimeField(_("date joined"), default=timezone.now)

    objects = UserManager()
    USERNAME_FIELD = "email"
    EMAIL_FIELD = "email"
    REQUIRED_FIELDS = ['username']

    class Meta:
        verbose_name = _("user")
        verbose_name_plural = _("users")

    def clean(self):
        super().clean()
        self.email = self.__class__.objects.normalize_email(self.email)

    def email_user(self, subject, message, from_email=None, **kwargs):
        send_mail(subject, message, from_email, [self.email], **kwargs)

objects

 User 모델로 설정하고 있는 objects 변수는 views.py에서 User 모델의 정보를 얻을 때 등에 이용한다. 예를 들면 다음과 같이 말이다.

user = User.objects.get(username="office54")

 objects 변수에 지정하는 클래스 (여기서는 UserManager())는 터미널에서 유저를 작성(manage.py createsuperuser등)할 때 호출된다.

USERNAME_FIELD

USERNAME_FIELD으로 지정한 필드는 로그인이나 메일 송신등에 이용한다. 여기를 email로 하면 메일 주소로 로그인할 수 있게 된다. 다만 지정한 필드는 유일해야할 필요가 있으므로 unique=True일 필요가 있다.

EMAIL_FIELD

터미널에서 유저 생성(manage.py createsuperuser등)을 할 때 표시되는 항목이다. 여자는 지정한 항목 값을 입력하도록 요구받는다. 여러 개의 필드를 설정하는 것도 가능하다.

 

3. 커스텀 UserManager의 작성

 터미널에서 유저를 만들 때(manage.py createsuperuser등 할 때) 호출되는 UserManager을 커스텀 User 모델용으로 바꿔 쓸 수 있다.

 UserManager 클래스에는 _create_user 함수와 create_user 함수, create_supteruser 3개가 있다. 각각 유저를 신규 생성할 때 호출되는 함수이다.

 아까 작성한 class User(AbstractBaseUser, PermissionsMixin) 위 쪽에 다음의 내용을 기재한다.

class UserManager(BaseUserManager):
    use_in_migrations = True

    def _create_user(self, username, email, password, **extra_fields):
        if not email:
            raise ValueError('Email을 입력해주세요.')
        email = self.normalize_email(email)
        username = self.model.normalize_username(username)
        user = self.model(username=username, email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self.db)
        return user
    def create_user(self, username, email, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(email, password, **extra_fields)

    def create_superuser(self, username, email, password, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)
        if extra_fields.get('is_staff') is not True:
            raise ValueError('is_staff=True일 필요가 있습니다.')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('is_superuser=True일 필요가 있습니다.')
        return self._create_user(username, email, password, **extra_fields)

 email은 필수 항목이므로, email가 비어있는 경우는 예외가 발생한다.

 

4. 인증 모델의 갱신

 커스텀 User 모델을 인증기능을 사용하여 인증 모델로 변경한다.  프로젝트의 settings.py에 아래와 같이 AUTU_USER_MODEL에 "어플리케이션명.모델명"을 지정하는 설정을 추가한다. 

AUTH_USER_MODEL = 'users.User'

 

5. migration의 실행

 여기까지 완료됐다면 터미널을 열어 migration을 만들고 적용하자.

python manage.py makemigrations
python manage.py migrate

 

6. 관리자 사이트 화면(admin)의 설정

 관리자 사이트에 커스텀 User을 이용할 수 있도록 하기 위해 어플리케이션의 admin.py을 변경한다. 또한 이 코드에서는 UserCreationForm 이나 UserCahngeForm도 그대로 이용할 수 없으므로 전용 Form을 만들었다.

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
from django.utils.translation import ugettext_lazy as _
from .models import User

class MyUserChangeForm(UserChangeForm):
    class Meta:
        model = User
        fields = '__all__'

class MyUserCreationForm(UserCreationForm):
    class Meta:
        model = User
        fields = ('email','username')

class MyUserAdmin(UserAdmin):
    fieldsets = (
        (None, {'fields': ('email', 'password', 'username')}),
        (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser',
                                       'groups', 'user_permissions')}),
        (_('Important dates'), {'fields': ('last_login', 'date_joined')}),
    )
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'password1', 'password2'),
        }),
    )
    form = MyUserChangeForm
    add_form = MyUserCreationForm
    list_display = ('email', 'username', 'is_staff')
    list_filter = ('is_staff', 'is_superuser', 'is_active', 'groups')
    search_fields = ('email', 'username')
    ordering = ('email',)

admin.site.register(User, MyUserAdmin)

 

 

마치며


커스텀 User 모델을 만드는 방법인 여기까지이다. 이 외에 본인이 필요한 설정에 맞게 변경하거나 추가하면 된다.


참고자료

https://office54.net/python/django/model-custom-user

728x90