본문 바로가기

Django

Django User model Custom

AbstractUser (사용자 정의 사용자 모델 대체)

일부 프로젝트에는 Django의 내장 사용자 모델이 항상 적절하지 않은 인증 요구 사항이 있을 수 있다.

예를 들어 일부 사이트에서는 사용자 이름 대신 이메일 주소를 식별 토큰으로 사용하는 것이 더 합리적이다.

Django를 사용하면 커스텀 모델을 참조하는 AUTH_USER_MODEL 설정 값을 제공하여 기본 사용자 모델을 재정의 할 수 있다.

AUTH_USER_MODEL = 'users.User'  # <myapp_name>.<user_model_name>

위의 코드 내용은 Django project에서 settings.py에 포함되어야 하며 선행 작업으로는 Django app이름이 INSTALLED_APPS 에 선언 되어 있어야 한다.

INSTALLED_APPS = [
  ...
  'users',
]

프로젝트를 시작할 때 커스텀 사용자 모델 사용

새 프로젝트를 시작할때 기본 사용자 모델이 충분하더라도 맞춤 사용자 모델을 설정하는 것이 좋다.

이 모델은 기본 사용자 모델과 동일하게 작동하지만 필요한 경우 나중에 맞춤 설정할 수 있다.

from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
      pass

위 코드를 작성하였다면 바로 migrate를 생각할텐데 먼저 AUTH_USER_MODEL을 settings.py에 선언안하였다면 migrate가 정상적으로 되지 않을것이다.

그리고 admin 페이지에서 내가 생성한 User 모델에을 보기 위해선 admin.py에 등록이 필요하다.

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

@admin.register(User)
class UserAdmin(admin.ModelAdmin):
      pass

<참고>

프로젝트 중간에 커스텀 사용자 모델로 변경

DB 테이블을 만들고 프로젝트를 한참 진행중에 아차 싶어서 User 모델을 급히 변경 시켜버리면 AUTH_USER_MODEL을 변경할때 외래키 및 다대다 관계에 영향을 미치기 때문에 어려움을 겪을 수 있다.

그래서 초반에 migrate하기전에 미리 셋팅을 해놓는걸 추천한다.

사용자 모델 참조

User 테이블을 직접 참조하는 대신 django.contrib.auth.get_user_model()을 사용하여 사용자 모델을 참조해야한다.

이 메서드는 현재 활성 사용자 모델을 반환한다. (지정된 경우 사용자 지정 사용자 모델, 그렇지 않은 경우 User를 반환)

User 모델에 대한 외래키 또는 다대다 관계를 정의 할때 AUTH_USER_MODEL 설정을 사용하여 사용자 지정 모델을 지정 해야한다.

from django.conf import settings
from django.db import models

class Article(models.Model):
      author = models.Foreignkey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

AbstractBaseUser (사용자 지정 사용자 모델 지정)

모든 사용자 관련 정보를 하나의 모델에 보관하면 관련 모델을 검색하기 위해 추가로 또는 더 복잡한 데이터베이스 쿼리가 필요하지 않다.

사용자 모델을 가능한 단순하게 유지하고 인증에 중점을 두고 Django가 사용자 지정 사용자 모델이 충족할 것으로 기대하는 최소 요구 사항을 준수한다는 의미이다.

기본 인증 백엔드를 사용하는 경우 모델에는 식별 목적으로 사용할 수 있는 단일 고유 필드가 있어야한다.

username, email 또는 기타 고유 한 속성이 될 수 있다.

고유하지 않은 username 필드는 이를 지원할 수 있는 사용자 지정 인증 백엔드를 사용하는 경우 허용된다.

난 개인적으로 되도록이면 고유한 필드값을 사용하는걸 추천한다. (unique=True가 담긴 속성을 사용)

호환되는 사용자 정의 사용자 모델을 구성하는 가장 쉬운 방법은 AbstratBaseUser에서 상속하는 것이다.

AbstratBaseUser는 해시 된 암호 및 토큰화 된 암호 재설정을 포함하여 사용자 모델의 핵심 구현을 제공한다.

USERNAME_FIELD

