본문 바로가기

프로젝트/미술관 뒤 백엔드 지금은 전시상황

최종 팀 프로젝트[back] drf_pagination

pagination을 구현하는 방법들 

1. pagination 로직을 직접 작성 

장점 : 원하는 기능만을 가지고 있는 로직을 작성 가능

단점 : 2번과 3번에 비해 긴 시간이 필요, 이해도가 모자랄 시 기능의 구현이 매끄럽지 못할 가능성 존재, 직접 작성하며 정의한 함수 혹은 코드 자체가 눈에 띄기 쉬움 

결론 : 필요한 시간과 특별한 custom이 필요하지 않고 로직을 노출시켜 가독성이 저하되는 것을 감수할 이유가 없음 

 

2. django의 pagination

https://docs.djangoproject.com/en/4.2/topics/pagination/

from django.core.paginator import Paginator
from django.shortcuts import render

from myapp.models import Contact


def listing(request):
    contact_list = Contact.objects.all()
    paginator = Paginator(contact_list, 25)  # Show 25 contacts per page.

    page_number = request.GET.get("page")
    page_obj = paginator.get_page(page_number)
    return render(request, "list.html", {"page_obj": page_obj})

 

장점 : views에서 작성해야할 코드의 양이 많지 않음 

단점 : 상당수의 예제가 render와 함께 장고 템플릿 문법을 사용한 경우가 많음

결론 : 프론트엔드를 django의 templates을 사용하지 않기 때문에 현재 상황에 알맞게 사용하기 애매함  

 

3. drf의 pagination

 

장점 : drf의 pagination으로 구현한 pagination을 정렬 기능을 구현하는 과정에서 수정해본 경험이 있음 

단점 : 직접 작성한 경험이 아닌 수정한 경험만 있으므로 custom 필요시 문제 발생 가능

결론 : 프로젝트 계획상 pagination의 custom 필요 가능성은 낮으며, 1번과 2번 보다 3번을 이해하고 사용하는 것이 생산성이 높을 것으로 예상됨 

 

1. 온전히 이해 가능한 pagination code 작성

 문제점

drf pagination의 class와 method의 구조를 찾기 어려움 

 시도해 본 것들

기존에 작동하던 코드 재사용

REST_FRAMEWORK = {
    "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
    "PAGE_SIZE": 9,
}

pagination 기능이 하나밖에 없어서 settings.py에서 정의한 설정으로 한 페이지에서 보여줄 데이터의 양을 정해줬다. 

 

from rest_framework.pagination import PageNumberPagination

class HomeView(APIView):
    permission_classes = [permissions.AllowAny]
    pagination_class = PageNumberPagination
    
    def get(self, request):
    	    articles = Article.objects.order_by("-created_at")
            paginator = self.pagination_class()
            paginated_articles = paginator.paginate_queryset(articles, request)

            serializer = HomeSerializer(paginated_articles, many=True)
            return paginator.get_paginated_response(serializer.data)

 class에서 PageNumberPagination을 변수에 담아주고 그것을 self로 class 객체 생성 

paginate_queryset으로 pagination 실행 

get_paginated_response으로 return 값에서 이전 페이지와 다음 페이지 확인 가능 

 

pagination.py에서 class와 method의 구조를 직접 파악해보기 

class PageNumberPagination(BasePagination):
    """
    A simple page number based style that supports page numbers as
    query parameters. For example:

    http://api.example.org/accounts/?page=4
    http://api.example.org/accounts/?page=4&page_size=100
    """
    # The default page size.
    # Defaults to `None`, meaning pagination is disabled.
    page_size = api_settings.PAGE_SIZE

    django_paginator_class = DjangoPaginator

    # Client can control the page using this query parameter.
    page_query_param = 'page'
    page_query_description = _('A page number within the paginated result set.')

    # Client can control the page size using this query parameter.
    # Default is 'None'. Set to eg 'page_size' to enable usage.
    page_size_query_param = None
    page_size_query_description = _('Number of results to return per page.')

    # Set to an integer to limit the maximum page size the client may request.
    # Only relevant if 'page_size_query_param' has also been set.
    max_page_size = None

    last_page_strings = ('last',)

    template = 'rest_framework/pagination/numbers.html'

    invalid_page_message = _('Invalid page.')

 

    def paginate_queryset(self, queryset, request, view=None):
        """
        Paginate a queryset if required, either returning a
        page object, or `None` if pagination is not configured for this view.
        """
        page_size = self.get_page_size(request)
        if not page_size:
            return None

        paginator = self.django_paginator_class(queryset, page_size)
        page_number = self.get_page_number(request, paginator)

        try:
            self.page = paginator.page(page_number)
        except InvalidPage as exc:
            msg = self.invalid_page_message.format(
                page_number=page_number, message=str(exc)
            )
            raise NotFound(msg)

        if paginator.num_pages > 1 and self.template is not None:
            # The browsable API should display pagination controls.
            self.display_page_controls = True

        self.request = request
        return list(self.page)

 

def get_paginated_response(self, data):
        return Response(OrderedDict([
            ('count', self.page.paginator.count),
            ('next', self.get_next_link()),
            ('previous', self.get_previous_link()),
            ('results', data)
        ]))

views에서  필요한 class와 method에 대한 정보 확인하기 

PageNumberPagination은 class로 객체로 선언하는 과정 필요

paginate_queryset과 get_paginated_response는 이전 코드에서 사용된 구조 그대로 사용하면 될 것 같다

 

 해결 방법

class ExhibitionView(APIView):
    def get(self, request):  # 전시회 목록 불러오기
        exhibitions = Exhibition.objects.order_by("-created_at")
        # 페이지네이션 class 객체 생성
        pagination = PageNumberPagination()
        # 페이지네이션 진행
        paginated_exhibitions = pagination.paginate_queryset(exhibitions, request)
        # 추후 일부 정보만 보여줘야 한다면 serializer 정의 필요
        serializer = ExhibitionSerializer(paginated_exhibitions, many=True)
        return pagination.get_paginated_response(serializer.data)

get에서만 pagination을 사용하므로 self를 통해서 사용할 필요가 없음 

 알게 된 점

import된 함수가 어떻게 만들어진 코드인지 이해한다면 예시 코드에서도 내게 필요하지 않은 코드를 충분히 알 수 있다.