【Django】pagination機能で一覧にページを表示する

※本サイトにはプロモーション・広告が含まれています。

(最終更新月:2023年2月)

✔このような方へ向けて書かれた記事となります

「Djangoで一覧をページ表示にするにはどうやれば良いだろうか?」

「Djangoのpaginationについて詳しく知りたい」

「Djangoのpaginationで実例が見てみたい」

✔当記事を通じてお伝えすること

  • paginationの基本
  • paginationの応用
  • paginationの実例

当記事では、Djangoのページネーション機能を基本から応用まで、すべて解説しています。

最後にはアプリ上での実装実例までご紹介します。

ぜひ最後までご覧ください。

筆者プロフィール

筆者プロフィールアイコン

【現職】プロダクトマネージャー

【副業】ブログ(月間17万PV)/YouTube/Web・アプリ制作

「プログラミング × ライティング × 営業」の経験を活かし、30後半からのIT系職へシフト。当サイトでは、実際に手を動かせるWebアプリの開発を通じて、プログラミングはもちろん、IT職に必要な情報を提供していきます。

【当ブログで紹介しているサイト】

当サイトチュートリアルで作成したデモ版日報アプリ

Django × Reactで開発したツール系Webアプリ

✔人に見せても恥ずかしくないコードを書こう

「リーダブルコード」は、わかりやすく良いコードの定義を教えてくれる本です。

  • 見るからにきれいなコードの書き方
  • コードの分割方法
  • 変数や関数の命名規則

エンジニアのスタンダートとすべき基準を一から解説しています。

何回も読むのに値する本なので、ぜひ手にとって読んでみてください。

【Django】paginationの基本

まずはDjangoのPaginationの基本について見ていきます。

paginationも見てみると意外と簡単。

  • paginationとは?
  • pagination機能の実装方法
  • Paginatorクラスについて

ひとつずつ解説します。

paginationとは?

pagination(ページネーション)とは、ListVew内で一度に表示するレコード数を決めて、残りを別ページにて表示する手法です。

レコード数が増えた際に、すべて載せてしまうと、見た目が悪く、特定のレコードを探しにくいのはもちろん、データが重くページ表示が遅くなってしまいます。

ページネーションの例としては以下のとおり。

CSSなどは適用していません

Djangoにページネーションを実装する方法として、ListViewクラスの場合、主に2通りの方法があります。

それぞれ見て、場面に合わせて使い分けましょう。

実装方法①:ListViewの変数に入れる

ページネーションを実装する方法のひとつめは、ListViewの変数に入れるものです。

変数名「paginate_by」に、一度に表示するレコード数を記述します。

from django.views.generic import ListView
from nippo.models import NippoModel

class NippoListView(ListView):
    model = NippoModel
    template_name = 'nippo_list.html'
    paginate_by = 10

実装方法②:ListViewの関数に入れる

実装方法の2つ目は、ListView内のメソッド「get_paginate_by」に入れるものです。

変数に入れる場合と異なり、受け取るパラメータなどの状況に合わせて、動的に変更できるのが特徴。

from django.views.generic import ListView
from nippo.models import NippoModel

class NippoListView(ListView):
    model = NippoModel
    template_name = 'nippo_list.html'

    def get_paginate_by(self, queryset):
        return 10

【Django】Paginatorクラスとは?

Djangoに備わっているPaginatorクラスをご紹介します。

ページネーションをおこなっているのは、Djangoに備わっているPaginatorクラスです。

  • Paginatorクラスの基本
  • Paginatorに備わっているメソッド
  • Paginatorの属性

Paginatorクラスの基本

DjangoアプリはPaginatorクラスによって、データをページングしています。

Paginatorクラスを活用すれば、リスト型データやクエリセットだけでなく、任意のイテラブルなオブジェクトを受け取り、ページングができるのです。

以下はDjangoのシェルでPaginatorクラスを使用した例です。

from django.core.paginator import Paginator

data_list = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
paginator = Paginator(data_list, 3) # ページあたりのアイテム数を3としてデータを分割
page = paginator.get_page(2) # ページ番号2のデータを取得
print(page.object_list) 
["d", "e", "f"]

Paginatorに備わっているメソッド

