[Django] pagination 구현

sudog·2023년 9월 16일
0

Django

목록 보기
12/13

장고에서 pagination을 구현해보자.

Pagintaion이란?

하나의 페이지에 지나치게 많은 콘텐츠가 존재할 때 여러 문제가 생길 수 있다. 데이터베이스에서 지나치게 많은 데이터를 가져와야 하고, 프론트에서 많은 요소를 렌더링하다 보면 페이지 로드 속도가 저하되고 스크롤이 지나치게 길어진다. 이러한 문제들을 해결하기 위해 콘텐츠를 여러 페이지로 분할하는 기법이다.

장고가 기본적으로 페이지네이션 기능을 제공하기 때문에 구현하는 것은 어렵지 않다. 페이지네이션을 구현한 예제를 만들어보자.

# views.py

from django.core.paginator import Paginator
# PAGE_RANGE=5, PER_PAGE=10
from config.settings import PAGE_RANGE, PER_PAGE

def paginate(posts, cur_page):
	# Paginator 클래스의 객체 paginator
    paginator = Paginator(posts, PER_PAGE)
    # page_obj는 현재 페이지의 콘텐츠를 담은 객체
    page_obj = paginator.get_page(cur_page)
    # 페이지 목록을 10개씩 보여주기 위한 로직
    if PAGE_RANGE * 2 > paginator.num_pages:
        page_head = 1
        page_tail = paginator.num_pages + 1
    else:
        page_head = cur_page - PAGE_RANGE
        page_tail = cur_page + PAGE_RANGE
        if page_head < 1:
            page_tail += 1 - page_head
            page_head = 1
        elif page_tail > paginator.num_pages:
            page_head -= page_tail - paginator.num_pages - 1
            page_tail = paginator.num_pages + 1
    return [page_obj, range(page_head, page_tail)]
    
    
def index_view(request):
    if request.method = "GET":
    ...
    # cur_page는 query string으로 전달
    # 현재 페이지가 없거나 1보다 작을 경우 1을 기본값으로 명시
    # 마지막 페이지를 넘어가는 경우에는 자동으로 마지막 페이지로 설정
    cur_page = max(int(request.GET.get("page", "1")), 1)
    pages, page_range = paginate(posts, cur_page)
    context = {
            "pages": pages,
            "page_range": page_range,
    }
    return render(request, "index.html", context)

PAGE_RANGE는 현재 페이지를 포함한 양옆의 페이지 범위를 의미한다. 즉 5라면 왼쪽으로 5개, 오른쪽으로 4개의 페이지 목록이 보일 것이다. 항상 10개의 페이지 목록을 볼 수 있도록 로직을 구현해 놓았다. 또한 PER_PAGE는 한 페이지에 보이는 콘텐츠의 개수를 의미한다. 이 두 상수는 settings.py에 정의한 값일 뿐이다. 즉, 다른 이름을 사용해도 상관이 없다.

pagination 함수로 분리한 이유는 다른 페이지에서도 페이지네이션을 사용할 수 있도록 하기 위해서이다. 특정 사용자의 페이지에 작성한 콘텐츠 볼 수 있도록 한다면 페이지네이션이 필요할 것이다.

이제 페이지네이션을 프론트에서 확인할 수 있도록 html파일을 만들어 보자.

<!-- pagination.html -->

<nav aria-label="Page navigation">
    <ul class="pagination justify-content-center">
        <!-- 첫 번째 페이지로 -->
        {% if posts.has_previous %}
        <li class="page-item">
            <a class="page-link" href="?page=1">
                <span aria-hidden="true">&laquo;</span>
            </a>
        </li>
        {% else %}
        <li class="page-item disabled">
            <!-- tabindex를 -1로 주면 tab키를 사용한 접근을 막습니다. -->
            <a class="page-link" tabindex="-1" aria-disabled="true" href="#">
                <span aria-hidden="true">&laquo;</span>
            </a>
        </li>
        {% endif %}
        <!-- 이전 페이지 버튼 -->
        {% if posts.has_previous %}
        <li class="page-item">
            <a class="page-link" href="?page={{ posts.number|add:-10 }}">
                <span aria-hidden="true">이전</span>
            </a>
        </li>
        {% else %}
        <li class="page-item disabled">
            <a class="page-link" tabindex="-1" aria-disabled="true" href="#">
                <span aria-hidden="true">이전</span>
            </a>
        </li>
        {% endif %}
        <!-- 모든 페이지 버튼 -->
        {% for page_number in page_range %}
        {% if page_number == posts.number %}
        <li class="page-item active" aria-current="page">
            <a class="page-link" href="?page={{ page_number }}">{{ page_number }}</a>
        </li>
        {% else %}
        <li class="page-item">
            <a class="page-link" href="?page={{ page_number }}">{{ page_number }}</a>
        </li>
        {% endif %}
        {% endfor %}
        <!-- 페이지 건너뛰기 -->
        {% if posts.has_next %}
        <li class="page-item">
            <a class="page-link" href="?page={{ posts.number|add:10 }}">
                <span aria-hidden="true">다음</span>
            </a>
        </li>
        {% else %}
        <li class="page-item disabled">
            <a class="page-link" tabindex="-1" aria-disabled="true" href="#">
                <span aria-hidden="true">다음</span>
            </a>
        </li>
        {% endif %}
        <!-- 마지막 페이지  -->
        {% if posts.has_next %}
        <li class="page-item">
            <a class="page-link" href="?page={{ posts.paginator.num_pages }}">
                <span aria-hidden="true">&raquo;</span>
            </a>
        </li>
        {% else %}
        <li class="page-item disabled">
            <a class="page-link" tabindex="-1" aria-disabled="true" href="#">
                <span aria-hidden="true">&raquo;</span>
            </a>
        </li>
        {% endif %}
    </ul>
</nav>

페이지네이션은 페이지를 보여주는 상황에서 사용해야 하기 때문에 GET메서드에서 사용할 수 있도록 쿼리 스트링으로 이동할 페이지를 전달해야 한다. 페이지 객체가 내포한 여러 속성을 이용하면 왼쪽, 오른쪽, 전체 페이지 개수를 가져올 수 있어서 이렇게 페이지 이동을 쉽게 구현할 수 있다.

profile
안녕하세요

0개의 댓글