글의 목록을 표시할 때, Listview
를 상속받아 작성하든, request
를 사용하여 목록을 받아 표시하든, 글의 갯수가 많아지면 로딩에도 걸리는 시간이 더 늘어나고, 내가 원하는 글을 찾기위해 스크롤을 계속 내려야하는 문제가 발생할 것 같았다.
어떤 사이트를 가던지, 글의 갯수가 많아지면 페이지를 나누어서 보여줬던것이 일반적이니, Django
에도 그런 기능이 있지 않을까 해서 찾아본게 시작이였다.
위의 사진처럼 우리가 일반적으로 보는 모든 사이트에 존재하는 페이지나누기, Pagination을 구현해주는 아주 고맙고도 편리한 기능이 되겠다.
Pagination을 사용하기 위해선 내가 views
를 무엇으로 구성했느냐에 따라 조금씩 다른데, Listview
로 views
를 구성했는가, 아니면 함수를 사용하여 views
를 구현했는가로 나뉜다.
class
를 이용해 ListView
를 상속받어 views
를 작성했다면, class
내부에 한줄만 추가하면 된다.
공식문서에서 제시하는 코드는 다음과 같다.
from django.views.generic import ListView
from myapp.models import Contact
class ContactListView(ListView):
paginate_by = 2 # Pagination
model = Contact
...
기본적으로 ListView
는 Pagination을 제공하고, 이를 관장하는 속성은 paginate_by
다.
ListView
가 상속받는 BaseListView
, 그중에서도 MultipleObjectMixin
을 보게 되면 아래와 같은 속성이 있다.
allow_empty = True
queryset = None
model = None
paginate_by = None
paginate_orphans = 0
context_object_name = None
paginator_class = Paginator
속성을 보면 기본적으로 paginate_by
는 None
이고, 내가 작성한 views
에 속성을 추가해주지 않으면 Pagination을 하지 않는다. 속성에 특정한 값을 입력해주어야지만 그 숫자만큼 Pagination이 되는 것이라고 보면 되겠다.
함수에서는 조금 복잡하다. ListView
쪽의 속성을 보면 paginator_class
가 있고, 속성값은 Paginator
인데 이를 사용해야 한다.
공식문서에서 제시하는 코드는 다음과 같다.
from django.core.paginator import Paginator # 1. Paginator를 import
from django.shortcuts import render
from myapp.models import Contact
def listing(request):
contact_list = Contact.objects.all()
# 2. Pagination을 시행할 객체를 가져오기
paginator = Paginator(contact_list, 25)
# 3. 객체를 지정한 갯수만큼 page단위로 저장, 여기서는 25개씩
page_number = request.GET.get('page')
# 4. request된 page를 저장
page_obj = paginator.get_page(page_number)
# 5. request된 page의 인덱스를 저장
return render(request, 'list.html', {'page_obj': page_obj})
# 6. 레코드를 `template`에 전달
아무래도 기본적으로 제공하는 ListView
의 Pagination에 비해선 조금 길다.
그래도 개인적으론 함수를 더 좋아하는게, 뭔가 입맛대로 꾸미기가 쉽기도 하고, 개인적으론 좀더 직관적이다. class
에 익숙치 않은것도 있겠지만..
Paginator
는 4개의 속성을 가진다.
class Paginator(object_list, per_page, orphans=0, allow_empty_first_page=True)
object_list
: Pagination을 시행할 객체, 리스트나 튜플, QuerySet등이 들어간다.per_page
: 페이지당 표시할 개체 수이 아래는 옵션이다.
orphans
: 마지막 페이지가 이 속성의 숫자보다 작다면, 페이지를 별도로 생성하지 않고 그 이전 페이지에 합산하여 표시한다.QuerySet에 22개의 데이터가 있고,
per_page
가 5,orphans
가 3이면 페이지의 수는 4가 된다. 1~3페이지는 5개의 데이터가, 4페이지(마지막)에는 7개의 데이터가 표시된다.
allow_empty_fires_page=True
: 첫 페이지를 비워둘 수 있는지 여부. 이것이 False가 되면 데이터가 없을때 EmptyPage
에러가 발생한다.Views.py
에서 Pagination을 완료했지만, 이걸 우리가 보는것은 template, html에서 본다. 나누기는 했지만 나누어진 페이지를 보기 위해선 template를 작성해야 한다.
장고 공식문서에서 제시하는 코드는 아래와 같다.
<div class="Pagination">
<!--이전으로 돌아가기-->
{% if posts.has_previous %}
<a href="?page=1">first</a>
<a href="?page={{page_obj.previous_page_number}}">previous</a>
{% endif %}
<!--현재-->
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
<span>
<!--다음으로 넘어가기-->
{% if page_obj.has_next %}
<a href="?page={{page_obj.next_page_number}}">next</a>
<a href="?page={{page_obj.paginator.num_pages}}">last</a>
{% endif %}
</div>
예시 사진과는 조금 다르지만, 이전으로 돌아가기, 현재 페이지, 다음으로 넘어가기 세가지 기능이 들어간다고 보면 되겠다.
이전과 다음의 경우는 이전 페이지가 있느냐, 다음 페이지가 있느냐에 따라서 표시가 되도록 Template Tag
를 사용해 if를 넣고, 해당되지 않으면 표기하지 않는 방식이다.
여기서 개인적으로 조금 헷갈렸던 부분이 a태그의 ?였다. Django
에서는 주소가 바뀌면 urls.py
를 수정해 주었어야 하니 urls.py
에 ?page=<??>
라는 주소를 추가해야 하는건가 해서 확인해보았는데, 공식 문서에도 관련 내용이 없고, 나보다 먼저 사용한 분들 그 누구도 urls.py
를 수정하진 않았다.
검색해보니 a태그의 ?의 의미는 변수로써 저 값을 전달한다는 의미로, template에서 get 요청을 통해 변수로써 page의 번호를 넘겨주게 되고, 이 변수가 Pagination의 결과를 보여준다는 의미였다.
사용하기위해 테스트도 할 겸, 연습해보기로 했다.
테스트용으로 글 몇개를 만들었다. 5개뿐이지만.
QuerySet
에 5개의 글을 작성하고, 우선 이를 받아오는 함수를 작성하고
def pagingtest(request):
test_list = Test.objects.all().order_by('-id')
context = {'test_list':test_list,}
return render(request, 'test.html', context)
여기에 함수에서 Paginator
를 사용하는 방법을 사용해서, 객체를 나누고, 페이지 번호를 가져온 뒤, 인덱싱 하는 것을 추가했다.
def pagingtest(request):
test_list = Test.objects.all().order_by('-id')
pagination = Paginator(test_list, 2) #객체를 2개씩 나누기
page_num = request.GET.get('page') # 페이지 번호 가져오기
test_list_index = pagination.get_page(page_num) # 페이지 인덱싱
context = {'test_list':test_list_index,}
return render(request, 'test.html', context)
그 다음, template인 test.html
에 들어가서
<div class="Test_List">
[전체글 목록]
{% for object in test_list %}
<li><a href="">{{ object.title }}</a> - {{object.id}}</li>
{% endfor %}
</div>
<div class="Pagination">
{% if test_list.has_previous %}
<a href="?page=1">First</a>
<a href="?page={{test_list.previous_page_number}}">Previous</a>
{% endif %}
<span>{{test_list.number}} of {{test_list.paginator.num_pages}}</span>
{% if test_list.has_next %}
<a href="?page={{test_list.next_page_number}}">Next</a>
<a href="?page={{test_list.paginator.num_pages}}">Last</a>
{% endif %}
</div>
test_list
의 항목을 for를 이용하여 출력하고, 그 밑에 페이지 이동용 코드를 추가하였다.
이를 저장하고, runserver를 통해서 확인해보면
두개씩 나뉘어서 잘 동작하는 것을 볼 수있다.
Django Paginator 공식문서
Django Pagination 공식문서
Bale의 Devlog - 페이지 나누기
테리의 일상 - 페이지네이션