Django 커스텀 페이지네이션 구현하기
Django 기본 Paginator의 한계와 커서 기반 커스텀 페이지네이션 구현
2025년 1월 12일4 min read
문서 목차
Django 기본 Paginator 이해
대량의 데이터를 한 번에 렌더링하면 응답 시간이 길어지고 메모리 사용량이 증가한다. Pagination은 데이터를 일정 단위로 나누어 요청할 때마다 한 페이지 분량만 반환하는 기법이다.
Django에서 가장 간단하게 페이지네이션을 적용하려면 Paginator 클래스를 사용한다.
python
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예외를 적절히 처리
템플릿 예시
html
{% 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로 대부분 처리할 수 있지만, 다음 경우에는 커스텀이 필요하다:
- 페이지 번호 대신 다른 식별자 사용 (해시값, 날짜, 슬러그 등)
- Infinite Scroll 구현 시 동적 데이터 로딩
paginate_by가 동적으로 변경되어야 할 때- 복잡한 필터/검색/정렬 조건과 함께 사용
Paginator 상속으로 커스텀하기
created_at 기준 페이지네이션
python
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]뷰에서 사용
python
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
python
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설정 방법
python
# 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 Pagination | API 응답, 동적 page_size |
프로젝트 요구사항에 따라 적절한 방식을 선택하면 된다.