본문 바로가기
카테고리 없음

2021.11.23 TIL : [Django] DRF5 - Pagination

by yeon_zoo 2021. 11. 23.

DRF를 이용해서 정보를 불러온 이후에 pagination을 통해서 불러올 개수를 조절하고 싶은 상황이 생겼다. 내가 pagination을 사용한 것은 무한 스크롤을 위해서인데, 프론트에 넘겨줄 때도 pagination을 이용해서 넘겨주면 된다. 그럼 프론트 측에서 페이지 사이즈가 넘어갔을 때 스크롤을 내리면 다음 페이지를 불러온다. 

 

DRF에서는 정말 제공하는 기능이 많은데, pagination도 그 중 하나이다. 특히 generic View를 사용하면 정말 간단하게 구현할 수 있다고 한다. 혹은 전체 프로젝트에서 페이지네이션을 하고 싶은 경우에는 settings.py에 딱 두 줄 정도만 추가하면 사용할 수 있다. 기본 Pagination도 세 가지가 있다.

이 중에서 나는 가장 기본적으로 많이 사용하는 PageNumberPagination을 이용했다.

 

 

내가 필요한 건 제네릭 뷰나 모델뷰셋에서가 아닌 APIView에서의 일이다. 영어로 찾아보면 레퍼런스될 만한 것들이 좀 나오긴 하는데, 한국어로 설명된 것은 찾아보기 어려웠다. 제네릭 뷰 혹은 ModelViewSet 에서 실행하는 방법이 궁금하다면 이 블로그를 참고해 보면 좋을 것 같다. 

 

먼저 나는 한 가지 app에서만 사용할 게 아니고, 여러 app의 일부 API에서 페이지네이션 기능을 사용하고 싶기 때문에 프로젝트 폴더 내에 mixin을 쓸 수 있는 pagination.py를 만들었다. pagination.py에는 다음과 같이 적어주면 된다. 

class PaginationHandlerMixin(object):
    @property
    def paginator(self):
        if not hasattr(self, '_paginator'):
            if self.pagination_class is None:
                self._paginator = None
            else:
                self._paginator = self.pagination_class()
        else:
            pass
        return self._paginator

    def paginate_queryset(self, queryset):
        if self.paginator is None:
            return None
        return self.paginator.paginate_queryset(queryset,
                   self.request, view=self)

    def get_paginated_response(self, data):
        assert self.paginator is not None
        return self.paginator.get_paginated_response(data)

이 부분은 pagination 관련 함수를 쓸 때 필요하다. 

 

그리고 나서는 views.py에 이런 내용들을 추가해준다. 

from rest_framework.pagination import PageNumberPagination
from myproject.pagination import PaginationHandlerMixin

class BasicPagination(PageNumberPagination):
    page_size_query_param = 'limit'
    page_size = 3
    page_query_param = 'p'

class MyListAPI(APIView, PaginationHandlerMixin):
    pagination_class = BasicPagination
    serializer_class = DatasetSerializer
    
    def get(self, request):
        instance = Dataset.objects.all()
        page = self.paginate_queryset(instance)
        if page is not None:
            serializer = self.get_paginated_response(self.serializer_class(page,
 many=True).data)
        else:
            serializer = self.serializer_class(instance, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)

먼저 BasicPagination을 통해서 PageNumberPagination을 상속받고, 기본 설정 해줄 수 있는 page size와 query param들을 지정해준다. 

  • page_size : 한 요청에 몇 개씩 가져올 것인지
  • page_query_param : 127.0.0.0:8000/blog/list/?p=2 이렇게 쿼리스트링을 만들어줄 때 어떤 문구로 해줄 것인가를 의미. 디폴트값은 page이다.
  • page_size_query_param : 127.0.0.0:8000/blog/list/?p=2&limit=5 이렇게 적어준다면, page_size는 limit값을 파라미터로 넘겨줄 때에 한해서 조정된다. 

그리고 나서 내가 페이지네이션 하려고 하는 get이 위치한 클래스에서 pagination_class 를 정해준다. get함수 내에서는 mixin에서 정의해준 함수들을 이용해서 페이지네이션 하면 되는데 전에 일반 django CRUD를 연습하면서 에러가 났던 것처럼 page_size보다 작은 수의 데이터는 에러가 난다. 이를 해결해주기 위해서 if else문을 사용해줬다. 

 

위의 view를 구성해주면 api는 이렇게 구성이 된다. 

이 때 사용되는 "count" 나 "next" 등을 커스텀하고 싶다면, views.py 내에서 정의한 BasicPagination 클래스에서 함수를 오버라이딩 해주면 된다.

from collections import OrderedDict

class BasicPagination(pagination.PageNumberPagination):
    page_size = 1
    page_size_query_param = 'page_size'
    max_page_size = 50
    page_query_param = 'page'

    def get_paginated_response(self, data):
        return Response(OrderedDict([
            ('feed_count', self.page.paginator.count),
            ('next_page', self.get_next_link()),
            ('previous_page', self.get_previous_link()),
            ('data', data)
        ]))

댓글