[Django] 파이보 서비스 개발 - 페이징

싱숭생숭어·2023년 5월 8일
0

Django

목록 보기
16/19
post-thumbnail

위 글은 점프 투 장고를 참고해 작성하였습니다.

현재 구현한 질문 목록 페이지는 페이징 처리(=페이지 나누기)가 안되기 때문에 게시물 300개를 작성하면 한 페이지에 300개의 게시물이 모두 표현된다. 이번 포스팅에서는 페이징(paging)을 적용해 이 문제를 해결해보자


대량 테스트 데이터 만들기

페이징을 구현하기 전에 페이징을 테스트할 수 있을 정도의 충분한 데이터를 생성하자.

대량의 테스트 데이터를 만드는 가장 좋은 방법은 장고셸을 이용하는 것 !

(mysite) gyu@DESKTOP-4OUGKIK:~/workspace/projects/mysite$ python manage.py shell
Python 3.10.6 (main, Mar 10 2023, 10:55:28) [GCC 11.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> 

장고셸을 실행하고 질문 데이터 생성을 위한 모듈 임포트

>>> from pybo.models import Question
>>> from django.utils import timezone

300개의 테스트 데이터를 생성

>>> for i in range(300):
...     q = Question(subject='테스트 데이터입니다:[%03d]' % i, content='내용무', create_date=timezone.now())
...     q.save()
...
>>>  

exit()으로 장고셸을 종료하고 python manage.py runserver를 통해 로컬 서버를 실행한 다음 질문 목록창을 조회해보면,

장고셸로 등록한 테스트 데이터가 보인다. 페이징을 구현하지 않았기 때문에 300개 이상의 데이터가 한 페이지 안에 전부 보여짐.(스크롤 크기 확인)


Paginator

장고에서 페이징을 위해 사용하는 클래스는 Paginator이다. Paginator 클래스를 사용해 다음과 같이 views.py의 index 함수에 페이징 기능을 적용하자.

from django.shortcuts import render, get_object_or_404, redirect
from django.utils import timezone
from .models import Question
from .forms import QuestionForm, AnswerForm
from django.core.paginator import Paginator  


def index(request):
    page = request.GET.get('page', '1')  # 페이지
    question_list = Question.objects.order_by('-create_date')
    paginator = Paginator(question_list, 10)  # 페이지당 10개씩 보여주기
    page_obj = paginator.get_page(page)
    context = {'question_list': page_obj}
    return render(request, 'pybo/question_list.html', context)

(... 생략 ...)
  • page = request.GET.get('page', '1')http://localhost:8000/pybo/?page=1 처럼 GET 방식으로 호출된 URL에서 page값을 가져올 때 사용한다. 만약 http://localhost:8000/pybo/ 처럼 page값 없이 호출된 경우에는 디폴트로 1이라는 값을 설정한다.

  • paginator = Paginator(question_list, 10)에서 첫번째 파라미터 question_list는 게시물 전체를 의미하는 데이터이고 두번째 파라미터 10은 페이지당 보여줄 게시물의 수

  • page_obj = paginator.get_page(page) 는 Paginator를 이용하여 요청된 페이지에 해당하는 페이징 객체(page_obj)를 생성 -> 이렇게 설정해야 장고 내부적으로 데이터 전체를 조회하지 않고 해당 페이지의 데이터만 조회하도록 쿼리가 변경됨

페이징 객체의 속성들

  • 항목설명
    paginator.count전체 게시물 개수
    paginator.per_page페이지당 보여줄 게시물 개수
    paginator.page_range페이지 범위
    number현재 페이지 번호
    previous_page_number이전 페이지 번호
    next_page_number다음 페이지 번호
    has_previous이전 페이지 유무
    has_next다음 페이지 유무
    start_index현재 페이지 시작 인덱스(1부터 시작)
    end_index현재 페이지의 끝 인덱스(1부터 시작)

템플릿에 페이징 적용하기

위의 index함수에서 질문 목록 템플릿(pybo/question_list.html)에 전달한 데이터(context)는 아래와 같다.

context = {'question_list': page_obj}  # question_list는 페이징 객체(page_obj)
return render(request, 'pybo/question_list.html', context)
  • 질문 목록 템플릿에 전달된 페이징 객체는 question_list

  • 이 페이징 객체인 question_list를 이용해 페이징 처리

projects\mysite\templates\pybo\question_list.html</table>태그 바로 밑에 다음 코드를 추가

(... 생략 ...)
    </table>
    <!-- 페이징처리 시작 -->
    <ul class="pagination justify-content-center">
        <!-- 이전페이지 -->
        {% if question_list.has_previous %}
        <li class="page-item">
            <a class="page-link" href="?page={{ question_list.previous_page_number }}">이전</a>
        </li>
        {% else %}
        <li class="page-item disabled">
            <a class="page-link" tabindex="-1" aria-disabled="true" href="#">이전</a>
        </li>
        {% endif %}
        <!-- 페이지리스트 -->
        {% for page_number in question_list.paginator.page_range %}
        {% if page_number == question_list.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 question_list.has_next %}
        <li class="page-item">
            <a class="page-link" href="?page={{ question_list.next_page_number }}">다음</a>
        </li>
        {% else %}
        <li class="page-item disabled">
            <a class="page-link" tabindex="-1" aria-disabled="true" href="#">다음</a>
        </li>
        {% endif %}
    </ul>
    <!-- 페이징처리 끝 -->
    <a href="{% url 'pybo:question_create' %}" class="btn btn-primary">질문 등록하기</a>
</div>
{% endblock %}
  • 이전 페이지가 있는 경우 "이전" 링크 활성화, 이전 페이지가 없는 경우 "이전" 링크 비활성화

  • 페이지 리스트를 루프 돌면서 해당 페이지로 이동 가능한 링크를 생성

    • 이 페이지가 현재 페이지와 같을 경우에는 active 클래스를 적용해 강조 표시

주요 페이징 기능

  • 페이징 기능코드
    이전 페이지가 있는지 체크{% if question_list.has_previous %}
    이전 페이지 번호{{ question_list.previous_page_number }}
    다음 페이지가 있는지 체크{% if question_list.has_next %}
    다음 페이지 번호{{ question_list.next_page_number }}
    페이지 리스트 루프{% for page_number in question_list.paginator.page_range %}
    현재 페이지와 같은지 체크{% if page_number == question_list.number %}
  • 페이지 리스트를 보기 좋게 표시하기 위해 부트스트랩의 pagination 컴포넌트를 이용


페이지 리스트

페이징 처리 이후 pybo 화면을 보면

페이징 처리는 잘 적용되었지만, 이동가능한 페이지가 모두 표시되는 문제점이 발생한다.

이 문제를 해결하기 위해 템플릿(question_list.html)을 수정하자

(... 생략 ...)
<!-- 페이지리스트 -->
{% for page_number in question_list.paginator.page_range %}
{% if page_number >= question_list.number|add:-5 and page_number <= question_list.number|add:5 %} <!--이 부분 if문 추가-->
{% if page_number == question_list.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 %}
{% endif %} <!--if문 닫는 구문 추가-->
{% endfor %}
(... 생략 ...)
  • 위 부분의 코드를 추가하여 페이지 표시 제한 기능을 구현
{% if page_number >= question_list.number|add:-5 and page_number <= question_list.number|add:5 %}
(... 생략 ...)
{% endif %}
  • 여기서 |add:-5, |add:5 는 템플릿 필터

    • |add:-5는 5만큼 빼라는 의미이고 |add:5는 5만큼 더하라는 의미
  • 위 코드는 페이지 리스트가 현재 페이지 기준으로 좌우 5개씩 보이도록 함

  • 현재 페이지를 의미하는 question_list.number보다 5만큼 크거나 작은 값만 표시되도록 만든 것

다시 로컬 서버를 실행해보면, 위와 같이 현재 페이지 기준으로 5개씩 페이지를 보이도록 하는 페이징 기능이 구현됨을 확인할 수 있다 !

profile
공부합시당

0개의 댓글