고유 식별자로 사용되는 사용자 모델의 필드 이름을 설명하는 문자열이다.

일반적으로 username이지만 email 또는 고유식별자 일 수 있다.

고유하지 않은 사용자 이름을 지원할 수 있는 사용자 지정 인증 백엔드를 사용하지 않는 한 필드는 고유해야한다.

위에서 말했듯이 (unique=True가 설정된 속성을 사용)

USERNAME_FIELD 사용 방법

from django.contrib.auth.models import AbstractBaseUser
from django.db import models

class User(AbstratBaseUser):
      """email이 아닌 고유식별자를 사용할 때"""
    identifier = models.CharField(max_length=40, unique=True)
    ...

    USERNAME_FIELD = 'identifier'

EMAIL_FIELD

사용자 모델의 이메일 필드 이름을 설명하는 문자열이다. 해당 값은 get_email_field_name()에 의해 반환된다.

EMAIL_FIELD가 선언되지 않았을 경우 email이 반환됨

REQUIRED_FIELDS

createsuperuser 관리 명령을 통해 사용자를 생성 할 때 프롬프트되는 필드 이름 목록이다.

REQUIRED_FIELDS는 관리자에서 사용자를 만드는 것과 같이 Django의 다른 부분에는 영향을 주지 않는다.

알기 쉽게 말해 project안에서 ./manage.py createsuperuser를 하게되면 REQUIRED_FIELDS에 선언한 내용이 createsuperuser로 등록할때 같이 설정 해줘야한다는 내용이다.

class User(AbstratBaseUser):
      date_of_birth = models.DateField()
    height = models.FloatField()

    REQUIRED_FIELDS = ['date_of_birth', 'height']

위의 코드로 보면 createsuperuser를 하게 되면 date_of_birthheight 을 같이 설정해주게 된다.

<참고>

REQUIRED_FIELDS는 사용자 모델의 모든 필수 필드를 포함해야 하지만 이러한 필드는 항상 프롬프트되므로 USERNAME_FIELD또는 password를 포함하지 않아야한다. 포함시 에러가 날것이다.

해당 구문은 필수적인 요소가 아니기에 넣어도 되고 안넣어도 된다.

is_active

사용자가 활성 으로 간주되는지 여부를 나타내는 Boolean 속성이다.

이 속성은 default=True이며 AbstractBaseUser의 속성으로 제공 된다.

만약, 어떠한 사용자 계정이 필요가 없어지면 계정을 삭제하는 대신 is_active를 False로 설정하는것이 더 효율적이다.

그렇게 되면 사용자에 대한 외래키가 있는 경우 외래키가 손상되지 않고 유지 되기 때문이다.

비활성 사용자의 로그인을 허용하려면 AllowAllUsersModelBackend or AllowAllUsersRemoteUserBackend를 사용할 수 있다.

has_perm()과 같은 권한 검사 방법과 Django 관리자의 인증은 모두 비활성 사용자에 대해 False를 반환한다.

get_full_name()

전체 이름과 같은 사용자의 이름을 리턴 해주고자 사용한다. 사용자의 fullname이 필요한 경우 first_name과 last_name이 필요한데 해당 메서드를 통해서 first_name과 last_name을 합쳐서 리턴하게 해줄 수 있다.

class User(AbstractBaseUser):
      first_name = models.CharField(max_length=10)
    last_name = models.CharField(max_length=20)
    def get_full_name(self):
          return f'{self.first_name}{self.last_name}'

get_short_name()

위의 내용과 비슷한 부분인데 간단하게 메서드 명과 같이 짧은 이름을 넣어주면 된다.

본인 프로젝트에 따라서 first_name, last_name 혹은 email을 넣어줘도 된다.

<참고>

AbstractBaseUser 및 BaseUserManager는 django.contrib.auth.base_user에서 가져올 수 있으므로 INSTALLED_APPS에 django.contrib.auth를 포함하지 않고도 가져 올 수 있다.

AbstractBaseUser의 속성 및 메서드

get_username()

USERNAME_FIELD에서 지정한 필드의 값을 반환한다.

clean()