Paginatorに備わっているメソッドで代表的なものをご紹介します。

  • 特定のページオブジェクトを取得する:get_page(self, number)
  • ページ番号の範囲を取得する:get_page_range(self, current_page, num_pages)

それぞれ覚えておくと、コードの幅が広がります。

Paginatorの属性

Paginatorの属性には以下のものがあります。

属性名:属性により返される値

  • count : ページネーションを適用するオブジェクトの総数
  • num_pages : ページネーションを適用するために必要なページ数
  • page_range : ページネーションのページ番号の範囲を示すrangeオブジェクト
  • per_page : 1ページあたりのオブジェクト数
  • orphans : 最後のページに許容されるオブジェクト数
  • object_list : ページネーションを適用するオブジェクトのリスト

【Django】ページリンクの設定方法

ページのリンクに関するメソッドを見ていきましょう。

レコード数に応じて表示するだけでなく、ページ間の移動をするのに必要だからです。

  • 次のページに関するメソッド
  • 前のページに関するメソッド
  • ほかで使えるメソッド

次のページに関するメソッド

次のページに移動するために使えるメソッドは2つ。

以下の2つを調べるものです。

  • 次のページはあるかどうか
  • 次のページ番号が何番か

Boolean:次のページはあるか

次のページがあるかどうかを調べるメソッドが必要です。

次のページが無ければ、次ページへのリンクは不要になります。

page.has_next()

返る値はBoolean型となり、TrueもしくはFalseです。

Integer:次のページ番号はなにか

次のページが存在する場合、次ページの番号を返すメソッドがあります。

page.next_page_number()

返す値はページ番号であるInteger型です。

前のページに関するメソッド

次ページだけでなく前のページに関するメソッドも見ていきます。

どちらも同じように使えるからです。

  • 前のページがあるかを調べる
  • 前のページ番号があるかを調べる

Boolean:前のページはあるか

前のページがあるかを調べるメソッドはこちら。

page.has_previous()

has_nextメソッドと同様で、Boolean型の値を返します。

Integer:前のページ番号はなにか

前のページ番号を見るメソッドも使えるようになりましょう。

リンクを作る際に使えます。

page.previous_page_number()

Integer型を返す関数です。

ほかで使えるメソッド

これまで出てきたもの以外にも、ページに関するメソッドがいくつかあります。

  • ページ上最初のレコードのインデックス
  • ページ上最後のレコードのインデックス
  • 前後のどちらかにページがあるかを調べる

Integer:ページ上最初のレコードのインデックス

現在のページ上で表示されている先頭のレコードが、全体の何番目かを返すメソッドです。

page.start_index()

Integer型を返します。

Integer:ページ上最後のレコードのインデックス

現在のページ上で表示されている最終のレコードが、全体の何番目かを返すメソッドです。

page.end_index()

Boolean:前後のどちらかにページがあるか

前後問わずに他ページが存在するかを返すメソッドです。

page.has_other_pages()

TrueもしくはFalseを返すメソッドになります。

例外時に呼び出される2つのクラス

例外時に呼び出されるクラスを見ていきます。

必ず正常なURLが打ち込まれるわけではないのです。

  • PageNotAnInteger
  • EmptyPage

それぞれがどんな場面で使われるのかを理解しましょう。

PageNotAnInteger

PageNotAnIntegerは、Pページネーション実行の際に、ページ番号として整数値ではない値(文字列や空の値など)が提供された場合に発生します。

このエラーは、django.core.paginator.PageNotAnIntegerという例外として定義されています。

例えばエラーが発生すると以下のような表示になります。

>>> paginator.page("test")
Traceback (most recent call last):
  File "C:\Users\spro_note7\dev\myCRM\venv\lib\site-packages\django\core\paginator.py", line 46, in validate_number
    number = int(number)
ValueError: invalid literal for int() with base 10: 'test'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\spro_note7\dev\myCRM\venv\lib\site-packages\django\core\paginator.py", line 73, in page
    number = self.validate_number(number)
  File "C:\Users\spro_note7\dev\myCRM\venv\lib\site-packages\django\core\paginator.py", line 48, in validate_number
    raise PageNotAnInteger(_('That page number is not an integer'))
django.core.paginator.PageNotAnInteger: <exception str() failed>

EmptyPage

