(最終更新月:2023年2月)
✔このような方へ向けて書かれた記事となります
「Djangoで一覧をページ表示にするにはどうやれば良いだろうか?」
「Djangoのpaginationについて詳しく知りたい」
「Djangoのpaginationで実例が見てみたい」
✔当記事を通じてお伝えすること
- paginationの基本
- paginationの応用
- paginationの実例
当記事では、Djangoのページネーション機能を基本から応用まで、すべて解説しています。
最後にはアプリ上での実装実例までご紹介します。
ぜひ最後までご覧ください。
【Django】paginationの基本
まずはDjangoのPaginationの基本について見ていきます。
paginationも見てみると意外と簡単。
- paginationとは?
- pagination機能の実装方法
- Paginatorクラスについて
ひとつずつ解説します。
paginationとは?
pagination(ページネーション)とは、ListVew内で一度に表示するレコード数を決めて、残りを別ページにて表示する手法です。
レコード数が増えた際に、すべて載せてしまうと、見た目が悪く、特定のレコードを探しにくいのはもちろん、データが重くページ表示が遅くなってしまいます。
ページネーションの例としては以下のとおり。
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">«</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">»</span>
<span class="sr-only">次へ</span>
</a>
</li>
{% endif %}
</ul>
</nav>
結果として、以下のようになります。
まとめ:Djangoのpaginationはわかれば簡単
当記事の内容をまとめます。
- Djangoのページネーションとは、レコードを分割してページ表示するためのもの
- Djangoでページネーションをおこなうには、変数とメソッド両方が使える
- Djangoのページネーションではいろいろなメソッドが使える
Djangoのページネーションは、レコードが増えたときのためにリストビューで使えるもの。
ブログや日報などさまざまなところで使えます。
ぜひうまく活用してください。