내배캠/TIL

TIL/36/20230501

woongpang 2023. 5. 1. 21:20
728x90

serializer 활용

serializer는 데이터 직렬화 외에도 data validation, create, update 기능을 사용할 수 있다.

 

validator

- serializer에서는 기본적으로 Meta class 내부 field에 포함되어 있는 항목에 맞게 validate를 진행한다.

- validator 예시(views.py)

from user.serializers import UserSerializer

...

class UserView(APIView):
    def post(self, request):
        # serializer의 data 인자에는 model로 지정 된 테이블의 field:value를 dictionary로 넘겨준다.
        user_serializer = UserSerializer(data=request.data)
        # serializer validator를 통과하지 않을 경우 .is_valid()가 False로 return된다.
        if user_serializer.is_valid():
            # validator를 통과했을 경우 데이터 저장
            user_serializer.save()
            return Response({"message": "정상"}, status=status.HTTP_200_OK)
        
        # .errors에는 validator에 실패한 필드와 실패 사유가 담겨져 있다.
        return Response(user_serializer.errors, status=status.HTTP_400_BAD_REQUEST)

# sample request.data
"""
{
    "username": "new_user",
    "password": "MyL0ve1yP@ssw0rd",
    "fullname": "myname",
    "userprofile": {
        "introduction": "자기소개입니다.",
        "birthday": "2000-1-01",
        "age": 30
    },
    "trash": "zczxcvx"
}
"""

- serializer에서 사용 가능한 옵션들

class UserSerializer(serializers.ModelSerializer):
    # 외래 키 관계에 있는 필드의 required를 설정하고 싶을 경우 인자로 넘겨줘야 한다.
    userprofile = UserProfileSerializer(required=False) # default : True
    ...
    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
                    },
            }

- view에서 사용 가능한 옵션들

# serializer의 인자에 object를 넣어 직렬화 된 데이터를 가져올 수 있다.
user = request.user
return Response(UserSerializer(user).data, status=status.HTTP_200_OK)

# object와 마찬가지로 queryset을 인자로 넣어 여러개의 직렬화 된 데이터를 가져올 수 있다.
hobbys = Hobby.objects.all()
# queryset을 인자로 넣을 경우 many=True 설정 필요하다.
return Response(HobbySerializer(hobbys, many=True).data, status=status.HTTP_200_OK)

# partial을 True로 설정할 경우 required field에 대한 validation을 수행하지 않는다.
# 주로 일부 필드를 update 할 때 사용된다.
user_serializer = UserSerializer(data=request.data, partial=True)

# raise_exception을 True로 설정할 경우 validation을 통과하지 못했을 때 exception을 발생시킨다.
user_serializer = UserSerializer(data=request.data, raise_exception=True)

- custom validator

  • custom validator는 validator 이후에 동작한다.
  • custom validator는 validator와 별개로 동작한다.
    • validator는 데이터의 requierd, invalid 등을 판단하고 custom validator에서는 사용자가 원하는 validation을 추가로 검증 할 수 있다.
  • custom validator 예시(serializers.py)
...
class UserSerializer(serializers.ModelSerializer):
    ...
    # validate 함수 선언 시 serializer에서 자동으로 해당 함수의 validation을 해줌
    def validate(self, data):
        # custom validation pattern
        if data.get("userprofile", {}).get("age", 0) < 12:
            # validation에 통과하지 못할 경우 ValidationError class 호출
            raise serializers.ValidationError(
                    # custom validation error message
                    detail={"error": "12세 이상만 가입할 수 있습니다."},
                )

        # validation에 문제가 없을 경우 data return
        return data
    ...

creator

- serializer에서는 validation을 통과할 경우 .save() 메소드를 통해 검증 된 오브젝트를 생성 할 수 있다.

- 사용 방법은 validator 예시에 작성한 코드와 동일하다.

- custom creator 코드는 기존 create 코드를 덮어쓰며, custom creator를 생성할 경우 기존 create 코드는 동작하지 않는다.

- custom creator (serializers.py)

...
class UserProfileSerializer(serializers.ModelSerializer):
    # hobby는 데이터를 직렬화 할 때, get_hobbys는 profile을 등록할 떄 사용된다.
    hobby = HobbySerializer(many=True, required=False, read_only=True)
    get_hobbys = serializers.ListField(required=False)

    class Meta:
        model = UserProfile
        fields = ["birthday", "age", "introduction", "hobby", "get_hobbys"]

class UserSerializer(serializers.ModelSerializer):
    userprofile = UserProfileSerializer()
    def create(self, validated_data):
        # object를 생성할때 다른 데이터가 입력되는 것을 방지하기 위해 미리 pop 해준다.
        user_profile = validated_data.pop('userprofile')
        get_hobbys = user_profile.pop("get_hobbys", [])

        # User object 생성
        user = User(**validated_data)
        user.save()

        # UserProfile object 생성
        user_profile = UserProfile.objects.create(user=user, **user_profile)
        
        # hobby 등록
        user_profile.hobby.add(*get_hobbys)
        user_profile.save()
...
    class Meta:
        model = User
        fields = ["username", "password", "fullname", "email", "userprofile"]
...
# sample request data
"""
{
    "username": "user_name",
    "password": "H0t$ix",
    "fullname": "이름",
    "email": "sample@email.com",
    "userprofile": {
        "introduction": "자기소개입니다.",
        "birthday": "2000-1-01",
        "age": 13,
        "get_hobbys": [3,4,5,6]
    }
}
"""

 

updater

- serializer를 사용해 기존 데이터들 쉽게 업데이트 할 수 있다.

from user.serializers import UserSerializer

...

class UserView(APIView):
    def post(self, request):
        user = request.user
        if user.is_anonymous:
            return Response({"error": "로그인 후 이용해주세요", status=status.HTTP_400_BAD_REQUEST}
        
        # 기본적인 사용 방법은 validator, creater와 다르지 않다.
        # update를 해줄 경우 obj, data(수정할 dict)를 입력한다.
        # partial=True로 설정해 주면 일부 필드만 입력해도 에러가 발생하지 않는다.
        user_serializer = UserSerializer(user, data=request.data, partial=True)

        if user_serializer.is_valid():
            # validator를 통과했을 경우 데이터 저장
            user_serializer.save()
            return Response({"message": "정상"}, status=status.HTTP_200_OK)
        
        return Response(user_serializer.errors, status=status.HTTP_400_BAD_REQUEST)

custom update 코드는 기존 update 코드를 덮어쓰며, custom updater를 생성할 경우 기존 create 코드는 동작하지 않는다.

custom update 예시(views.py)

class UserSerializer(serializers.ModelSerializer):
    userprofile = UserProfileSerializer()
    ...
    def update(self, instance, validated_data):
        # instance에는 입력된 object가 담긴다.
        for key, value in validated_data.items():
            if key == "password":
                instance.set_password(value)
                continue
            
            setattr(instance, key, value)
        instance.save()
        return instance
    ...
    class Meta:
        model = User
        fields = ["username", "password", "fullname", "email", "userprofile"]
728x90