- python3.6, django 2.1, django-rest-framework 3.8 을 사용합니다.
Django는 paginated 즉 “이전/다음”링크를 사용하여 여러 페이지로 나누어진 데이터를 관리하는데 도움이 되는 몇 가지 클래스를 제공합니다. (Paginator 클래스) 또한 DRF에서도 이러한 pagination 기능을 제공하고 있습니다.
PageNumberPagination / LimitOffsetPagination
pagination 설정에는 두 가지 방법이 있습니다.
- PageNumberPagination
- page : 몇 번째 페이지인지 표시해줍니다. 페이지는 1부터 시작합니다.
- page_size : 한 페이지에 몇 개의 레코드를 보여줄지 표시해줍니다.
- LimitOffsetPagination
- offset : 몇 번째 레코드부터 보여줄지 설정해줍니다. 설정하지 않을 시 첫 번째 레코드부터 보여줍니다.
- limit : 몇 개의 레코드를 보여줄지 설정합니다. offset 번째 레코드부터 offset+limit-1 번째 레코드까지 보여줍니다.
두 가지 모두 페이지네이션 정의 클래스이나, 파라미터와 리턴 값이 다르다고 생각하시면 됩니다.
저는 PageNumberPagination 클래스를 통해 작성하였습니다.
Pagination 전역 설정
settings.py의 REST_FRAMEWORK에 pagination에 관한 설정을 추가해 줍니다.
# settings.py
REST_FRAMEWORK = {
....
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
‘PAGE_SIZE’: 2,
...
}
그리고 이전 포스팅에서 views.py에 작성한 list 부분을 삭제하고, get_queryset()으로 변경해 줍니다.
해당 pagination의 동작은 ModelViewSet의 pagination_class에서 get_queryset()의 쿼리를 기반으로 가져오기 때문에 list() 선언 시 오버라이딩 되면서 작성한 list()로 동작되어 pagination_class는 동작하지 않습니다.
def get_queryset(self):
return super().get_queryset().filter(owner=self.request.user)
# def list(self, request, *args, **kwargs):
# posts = self.get_queryset().filter(owner=request.user)
# serializer = self.get_serializer(posts, many=True)
# return Response({"results": serializer.data})
이제 아래와 같이 파라미터를 추가한 후 API를 호출해 보면 리턴 값이 변경된 것을 볼 수 있습니다.
http://127.0.0.1:8080/api/v1/post/?page=1
리턴 값에서 count는 전체 객체의 숫자를 뜻하며, next / previous는 다음/이전 페이지의 링크를 전달해 줍니다.
Pagination 개별 설정
이번엔 전역 pagination의 설정이 아닌 API별 설정을 해 보도록 하겠습니다.
먼저 pagination.py를 만들어 줍니다. 그리고 아래와 같이 CustomResultsSetPagination()를 추가해 줍니다.
보시는 바와 같이 PageNumberPagination 클래스를 상속받고 있으며, 자세한 옵션과 함수는 PageNumberPagination를 보시면 됩니다.
# pagination.py
# -*- coding: utf-8 -*-
from rest_framework.pagination import PageNumberPagination
class CustomResultsSetPagination(PageNumberPagination):
page_size = 100
page_size_query_param = 'page_size'
이번에 위의 선언한 CustomResultsSetPagination를 views.py에 pagination_class 파라미터에 추가해줍니다.
# views.py
.........
from .pagination import CustomResultsSetPagination
.........
class PostViewSet(ModelViewSet):
.....
pagination_class = CustomResultsSetPagination
이제 API를 호출해 봅니다. 이전에 호출한 API에서 파라미터가 추가된 것을 볼 수 있습니다.
http://127.0.0.1:8080/api/v1/post/?page_size=2&page=1
이렇게 PageNumberPagination를 상속받아 선언함으로써 pagination을 커스텀할 수 있습니다.
pagition 직접 구현하기
직접 구현하기라 썼지만, 상속받았던 PAgeNumberPagination 부분을 오버 라이딩하는 것을 알아보도록 하겠습니다.
먼저 PageNumberPagination 클래스를 살펴보면 BasePagination을 상속받은 것을 볼 수 있습니다.
# rest_framework/pagination.py
class PageNumberPagination(BasePagination):
"""
A simple page number based style that supports page numbers as
query parameters. For example:
......
다시 BasePagination 클래스를 살펴보면 paginate_queryset()와 get_paginated_response()가 NotImplementedError로 선언된 것으로 보아 추상 클래스로 구현해야 하는 것을 볼 수 있습니다.
(to_html()는 클라이언트 화면에서 사용 시 호출되는 함수로 api로 사용하는 현재의 프로젝트에서는 구현하지 않아도 됩니다.)
# rest_framework/pagination.py
class BasePagination:
display_page_controls = False
def paginate_queryset(self, queryset, request, view=None): # pragma: no cover
raise NotImplementedError('paginate_queryset() must be implemented.')
def get_paginated_response(self, data): # pragma: no cover
raise NotImplementedError('get_paginated_response() must be implemented.')
def get_paginated_response_schema(self, schema):
return schema
def to_html(self): # pragma: no cover
raise NotImplementedError('to_html() must be implemented to display page controls.')
def get_results(self, data):
return data['results']
def get_schema_fields(self, view):
assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`'
return []
def get_schema_operation_parameters(self, view):
return []
이제 pagination.py에 구현해 보도록 하겠습니다.
# -*- coding: utf-8 -*-
from rest_framework.pagination import PageNumberPagination
from django.core.paginator import InvalidPage
from rest_framework.exceptions import NotFound
from rest_framework.response import Response
from collections import OrderedDict
class CustomMakePagination(PageNumberPagination):
page_size_query_param = 'page_size'
def paginate_queryset(self, queryset, request, view=None):
# page_size를 가져옵니다.
page_size = self.get_page_size(request)
if not page_size:
return None
"""
django_paginator_class는 django의 Paginator (django/core/paginator.py) 클래스로 pagination의 전체 동작을 담당합니다.
page_size와 queryset을 pagniator 객체를 선언합니다.
"""
paginator = self.django_paginator_class(queryset, page_size)
page_number = request.query_params.get(self.page_query_param, 1)
try:
# 가져온 paginator중에서 사용자가 지정한 페이지를 가져 옵니다.
self.page = paginator.page(page_number)
except InvalidPage as e:
msg = self.invalid_page_message.format(
page_number=page_number, message=str(e)
)
raise NotFound(msg)
return list(self.page)
def get_paginated_response(self, data):
# pagination의 리턴시 보여줄 데이터 객체 입니다.
return Response(OrderedDict([
('count', self.page.paginator.count),
('results', data)
]))
이제 위의 페이지네이션 구현 클래스를 Modelsets에 적용합니다.
# views.py
.........
from .pagination import CustomMakePagination
.........
class PostViewSet(ModelViewSet):
.....
pagination_class = CustomMakePagination
이전과 동일한 API를 호출해 보면 리턴되는 값이 달라진 것을 확인할 수 있습니다.
http://127.0.0.1:8080/api/v1/post/?page=1&page_size=1
이처럼 PageNumberPagination 클래스를 오버라이딩 함으로써 필요한 부분을 커스텀하여 직접 구현할 수 있습니다.
'web > Django_rest_framework' 카테고리의 다른 글
[Django rest framework] 5. test (1) | 2020.07.05 |
---|---|
[Django rest framework] 4. filters (0) | 2020.06.21 |
[Django rest framework] 2. token / authentication (0) | 2020.06.07 |
[Django rest framework] 1. 기존 셋팅 + CRUD (0) | 2020.06.04 |
[Django rest framework] 튜토리얼을 시작하며. (0) | 2020.06.02 |