본문 바로가기
DEV/Web 개발

Web 개발 :: DRF 리뷰 _TIL#50

by 올커 2022. 11. 15.

■ JITHub 개발일지 50일차

□  TIL(Today I Learned) ::

Django Rest Framework 코드 리뷰

 - 유저 정보 유효성 검사(validation)

 - UserSerializer에서 비밀번호를 재확인하기 위해서 repassword라는 변수를 생성했다.

 - 임의의 인자를 추가로 받기 위해서 DRF에서 제공하는 extra_kwargs를 통해 error_messages를 지정해주었다. 항목은 required, invaild, blank를 지정했다.

---------------------------------------------------------------------------------------
class Meta:
        ...

        extra_kwargs = {
            # write_only : 해당 필드를 쓰기 전용으로 만들어 준다.
            # 쓰기 전용으로 설정 된 필드는 직렬화 된 데이터에서 보여지지 않는다.
            'password': {'write_only': True}, # default : False
            'email': {
                # error_messages : 에러 메세지를 자유롭게 설정 할 수 있다.
                'error_messages': {
                    # required : 값이 입력되지 않았을 때 보여지는 메세지
                    'required': '이메일을 입력해주세요.',
                    # invalid : 값의 포맷이 맞지 않을 때 보여지는 메세지
                    'invalid': '알맞은 형식의 이메일을 입력해주세요.'
                    },
                    # required : validator에서 해당 값의 필요 여부를 판단한다.
                    'required': False # default : True
                    },
            }

  1) required : 비어있을 경우 나타내는 에러메시지

  2) invalid : 값의 포맷이 맞지 않을 경우 나타내는 에러메시지

  3) blank : ???

class UserSerializer(serializers.ModelSerializer):
    repassword = serializers.CharField(error_messages={'required':'비밀번호를 입력해주세요', 'blank':'비밀번호를 입력해주세요.'})
    
    class Meta:
        model = User
        fields = ('email', 'nickname', 'password', 'repassword','profile_image',)
        
        # 각 필드에 해당하는 다양한 옵션 지정
        extra_kwargs = {'email':{
                            'error_messages':{
                                'required':'이메일을 입력해주세요',
                                'invalid' : '알맞은 형식의 이메일을 입력해주세요',
                                'blank' : '이메일을 입력해주세요',
                            }},
                            ...

 - class Meta에 대한 설정을 완료하고, validate를 지정해주어야 한다.

 - 유효성검사 케이스는 닉네임, 패스워드이다. data.get을 통해 유효성 검사에 필요한 각각의 값들을 가져온다.

 - 아래 함수들을 validators.py에 생성함을 통해 유효성검사에 활용할 수 있다.

  1) contains_special_character(<str>) : 특수문자가 포함되어있는지 확인

  2) contains_uppercase_letter(<str>) : 대문자가 포함되어있는지 확인

  3) contains_lowercase_letter(<str>) : 소문자가 포함되어있는지 확인

  4) contains_number(<str>) : 숫자가 포함되어있는지 확인

import string

#특수문자 유효성검사
def contains_special_character(value):
    for char in value:
        if char in string.punctuation:
            return True
    return False

#영어 대문자 유효성 검사
def contains_uppercase_letter(value):
    for char in value:
        if char.isupper(): 
            return True
    return False

#영어 소문자 유효성 검사
def contains_lowercase_letter(value):
    for char in value:
        if char.islower():
            return True
    return False

#숫자 유효성 검사
def contains_number(value):
    for char in value:
        if char.isdigit():
            return True
    return False

 - 유효성 검사에 맞지 않을 경우 serializers.ValidationError(detail={"<이름>":"<오류내용>"})을 사용하여 에러 메시지를 전달한다.

 

