Quantcast
Channel: プログラミング
Viewing all articles
Browse latest Browse all 8646

Djangoでのpaginator機能の追加 - Engineer's Memorandum

$
0
0

paginator機能の実装例

Webアプリ開発フレームワークDjangoの書籍1を勉強中です。 書籍の1章で説明されているコードSnippetの共有アプリ開発の実装を終え、演習も含めてアプリを作り終えました。 このアプリをベースに機能を追加して勉強を継続しています。

今回、paginator機能を追加したので、その内容をメモしています。 paginator機能とは、以下のように出力件数が多いとき、ページごとの出力件数を設定し、ページ間のリンクを生成する機能です。

機能追加前

Snippetの登録件数が10件なのでそれほど問題とはなりませんが、これが数百件となると見づらくなってきます。

paginator機能実装前

機能追加後

paginator機能の動作検証のため、ページ当たりの表示件数を1件としています。 10件なので10ページに分けて表示されています。 登録件数が増えても見やすいレイアウトとなります。

paginator機能実装後

ソースコード

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を学ぶことができます。 今後もこのアプリを拡張して学習してみたいと思います。


  1. 芝田 将 "実践DjangoPythonによる本格Webアプリケーション開発"

Viewing all articles
Browse latest Browse all 8646