새소식

ISSUE

[ISSUE] gernerics view, DestroyAPIView 적용

  • -

[22.11.20] Try to apply generics DestroyAPIView

Generics View를 사용하지 않는 이유

✔️ DRF로 서버단을 rest_framework가 지원해주는 generics View를 사용하지 않고 APIView만을 이용하여 프로젝트를 진행하기로 결정하였다.

 

✔️ 그렇게 결정한 이유는 generics 한 View를 사용하면 코드가 간결해지고 개발하기 편리해지긴 하지만 팀 프로젝트를 진행하며 그렇게 간결해지고 작동되는 로직을 파악하지 않고 무분별하게 사용하는 것을 우려하여 처음에는 자유롭게 customizing이 가능한 APIView를 사용하기로 했다.

 

APIView로 작성된 Bulk Delete

✔️ 항목을 다중으로 삭제하기 위한 기능 개발을 위해 클라이언트 단에서 선택한 항목의 id(식별자)를 리스트로 전달하여 서버 단에서 받아 그 항목들을 모두 삭제하는 로직을 개발하였다.

클라이언트 단

function setFetchData(method, body){
    /* Fetch data 셋팅 */

    let csrftoken = getCookie('csrftoken');

    const data = {
        method: method,
        headers: {
            'content-type': 'application/json',
            'X-CSRFToken' : csrftoken,        
        },
        body: JSON.stringify(body)
    }
    return data
}

function bulkDelete() {
    /* 벌크 삭제 이벤트 */

    const data = setFetchData("DELETE", {
        pk_ids: selected_articles, // [1, 2, 3, 4, 5]
    })

    fetch(`/api/sites/bulk`, data)
        .then(response => {
            let status = response.status

            if(status == 200)
                alert('삭제에 성공하였습니다.')                    
        })
        .then(() => getSiteList())
        .then(() => changeSelected())
        .catch(error   => console.log(error))
}

 

서버 단

# urls.py
path('sites/bulk', SiteBulkAPIView.as_view())


# views.py
class SiteBulkAPIView(APIView):
    """
    벌크 항목 삭제 api
    """

    def delete(self, request):

        pk_ids: list = self.request.data.get('pk_ids')

        sites = Site.objects.filter(id__in=pk_ids)

        for site in sites:
            site.delete()

        return Response({'msg': 'Deleted successfully'}, status=status.HTTP_200_OK)

 

APIView

# 상속받은 APIVIEW
class APIView(View):

    # The following policies may be set at either globally, or per-view.
    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES

    ...

# APIView가 상속받은 View
class View:
    """
    Intentionally simple parent class for all views. Only implements
    dispatch-by-method and simple sanity checking.
    """

    http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']

    def dispatch(self, request, *args, **kwargs):
        # Try to dispatch to the right method; if a method doesn't exist,
        # defer to the error handler. Also defer to the error handler if the
        # request method isn't on the approved list.
        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed
        return handler(request, *args, **kwargs)

 

✔️ 상속받은 APIView가 어떻게 HTTP 메소드에 따라 어떻게 작성한 내부 메서드로 들어와 처리가 될 수 있는지 APIView를 타고 들어가 부모 클래스인 View까지 타고 들어가 코드를 확인해 보니 View클래스에서 http 매소드 명에 따라 작성한 내부 메서드로 핸들링해주고 있었다.

 

DestroyAPIView로 delete처리 적용

✔️ Delete 처리를할 때 rest_framework의 DestroyAPIView로 처리한다고 공식문서를 확인하여 이를 이용하여 로직을 변경해보려 했다.

 

✔️ 하지만 DestroyAPiView는 하나의 id(식별자) 값만 path파라미터로 받아 처리를 해주는 클래스이기 때문에 bulk처리에는 적합하지 않았다. 그래서 bulk처리가 아닌 단일 삭제 처리 기능에서 DestroyAPIView를 적용해 보았다.

출처 : DRF 공식문서

클라이언트 단

function deleteSite() {

    const data = setFetchData('DELETE', '')

    fetch(`/api/sites/${site.id}`, data)
        .then(response => {
            const status = response.status

            if (status === 200) {
                console.log('삭제 완료했습니다.')

            } else if (status === 404) {
                console.log('해당 항목이 존재하지 않습니다.')

            } 
            return response.json() 
        })
        .catch(error => console.log('Error:', error))
}

 

서버 단

# urls.py
path('sites/<int:pk>', SiteDetailAPIView.as_view()),

# views.py
class SiteDetailAPIView(DestroyAPIView):
    """
    항목 단일 삭제 api
    """

    queryset = Site.objects.all()
    serializer_class = SiteSerializer

 

✔️ 위 와 같이 작성 후 삭제를 시도하면 삭제되는 코드를 작성하지 않았지만 DestroyAPIView를 상속받은 것 만으로 전달받은 pk값을 인식하여 해당하는 데이터를 처리해준다.

 

DestroyAPIView

# DestroyAPIView
class DestroyAPIView(mixins.DestroyModelMixin,
                     GenericAPIView):
    """
    Concrete view for deleting a model instance.
    """
    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

# mixins.DestroyModelMixin
class DestroyModelMixin:
    """
    Destroy a model instance.
    """
    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        self.perform_destroy(instance)
        return Response(status=status.HTTP_204_NO_CONTENT)

    def perform_destroy(self, instance):
        instance.delete()
코드 작성을 하지 않고 처리가 되는 과정을 확인해 보기 위해 상속받은 DestroyAPIView를 확인해 보았다. DestroyAPIView가 또 다른 두 개의 클래스 mixins.DestroyModelMixin와 GenericAPIView를 상속받고 있는데 삭제 처리가 작성되어 있는 DestroyModelMixin 클래스를 타고 들어가 확인해 보면 destroy내부 함수에 perform_destroy로 해당 인스턴스를 삭제하는 코드가 작성된 것을 확인할 수 있다. 즉, 우리는 이 클래스를 상속받아 우리가 작성해야 하는 코드대신 그 안에서 작성된 코드가 실행되어 처리된다는 것을 알 수 있다.

 

 

'ISSUE' 카테고리의 다른 글

[ISSUE] REST API GET방식에서 POST로의 변경  (0) 2023.04.19
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.