DRF django로 회원가입, 로그인을 구현하는 방법에 대해 설명하도록 하겠다.

여기서 유의할 점은, 우리 프로젝트에서는 id가 아니라 email을 username으로 사용하였다는 것이다.

이를 변경하고 싶은 분들이 참고하면 좋을 것 같다. 

 

우선, model을 정의해야 한다.

from django.contrib.auth.models import AbstractUser, AbstractBaseUser, BaseUserManager
from django.db import models
from django.conf import settings
from django.contrib.auth.models import UserManager, PermissionsMixin

# Create your models here.
class UserManager(BaseUserManager):
    def create_user(self, email, password, **extra_fields):
        if not email:
            raise ValueError(('The Email must be set'))
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save()
        return user

    def create_superuser(self, email, password, **extra_fields):
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)
        extra_fields.setdefault('is_active', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError(('Superuser must have is_staff=True.'))
        if extra_fields.get('is_superuser') is not True:
            raise ValueError(('Superuser must have is_superuser=True.'))
        return self.create_user(email, password, **extra_fields)

class User(AbstractBaseUser, PermissionsMixin):
    """custom user model"""
     username = None
    GENDER_MALE = "male"
    GENDER_FEMALE = "female"
    GENDER_OTHER = "other"
    GENDER_CHOICES = (
        (GENDER_MALE, "Male"),
        (GENDER_FEMALE, "Female"),
        (GENDER_OTHER, "Other"),
    )
    gender = models.CharField(choices=GENDER_CHOICES, max_length=10, blank=True)
    nickname = models.CharField(max_length=20, blank=True)
    birthdate = models.DateField(blank=True, null=True)
    email = models.EmailField(max_length=64,unique=True)
    address = models.CharField(max_length=100, blank=True)

    is_staff = models.BooleanField(default=False)
    is_admin = models.BooleanField(default=False)
    is_active = models.BooleanField(default=False)
    is_superuser = models.BooleanField(default=False)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []
    objects = UserManager()

    def __str__(self):
        return self.email

https://tech.serhatteker.com/post/2020-01/email-as-username-django/

 

How to Use Email as Username for Django Authentication

0: Intro The aim of this post will be explaining how to create a custom User Model in Django. So we can use that email address as the primary ‘user identifier’ instead of a username for authentication. Default Django app will give you a User Model that

tech.serhatteker.com

위의 링크를 참고하여 작성하였다.

우선, 원하는 User 모델을 정의한다. 

여기서는 abstractbaseuser를 상속받았는데, 이는 이메일 필드를 Username으로 설정하고, 추후 소셜 로그인을 구현하기 위함이다.

 

abstractbaseuser와 abstractuser를 어떤 때에 사용해야 할지 헷갈리는 사람들은 

아래 게시물이나 django 공식문서를 참고하면 좋을 것 같다!

https://whatisthenext.tistory.com/128

 

[DJango] AbstractBaseUser vs AbstractUser 비교하기

AbstractUser vs AbstractBaseUser AbstractUser vs AbstractBaseUser AbstractBaseUser를 상속하게 되면 Class MyUser(AbstractBaseUser):   pass 꼴랑 2개밖에 안던져준다(id는 자동생성 되는 필드니까). 이..

whatisthenext.tistory.com

 

 

하나하나 설명을 좀 해보자면, 

첫번째, Usermanager는

BaseUserManager를 상속받아 User를 생성할 때 사용하는 헬퍼 클래스이다.

이는 모델을 관리하는 클래스로, user를 생성할 때 그리고 superuser를 생성할 때의 행위를 지정한다.

모든 django model은 manager를 통해서 queryset을 받기 때문에 이를 설정해주어야 한다. 

model에 objects = UserManager()로 이를 설정해주었다.

특히, 우리는 username field를 email로 사용하겠다고 설정해주었기 때문에, usermanager를 재정의 해주는 과정이 필요하다. 재정의 하지 않은 채, usermanager를 사용하겠다고 선언하면, default의 usermanager를 사용하기 때문에 에러가 난다.

 

두번째, User 모델은

abstractbaseuser를 상속받아 설정해주었다.

또한 기본 AbstractBaseUser 상속과 더불어 장고의 기본 그룹, 허가권 관리 기능을 재사용하는 PermissionsMixin 도 상속해주었다. 

 

username = None

 

user class에서 username = None으로 username을 받지 않겠다고 설정한다.

 

is_staff = models.BooleanField(default=False)
is_admin = models.BooleanField(default=False)
is_active = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)

 

is_staff, is_active, is_superuser, is_admin은 abstractbaseuser와 usermanager에서 필요로 하는 값들이기 때문에 설정해주었다.

 

USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []

 

이 두 설정은 USERNAME을 email로 받겠다는 것이고 required_fields는 superuser를 생성할 때 받는 값인데,

