Django - Paginator

nathan·2021년 7월 20일
0

Django

목록 보기
18/22

Pagination

  • 우리가 여태껏 만들었던 블로그를 보면 글이 한 줄로 쭉 나열되어 있다.
  • 하지만 우리가 일반적으로 이용하는 사이트는 이렇지 않다.
  • 아래는 티스토리 블로그에서 캡쳐한 사진이다.

  • 이처럼 일반적으로 사용하는 사이트는 이와 같이 데이터를 페이지 별로 나눠 보내는데, 이러한 기능을 Pagination이라고 한다.

  • Django 는 모듈을 통해 Pagination 기능을 제공한다.


Pagination을 쓰는 이유

    1. UI/UX적인 편리함을 제공하기 위함
    1. 서버의 부하를 줄이기 위함
    • 여러 사용자가 매 request마다 수 백, 수 천개의 데이터를 요구한다면 서버의 부하가 심해짐

Django의 Paginator

blog/views.py

# 이전 import문들..
from django.core.paginator import Paginator

def home(request):
    blogs = Blog.objects.all() #blog 객체를 모두 가져온다
    paginator = Paginator(blogs, 3) # blogs를 3개씩 쪼갠다
    page = request.GET.get('page') # 해당 정보가 오지 않아도 넘어간다
    paginated_blogs = paginator.get_page(page)
    return render(request, 'home.html', {'blogs': paginated_blogs})
  • paginator = Paginator(blogs, 3)
    • 첫 번째 인자로 분할할 객체(blogs), 두 번째 인자로 한 페이지에 담길 객체의 수(3)를 넣어준다.
  • page = request.GET.get('page')
    • 그동안 봤던 request.GET['page']와 다른 메소드를 사용해보자.
    • request.GET['page']는 request에 'page'가 없다면 MultiValueDictKeyError 를 발생시킨다.
    • 그러나, request.GET.get('page')는 request에 'page'가 없다면 None을 리턴한다.
  • paginated_blogs = paginator.get_page(page)
    • 페이지 번호를 받아 해당 페이지를 반환한다.
    • 만약 인자로 None이 들어간다면 첫 번째 페이지를 반환한다.

템플릿 수정하기

blog/templates/home.html

{% extends 'base.html' %}
  {% block content %}
    {% if user.is_authenticated %}
      {{user.location}}에 사는 {{user.university}} 다니는 {{user.nickname}}님 안녕하세요!
    {% endif %}
    <h1>Blog Project</h1>
    <h4><a href="{% url 'blog:new' %}">New Post</a></h4>

    <div class="conatainer">
      {% for blog in blogs%}
      <div>
        <h3>{{ blog.title }}</h3>
        {{ blog.id }} <br />
        {{ blog.writer }} <br />
        {{ blog.summary }}
        <a href="{% url 'blog:detail' blog.id %}">...more</a>
      </div>
      {% endfor %}
    </div>

    <br/>

    <div class="paginator">
      {% if blogs.has_previous %}
      <a href="?page=1">처음</a>
      <a href="?page={{blogs.previous_page_number}}">이전</a>
      {% endif %}
      <span>{{blogs.number}}</span>
      <span>of</span>
      <span>{{blogs.paginator.num_pages}}</span>
      {% if blogs.has_next %}
      <a href="?page={{blogs.next_page_number}}">다음</a>
      <a href="?page={{blogs.paginator.num_pages}}">마지막</a>
      {% endif %}
    </div>
  {% endblock %}
  • 코드가 조금 지저분하므로 다음과 같이 <div class="container">와 <div class="paginator">로 나눠주었다.

  • {% if blogs.has_previous %}

    • has_previous : 이전 페이지의 유무를 boolean 형태로 반환
    • 페이지가 이전으로 돌아갈 수 있다면, 즉 첫 번째 페이지가 아니라면 if 문 내부의 요소들을 보여준다.
  • {{blogs.previous_page_number}}

    • 이전 페이지를 반환한다.
  • {{blogs.next_page_number}}

    • 다음 페이지를 반환한다.
  • {{blogs.paginator.num_pages}}

    • 총 페이지 수를 반환한다.
    • page == 총 페이지 수 라면, 마지막 페이지를 의미하는 것이다.
  • <a href="?page=1">

    • 쿼리스트링 : 데이터를 전달하는 데 사용
    • request.GET['page']나 request.GET.get('page')의 형태로 해당하는 데이터를 얻을 수 있다.
    • 쿼리스트링은 "?"로 시작하며, "&"를 통해 여러 데이터를 동시에 전달할 수 있다.
코드를 작성하면 다음과 같이 Paginator가 생기는 것을 확인할 수 있다.

