오늘도 개발
[1차 프로젝트 록차] 상품 목록 GET api 제작기 4 - django paginator로 페이지네이션 구현하기 본문
[1차 프로젝트 록차] 상품 목록 GET api 제작기 4 - django paginator로 페이지네이션 구현하기
Sueeeeeee 2022. 7. 23. 01:32필터는 어느정도 구현을 마쳤는데 페이지네이션을 해야 했다.
페이지네이션은 데이터를 원하는 만큼 끊어서 보여주는 기능이다.
클라이언트가 쿼리 파라미터로 몇 개씩 끊어 보여달라고 요청하면
그걸 받아 응답할 Queryset에 적용하면 될 거라는 생각은 들었는데
정확히 어떻게 구현해야 할 지는 감이 잡히지 않았다.
각 HTTP 통신은 stateless한데 내 api가 '다음 페이지를 보여줘'라는 요청을 어떻게 이해할 수 있을까?
클라이언트가 현재 보고있는 페이지를 몇 번인지 내 api가 어떻게 추적할 수 있을까?
두 가지로 고민을 했는데 검색을 해보니 페이지네이션을 하겠다고 클라이언트의 위치를 내 api로 추적할 필요는 없었다.
페이지네이션의 기본 동작
우선 클라이언트는 그냥 '다음 페이지를 보여줘'라고 요청하지 않는다.
클라이언트는 쿼리 파라미터에 몇 개씩 끊어 보여달라(limit)는 정보와
현재 페이지 넘버(offset)를 담아 요청한다고 가정해야 한다.
그럼 데이터가 총 10개 있는데
chars = ['a', 'b', 'c', 'd', 'e']
클라이언트가 /chars?limit=3&offset=2라고 요청하면 두 번째 페이지는 항상 ['d', 'e']가 된다.
클라이언트의 위치를 추적할 필요가 없는 것이다.
즉, 백에서는 응답 메시지에
1) limit을 적용했을 때 나오는 총 페이지 개수
2) 현재 offset에서 보여줄 데이터 리스트
두 가지만 담아 보내주면 된다.
django Paginator class
django에서는 페이지네이션을 구현할 수 있는 Paginator 클래스를 제공한다.
Paginator 클래스에 오브젝트 리스트와 각 페이당 보여줄 데이터 개수를 입력하면
Paginator 인스턴스를 만들 수 있다.
from django.core.paginator import Paginator
objects = ['apple', 'orange', 'bannana', 'pear', 'mango']
p = Paginator(objects, 2)
생성한 Paginator 인스턴스에는 Paginator가 제공하는 각종 메서드를 적용할 수 있다.
# 전체 데이터 개수
p.count
# 5
# 페이지 개수
p.num_pages
# 3
# 페이지의 range 오브젝트(iteration할 수 있음)
p.page_range
# range(1, 4)
# 페이지 하나씩 가져오기
page1 = p.page(1)
print(first_page)
# <Page 1 of 3>
# 한 페이지에 포함된 데이터 리스트
page1.object_list
# ['apple', 'orange']
# 다음 페이지가 있는지 확인하기
page1.has_next()
# True
# 이전 페이지가 있는지 확인하기
page1.has_previous()
# False
# 이전 페이지나 다음 페이지가 있는지 확인하기
page1.has_other_pages()
# True
page1.previous_page_number()
# 이전이나 다음 페이지가 없는데 호출하면 EmptyPage 에러남
p.page(0)
# 없는 페이지를 호출해도 EmptyPage 에러남
페이지네이션 구현
공부한 내용을 바탕으로 다음과 같이 페이지네이션을 구현했다.
쿼리 파라미터로 한 페이지에 보여줄 상품 개수를 명시하지 않는다면 디폴트로 10개씩 끊어 보여주며,
쿼리 파라미터로 특정한 페이지를 명시하지 않는다면 1페이지를 보여준다.
from django.core.paginator import Paginator
class ProductListView(View):
def get(self, request):
# ... 윗줄 생략
limit = int(request.GET.get("limit", 10))
offset = int(request.GET.get("offset", 1))
products = Product.objects.filter(queries).order_by(ordering).distinct()
# 페이지네이터 오브젝트 생성
p = Paginator(products, limit)
pages_count = p.num_pages
# 존재할 수 없는 페이지를 요청받은 경우 오류 반환
if offset < 1 or offset > pages_count:
return JsonResponse({'result': 'INVALID_PAGE'}, status=404)
# 총 상품 개수, 총 페이지 수, 현재 페이지 수, 페이지 당 상품 수 정보 반환
total = {
'total_items' : products.count(),
'total_pages' : pages_count,
'current_page': offset,
'limit' : limit
}
# 해당 페이지의 상품만 반환
page_items = p.page(offset)
products = [{
'id' : page_item.id,
'title' : page_item.title,
'price' : page_item.price,
'stock' : page_item.stock,
'thumbnail_images': [image.url for image in page_item.thumbnail_images.all()],
'types' : [type.name for type in page_item.types.all()]
} for page_item in page_items]
return JsonResponse({'total' : total, 'products': products}, status=200)
참고
'TIL & 프로젝트 회고' 카테고리의 다른 글
| [1차 프로젝트 록차] 상품 목록 GET api 제작기 5 - 리팩토링 (0) | 2022.07.27 |
|---|---|
| [1차 프로젝트 록차] Postman으로 batch request 보내기 (0) | 2022.07.23 |
| [1차 프로젝트 록차] 상품 목록 GET api 제작기 3 - Django Q 오브젝트 사용하기 (0) | 2022.07.22 |
| [1차 프로젝트 록차] 상품 목록 GET api 제작기 2 - 어떻게 필터링을 구현할까 (0) | 2022.07.21 |
| [1차 프로젝트 록차] 상품 목록 GET api 제작기 1 - RESTful한 엔드포인트 만들기 (0) | 2022.07.21 |