본문 바로가기

문돌이 존버/Django 스터디

DRF 유저 모델(Profile), 아이디 및 이메일 중복확인

반응형

이번엔 유저 모델을 생성하여 회원가입시 입력한 정보를 DB에 저장하도록 하겠습니다. 먼저 아래 views.py 를 보시면 Profile이란 모델이 있습니다. django auth models 의 AbstractUser를 상속하면 추가하고 싶은 필드를 자유롭게 쓰면 됩니다. 이와 관련된 내용은 제 다른 글을 참고해주세요.

Django DRF JWT를 이용한 회원가입/로그인 구현

 

Django DRF JWT를 이용한 회원가입/로그인 구현 - (2)

DRF에선 기본적인 유저 모델을 사용할 수도 있고, 이를 확장해서 커스텀 유저 모델을 사용할 수도 있습니다. UserManager, AbstractUser, AbstractBaseUser 등이 있으며 각 방식에는 조금씩 차이가 있는데 관

moondol-ai.tistory.com

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

from PIL import Image
from PIL import ExifTags
from io import BytesIO
from djagno.core.files import File

class Profile(AbstractUser):
    nickname = models.CharField(max_length=30, unique=True, blank=False)
    profile_image = models.ImageField(upload_to='profile_image/', null=True, blank=True)

    def __str__(self):
        return '%s' % (self.username)

    def save(self, *args, **kwargs):
        if self.profile_image:
            pilImage = Image.open(BytesIO(self.profile_image.read()))
            try:
                for orientation in ExifTags.TAGS.keys():
                    if ExifTags.TAGS[orientation] == 'Orientation':
                        break
                exif = dict(pilImage._gettextif().items())

                if exif[orientation] == 3:
                    pilImage = pilImage.rotate(180, expand=True)
                elif exif[orientation] == 6:
                    pilImage = pilImage.roate(270, expand=True)
                elif exif[orientation] == 8:
                    pilImage = pilImage.rotate(90, expand=True)
                
                output = BytesIO()
                pilImage.save(output, format='JPEG', quality=100)
                output.seek(0)
                self.profile_image = File(output, self.profile_image.name)
            except:
                pass

        return super(Profile, self).save(*args, **kwargs)

위에서 유저가 프로파일 이미지용으로 어떤 사진을 업로드하면 PIL 라이브러리를 통해 전처리를 수행하고 최종 DB에 저장하는데요. 교환 이미지 파일 형식, Exchangable Image File format)디지털 카메라에서 이용되는 이미지 파일 포맷이라고 합니다. Exif 태그 중에서 orientation을 가져오면 이미지 회전과 관련된 정보를 알 수 있습니다. orientation 라벨링이 의미하는 바는 아래 사진을 보시면 됩니다.

<출처: impulseadventure>

이제 회원가입 시에 필요한 각종 작업들(유저 정보 표시, 유효성 검사, 아이디 및 이메일 중복 확인 등)을 처리하는 방법을 소개하겠습니다. 

우선 유저 이름이 적절한지 유효성을 판단하기 위해 앱 폴더에서 validators.py 파일을 새롭게 만들고 아래와 같이 작성해줍니다. 알파벳이 아닌 다른 기호 등은 사용하지 못하도록 했습니다. 장고에서 제공하는 RegexValidators 를 상속받아 커스터마이징 한 것입니다.

import re
from django.core import validators
from django.utils.deconstruct import deconstructible
from django.utils.translation import gettext as _

@deconstructible
class CustomASCIIUsernameValidator(validators.RegexValidator):
    regex = r'^[\w]+$'
    message = _(
        'Please enter a vliad username. You input something wrong.'
    )

RegexValidators 파라미터는 공식 홈페이지를 참고해주세요. 참고로 message의 디폴트 값은 "Enter a valid name"이네요. regex와 관련된 내용은 파이썬의 정규표현식 re 홈페이지를 참고해주세요. @deconstructible은 기존 RegexValidaotr 코드에도 명시되어 있어 그대로 따랐습니다.

참고: 정규표현식(regular expression)을 확인하려면 다음 사이트를 이용해보세요. 실습으로 간단하게 확인할 수 있습니다.

설명을 보니 커스터마이징 한 클래스를 seriazlie 할 수 있도록 지원하는 것 같네요. 데코레이터 @deconstructible로 deconstruct() method를 추가하지 않으면 makemigrations를 할 때 오류가 발생한다고 합니다. python - What's the purpose of Django "deconstruct" model field function? - Stack Overflow

 

What's the purpose of Django "deconstruct" model field function?

In order to develop custom Django model fields, I'm reading the documentation. I have already developed my custom field (which is almost equal to that of the example, HandField: a field mapped ove...

stackoverflow.com

다국어 번역을 지원하기 위해 translation 라이브러리의 gettext를 가져옵니다. lazy_gettext 버전도 있는데, 이는 실제 사용자 클라이언트로부터 요청이 들어와야 번역을 하는 것입니다. 반면, gettext는 웹 페이지를 실행하는 동시에 번역을 하는 것으로 보입니다. 둘 간의 차이점을 설명한 곳은 여기입니다. 

