AbstractBaseUser를 활용한 Django 커스텀 유저 모델 만들기

김동환·2021년 5월 13일
6

들어가며

Django는 기본적으로 django.contrib.auth.models에 포함된 User 모델을 제공합니다. 내장으로 permission이나 authentication 등 다양한 기능을 제공해주는 훌륭한 모델이지만 이 User 모델은 아래와 같이 굳이 필요하지 않은 요소들을 가지고 있고, Profile 모델을 따로 만들어 상속받지 않으면 추가로 원하는 필드들을 제공할 수 없다는 단점이 있습니다.

  • username
  • password
  • email
  • first_name
  • last_name

AbstractUser

Django의 기본 유저 모델이 제공하는 다양한 인증 기능들을 사용하고, 굳이 위의 예시에 있는 요소들이 유저 테이블에 있는 것이 문제되지 않는다면 AbstractUser 모델을 상속받도록 유저 모델을 만드는 것도 좋은 방법입니다. 다만 개인적으로는 Django가 제공하는 기본 User 모델의 필드들이 너무 과하다는 생각이 있어서 이보다는 AbstractBaseUser를 사용하는 것을 선호합니다.

다만, AbstractUser나 AbstractBaseUser의 사용은 프로젝트 시작 전에 하는 것이 좋습니다. 유저 모델에 따라 변경되는 데이터베이스들이 많기 때문에 이를 다시 변경하려고 하면 연결되어 있는 스키마들이 너무 많아 안정적으로 변경하기 쉽지 않기 때문입니다.

AbstractBaseUser

AbstractBaseUser를 사용하면 로그인 방식도 변경할 수 있고, 원하는 필드들로 유저 모델을 구성할 수 있습니다. 아래는 email, password를 활용하여 로그인하고 추가적으로 소속된 기관 정보만을 가지는 간단한 유저 모델을 구현하는 예시입니다. 이 때 BaseUserManager를 상속하는 UserManager를 함께 정의하여 일반 유저 및 슈퍼유저의 생성 방식을 정의해줘야 합니다. 또한 PermissionsMixin 을 함께 상속하면 Django의 기본그룹, 허가권 관리 등을 사용할 수 있습니다.

# app/models.py

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


class UserManager(BaseUserManager):    
   
   use_in_migrations = True    
   
   def create_user(self, email, organization, password):        
       
       if not email:            
           raise ValueError('must have user email')
       if not password:            
           raise ValueError('must have user password')

       user = self.model(            
           email=self.normalize_email(email),
           organization=organization              
       )        
       user.set_password(password)        
       user.save(using=self._db)        
       return user

   def create_superuser(self, email, organization, password):        
   
       user = self.create_user(            
           email = self.normalize_email(email),
           organization=organization,                       
           password=password        
       )
       user.is_admin = True
       user.is_superuser = True
       user.save(using=self._db)
       return user 


class User(AbstractBaseUser, PermissionsMixin):    
   
   objects = UserManager()
   
   email = models.EmailField(        
       max_length=255,        
       unique=True,    
   )
   organization = models.CharField(max_length=30)
   is_active = models.BooleanField(default=True)
   is_admin = models.BooleanField(default=False)

   USERNAME_FIELD = 'email'    
   REQUIRED_FIELDS = ['organization']

   def __str__(self):
       return self.email

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

Admin

새로 만든 유저 모델을 Django Admin 에서 사용하기 위해서는 admin.py 에 관련 정보를 등록해줘야 합니다. 아래는 그 예시입니다.

# app/admin.py

from django import forms
from django.contrib import admin
from django.contrib.auth.models import Group
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from django.core.exceptions import ValidationError

from .models import User


class UserCreationForm(forms.ModelForm):
   """A form for creating new users. Includes all the required
   fields, plus a repeated password."""
   password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
   password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)

   class Meta:
       model = User
       fields = ('email', 'organization')

   def clean_password2(self):
       # Check that the two password entries match
       password1 = self.cleaned_data.get("password1")
       password2 = self.cleaned_data.get("password2")
       if password1 and password2 and password1 != password2:
           raise ValidationError("Passwords don't match")
       return password2

   def save(self, commit=True):
       # Save the provided password in hashed format
       user = super().save(commit=False)
       user.set_password(self.cleaned_data["password1"])
       if commit:
           user.save()
       return user


class UserChangeForm(forms.ModelForm):
   """A form for updating users. Includes all the fields on
   the user, but replaces the password field with admin's
   disabled password hash display field.
   """
   password = ReadOnlyPasswordHashField()

   class Meta:
       model = User
       fields = ('email', 'password', 'organization', 'is_active', 'is_admin')


class UserAdmin(BaseUserAdmin):
   # The forms to add and change user instances
   form = UserChangeForm
   add_form = UserCreationForm

   # The fields to be used in displaying the User model.
   # These override the definitions on the base UserAdmin
   # that reference specific fields on auth.User.
   list_display = ('email', 'organization', 'is_admin')
   list_filter = ('is_admin',)
   fieldsets = (
       (None, {'fields': ('email', 'password')}),
       ('Personal info', {'fields': ('organization',)}),
       ('Permissions', {'fields': ('is_admin',)}),
   )
   # add_fieldsets is not a standard ModelAdmin attribute. UserAdmin
   # overrides get_fieldsets to use this attribute when creating a user.
   add_fieldsets = (
       (None, {
           'classes': ('wide',),
           'fields': ('email', 'organization', 'password1', 'password2'),
       }),
   )
   search_fields = ('email',)
   ordering = ('email',)
   filter_horizontal = ()


# Now register the new UserAdmin...
admin.site.register(User, UserAdmin)

settings.py 등록

생성한 유저 모델을 인증을 위한 유저 모델로 사용하기 위해서는 아래와 같이 settings.py에 등록해줘야 합니다.

# project/settings.py
...
AUTH_USER_MODEL = 'core.User'
...

superuser 생성 및 확인

아래와 같이 명령어를 입력해서 슈퍼유저를 생성합니다. 위에서 필수로 지정한 Organization 필드를 함께 받는 것을 확인할 수 있습니다.

$ python manage.py createsuperuser
Email: admin@admin.com
Organization: Velog
Password: 
Password (again): 
Superuser created successfully.

Admin 페이지 접속 및 확인

python manage.py runserver를 통해 개발 서버를 실행시킨 후, localhost:8000/admin/ 으로 접속해서 위의 유저로 로그인했을 때 아래와 같이 정상적으로 화면이 나오면 성공입니다.


참고

profile
개발을 통해 다양한 세상을 경험하는 것을 즐기는 개발자입니다.

1개의 댓글

좋은 글 잘 보고 갑니다~~ 괜찮으시면 서로 팔로우해서 개발지식 공유하는 거 어떠신가요?!

답글 달기