normalize_username()을 호출하여 사용자 이름을 정규화 한다.
이 메서드를 재정의 하는 경우 정규화를 유지하려면 super()를 호출해야한다.

is_authenticated (로그인 여부에 대해 확인)

항상 True인 읽기 전용 속성이다. (항상 False인 AnonymousUser.is_authenticated와 반대)

사용자가 인증되었는지 확인하는 방법이며 권한을 의미하지 않고 사용자가 활성 상태인지 또는 유효한 세션이 있는지 확인하지 않는다.

사용방법은 request.user에서 속성을 확인하며 AuthenticationMiddleware(현재 로그인 한 사용자를 나타냄)에 의해 채워 졌는지 여부를 확인하지만 속성이 모든 사용자 인스턴스에 대해 True임을 알아야한다.

is_anonymous (로그아웃 여부에 대해 확인)

항상 False인 읽기 전용 속성이다. 이것은 User 및 AnonymousUser 개체를 구별하는 방법이기도 하다.

일반적으로 해당 속성을 사용하기보다 is_authenticated를 사용하는걸 추천한다.

set_password(raw_password)

사용자의 암호를 지정된 원시 문자열로 설정하여 암호를 해싱 처리한다.

raw_password가 None이면 set_unusable_password() 가 사용 된 것처럼 암호는 사용할 수 없는 암호로 설정된다.

check_password(raw_password)

주어진 문자열에대해 사용자의 올바른 암호인지 확인할 수 있다. 올바르면 True 아니면 False 로 반환
(비교는 암호 해싱을 처리한다.)

BaseUserManager(사용자 모델을 위한 관리자 작성)

사용자 모델에 대한 사용자 지정 관리자를 정의해야한다.

사용자 모델이 username, is_staff, is_active, is_superuser, last_login, data_joined 필드를 django의 기본 사용자와 동일하게 정의하는 경우 django의 UserManager를 설치 할 수 있다.

사용자 모델이 다른 필드를 정의하는 경우 두가지 추가 메소드를 제공하는 BaseUserManager를 확장하는 사용자 정의 관리자를 정의해야한다.

create_user(username_field, password=None, other_fields) -> other_fields: kwargs

create_user는 사용자 이름 필드와 모든 필수 필드를 인수로 받아들여야 한다.

사용자 모델이 username_field를 email을 사용하고 필수 필드로 date_of_birth가 있는 경우 create_user를 다음과 같이 정의해야한다.

def create_user(self, email, date_of_birth, password=None):
      # 사용자 생성 정보 입력

create_superuser(username_field, password=None, other_fields) -> other_fields: kwargs

create_superuser는 사용자 이름 필드와 모든 필수 필드를 인수로 받아들여야 한다.

사용자 모델이 username_field를 email을 사용하고 필수 필드로 date_of_birth가 있는 경우 create_user를 다음과 같이 정의해야한다.

def create_superuser(self, email, date_of_birth, password=None):
      # 슈퍼유저 생성 정보 입력

normalize_email(email)

이메일 주소의 도메인 부분을 소문자로 하여 이메일 주소를 정규화 한다.

커스텀 사용자 및 django.contrib.admin

AbstractBaseUser가 관리자와 함께 작동하도록 할면 사용자 모델이 몇가지 추가 속성 및 메서드를 정의해야함.

이러한 방법을 통해 관리자는 관리자 콘텐츠에 대한 사용자의 액세스를 제어 할 수 있다.

아래 설명하는 구문들은 메서드이다.

is_staff

사용자가 관리 사이트에 액세스 할 수 있는 경우 True를 반환 한다.

def is_staff(self):
      return self.superuser  # 또는 self.is_admin

본인 프로젝트의 User 테이블에서 관리자를 나타낼 수 있는 속성이 있다면 해당 메서드에서 리턴되게 설정하면 된다.

is_active

사용자 계정이 현재 활성 상태이면 True를 반환 한다.

def is_active(self):
      return self.is_active

보이는 그대로 계정이 활성화가 되어있는지 아닌지 확인 하는 메서드이다.

has_perm(perm, obj=None)

사용자에게 권한이 있으면 True를 반환 한다.

obj가 제공되면 특정 개체 인스턴스에 대해 권한을 확인해야함.

