【Django】ページネーション(ページング):記事一覧を複数ページに分ける方法

/

【Django】ページネーション(ページング):記事一覧を複数ページに分ける方法

大量のデータ一覧を表示する場合、複数のページに分けて表示したいときがあります。そういった場合は、ページネーション(ページング)と呼ばれる技術を使います。

例えば、Webアプリケーションで自社のユーザー一覧を表示する場合、10人程度であれば1つのページに表示するのは問題ないです。
しかし何百人もいる場合は、ページを分けた方が使い勝手やページの読みやすさはいいですよね。

また、ブログで記事数が100以上ある場合、1つのページに記事一覧すべてを表示なんかしないですよね。

このような場合に、ページを複数に分けるページネーション(ページング)を使用して、1画面に表示する件数を制限してコンパクトにします。

今回は、このページネーションをDjangoでどのように実装するのか解説していきます。

環境はPython 3.7.5、Django3.0.0です。

完成ページ

最終的に完成させるページネーションの表示は以下のようになります。

Django:ページネーション

どのようにして、このページネーションをDjangoで作成するか解説していきます。

Djangoにおけるページネーション(ページング)

Djangoにはページネーションをサポートする機能がデフォルトで実装されています。

ページネーション機能を使うために、view.pyではDjango.core.paginatorモジュール内のPaginator、EmptyPage、PageNotAnIntegerを使用します。

Paginatorクラス

Paginatorに、分割するオブジェクトリスト、1ページ当たりに表示するデータの個数を指定して、インスタンスを作成します。

例えば、Articleモデルからリストを全件取得して、1ページ当たり20件データを表示させる場合は以下のようになります。

articles = Article.objects.all()
paginator = Paginator(articles, 20)

リクエストされたページのリストを表示する場合、まずページ番号をURLから取得します。
GET送信されたoffice54.net/article/?page=4 のようなURLから、ページ番号を以下で取得できます。

page = request.GET.get('page')

この取得したページ番号を使って、リクエストされたページのリストを取得します。

pages = paginator.page(page)

リクエストされたページのリストを受け取ったpagesはPageオブジェクトと呼びます。

Pageオブジェクトのメソッドは後述いたします。

ここまでがview.pyでのPaginatorクラスの基本的な使用方法です。

Pageオブジェクト

Pageオブジェクトはhtmlファイル内で使用します。

Pageオブジェクトを使用して、現在のページを起点として次ページの有無、前ページの有無、他のページの有無などを取得することができます。

以下にメソッドおよび属性一覧を記します。

has_next()メソッド
次のページがある場合:True

has_previous()メソッド
前のページがある場合:True

has_other_pages()メソッド
前後どちらかにページがある場合:True

next_page_number()メソッド
次ページのページ番号を返す(次ページが存在していなくても)

previous_page_number()メソッド
前ページのページ番号を返す(前ページが存在していなくても)

number属性
現在のページ番号を返す

例外処理

リクエストされたページ番号が有効でない場合に備えて、PageNotAnIntegerとEmptyPageを使います。

PageNotAnIntegerは、整数でない値がリクエストされた際に呼び出されます。
EmptyPageは、有効な値がリクエストされているが、そのページが存在していない際に呼び出されます。

サンプルプログラム

では実際にWebアプリケーションを作成してみましょう。

プロジェクト名はmyproject、アプリケーション名はblogとします。
myprojectのurls.py、blogアプリのurls.py、models.py、views.py、index.htmlは以下のようになっております。

# myproject/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('blog.urls')),
]
# blog/urls.py
from django.urls import path
from . import views
urlpatterns = [
    path('', views.index, name='index'),
]
# blog/models.py
from django.db import models
from django.utils import timezone

class Post(models.Model):
    title = models.CharField(max_length=200)
    text = models.TextField()
    created_date = models.DateTimeField(default=timezone.now)

    def __str__(self):
        return self.title
# blog/views.py
from django.shortcuts import render
from .models import Post

def index(request):
    return render(request, 'index.html', {})
# blog/template/index.html
<html>
<head>
</head>
<body style="margin:20px;">
  <h1>ページネーション確認ページ</h1>
</body>
</html>

views.pyの変更

それではviews.pyを編集し、テンプレートにPageオブジェクトを渡すようにします。

# blog/views.py
from django.shortcuts import render
from .models import Post
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger

def index(request):
    Posts = Post.objects.order_by('created_date').reverse()
    paginator = Paginator(Posts, 3)
    page = request.GET.get('page', 1)
    try:
    	pages = paginator.page(page)
    except PageNotAnInteger:
    	pages = paginator.page(1)
    except EmptyPage:
    	pages = paginator.page(1)
    context = {'pages': pages}
    return render(request, 'index.html', context)

3行目では、django.core.paginatorモジュールからPaginator、EmptyPage、PageNotAnIntegerをインポートしています。

6行目では、Postモデルからオブジェクト全件を取得、created_dateで順番を整列しています。

7行目のpaginator = Paginator(Posts, 3)では、取得したオブジェクトのリストを1ページ当たり3件ずつ表示するようにしています。

8行目のrequest.GET.get(‘page’, 1)では、現在のページ番号をURLから取得しています。

10行目のpages = paginator.page(page)では、リクエストされたページ番号のリストを取得し、pagesに格納しています。

次に画面表示されるテンプレートindex.htmlを編集していきます。

表示するhtmlファイルの設定

テンプレートのindex.htmlを編集し、ページネーションが表示されるようにします。
BOOTSTRAP4を使って見栄えもよくしますので、Bootstrap4が使用できるようにスタイルシートをlinkタグでダウンロードしています。

<html>
<head>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="… " crossorigin="anonymous">
</head>
<body style="margin:20px;">
  <h1>ページネーション確認ページ</h1>
  {% for page in pages %}
  <p>{{ page.title }} : {{ page.text }}</p>
  {% endfor %}
  {% if pages.has_other_pages %}
    <nav aria-label="Page navigation example">
        <ul class="pagination">
            {% if pages.has_previous %}
                <li><a class="page-link text-primary d-inline-block" href="?page={{ pages.previous_page_number }}"><<</a></li>
            {% else %}
                <li class="disabled"><div class="page-link text-secondary d-inline-block disabled" href="#"><<</div></li>
            {% endif %}

            {% for page_num in pages.paginator.page_range %}
                {% if page_num %}
                    {% if page_num == pages.number %}
                        <li class="disabled"><div class="page-link text-secondary d-inline-block disabled" href="#">{{ page_num }}</div></li>
                    {% else %}
                        <li><a class="page-link text-primary d-inline-block" href="?page={{ page_num }}">{{ page_num }}</a></li>
                    {% endif %}
                {% else %}
                    <li class="disabled"><a class="page-link text-secondary d-inline-block text-muted" href="#">・・・</a></li>
                {% endif %}
            {% endfor %}

            {% if pages.has_next %}
                <li><a class="page-link text-primary d-inline-block" href="?page={{ pages.next_page_number }}">>></a></li>
            {% else %}
                <li class="disabled"><div class="page-link text-secondary d-inline-block disabled" href="#">>></div></li>
            {% endif %}
        </ul>
    </nav>
  {% endif %}
</body>
</html>

上記index.htmlにより、以下のようにページネーションが表示されます。

Django:ページネーションの確認

まとめ

Djangoにおけるページネーションの使い方の解説でしたが、いかがでしたか?

ぜひWebアプリケーションに取り入れていただき、使いやすいアプリ作成を実現させてください。