위 글은 점프 투 장고를 참고해 작성하였습니다.
현재 구현한 질문 목록 페이지는 페이징 처리(=페이지 나누기)가 안되기 때문에 게시물 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 클래스를 사용해 다음과 같이 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 %}
이전 페이지가 있는 경우 "이전" 링크 활성화, 이전 페이지가 없는 경우 "이전" 링크 비활성화
페이지 리스트를 루프 돌면서 해당 페이지로 이동 가능한 링크를 생성
주요 페이징 기능
페이징 기능 코드 이전 페이지가 있는지 체크 {% 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 컴포넌트를 이용
템플릿에 사용한 pagination
, page-item
, page-link
등이 부트스트랩 pagination 컴포넌트의 클래스
부트스트랩 pagination - https://getbootstrap.com/docs/5.1/components/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개씩 페이지를 보이도록 하는 페이징 기능이 구현됨을 확인할 수 있다 !