def validate(self, data):
        nickname = data.get('nickname')
        password = data.get('password')
        repassword = data.get('repassword')
        
        # nickname 유효성 검사
        if contains_special_character(nickname) or len(nickname)<3:
            raise serializers.ValidationError(detail={
                "nickname":"닉네임은 2자 이하 또는 특수문자를 포함할 수 없습니다."
            })
        
        # 비밀번호 유효성 검사
        if password:
            # 비밀번호 일치여부
            if password != repassword:
                raise serializers.ValidationError(detail={
                    "password":"비밀번호가 일치하지 않습니다."
                })
        
            # 비밀번호 유효성
            if (len(password) < 8 or len(password) > 17
                or not contains_uppercase_letter(password)
                or not contains_lowercase_letter(password)
                or not contains_number(password)
                or not contains_special_character(password)):
                raise serializers.ValidationError(detail={
                    "password":"비밀번호는 8자 이상 16자 이하의 영문 대/소문자, 숫자, 특수문자 조합이어야 합니다."
                })
        return data

 - UserSerializer 중 회원 가입 기능

 view에서는 request.data를 가져와서 serializer에서 validated_data로 받아온다. 

 받아온 데이터에서 email과 nickname을 각각 추출하는데 이 때 dict형식으로 가져오므로 key타입으로 가져와서 각각 변수에 지정하였다.

 지정된 변수는 User 모델에 맞게 입력해 넣고, password는 set_password를 통해 해싱처리한 후 마지막에 save()를 통해 저장한다.

    # 회원 가입
    def create(self, validated_data):
        # request.data를 통해 받아온 데이터(validated_data) email과 nickname을 각 변수에 넣고 User모델에 넣어 패스워드를 해싱처리 후 user에 저장
        email = validated_data['email']
        nickname = validated_data['nickname']
        user = User(
            email=email,
            nickname=nickname
        )
        user.set_password(validated_data['password'])      # set_password : 패스워드 해싱처리
        user.save()
        return user

 - 회원 정보 수정시에는 instance가 추가된다. instance는 view에서 이미 user = get_object_or_404(User, id=request.user.id)를 통해 가져온 유저정보이며, validated_data는 위의 회원가입과 동일하게 request.data이다.

 이는 정보수정할 때에 기존의 user정보를 가져와서 입혀넣어야 하기 때문이며, view에서는 partial = True를 통해 기존의 데이터에 수정사항이 없을 경우 기존 데이터를 입혀넣을 수 있도록 한다.

serializer = UserSerializer(user, data=request.data, partial=True)

 - 회원가입과 동일하게 기존의 instance의 각 항목에 validated_data의 각 항목을 넣어준다. 이 때 .get을 사용하여 항목들을 가져왔는데, 아래 drf docs를 참고하였다.

    # 수정
    def update(self, instance, validated_data):     # instances는 현재 user를, validated_data는 request.data를 받아온다.
        # 아래 처럼 instance에 request를 통해 가져온 데이터를 넣는다.
        instance.nickname = validated_data.get("nickname", instance.nickname)
        instance.profile_image = validated_data.get('profile_image', instance.profile_image)
        instance.save()
        return instance

*출처 (DRF docs) : https://www.django-rest-framework.org/api-guide/serializers/

 - 백엔드에서 모두 셋팅을 해주었다고 생각했지만 반응이 없어 콘솔 창을 확인해본 결과, CORS 에러가 있었다. settings.py에서 아래 사항을 모두 확인해주어야 한다. 마지막 코드인 CORS_ALLOW_ALL_ORIGINS = True를 누락해서 계속 에러가 발생하고 있었던 것이다.

INSTALLED_APPS = [
    ...
    'corsheaders',
    ...
]
MIDDLEWARE = [
    ...
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
CORS_ALLOW_ALL_ORIGINS = True

 

 - 이제 Song app에 대한 세팅을 해준다.

 - 모델과 Serializer를 정의해주는데, song의 기능 중 '좋아요'가 있다. 좋아요는 User모델과 manytomany로 엮고, 좋아요 숫자를 count해야 한다. 아래와 같이 필요한 모델을 셋팅하는데 맨 아래줄 처럼 likes를 정의한다.

class Song(modesl.Model):
    year = models.CharField(max_length=70, blank=True)
    rank = models.CharField(max_length=70, blank=True)
    title = models.CharField(max_length=70, blank=True)
    genre = models.CharField(max_length=70, blank=True)
    singer = models.CharField(max_length=70, blank=True)
    type = models.CharField(max_length=70, blank=True)
    lyrics = models.TextField(blank=True)
    likes = models.IntegerField(blank=True)
    image = models.TextField(blank=True)
    genre_no = models.IntegerField(blank=True)
    
    song_likes = models.ManyToManyField(User, related_name="like_song",blank=True)

 - serializer를 셋팅해야 하는데 위의 특성을 위해 serializermethodfield를 사용한다.

class SongSerializer(serializers.ModelSerializer):
    class Meta:
        model = Song
        fields = "__all__"
        
    song_likes = serializers.StringRelatedField(many=True)
    song_likes_count = serializers.SerializerMethodField()
    ...
    
    def get_song_likes_count(self, obj) :    
        return obj.song_likes.count()

 *SerializerMethodField는 아래의 케이스에 사용한다.

  - 모델에 없는 필드이지만 JSON에 특정 필드를 추가하여 전달할 경우 
  - 모델에 정의된 필드 값을 변경해서 JSON으로 전달할 경우

 

위에서 정의한 SerializerMethodField는 클래스 아래에 get_song_likes_count와 같이 함수로 정의해준다.

 

반응형

댓글