paginator機能の実装例
Webアプリ開発フレームワークDjangoの書籍1を勉強中です。 書籍の1章で説明されているコードSnippetの共有アプリ開発の実装を終え、演習も含めてアプリを作り終えました。 このアプリをベースに機能を追加して勉強を継続しています。
今回、paginator機能を追加したので、その内容をメモしています。 paginator機能とは、以下のように出力件数が多いとき、ページごとの出力件数を設定し、ページ間のリンクを生成する機能です。
機能追加前
Snippetの登録件数が10件なのでそれほど問題とはなりませんが、これが数百件となると見づらくなってきます。
機能追加後
paginator機能の動作検証のため、ページ当たりの表示件数を1件としています。
10件なので10ページに分けて表示されています。
登録件数が増えても見やすいレイアウトとなります。
ソースコード
Djangoのpaginator機能は、関数ベース・クラスベースどちらでも実装可能ですが、本記事ではクラスベースで実装し、ListViewクラスを用いています。 変更が必要なのはview.pyとHTML templateのみです。
view.py
追加前
クラス変数context_object_nameに何も設定しなければテンプレートではpage_objという変数にモデルクラスのインスタンス情報が格納されます。 ここでは、テンプレート側の可読性を上げるためにsnippetsという変数に格納するようにしています。
from django.views.generic import ListView from snippets.models import Snippet classTopView(ListView): template_name = "snippets/top.html" model = Snippet context_object_name = "snippets"# 変数名をpage_objから変更
追加後
クラス変数paginate_byに、1ページ当たりの表示件数を代入するだけでpaginator機能が使用可能です。 ここでは、ページ数のリストを自動生成するため、get_elided_page_rangeメソッドを用いています。 関数ベースの場合このメソッドは直接利用できるのですが、クラスベースでの利用方法で行き詰りました。 stack-over-flowを調べると、 get_context_dataメソッドをオーバーライドしてクラス変数contextにget_elided_page_rangeメソッドの結果を代入することで、 template内でpaginator_rangeという変数を使用できるようになります(contextのkeyによって変数名が決まる)。 引数on_each_sideとon_endsから隣接ページの表示件数を設定できます。 例えば、5ページが基準の場合、2件隣接まで表示するならば、..., 3, 4, 5, 6, 7, ...というリストが格納されています。
from django.views.generic import ListView from django.core.paginator import Page from snippets.models import Snippet classTopView(ListView): template_name = "snippets/top.html" paginate_by = 1# ページごとのsnippet表示件数 model = Snippet context_object_name = "snippets" ordering = "id"defget_context_data(self, *, object_list=None, **kwargs): context = super().get_context_data(**kwargs) page: Page = context['page_obj'] # get_elided_page_rangeの結果を、paginator_range変数から使用可能 context['paginator_range'] = page.paginator.get_elided_page_range( page.number, on_each_side=2, on_ends=1) return context
template
追加前
Snippetデータをテーブル形式で記載しています。
<tableclass="table"><thead><tr><th>投稿者</th><th>投稿日</th><th>タイトル</th></tr></thead><tbody> {% for snippet in snippets %} <tr><th>{{ snippet.created_by.username}}</th><th>{{ snippet.created_at}}</th><th><ahref="{% url 'snippet_detail' snippet.id %}">{{ snippet.title }}</a></th></tr> {% endfor %} </tbody></table>
追加後
変数snippetsに格納されるインスタンスは、paginator機能で自動で調整されるので、tableタグ内の変更は不要です。
is_paginatedは、ListViewクラスからテンプレートへ自動で渡される変数です。 ELLIPSIS(ここでは"...")による場合分けはあるものの、get_elided_page_rangeメソッドを使用したおかげで簡潔にページごとのリンクを張ることができます。 なお、CSSで使用するクラス名は、書籍にも解説のあるbootstrapで定義されているものを使用しています (Bootstrap 4 Cheat Sheet)。
<!-- tableタグ内は変更なし --><tableclass="table"><thead><tr><th>投稿者</th><th>投稿日</th><th>タイトル</th></tr></thead><tbody> {% for snippet in snippets %} <tr><th>{{ snippet.created_by.username }}</th><th>{{ snippet.created_at }}</th><th><ahref="{% url 'snippet_detail' snippet.id %}">{{ snippet.title }}</a></th></tr> {% endfor %} </tbody></table><!-- 以下を追加 --> {% if is_paginated %} <ulclass="pagination"> {% for i in paginator_range %} {% if page_obj.number == i %} <liclass="active page-item"><spanclass="page-link">{{ i }}</span></li> {% else %} {% if i == paginator.ELLIPSIS %} <liclass="page-item"><spanclass="page-link">{{ paginator.ELLIPSIS }}</span></li> {% else %} <liclass="page-item"><aclass="page-link"href="?page={{ i }}">{{ i }}</a></li> {% endif %} {% endif %} {% endfor %} </ul> {% endif %}
書籍
まず実際にWebアプリを作り、その後詳細な機能を解説する形式で、Djangoを学ぶことができます。 今後もこのアプリを拡張して学習してみたいと思います。