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패널에 로그인 되어있는 유저와 방을 생성한 유저 일치 여부
[** 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
[** Validate Serializer **]
- serializer는 스키마 역할로 사용할수도 있지만 validation역할로도 사용할 수 있다.
- validation역할로 사용하는 방법은 2가지가 있다.
- 필드 각각을 validation: 필드 각각에 대한 validation함수를 만들어준다
- def validate_<필드명>(self, value)
- 한번에 모든 필드 validation: 함수 하나에 validation 하고자 하는 필드를 모두 담는다
- def validate(self, data)
- 필드 각각을 validation: 필드 각각에 대한 validation함수를 만들어준다
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