이종관
Back to Posts

Django 커스텀 페이지네이션 구현하기

Django 기본 Paginator, 커스텀 페이지네이션

2025년 1월 12일·4 min read·
backend
django
python
pagination
drf
api

Django 기본 Paginator 이해

Django에서 가장 간단하게 페이지네이션을 적용하려면 Paginator 클래스를 사용합니다.

from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.shortcuts import render
from .models import Post

def post_list_view(request):
    post_qs = Post.objects.all().order_by('-created_at')

    # Paginator 객체 생성 (페이지당 10개)
    paginator = Paginator(post_qs, 10)

    page = request.GET.get('page', 1)

    try:
        posts = paginator.page(page)
    except PageNotAnInteger:
        posts = paginator.page(1)
    except EmptyPage:
        posts = paginator.page(paginator.num_pages)

    return render(request, 'blog/post_list.html', {'posts': posts})

핵심 개념

  • Paginator(queryset, per_page): queryset과 페이지당 개수를 인자로 받음
  • paginator.page(number): 해당 페이지의 Page 객체 반환
  • 예외 처리: PageNotAnInteger, EmptyPage 예외를 적절히 처리

템플릿 예시

{% for post in posts %}
  <h2>{{ post.title }}</h2>
  <p>{{ post.content }}</p>
{% endfor %}

<div class="pagination">
  {% if posts.has_previous %}
    <a href="?page={{ posts.previous_page_number }}">이전</a>
  {% endif %}

  <span>{{ posts.number }} / {{ posts.paginator.num_pages }}</span>

  {% if posts.has_next %}
    <a href="?page={{ posts.next_page_number }}">다음</a>
  {% endif %}
</div>

커스텀 Paginator가 필요한 상황

기본 Paginator로 대부분 처리할 수 있지만, 다음 경우에는 커스텀이 필요합니다:

  1. 페이지 번호 대신 다른 식별자 사용 (해시값, 날짜, 슬러그 등)
  2. Infinite Scroll 구현 시 동적 데이터 로딩
  3. paginate_by가 동적으로 변경되어야 할 때
  4. 복잡한 필터/검색/정렬 조건과 함께 사용

Paginator 상속으로 커스텀하기

created_at 기준 페이지네이션

from django.core.paginator import Paginator

class CreatedAtPaginator(Paginator):
    """created_at 기준 커스텀 Paginator"""

    def __init__(self, object_list, per_page, **kwargs):
        super().__init__(object_list, per_page, **kwargs)

    def page_by_timestamp(self, timestamp):
        """timestamp 이후 데이터를 per_page만큼 반환"""
        if timestamp:
            filtered_qs = self.object_list.filter(created_at__lt=timestamp)
        else:
            filtered_qs = self.object_list

        return filtered_qs.order_by('-created_at')[:self.per_page]

뷰에서 사용

def post_list_by_timestamp(request):
    timestamp = request.GET.get('timestamp', None)

    post_qs = Post.objects.all()
    paginator = CreatedAtPaginator(post_qs, 10)
    posts = paginator.page_by_timestamp(timestamp)

    next_timestamp = posts.last().created_at.isoformat() if posts.exists() else None

    return render(request, 'blog/post_list.html', {
        'posts': posts,
        'next_timestamp': next_timestamp,
    })

Django Rest Framework 페이지네이션

DRF에서는 클래스 기반 Pagination을 제공합니다:

  • PageNumberPagination: 페이지 번호 기반
  • LimitOffsetPagination: limit, offset 파라미터 사용
  • CursorPagination: 커서 기반 (보안성 높음)

커스텀 PageNumberPagination

from rest_framework.pagination import PageNumberPagination

class CustomPageNumberPagination(PageNumberPagination):
    page_size = 10
    page_size_query_param = 'page_size'  # ?page_size=20 가능
    max_page_size = 100

설정 방법

# settings.py - 전역 설정
REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'myapp.paginations.CustomPageNumberPagination',
    'PAGE_SIZE': 10,
}

# 또는 개별 ViewSet에서
class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    pagination_class = CustomPageNumberPagination

정리

방식사용 사례
기본 Paginator단순 페이지 번호 기반
커스텀 Paginator시간/슬러그 기반, 특수 로직
DRF PaginationAPI 응답, 동적 page_size

프로젝트 요구사항에 따라 적절한 방식을 선택하면 됩니다.