FrameWork/Django

DRF Serializer

mansoorrr 2024. 7. 18. 13:26

Serializer는 아래와 같은 용도로 사용됐다.

  • get:  조회할 필드를 정하는 스키마의 역할
  • post, put:  생성이나 수정할때 입력받을 form
  • validation: 데이터 유효성 검사

그냥 serializer를 사용할 경우, model에서 필드를 정의할때 입력한 부분들을 다시 입력해야 하는 번거러움이 있다. 또한, create, update매서드를 직접 만들어야 한다. 이를 해결하기 위해 ModelSerializer를 사용한다.

 

 

[** ModelSerializer **]

  • 그냥 serializer를 사용할 경우, model에서 필드를 정의할때 입력한 부분들을 다시 입력해야 하는 번거러움 존재
  • 또한, create, update매서드를 직접 만들어야 함
  • model의 필드를 자동으로 가져옴
  • validator를 자동으로 수행
  • create(), update() 매서드를 기본으로 제공
#---------- categories/serializers.py 변경 전
class CategorySerializer(serializers.Serializer):
    pk = serializers.IntegerField(read_only=True)
    name = serializers.CharField()
    kind = serializers.ChoiceField(        
        choices=Category.CategoryKindChoice.choices,
    )
    created_at = serializers.DateTimeField(read_only=True)

    def create(self, validated_data):
        return Category.objects.create(**validated_data)
    
    def update(self, instance, validated_data):
        instance.name = validated_data.get('name', instance.name)
        instance.kind = validated_data.get('kind', instance.kind)
        instance.save()
        return instance



#---------- categories/serializers.py 변경 후
class CategorySerializer(serializers.ModelSerializer):
	#serializer를 정의
    class Meta:
        model = Category #어떤 모델을 serializer할 것인지?
        fields = "__all__" #모든필드, (필드1, 필드2)
        #exclude = (필드1, 필드2...) # 제외할 필드

 

 

[** SerializerMethodField **]

  • 모델에 정의되지 않은 필드(계산식 적용 등의 이유)를 serializer로 내보내기 위해 사용
  • 모델에 메서드로 만들어둔 필드가 있다면 그 필드를 사용할 수 있음
  • 필드를 변수로 정의 > 변수에 대한 함수 만들기(get_변수명(self, transaction)) > fields에 적용 순으로 이루어짐
'''
1. RoomListSerializer를 사용하면 "pk", "name", "country", "city", "price" 필드 데이터만 추출
2. Room 모델에 매서드로 만들어둔 rating 필드도 추출하길 원함
3. RoomListserializer 수정
'''

#-------------------- Rooms/models.py
class Room(CommonModel):

    '''Room definition'''
    
    <생략...>
    
    # 변수를 조합하여 만든 필드
    def rating(self):
        #backref사용하여 코드작성        
        review_count = self.reviews.count()
        if review_count == 0:
            return 'no reviews'
        total_rating = 0
        for rating in self.reviews.values('rating'):
            total_rating += rating['rating']
        return round(total_rating / review_count, 2)
        
 #---------- Rooms/serializers.py
 class RoomListSerializer(ModelSerializer):

	#1. 새로운 변수 정의
	rating = SerializerMethodField()

	class Meta:
		model = Room
        
        #3. fields에 적용
		fields = (
			"pk", "name", "country", "city", "price", "rating",
		)
	
    #2. 정의한 변수에 대한 매서드 만들기
	def get_rating(self, room):
		return room.rating() #models.py에 만들어 놓은 매서드 활용

좌: 적용 전 / 우: 적용 후

 

 

[** Serializer Context **]

  • views.py에서 Serializer를 사용할때 'context'라는 parameter를 사용가능
  • 그러면 views > serializer로 데이터를 보낼 수 있음
  • 보내진 데이터는 serializer에서 가공 및 사용 가능(예: SerializerMethodField)
