DRF의 put과 patch 요청

Category
Published
August 16, 2024
Last updated
Last updated September 7, 2024
💡
이 포스트는 현재 작성중입니다

개요

해당 글에서는 DRF에서 수정 요청인 PUT과 PATCH 요청 시의 작동 과정, 그리고 PATCH 요청시 partital_update()가 어떤식으로 작동하는지를 중점적으로 다룬다.

PUT vs. PATCH

PUT과 PATCH 요청은 둘다 리소스 수정을 요청하는 메소드라는 공통점을 가지고 있다. 이들의 차이점은 PUT은 대상 리소스의 모든 속성을 한번에 수정한다는 것이고(모든 정보를 받아야함), PATCH는 일부 속성만 수정할 수 있다는 것이다.
자세한 내용은 다음 글에 설명되어 있다.

DRF에서의 PUT과 PATCH

DRF에서는 mixins.UpdateModelMixin mixin을 포함한 generic(generics.UpdateAPIView, generics.RetrieveUpdateAPIView generics.RetrieveUpdateDestroyAPIView)을 상속하여 View를 만들어 리소스 수정 기능을 간단히 추가할 수 있다.

수정이 포함된 generic 구조

수정 기능만 들어간 UpdateAPIView의 구조를 살펴보자.
class UpdateAPIView(mixins.UpdateModelMixin, GenericAPIView): """ Concrete view for updating a model instance. """ def put(self, request, *args, **kwargs): return self.update(request, *args, **kwargs) def patch(self, request, *args, **kwargs): return self.partial_update(request, *args, **kwargs)
rest_framework/generics.py
put과 patch 요청시 각각 self.update(), self.partial_update()를 호출한다. 위에서 언급했듯이 PATCH는 리소스 속성의 부분적인 수정을 허용하기에 partial_update() 라는 부분적인 업데이트를 의미하는 이름의 메소드를 호출하는 것을 볼 수 있다.

UpdateModelMixin mixin의 update(), partital_update()

두 메소드는 다음과 같이 구현되어 있다.
class UpdateModelMixin: """ Update a model instance. """ def update(self, request, *args, **kwargs): partial = kwargs.pop('partial', False) instance = self.get_object() serializer = self.get_serializer(instance, data=request.data, partial=partial) serializer.is_valid(raise_exception=True) self.perform_update(serializer) if getattr(instance, '_prefetched_objects_cache', None): # If 'prefetch_related' has been applied to a queryset, we need to # forcibly invalidate the prefetch cache on the instance. instance._prefetched_objects_cache = {} return Response(serializer.data) def perform_update(self, serializer): serializer.save() def partial_update(self, request, *args, **kwargs): kwargs['partial'] = True return self.update(request, *args, **kwargs)
rest_framework/mixins.py
partital_update()kwargs[’partital’]True로 설정한 뒤 update()와 같이 처리되는 것을 확인할 수 있다.

Serializer 리소스 수정 방법

호출된 update는 serializer 단계로 이어져 나간다.
Serializer에서 instance와 data를 둘다 제공한다면 기존에 존재하던 instance를 수정하는 방식으로 작동한다.
# https://www.django-rest-framework.org/api-guide/serializers/#saving-instances # .save() will update the existing `comment` instance. serializer = CommentSerializer(comment, data=data) # https://www.django-rest-framework.org/api-guide/serializers/#partial-updates # Update `comment` with partial data serializer = CommentSerializer(comment, data={'content': 'foo bar'}, partial=True)

ModelSerializer에서 update() 방식

DRF는 편의를 위해 모델을 기반으로 자동으로 Serializer를 설정해주는 ModelSerializer를 제공한다.
다음은 ModelSerializer에서의 update() 메소드 구현체이다.
class ModelSerializer(Serializer): def update(self, instance, validated_data): raise_errors_on_nested_writes('update', self, validated_data) info = model_meta.get_field_info(instance) # Simply set each attribute on the instance, and then save it. # Note that unlike `.create()` we don't need to treat many-to-many # relationships as being a special case. During updates we already # have an instance pk for the relationships to be associated with. m2m_fields = [] for attr, value in validated_data.items(): if attr in info.relations and info.relations[attr].to_many: m2m_fields.append((attr, value)) else: setattr(instance, attr, value) instance.save() # Note that many-to-many fields are set after updating instance. # Setting m2m fields triggers signals which could potentially change # updated instance and we do not want it to collide with .update() for attr, value in m2m_fields: field = getattr(instance, attr) field.set(value) return instance
여기서 핵심 로직은 첫번째 for문이다. 해당 for문에서는 m2m 필드가 아닌 경우에 setattr(instance, attr, value)를 통해 필드를 찾아 지정해준다.

Serializer(, , partial=True)