2019/07/09
Django【Django】ページング入門
Webサービスでは、コンテンツの量が多い場合には複数のページに分けてコンテンツを表示することが多いです。
この複数のページに分ける処理をページング(ページネーション)といい、Djangoにもそれをサポートする機能が実装されています。
この記事では、Djangoでのページングの基本から、UI別の実装サンプルを紹介します。
Djangoでのページング基礎
Djangoではdjango.core.paginatorというモジュールにPaginator
クラスがあります。Paginator
クラスを使ってページング処理を実装します。
Paginatorの基本をコンソールで確認する
Paginator
クラスには、分割したいオブジェクトのリストと1ページに表示するコンテンツの個数を指定してイニシャライズします。
以下の例では、5つの要素を持ったリストを、1ページあたり2つの要素を持つように指定したPaginator
インスタンスを作成しています。
console
>>> from django.core.paginator import Paginator
>>> objects = ['Hello', 'this', 'is ', 'Django', 'Brothers']
>>> p = Paginator(objects, 2)
Paginator
インスタンスや、そこから取得できるPage
オブジェクトには様々なメソッドが用意されています。
console
# 全体のページ数を取得する
>>> p.num_pages
3
# コンテンツの量を取得する
>>> p.count
5
# 2ページ目を取得する(Pageオブジェクト)
>>> page2 = p.page(2)
>>> page2
<Page 2 of 3>
# 2ページ目のコンテンツを表示する
>>> page2.object_list
['is ', 'Django']
# 前のページ(1ページ目)、次のページ(3ページ目)、他のページの存在の確認
>>> page2.has_previous()
True
>>> page2.has_next()
True
>>> page2.has_other_pages()
True
# 前と後のページ番号の取得
>>> page2.previous_page_number()
1
>>> page2.next_page_number()
3
Paginator
のインデックスは1から始まるという点に注意しましょう。プログラミングでは0からインデックスが始まることが多いと思いますが、ページを数えるときは0からではなく1から数えるのが普通ですので、こちらの方が直感的かと思います。
また、オブジェクトのリストは、Pythonのリストやタプルだけでなく、DjangoのQuerySetなどを取ることができます。正確には、count()
メソッドか__len__()
メソッドを持つどんなオブジェクトも与えることができます。
そのため、Viewから特定のモデルのQuerySetを取得して、それをテンプレートで表示する、といった実装はよく利用します。
Viewでページングを利用する
DjangoのViewでページングを実装する方法です。このサンプルでは、Article
というモデルのリストを取得し、それをページに表示します。
views.py
from django.core.paginator import Paginator
from django.shortcuts import render
from .models import Article
def index(request):
all_articles = Article.objects.all()
paginator = Paginator(all_articles, 10) # 1ページに10件表示
p = request.GET.get('p') # URLのパラメータから現在のページ番号を取得
articles = paginator.get_page(p) # 指定のページのArticleを取得
return render(request, 'index.html', {'articles': articles})
次に表示するページの番号は、URLで指定されます。
例えば、sample.com/articles/?p=3
のように指定すると、Article一覧ページの3ページ目を指定することになります。このp=3
の数値をrequest.GET.get('p')
で取得しています。
また、ポイントは、Paginator
のget_page()
メソッドです。
ページングの基本を紹介した際にはpage()
メソッドを使ってPage
オブジェクトを取得していましたが、get_page()
メソッドでは不適切なページ数の指定もうまく処理してくれます。
例えば、指定されたURLが、sample.com/articles/?p=aaa
のように、pパラメータが数字じゃない場合には最初のページを、数値がページの範囲外(マイナスの数字や全体のページ数より大きい数字)の場合には最後のページを返してくれます。
続いて、これに対応するテンプレートも実装します。やっていることはPaginator
の各メソッドにアクセスしているだけです。
index.html
<!-- コンテンツの表示(通常のモデルオブジェクトとして扱える) -->
{% for article in articles %}
{{ article.title }}<br>
{% endfor %}
<div class="pager">
<!-- 前のページへのリンク -->
{% if articles.has_previous %}
<a href="?p={{ articles.previous_page_number }}">前へ</a>
{% endif %}
<!-- 現在のページ番号と全体のページ数 -->
<span>
{{ articles.number }} / {{ articles.paginator.num_pages }}
</span>
<!-- 次のページへのリンク -->
{% if articles.has_next %}
<a href="?p={{ articles.next_page_number }}">次へ</a>
{% endif %}
</div>
Bulmaを使ったサンプル
人気のCSSフレームワークのBulmaにもページャーのUIがあります。 こんな感じのページャーが表示されます。
以下がコードサンプルです。
index.html
<div class="paginator">
<nav class="pagination is-centered" role="navigation" aria-label="pagination">
{% if articles.has_previous %}
<a class="pagination-previous" href="?p={{ articles.previous_page_number }}">Previous</a>
{% endif %}
{% if articles.has_next %}
<a class="pagination-next" href="?p={{ articles.next_page_number }}">Next</a>
{% endif %}
<ul class="pagination-list">
{% if articles.has_previous %}
{% if articles.previous_page_number != 1 %}
<li><a class="pagination-link" href="?p=1">1</a></li>
<li><span class="pagination-ellipsis">…</span></li>
{% endif %}
<li><a class="pagination-link" href="?p={{ articles.previous_page_number }}">{{ articles.previous_page_number }}</a></li>
{% endif %}
<li><a class="pagination-link is-current" href="?p={{ articles.number }}" >{{ articles.number }}</a></li>
{% if articles.has_next %}
<li><a class="pagination-link" href="?p={{ articles.next_page_number }}">{{ articles.next_page_number }}</a></li>
{% if articles.next_page_number != articles.paginator.num_pages %}
<li><span class="pagination-ellipsis">…</span></li>
<li><a class="pagination-link" href="?p={{ articles.paginator.num_pages }}">{{ articles.paginator.num_pages }}</a></li>
{% endif %}
{% endif %}
</ul>
</nav>
</div>