본문 바로가기

내일 배움 캠프/TIL

Pagination (페이지네이션) 구현하기

 

Pagenation (페이지네이션) 구현하기

1. Pagenation (페이지네이션) 이란?

일정 기준으로 데이터를 분할해서 전달하는것을 의미한다. 아래 그림과 같이 거의 모든 웹사이트에서 쓰이는 기능인데, 글을 '페이지' 순으로 넘기는 것과, '더보기'를 통해 아래로 계속 내려가며 보는것 둘다 페이지네이션으로 구현 가능하다. 당연하지만 구현하는 방식이 다르다 - offset(페이지 순) 과 cursor(더보기) 방식으로 나뉨.

 

또는 

 

 

 

 

2. pagination 구현

Django REST Framework에서는 pagination을 내장함수로 가지고 있다. 그래서 import만 해주면 쉽게 구현이 가능하다.

 

 

pagination 클래스 종류

  1. PageNumberPagination
    • page : 데이터를 일정 크기의 페이지로 나누고 클라이언트가 특정 페이지를 요청할 수 있게 한다.
  2. LimitOffsetPagination
    • 클라이언트가 반환할 항목 수와 데이터 컬렉션 내에서 시작 지점을 지정할 수 있다.
  3. CursorPagination
    • 큰 데이터 세트에 대해 더 효율적으로 커서 기반의 페이지네이션을 제공한다.

 

 

pagination DRF 설정

  • 전역 설정
# settings.py

REST_FRAMEWORK = {
    ...
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 4,
    ...
}


"""
page_size : 한 페이지에 몇 개의 글들을 보여줄지 표시하는 변수
"""

 

  • 사용자 정의 설정
# views.py

from rest_framework.pagination import PageNumberPagination

class ArticlePagination(PageNumberPagination):
    page_size = 10

 

 

 

자, DRF 설정은 끝났다. 이제는 구현을 해볼 차례이다. 위에 설정을 보면 알겠지만 이번엔 PageNumberPagination 으로 구현을 할 것 이다.

 

먼저 blog 앱에 pagination.py 파일을 하나 추가해준다. 이 파일 안에는 페이징이 필요한 API들이 여러개가 있을 경우를 대비해 만든것이다.

# pagination.py

class PaginationMixin(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()
        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)
  • paginator
    • hasattr 함수를 통해 object에 '_paginator'의 속성이 존재하면 True, 아니면 False를 반환하게 된다. 그 후에  pagination_class를 비교해 paginator를 리턴해준다. pagination_class는 조금 뒤에 나오니 그냥 보자.
  • paginate_queryset :
    • 이름 그대로 queryset을 받아오는 함수로, 글들을 받아와 저장하는 기능을 한다.
  • get_paginated_response :
    • 이름 그대로 response 형태로 내보내는 함수로,  serializer를 사용해 데이터를 불러와 저장한다.

 

 

 

거의다 왔다. 남은건 페이징을 할 클래스를 기존의 코드에서 수정해주면 끝!

# views.py

from .pagination import PaginationManage

class ArticleView(APIView):
    pagination_class = ArticlePagination
    
    def get(self, request, blog_name):
        """블로그 전체게시글"""
        blog = Blog.objects.filter(blog_name=blog_name)
        articles = Article.objects.filter(blog_id=blog[0].id).order_by("-created_at")
        
        page = PaginationManage.paginate_queryset(articles)
		
        if page is not None:
            serializer = PaginationManage.get_paginated_response(ArticleSerializer(page, many=True).data)
        return Response(serializer.data, status=status.HTTP_200_OK)

 

위에 '사용자 정의 설정' 에서 선언해준 ArticlePagination 클래스를 pagination_class 라는 변수에 넣어준다. 이것이 paginator 함수에서 사용되는 것이다.

page 안에 기존의 게시글 queryset을 불러오는 articles를 paginate_queryset() 함수 안에 넣어주고, get_paginated_response 함수 안에 사용할 serializer를 넣어주면 된다.

 

 

자 과연 결과는..?

 

오류가 난다 ....  어째서..?

 

 

 

해결

찾아보니 이거와 비슷한 종류의 오류가 있었다. 바로 클래스안에 앞에 import해준 PaginationManage를 인자값으로 안넣어 주었기 때문이였다! 변경된 부분만 적어 놓았다.

class ArticleView(APIView, PaginationManage):

    def get(self, request, blog_name):
        page = self.paginate_queryset(articles)
        serializer = self.get_paginated_response(ArticleSerializer(page, many=True).data)

 

 

 

결과

앞에 설정해둔것처럼 4개씩만 페이징 되는걸 알 수 있다! 

 

다음에 기회가 있다면 cursor 방식으로 구현해봐야지..