#----------- Rooms/views.py
class Rooms(APIView):
     def get(self, request):
          all_rooms = Room.objects.all()
          # context 추가
          serializer = RoomListSerializer(
               all_rooms, many=True, context={'request':request}
          )
          return Response(serializer.data)
          
  #----------- Rooms/views.py
  class RoomListSerializer(ModelSerializer):

	#새로운 변수 정의
	rating = SerializerMethodField()
	is_owner = SerializerMethodField()

	class Meta:
		model = Room
		fields = (
			"pk", "name", "country", "city", "price", "rating", "is_owner",
		)

	def get_rating(self, room):
		return room.rating()
        
	# context 활용
	def get_is_owner(self, room):
		request = self.context #view > serializer로 보낸 데이터 받기
		return request['request'].user == room.owner #admin패널에 로그인 되어있는 유저와 방을 생성한 유저 일치 여부

좌: context 적용 전 / 우: context 적용 후

 

 

[** Reverse Serializer **]

  • 역접근자를 활용한 serializer custom을 의미
  • ForeignKey, ManyToMany를 사용한 필드들의 경우 역접근을 제어하기 위해  django에서는 <변수>_set를 제공
  • 또한 이와 같은 기능으로 related(=backref)도 있음
  • serializer에서 역접근자를 이용해 데이터를 출력 가능
  • 역참조 기능으로 인해 너무 많은 데이터가 출력될 수 있음. 이것을 방지하기 위해 pagination 적용 가능
'''
1. 1개 room에 달린 review들을 출력하고 싶음
2. Review모델에 room필드는 Room과 ForeignKey관계, related=reviews로 역참조
3. 따라서 ReviewSerializer를 만들어 RoomdetailSerializer에 적용
'''

#---------- Review/serilizers.py
from rest_framework import serializers

from reviews.models import Review


class ReviewSerializer(serializers.ModelSerializer):
	class Meta:
		model = Review
		fields = "__all__"
        
        
#---------- Room/serializers.py
class RoomDetailSerializer(ModelSerializer):

	owner = TinyUserSerializer(read_only=True)
	category = CategorySerializer(read_only=True)
	amenities = AmenitySerializer(read_only=True, many=True)

	rating = SerializerMethodField()
	is_owner = SerializerMethodField()
	reviews = ReviewSerializer(read_only=True, many=True) #reviews 추가

	class Meta:
		model = Room
		fields = "__all__"
		
	def get_rating(self, room):
		return room.rating()
	
	def get_is_owner(self, room):
			request = self.context["request"]
			return room.owner == request.user

1개의 room에 달린 reviews의 정보를 확인 가능

 

[** Validate Serializer **]

  • serializer는 스키마 역할로 사용할수도 있지만 validation역할로도 사용할 수 있다.
  • validation역할로 사용하는 방법은 2가지가 있다.
    • 필드 각각을 validation: 필드 각각에 대한 validation함수를 만들어준다
      • def validate_<필드명>(self, value)
    • 한번에 모든 필드 validation: 함수 하나에 validation 하고자 하는 필드를 모두 담는다
      • def validate(self, data)
class CreateBookingSerializer(serializers.ModelSerializer):

    #override
    check_in = serializers.DateField()
    check_out = serializers.DateField()

    class Meta:
        model = Booking
        fields = ('guests', 'check_in', 'check_out')

    #-----validate fields
    def validate_check_in(self, value):
    	#django.utils.timezone: django에서 시간을 조정할때 사용
        now = timezone.localtime(timezone.now()).date()
        if value < now:
            raise serializers.ValidationError("Can't book in the past")
        return value
    
    def validate_check_out(self, value):
        now = timezone.localtime(timezone.now()).date()
        if value < now:
            raise serializers.ValidationError("Can't book in the past")
        return value
    
    #----- validate total
    def validate(self, data):
    	# 프론트에서 입력받는 데이터는 data에 dict형태로 들어옴
        # check_in < check_out        
        if data['check_out'] <= data['check_in']:
            raise serializers.ValidationError('check in should be smaller than check out')
        
        # already bookings
        if Booking.objects.filter(
            check_in__lte = data['check_out'],
            check_out__gte = data['check_in'],
        ).exists():
            raise serializers.ValidationError('already taken')
        return data