DRF를 활용하여 프로젝트 리팩토링하기 (1)

gemma. K·2021년 3월 21일
3

django restframework

목록 보기
2/3
post-thumbnail

1. 회원가입 기능

Logic

  • 회원가입은 email과 username이 이미 존재하는 지 확인하는 절차가 필요하다.
  • 만약 존재하는 회원이 없는 경우, 클라이언트에서 전달 받은 정보들에 대한 유효성 검사를 한다.
  • 유효성 검사도 모두 통과한 회원정보는 데이터 베이스에 저장되며 회원 가입이 완료된다.

유효성 검사 1 : is_valid & validate_(obj) 활용하기

# views.py
class UserManage(APIView):
    def post(self, request):
        serializer = UserSerializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
        ...
        
# serializers.py
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'email', 'username', 'password', 'profile_image_url']
       
    def validate_email(self, obj):
        if email_isvalid(obj):
            return obj
        raise serializers.ValidationError('메일 형식이 올바르지 않습니다.')
        
# utils.py
def email_isvalid(value):
    try:
        validation = re.compile(r'^[a-zA-Z0-9+-_.]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$')
        if re.match(validation, value):
            return value
        raise Exception('메일 형식이 올바르지 않습니다.')
    except Exception as e:
        print('예외가 발생했습니다.', e)
  • 이 두 조합은 서로가 존재하지 않으면 불가능한 기능이다.
  • is_valid 이 실행되는 동시에 serializer의 validate_로 명명된 해당 필드들이 유효성 검사를 하게 된다.
  • 만약 is_valid이 False로 판명나면 해당 메시지를 클라이언트에 전달하며 회원가입을 할 수 없도록 했다.
  • 이메일, 비밀번호, 유저 네임에 관련된 유효성 검사 함수를 utils.py에 저장하여 재사용에 유리하도록 하였다.

유효성 검사 2: UniqueValidator

extra_kwargs = {
    'username': {
        'validators': [UniqueValidator(queryset=User.objects.all())]
    },
    'email': {
       'validators': [UniqueValidator(queryset=User.objects.all())]
    },
}
  • 이전에는 User.objects.get(email="")을 통해 회원이 존재하지 않는 경우를 체크해야 했고, 이메일은 괜찮더라도 해당 유저 네임을 이미 사용하고 있는 회원이 있다면 회원가입을 못하게 처리했다.
  • UniqueValidator를 활용하여 해당 테이블에 존재하는 이메일과 유저네임이 있는지 유효성 검사를 간단히 할 수 있다.

디테일한 오류 정보 전달하기

# views.py
if serializer.is_valid(raise_exception=True):
    serializer.save()
    return Response(data=serializer.data, status=200)
return Response(data="INFO_INVALID", status=400)
  • is_valid를 통해 serializer를 확인하고, 데이터 베이스에 해당 유저 정보를 저장할 수 있다.
  • serializer가 유효성 검사를 통과하지 못하면 data="INFO_INVALID를 담은 response를 전달해도 된지만 좀 더 디테일한 에러 정보를 전달하고 싶었다.
  • raise_exception=Trueis_valid 뒤에 추가해 주면 raise된 에러를 가시적으로 클라이언트에 전달하는 것이 가능하다.

예시1 이메일이 이미 있는 경우

{
    "email": [
        "This field must be unique."
    ]
}

예시2 비밀번호가 유효성 검사를 통과하지 못한 경우

{
    "password": [
        "비밀번호는 8 자리 이상이어야 합니다."
    ]
}

전달할 정보 제한하기

extra_kwargs = {
    'password': {'write_only': True},
}
  • 비밀번호는 보안상 암호화가 되었다 하더라도 클라이언트 측에 전달하지 못하게 하기 위해 write_only를 사용해 쓰기 기능으로만 사용하도록 수정하였다.



2. 회원정보 변경 기능

Logic

  • 기본적으로는 회원가입 serializer를 그대로 사용하고, update 함수를 통해 변경하고자 하는 정보를 전달하는 방식이다.