유저 모델에 nickname과 profile_image 필드를 추가로 작성했고, 이를 response로 내보내거나 회원가입할 때 표시하려면 aullauth의 adapter 를 이용해야 합니다. 이 부분은 이전에도 설명한 적이 있습니다. 관련 내용은 홈페이지를 참고해주세요. adapter.py 파일을 앱 폴더 내에 별도로 작성해야 합니다. 

# adapter.py
from allauth.account.adapter import DefaultAccountAdapter
from django.conf import settings

class CustomAccountAdapter(DefaultAccountAdapter):
    # 유저 모델 추가 필드 나타내기
    def save_user(self, request, user, form, commit=False):
        user = super().save_user(request, user, form, commit)
        data = form.cleaned_data
        user.nickname = data.get('nickname')
        user.profile_image = data.get('profile_image')
        user.save()
        return user

DefaultAccountAdapter 원본 코드는 여기를 참고해주세요. 

다음은 serializers.py 에 추가할 내용입니다. rest-auth의 RegisterSerializer 를 상속받아 저희만의 시리얼라이저를 작성합니다. 

# serializers.py
from rest_framework import serializers
from .models import *

from rest_auth.registration.serializers import RegisterSerializer
from rest_framework.validators import UniqueValidator

from .validators import CustomASCIIUsernameValidator
from django.utils.translation import gettext as _

class ProfileSerializer(RegisterSerializer):
    username = serializers.CharField(
        required=True,
        min_length=3,
        max_length=30,
        # multiple validators
        validators=[UniqueValidator(queryset=Profile.objects.all()), CustomASCIIUsernameValidator()],
        error_message={
            'unique': _('The username has already used by others.'),
        },

    )

    nickname = serializers.CharField(
        required=True,
        min_length=1,
        max_length=30,
        validators=[UniqueValidator(queryset=Profile.objects.all())]
    )
    profile_image = serializers.ImageField(required=False)

    class Meta:
        model = Profile

    def get_cleaned_data(self):
        data_dict = super().get_cleaned_data()
        data_dict['nickname'] = self.validated_data.get('nickname', '')
        data_dict['profile_image'] = self.validated_data.get('profile_image', '')
        return data_dict

DRF의 serializers 필드의 validators=[] 관련 정보는 공식 홈페이지를 참고해주세요. 여러 가지 validators를 함께 적용시킬 수 있습니다. UniqueValidator 함수는 말 그대로 해당 아이디가 유니크한지 체크하고 True/False를 리턴합니다. 

마지막으로 이를 적용하기 위해 settings.py 에서 저희가 커스터마이징한 ProfileSerializer로 rest_auth 및 aullauth의 configuration을 오버라이딩 합니다. 

# settings.py
# 커스터마이징한 유저 모델을 기본으로 설정
AUTH_USER_MODEL = 'accounts.Profile'

# response에 나타날 정보
REST_AUTH_SERIALIZERS = {'USER_DETAILS_SERIALIZER': 'accounts.serializers.ProfileSerializer'}
REST_AUTH_REGISTER_SERIALIZERS = {'REGISTER_SERIALIZER': 'accounts.serializers.ProfileSerializer'} 

ACCOUNT_ADAPTER = 'accounts.adapter.CustomAccountAdapter'
python manage.py makemigrations accounts
python manage.py migrate accounts
python manage.py migrate

저는 기능별로 앱을 따로 작성했기 때문에 위와 같이 별도로 makemigrations를 실행해주었습니다. 주의할 점은 migrate는 2번 실행해야 합니다. 지정 앱의 migrate와 프로젝트 자체의 migrate가 필요합니다. 

위의 모든 과정을 거치면 아래 회원가입을 할 때 입력한 요소가 response로 보여집니다. 

(참고) admin 페이지에서 유저 정보를 보려면 아래 admin.py 파일에 내용을 입력하면 됩니다. 따로 유저 모델을 커스터마이징 했기 때문에 추가하지 않으면 유저 정보가 admin 페이지에서 안 보입니다.

# admin.py
from django.contrib import admin

from .models import *

class ProfileAdmin(admin.ModelAdmin):
    list_display = ('username', 'nickname')

admin.site.register(Profile, ProfileAdmin)

테스트 차원으로 아이디를 아래와 같이 기호로만 입력해봤습니다. 위에서 설정한 메세지대로 잘 나타내고 있네요. 

그리고 이메일 중복은 기본적으로 DefaultAccountAdapter 를 상속하면서 설정이 됩니다. 아래 부분에서 "email_taken"을 확인하시면 위의 메시지와 똑같은 걸 알 수 있겠죠? 보시다시피 "username_taken"에 따라 아이디도 중복 확인이 자동으로 됩니다. 

728x90
반응형