EmptyPageエラーは、Paginatorクラスによってページネーションを実行する際に、指定されたページに対応するオブジェクトが存在しない場合に発生するもの。

例えば、ページングされた最終ページであるにもかかわらず、ユーザーが検索クエリを変更し、該当のオブジェクトがない場合などに発生することがあります。

このエラーは django.core.paginator.EmptyPage という例外として定義されています。

以下のようなエラーとなります。

>>> paginator.page(6)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Users\spro_note7\dev\myCRM\venv\lib\site-packages\django\core\paginator.py", line 73, in page
    number = self.validate_number(number)
  File "C:\Users\spro_note7\dev\myCRM\venv\lib\site-packages\django\core\paginator.py", line 55, in validate_number
    raise EmptyPage(_('That page contains no results'))
django.core.paginator.EmptyPage: <exception str() failed>

【Django】paginationの実例|日報アプリで実装

日報アプリでの実例をご覧いただきます。

実際のアプリケーションでのコードを見ることで、どんな風に使えば良いかがイメージできるでしょう。

  • views.py:ページネーションのコード
  • templateファイル:template上での変数の書き方
  • templateファイル:Bootstrapを適用して見た目を整える

views.py:ページネーションのコード

当ブログで開発した日報アプリ内でのページネーションのコードはこちら。

class NippoListView(ListView): #クラス作成
    template_name = "nippo/nippo-list.html" #変数
    model = NippoModel #変数
    paginate_by = 5

#その他のメソッドは割愛。

templateファイル:template上での変数の書き方

テンプレートファイルでは、以下のように記述します。

 {% for obj in page_obj %}
                <div class="card mx-auto mb-2" style="max-width:700px; min-width:300px;">
                    <div class="card-header">
                        {{ obj.date|date:"Y年n年j日" }}
                        {% if not obj.public %}
                            <span class="badge bg-secondary">下書き</span>
                        {% endif %}
                    </div>
{% comment %} 他のメソッドは割愛 {% endcomment %}

{% commend %}以下は他ページへのリンクです。{% endcomment %}
            <div class="pagination">
                {% if page_obj.has_previous %}
                    <a href="?page={{ page_obj.previous_page_number }}">前へ</a>
                {% endif %}
            
                <span class="current-page">{{ page_obj.number }}</span>
            
                {% if page_obj.has_next %}
                    <a href="?page={{ page_obj.next_page_number }}">次へ</a>
                {% endif %}
            </div>

object_listやfilter.qsではなく、page_objを使うのです。

templateファイル:Bootstrapを適用して見た目を整える

Bootstrapを使って見た目を整えるには以下のようにすればよいでしょう。

<nav aria-label="Page navigation">
                <ul class="pagination text-center">
                  {% if page_obj.has_previous %}
                    <li class="page-item">
                      <a class="page-link" href="?page={{ page_obj.previous_page_number }}" aria-label="Previous">
                        <span aria-hidden="true">&laquo;</span>
                        <span class="sr-only">前へ</span>
                      </a>
                    </li>
                  {% endif %}
              
                  {% for num in page_obj.paginator.page_range %}
                    {% if page_obj.number == num %}
                      <li class="page-item active"><a class="page-link" href="?page={{ num }}">{{ num }}</a></li>
                    {% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
                      <li class="page-item"><a class="page-link" href="?page={{ num }}">{{ num }}</a></li>
                    {% endif %}
                  {% endfor %}
              
                  {% if page_obj.has_next %}
                    <li class="page-item">
                      <a class="page-link" href="?page={{ page_obj.next_page_number }}" aria-label="Next">
                        <span aria-hidden="true">&raquo;</span>
                        <span class="sr-only">次へ</span>
                      </a>
                    </li>
                  {% endif %}
                </ul>
              </nav>

結果として、以下のようになります。

まとめ:Djangoのpaginationはわかれば簡単

当記事の内容をまとめます。

  • Djangoのページネーションとは、レコードを分割してページ表示するためのもの
  • Djangoでページネーションをおこなうには、変数とメソッド両方が使える
  • Djangoのページネーションではいろいろなメソッドが使える

Djangoのページネーションは、レコードが増えたときのためにリストビューで使えるもの。

ブログや日報などさまざまなところで使えます。

ぜひうまく活用してください。

タイトルとURLをコピーしました