우리는 없으므로 아무것도 넣지 않았다.

아무 값이 없다고 해서 저 줄을 추가해주지 않으면 error가 난다.

 

objects = UserManager()

 

UserManager를 불러오는 코드이다. 참고로, UserManager를 User class보다 models.py 상에서 위로 설정해주어야지, 

안그러면 default usermanager를 불러와서 게속 에러가 난다.

 

def __str__(self):
        return self.email

 

admin이나 db에서 문자열로 model 객체를 구별하기 위해서 문자열 반환값을 설정해준다.

 

custom usermodel을 설정해주었으면, settings.py에서

 

AUTH_USER_MODEL = "users.User"

 

추가 해주어야 한다. 그래야 django에서 우리가 설정한 customusermodel을 usermodel로 인식한다.

 

+)makemigrations & migration 필수!

 

두번째, admin을 설정해준다.

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from . import models

# Register your models here.


@admin.register(models.User)
class CustomUserAdmin(admin.ModelAdmin):
    """Custom User Admin"""
    list_display = ('email', 'is_staff', 'is_active',)
    list_filter = ('email', 'is_staff', 'is_active',)
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        ('Permissions', {'fields': ('is_staff', 'is_active')}),
    )
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'password1', 'password2', 'is_staff', 'is_active')}
        ),
    )
    search_fields = ('email',)
    ordering = ('email',)

abstractbaseuser는 ModelAdmin을 상속받아야 한다. 아니면 에러가 난다. 

또, 주의해야할 점은 email을 username으로 사용하기로 했기 때문에 

list_display나 list_filter, search_fields 등등 modeladmin의 기본적인 설정들을 overriding 해주어야 한다는 점이다.

이를 모르면 admin 창에서 error가 계속 난다.

 

여기까지 설정하였으면 admin 창에서 내가 설정한 usermodel을 확인해볼 수 있을 것이다.

 

내가 겪은 ERROR들

1.

create_superuser() missing 1 required positional arguement, 'username'.

UserManager를 User 클래스 보다 위에 설정해주어야 한다!

당연하게 느껴질 수 있지만, 나는 바보같이 한참 헤맸다..

2.

'Manager' object has no attribute 'get_by_natural_key’

UserManager 설정해주어서 해결! 

BaseUserManager를 상속하는 UserManager도 함께 정의해서 유저 및 superuser의 생성 방식을 정의해 주어야 오류가 나지 않음.

3.

users.User: (auth.E002) The field named as the 'USERNAME_FIELD' for a custom user model must not be included in 'REQUIRED_FIELDS'.

REQUIRED_FIELDS = []를 넣어주야아 한다.

아무 값이 없더라도 추가해주어야 에러가 나지 않음

4.

<class 'users.admin.CustomUserAdmin'>: (admin.E019) The value of 'filter_horizontal[0]' refers to 'groups', which is not a field of 'users.User'. <class 'users.admin.CustomUserAdmin'>: (admin.E019) The value of 'filter_horizontal[1]' refers to 'user_permissions', which is not a field of 'users.User'. <class 'users.admin.CustomUserAdmin'>: (admin.E033) The value of 'ordering[0]' refers to 'username', which is not a field of 'users.User'. <class 'users.admin.CustomUserAdmin'>: (admin.E108) The value of 'list_display[0]' refers to 'username', which is not a callable, an attribute of 'CustomUserAdmin', or an attribute or method on 'users.User'. <class 'users.admin.CustomUserAdmin'>: (admin.E108) The value of 'list_display[2]' refers to 'first_name', which is not a callable, an attribute of 'CustomUserAdmin', or an attribute or method on 'users.User'. <class 'users.admin.CustomUserAdmin'>: (admin.E108) The value of 'list_display[3]' refers to 'last_name', which is not a callable, an attribute of 'CustomUserAdmin', or an attribute or method on 'users.User'. <class 'users.admin.CustomUserAdmin'>: (admin.E108) The value of 'list_display[4]' refers to 'is_staff', which is not a callable, an attribute of 'CustomUserAdmin', or an attribute or method on 'users.User'. <class 'users.admin.CustomUserAdmin'>: (admin.E116) The value of 'list_filter[0]' refers to 'is_staff', which does not refer to a Field. <class 'users.admin.CustomUserAdmin'>: (admin.E116) The value of 'list_filter[1]' refers to 'is_superuser', which does not refer to a Field. <class 'users.admin.CustomUserAdmin'>: (admin.E116) The value of 'list_filter[2]' refers to 'is_active', which does not refer to a Field. <class 'users.admin.CustomUserAdmin'>: (admin.E116) The value of 'list_filter[3]' refers to 'groups', which does not refer to a Field.

admin에서 modeladmin을 overriding 해주어야 한다!

위의 코드 참조..

 

다음 편에서는 serializers.py와 views.py를 다루도록 하겠다.

BELATED ARTICLES

more