def has_perm(self, perm, obj=None):
      if obj:
          return True
    return False

has_module_perms(app_label)

사용자에게 주어진 앱의 모델에 액세스 할 수 있는 권한이 있는 경우 True를 반환 한다.

def has_module_perms(app_label):
      return True

전체 예시

from django.db import models
from django.contrib.auth.models import BaseUserManager, AbstrctBaseUser


class UserManager(BaseUserManager):
      def create_user(self, email, date_of_birth, password=None):
          """지정된 email, data_of_birth로 사용자를 생성하고 저장한다."""
        if not email:
              # 사용자 email이 없을때 
              raise ValueError('Users must have an email address')

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

    def create_superuser(self, email, date_of_birth, password=None):
          """주어진 email, date_of_birth로 슈퍼 유저를 생성하고 저장한다."""
        user = self.create_user(
                email,
              password=password,
              date_of_birth=date_of_birth,
        )
        user.is_superuser = True
        user.save(using=self._db)
        return user

class User(AbstractBaseUser):
      email = models.EmailField(verbose_name='email address', max_length=255, unique=True)
    date_of_birth = models.DateField()
    is_active = models.BooleanField(default=True)
    is_superuser = models.BooleanField(default=False)

    objects = UserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    def __str__(self):
          return self.email

    def has_perm(self, perm, obj=None):
          """사용자에게 특정 권한이 있는지 확인"""
        return True

    def has_module_perms(self, app_label):
          """사용자가 app_label 앱을 볼 수 있는 권한이 있는지 확인"""
        return True

    @property
    def is_staff(self):
          """User가 관리자인지 확인"""
        return self.is_superuser
from django import forms
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from django.core.exceptions import ValidationError
from .models import User

class UserCreateForm(forms.ModelForm):
      """새 사용자를 만들기 위한 양식 / 필요한 모든 것을 포함 필드와 반복되는 암호"""
    password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
    password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)

    class Meta:
          model = User
        fields = ('email', 'data_of_birth')  # "__all__"

    def clean_password2(self):
          """두 암호가 일치하는지 확인"""
        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):
          """제공된 비밀번호를 해시 형식으로 저장"""
        user = super().save(commit=False)
        user.set_password(self.cleaned_data['password1'])
        if commit:
              user.save()
        return user

class UserChangeForm(forms.ModelForm):
      """
      사용자 업데이트를 위한 양식이다.
      사용자이지만 암호 필드를 관리자의 비활성화된 암호 해시로 표시한다.
      """
    password = ReadOnlyPasswordHashField()

    class Meta:
          model = User
        fields = ('email', 'password', 'date_of_birth', 'is_active', 'is_superuser')

class UserAdmin(UserAdmin):
      """사용자 인스턴스를 추가하고 변경하는 양식"""
    form = UserChangeForm
    add_form = UserCreateForm

    # 사용자 모델을 표시하는데 사용할 필드
    list_display = ('email', 'date_of_birth', 'is_superuser',)
    list_filter = ('is_superuser',)
    fieldsets = (
            (None, {'fields': ('email', 'password')}),
          ('Personal info', {'fields': ('date_of_birth',)}),
          ('Permissions', {'fields': ('is_admin',)}),
    )

    add_fieldsets = (
            (None, {
              'classes': ('wide',),
              'fields': ('email', 'date_of_birth', 'password1', 'password2',),
        }),
    )
    search_fields = ('email',)
    ordering = ('email',)
    filter_horizontal = ()

# 새 UserAdmin을 등록하기
admin.site.register(User, UserAdmin)

마지막으로 settings.py의 AUTH_USER_MODEL 설정을 사용하여 커스텀 모델을 프로젝트의 기본 사용자 모델로 지정한다.

AUTH_USER_MODEL = 'users.User'

'Django' 카테고리의 다른 글

Django seed로 테스트 데이터 자동 생성  (0) 2021.06.30
Django management command 사용하기  (0) 2021.06.30
DRF ModelSerializer  (0) 2021.06.19
DRF serializer  (0) 2021.06.19
DRF 함수형 뷰 (@api_view)  (0) 2021.06.19