Object Instance를 활용하여 Serializer 생성하기

# views.py
user = User.objects.get(id=request.user.id)
serializer = UserSerializer(user, data=request.data)
  • User 테이블에서 가져온 유저 객체를 UserSerializer의 매개변수로 사용해서 존재하는 유저 객체를 담은 Serializer를 생성한다.
  • 수정하고자 하는 정보는 request.data를 통해 전달 받는다.

부분적인 업데이트 허용하기

# views.py
user = User.objects.get(id=request.user.id)
serializer = UserSerializer(user, data=request.data, partial=True)
  • pariail=True를 Serializer 생성에 추가하면 부분적인 .update를 허용하게 되고, 유효성 검사에서 수정하고자 하는 부분만 유효성 검사를 하게 된다.
  • 예를 들어, 회원이 비밀번호를 바꾼다고 할 때, 새로운 비밀번호와 access_token만 전달해 준다면 email이 있는지 없는지 확인하는 회원가입 Serializer를 재사용하더라도 업데이트 하고자 하는 새로운 정보만 유효성 검사를 하게된다.

.create() 혹은 .update()

# views.py
if serializer.is_valid(raise_exception=True):
    serializer.save()
    return Response(data=serializer.data, status=200)
    
# serializers.py
def update(self, obj, validated_data):
    obj.email = validated_data.get('email', obj.email)
    obj.username = validated_data.get('username', obj.username)
    obj.password = validated_data.get('password', obj.password)
    obj.profile_image_url = validated_data.get('profile_image_url', obj.profile_image_url)
    obj.save()
    return obj
  • serializer.data에 접근하기 위해서는 유효성 검사가 필수이며 동시에 업데이트 하고자 하는 정보에 대한 유효성 검사가 필요하므로 .is_valid()를 통해 유효성 검사를 한다.
  • 유효성 검사가 정상적으로 이루어진 경우, .save()를 사용하여 인스턴스를 업데이트 한다.
  • .save()는 해당 인스턴스가 데이터 베이스가 존재하는 경우, update 함수를 사용하여 인스턴스를 업데이트한다.
  • update 함수는 .get()을 사용하여 request.data로 전달 받은 해당 키 값이 있는 경우엔 그 값으로 수정하고, 아닌 경우 본 인스턴스의 정보를 그대로 사용하여 업데이트 하지 않는다.



Total Codes 🗂

# views.py
class UserManage(APIView):
    def post(self, request):
        serializer = UserSerializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response(data=serializer.data, status=200)
        return Response(data="INFO_INVALID", status=400)
        
    @login_decorator
    def patch(self, request):
        user = User.objects.get(id=request.user.id)
        serializer = UserSerializer(user, data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response(data=serializer.data, status=200)
        return Response(data='INFO_INVALID', status=400)

# serializers.py
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'email', 'username', 'password', 'profile_image_url']
        extra_kwargs = {
            'password': {'write_only': True},
            'username': {
                'validators': [UniqueValidator(queryset=User.objects.all())]
            },
            'email': {
                'validators': [UniqueValidator(queryset=User.objects.all())]
            },
        }

    def validate_email(self, obj):
        if email_isvalid(obj):
            return obj
        raise serializers.ValidationError('메일 형식이 올바르지 않습니다.')

    def validate_password(self, obj):
        if password_isvalid(obj):
            return hash_password(obj)
        raise serializers.ValidationError("비밀번호는 8 자리 이상이어야 합니다.")

    def validate_username(self, obj):
        if username_isvalid(obj):
            return obj
        raise serializers.ValidationError('닉네임은 한 글자 이상이어야 합니다.')
        
    def update(self, obj, validated_data):
        obj.email = validated_data.get('email', obj.email)
        obj.username = validated_data.get('username', obj.username)
        obj.password = validated_data.get('password', obj.password)
        obj.profile_image_url = validated_data.get('profile_image_url', obj.profile_image_url)
        obj.save()
        return obj

0개의 댓글