はじめに
Djangoの標準ビューの一つでモデルを単純に利用した表示を行うListlViewが用意されています。
前回はListViewの基本的な機能をまとめましたが、触れていなかったページネーションについてまとめてみました。
ページネーションとは
多数のコンテンツを提供する際に、コンテンツを一定の数で区切ってページに分ける機能のことです。
(Google検索等でいう、結果が1ページ10件ずつ表示していくような感じのものです)
環境
- Python 3.9
- Django 3.2
モデル
本記事では、画面表示するモデルとして下記を使用することにします。
class Article(models.Model):
title = models.CharField(max_length=128)
content = models.TextField()
is_published = models.BooleanField()
使い方
ページネーションの実装
ListViewではクラス変数として、pagenate_by
に分割数を指定するだけでページネーションを実装することができます。
この変数を設定することで、GETパラメータ引数として与えられるページ番号をオフセットとして、表示対象のモデルオブジェクトが指定数で分割されてテンプレートへ渡されます。
テンプレートからはpage_obj
変数(ページネーション管理オブジェクト)でページ状態を参照できるようになるため、これを利用してテンプレートにページ送り用のUI実装します。
モデルオブジェクトのobject_list
には、分割後に該当のページ内で有効なオブジェクト(1ページ目であればソート順に1~5番目、2ページ目であれば、6~10番目...)のみにフィルタされた状態でテンプレートへ渡されます。
アクセス時のページ番号の指定には、GETパラメータpage
にページ番号を渡す必要があります。リクエスト時に指定されていない場合は、1ページ目を指定したものとして扱われます。
テンプレート内で次ページへのリンク等を張る際にはリンク先URLのパラメータに目的のページの指定を行います。
from django.views.generic import ListView
from .models import Article
class ArticleListView(Listview):
template_name = 'article_list.html'
model = Article
pagenate_by = 5 # モデルの分割数を追加
<!DOCTYPE html>
<html>
<head></head>
<body>
<ul>
<!-- object_list は該当ページ内で有効なオブジェクトのみにフィルタされて渡される-->
{% for article in object_list %}
<li>{{ article.title }}</li>
{% endfor %}
</ul>
<!-- ページネーション用のUI -->
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
<!-- 現在よりも前のページが存在する場合 -->
<a href="?page=1">« 先頭</a>
<a href="?page={{ page_obj.previous_page_number }}">前へ</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 }}">次へ</a>
<a href="?page={{ page_obj.paginator.num_pages }}">最後 »</a>
{% endif %}
</span>
</div>
</body>
</html>
ページオブジェクトについて
ページネーションを有効化するとテンプレートへ自動で渡されるようになるページオブジェクト変数page_obj
で取得できる要素としては、下記のようなものがあります。
要素名 | 説明 |
---|---|
has_next | 現在より次のページが存在する場合True
|
has_previous | 現在より前のページが存在する場合True
|
has_other_pages | 現在とは他のページ(前or次)が存在する場合True
|
next_page_number | 次のページ番号 |
previous_page_number | 前のページ番号 |
ページネーション不要な際にUIを非表示にする
テンプレートへ自動で渡されるis_paginated
変数でページネーションの有無(オブジェクト数が分割数以下(全1ページ)の場合false
、2ページ以上になればtrue
)を取得できます。
これを利用してページネーションが不要な場合にif文を利用して表示しない対象のコードを囲む等でUIの表示切り替えができます。
{% if is_paginated %}
<!-- ページネーションのUIをここに書く -->
{% endif %}
はみ出し数の指定する
使いどころの難しい機能ですが、クラス変数としてpaginate_orphans
を指定するとページネーションにより分割した際に、最後となるページで中途半端にはみ出してしまう場合に最終ページだけ例外的に分割数を超えて余剰分を表示できます。
例えば、総数が17個で分割数が5の場合、通常は4ページ構成(5-5-5-2)となりますが、はみ出し数を3とすると、最終ページで最大+3個まで分割数を超えることができるようになり、3ページ構成(5-5-7)となります。
from django.views.generic import ListView
from .models import Article
class ArticleListView(Listview):
template_name = 'article_list.html'
model = Article
pagenate_by = 5 # モデルの分割数
paginate_orphans = 3 # はみ出し数指定を追加
ページ番号のGETパラメータ名を変更する
ページ数指定の受け渡し時のGETパラメータ名としてpage
を利用していましたが、これを任意の名前に変更することもできます。
パラメータ名を変更する場合は、クラス変数page_kwarg
に使用したいGETパラメータ名を指定します。
from django.views.generic import ListView
from .models import Article
class ArticleListView(Listview):
template_name = 'article_list.html'
model = Article
pagenate_by = 5
page_kwarg = 'result' # パラメータ名を変更
参考
Pagination | Django documentation | Django
https://docs.djangoproject.com/ja/3.2/topics/pagination/#paginating-a-listview
Page class | Django documentation | Django
https://docs.djangoproject.com/ja/3.2/ref/paginator/#page-class