DjangoBrothers BLOG ✍️

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')で取得しています。

また、ポイントは、Paginatorget_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">&hellip;</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">&hellip;</span></li>
          <li><a class="pagination-link" href="?p={{ articles.paginator.num_pages }}">{{ articles.paginator.num_pages }}</a></li>
        {% endif %}
      {% endif %}
    </ul>
  </nav>
</div>