ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Django] serializer bulk create, update 구현하기
    언어/파이썬 & 장고 2019. 12. 3. 21:04

    장고의 DRF를 사용하여 viewset을 구현하면 제목과 같은 기능이 필요할 때가 있습니다. 기본 ORM을 사용한다면 쉽지만 유효성 검사를 직접 구현해줘야 합니다. DRF의 serializer를 사용하여 처리를 하는 방식을 아래에서 설명합니다.

    bulk create

    bulk create는 큰 구현이 필요하지 않습니다. viewset에서 def list() 함수를 상속 받은 다음, 요청 데이터 타입이 list일 때만 serializer의 파라미터에 {'many': True}를 주면 끝납니다. 

    class AdminViewSet(viewsets.ModelViewSet):
    	def create(self, request, *args, **kwargs):
        	kwargs["many"] = isinstance(request.data, list)
    	    serializer = self.get_serializer(data=request.data, **kwargs)
    	    serializer.is_valid(raise_exception=True)
    	    serializer.save()
    	    return Response(serializer.data, status=status.HTTP_201_CREATED)


    이후 해당 serializer 에서 특별한 작업없이 동작합니다.

    bulk update

    bulk update의 경우는 해당 serializer에 작업이 필요합니다. 아래는 create 부분과 같이 viewset의 def partial_update()를 수정합니다. patch 메소드를 사용하므로 {'partial': True} 이 들어가 있지만 put 메소드를 사용하여 def update()를 구현할 경우에는 필요없는 파라미터입니다. 

    class AdminViewSet(viewsets.ModelViewSet):
    	def partial_update(self, request, *args, **kwargs):
        	kwargs["partial"] = True
    	    if kwargs.pop("pk", None):
        	    serializer = self.get_serializer(
            	    instance=self.get_object(), data=request.data, **kwargs
    	        )
        	else:
            	kwargs["many"] = isinstance(request.data, list)
    	        serializer = self.get_serializer(
        	        self.get_queryset(), data=request.data, **kwargs
    	        )
        	serializer.is_valid(raise_exception=True)


    /admin/ 과 같은 API가 있다고 가정할 때 위 코드는 /admin/4/ 와 같이 단일 수정 건이나 /admin/ 경로의 body에 다중 업데이트 건이 들어올 경우 수용할 수 있도록 처리한 코드입니다. 

    예를 들어 아래와 같은 형식입니다.

    단일 수정

    PATCH: /admin/4/

    {
    "admin_name": "lee"
    }


    복수개 수정

    PATCH: /admin/

    [{
    "admin_pk": "4",
    "admin_name": "lee"
    },
    {
    "admin_pk": "5",
    "admin_name": "park"
    },
    {
    "admin_pk": "7",
    "admin_name": "kim"
    }]


    다음은 해당 serializer파일에 아래와 같은 코드를 추가합니다.

    import inspect
    from django.utils.translation import ugettext_lazy as _
    from rest_framework import serializers
    from rest_framework.exceptions import NotFound
    
    
    class BulkListSerializer(serializers.ListSerializer):
        def update(self, queryset, validated_data):
            id_attr = getattr(self.child.Meta, 'update_lookup_field')
            update_data = {i.get(id_attr): i for i in validated_data}
    
            if not all((bool(i) and not inspect.isclass(i) for i in update_data.keys())):
                raise NotFound(_('Could not find all objects to update.'))
    
            objects = queryset.filter(**{'{}__in'.format(id_attr): update_data.keys()})
    
            if len(update_data) != objects.count():
                raise NotFound(_('Could not find all objects to update.'))
    
            ret = []
            for id, data in update_data.items():
                for obj in objects:
                    if str(getattr(obj, id_attr)) == str(id):
                        ret.append(self.child.update(obj, data))
    
            return ret


    다음 다중 업데이트를 처리할 serializer의 class Meta: 부분에 아래를 추가합니다.

    class AdminSerializer(serializers.ModelSerializer):
        admin_pk = serializers.IntegerField()
        
        class Meta:
            model = Admin
            fields = '__all__'
            update_lookup_field = 'admin_pk'
            list_serializer_class = BulkListSerializer


    class Meta: 하위의 update_lookup_field는 pk 변수명을 작성합니다. 만약 API에서 pk가 아닌 다른 유니크한 키를 기준으로 업데이트를 하려고 하면 해당되는 변수명을 작성하면 됩니다.

    그 아래의 list_serializer_class에는 위에서 정의한 클래스 명을 작성합니다. 


    bulk update는 위와 같은 순서만 따라하면 쉽게 구현할 수 있습니다. bulk update의 동작은 리스트 내의 데이터들을 한 번에 전부 처리하는 것이 아닌 반복문을 돌면서 단 건으로 처리합니다. 

    댓글