Django でページング

よくある話、ページングしたかった。で、実際そういう機能がある。前は djang-pagination っていうのをつかったことがあったけど 1.3 くらいから使えなくなったとか、また、まあ別にそんなに難しい話でもなさそうだったので普通にフレームワークの機能で実装した。

日本語 1.0::ペジネータ (paginator) — Django v1.0 documentation
公式 1.5::Pagination | Django documentation | Django

最初から英語でみてたけど、英語のほうがサンプルコードが充実していてよい。

概要

  • Paginator クラスにクエリセットとページ指定をしてインスタンスにして
  • paginator.page(page)で実際のページを表示させる

基本はこれだけ。

ちなみに

以下にかくことをそれっぽくいい感じにお手軽に提供してくれるのが django.views.generic.ListView (class base view) ですね

サンプル

公式にあるサンプルに自分の理解でコメントをつける。

# 必用なものをとりこむ
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger

def listing(request):
    # ページング対象にしたいオブジェクトをすべて取得しておく
    contact_list = Contacts.objects.all()
    # Paginator でインスタンスにする。この場合 25 件づつページングする
    paginator = Paginator(contact_list, 25)

    # クエリストリングで page の値を受け取る
    page = request.GET.get('page')
    try:
        contacts = paginator.page(page)
    except PageNotAnInteger:
        # int じゃなかったらとりあえず 1 ページ目に返す
        contacts = paginator.page(1)
    except EmptyPage:
        # 範囲外ならこの場合最後に飛ばす
        contacts = paginator.page(paginator.num_pages)
    # 辞書で渡してやる
    return render_to_response('list.html', {"contacts": contacts})

素の render_to_response だと RequestContext をつつんでくれないかもだからそこらへんは自分でよしなにする。今回は Class Base View で DetailView とかの中で get_context_data で context に上書きするような形にしていたので特に意識しなかった。

で、テンプレート

<!-- 普通に for で回して表示させる -->
{% for contact in contacts %}
    {# Each "contact" is a Contact model object. #}
    {{ contact.full_name|upper }}<br />
    ...
{% endfor %}

<!-- ページングの処理 -->
<div class="pagination">
    <span class="step-links">
        {% if contacts.has_previous %}
            <!-- ここでクエリストリングにページをわたしてやる。 -->
            <a href="?page={{ contacts.previous_page_number }}">previous</a>
        {% endif %}

        <span class="current">
            Page {{ contacts.number }} of {{ contacts.paginator.num_pages }}.
        </span>

        {% if contacts.has_next %}
            <a href="?page={{ contacts.next_page_number }}">next</a>
        {% endif %}
    </span>
</div>

難しいことはやってないんだけどもう一度いうと

  • paginator オブジェクトがページング全体の情報を持っている
  • pages = paginator.page(page) みたいにして pages オブジェクトにオブジェクトの配列にして返してやってつかったりする
  • そのために ?page= というように request.GET の辞書につめこんでいる

という流れになっている。なので、 urls.py とかでうけとってよしなにしようとか paginator オブジェクトの中とは別で pages でオブジェクトを生成する必要があるっていうあたりがハマりどろだった。

まあ

思えばはじめて Django というか Web アプリケーションフレームワークさわったちょうど2年前くらいはページングの実装とか自分でがんばってたなーとかうまくいかないなーとか*1、思い出した。素直にドキュメント読んで使えるレベルになっただけ、2年前とは変わったのかなぁ、なんて。

*1:それで django-pagination をつかった