ORM을 다양하게 활용하기

  • 우리가 반든 블로그는 오래된 글부터 순차적으로 보여줬다.(오름차순)
  • 일반적으로 타 사이트에서는 최신글부터 순차적으로 보여주는데, 이는 장고 ORM에서 제공하는 order_by() 메소드를 통해 구현할 수 있다.
  • 또한, 원하는 데이터만 정제해서 보여주는 filter() 메소드도 사용해서 검색 기능을 구현할 수도 있다.

blog/views.py

def home(request):
    query = request.GET.get('query')
    search = request.GET.get('search')
    if query:
        blogs = Blog.objects.filter(title__icontains = query).order_by('-pub_date')
    elif search:
        author = request.GET.get('user')
        user = CustomUser.objects.get(username=author)
        blogs = Blog.objects.filter(user = user).order_by('-pub_date')      
    else:
        blogs = Blog.objects.order_by('-pub_date')

    paginator = Paginator(blogs, 3)
    page = request.GET.get('page')
    paginated_blogs = paginator.get_page(page)
    return render(request, 'home.html', {"blogs" : paginated_blogs, "search": search})
  • order_by(): 정렬해서 데이터 가져옵니다.

    • 정렬 조건을 인자에 넣어줍니다.
    • 정렬 조건에 '-'를 붙이면 역순, 즉 내림차순으로 정렬합니다.
    • 최신순으로 정렬하기 위해 '-pub_date'를 인자로 넣어줍니다.
  • filter(): 조건을 걸어 원하는 데이터만 가져옵니다.

    • 위의 코드에선 title필드의 값에 query가 포함되어 있는 객체들만 가져옵니다.
    • 이를 이용하여 검색 기능을 구현할 수 있습니다.
  • 더욱 다양한 쿼리셋 API 알아보기 : https://docs.djangoproject.com/en/3.2/ref/models/querysets/

template 수정하기
blog/templates/home.html

{% extends 'base.html' %} {% block content %}
<body>
  {% if user.is_authenticated %} {{user.location}}에 사는
  {{user.university}}다니는 {{user.nickname}} 안녕하세요! {% endif %}
  <form action="{% url 'home' %}">
    <p>제목으로 게시글 검색</p>
    <input type="text" name="query" />
    <input type="submit" value="Search" />
  </form>

  <!-- <h1>Blogs : {{blogs}}</h1> -->
  <a href="?search=true&user={{user.username}}">내가 쓴 글</a>
  <hr />
  <a href="{% url 'blog:new_django_form' %}">새 블로그 생성하기!</a>
  <hr />

  {% for blog in blogs %} 글번호 : {{blog.id}}
  <br />
  제목 : {{blog.title}}
  <br />
  작성자 : {{blog.writer}}
  <br />
  내용 : {{blog.summary}}
  <br />
  <a href="{% url 'blog:detail' blog.id %}">more..</a>
  <br />
  <a href="{% url 'blog:update' blog.id %}">update..</a>
  <br />
  <a href="{% url 'blog:delete' blog.id %}">delete..</a>
  <hr />
  {% endfor %}

  <br />
  <div class="paginator">
    {% if blogs.has_previous %}
    <a href="?page=1">처음</a>
    {% if search %}
    <a
      href="?search=true&user={{user.username}}&page={{blogs.previous_page_number}}"
      >이전</a
    >{% else %}
    <a href="?page={{blogs.previous_page_number}}">이전</a>
    {% endif %} {% endif %}

    <span>{{blogs.number}}</span>
    <span>of</span>
    <span>{{blogs.paginator.num_pages}}</span>
    {% if search %} 
      {% if blogs.has_next %}
    <a href="?search=true&user={{user.username}}&page={{blogs.next_page_number}}">다음</a>
    <a href="?search=true&user={{user.username}}&page={{blogs.paginator.num_pages}}">마지막</a>
      {% endif %}
    {% else %} 
      {% if blogs.has_next %}
    <a href="?page={{blogs.next_page_number}}">다음</a>
    <a href="?page={{blogs.paginator.num_pages}}">마지막</a>
      {% endif %} 
    {% endif %}
  </div>
</body>

{% endblock %}

최종 화면

  • 시간 순서대로 글이 정렬된다.
  • 그리고 내가 쓴 글을 클릭하더라도 search를 request로 받았다면, 내가 쓴 글 안에서 paginator가 작동한다.
  • 쿼리스트링을 통한 filtering이 이루어져서 제목으로 게시글을 검색할 수 있다.
profile
나는 날마다 모든 면에서 점점 더 나아지고 있다.

0개의 댓글