<TIL - 0029> Serializer?

개발일지·2023년 4월 26일
0

til

목록 보기
29/43


Serializer란?

  • django의 object, queryset 인스턴스 등 복잡한 테이터들을 json같은 다른 콘텐츠 유형으로 쉽게 변환 할 수 있다.
  • create, update 시 validation 기능을 제공한다.
  • serializer Meta class
    • serializer에서 사용되는 설정 파일이다.
    • model에 사용 될 테이블을 적어주고, field에 사용될 필드를 적어준다.
    • extra_kwargs, read_only_fields와 같은 옵션을 통해 다양한 설정이 가능하다.
  • 기본적인 serializer 사용법
    • serializers.py
      from rest_framework import serializers
      
      class UserSerializer(serializers.ModelSerializer):
         class Meta:
              # serializer에 사용될 model, field지정
              model = User
              # 모든 필드를 사용하고 싶을 경우 fields = "__all__"로 사용
              fields = ["username", "password", "fullname", "email"]
    • views.py
      from rest_framework.response import Response
      from rest_framework import status
      
      from user.serializers import UserSerializer
      
      def get(self, request):
          user = request.user
          # serializer에 queryset을 인자로 줄 경우 many=True 옵션을 사용해야 한다.
          serialized_user_data = UserSerializer(user).data
          # context= 를 통해 원하는 데이터를 serializer에 넘겨주고, self.context를 사용해 호출 가능하다.
          # serialized_user_data = UserSerializer(user, context={"some_key": "some_value"}).data
          return Response(serialized_user_data, status=status.HTTP_200_OK)
      
      # return data
      """
      {
          "username": "user",
          "password": "pbkdf2_sha256$320000$u5YnmKo9luab9csqWpzRsa$pKfqHnBiF5Rgdo1Mj9nxNOdhpAl9AhPVXFPXkbPz7Mg=",
          "fullname": "user's name",
          "email": "user@email.com"
      }
      """
  • 외래 키 관계에 있는 테이블이 있을 경우, 해당 테이블의 serializer를 생성해 함께 사용할 수 있다.
    • code(serializer.py)
      class UserProfileSerializer(serializers.ModelSerializer):
          class Meta:
              model = UserProfile
              fields = "__all__"
      
      class UserSerializer(serializers.ModelSerializer):
          """
          외래 키는 UserProfile에서 User 테이블로 설정되어 있지만
          one to one 필드기 때문에 userprofile이라는 명칭으로 역참조가 가능하다.
          """
          userprofile = UserProfileSerializer()
          class Meta:
              model = User
              fields = ["username", "password", "fullname", "email", "userprofile"]
  • SerializerMethodField를 활용해 원하는 필드를 추가하고, 더 나아가서 여러 serializer들을 함께 사용할 수 있다.
    • code(serializers.py)
      class HobbySerializer(serializers.ModelSerializer):
          # serializers.SerializerMethodField()를 사용해 원하는 필드를 생성한다.
          same_hobby_users = serializers.SerializerMethodField()
          def get_same_hobby_users(self, obj):
              user_list = []
              for user_profile in obj.userprofile_set.all():
                  user_list.append(user_profile.user.username)
      
              return user_list
      
          class Meta:
              model = Hobby
              fields = ["name", "same_hobby_users"]
      
      class UserProfileSerializer(serializers.ModelSerializer):
          # 외래 키 관계로 이어져 있는 필드는 Serializer를 바로 호출할 수 있다.
          hobby = HobbySerializer(many=True)
      
          class Meta:
              model = UserProfile
              fields = "__all__"
      
      class UserSerializer(serializers.ModelSerializer):
          # One-to-one 관계에서는 fk처럼 사용 가능하다.
          userprofile = UserProfileSerializer()
      
      ****    class Meta:
              model = User
              fields = ["username", "password", "fullname", "email", "userprofile"]
      
      # views.py
      ...
      class UserView(APIView)
          def get(self, request):
              user = request.user
              return Response(UserSerializer(user).data, status=status.HTTP_200_OK)
      
      # response data
      """
      {
          "username": "admin",
          "password": "pbkdf2_sha256$320000$u5YnmKo9luab9csqWpzRsa$pKfqHnBiF5Rgdo1Mj9nxNOdhpAl9AhPVXFPXkbPz7Mg=",
          "fullname": "zxcv",
          "email": "zxv@asd.com",
          "userprofile": {
              "birthday": "2022-06-08",
              "age": 1,
              "introduction": "asdac",
              "hobby": [
                  {
                      "name": "독서",
                      "same_hobby_users": [
                          "user1",
                          "user2",
                          "user3"
                      ]
                  }
              ]
          }
      }
      """

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를 사용해 기존 데이터들 쉽게 업데이트 할 수 있다.
    • update 예시(views.py)
      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"]


profile
아닐지

0개의 댓글