This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

More than 3 years have passed since last update.

[Day 27]ページネーションを使う

Last updated at Posted at 2021-02-05

February 5, 2021
←前回:Day 26 検索画面の制作

「Djangoを学びたい」とのことでありましたら[Day 1]Djangoの開発環境から読むことをおすすめします。

はじめに

今回はWEBアプリでつきもののページネーションについて触れていきます。Djangoでは標準で手軽に使えるページネーション機能が組み込まれています。

クラスベースビューでページネーションを使う

ページネーションは関数ベースでもクラスベースビューでも使えるのですが、クラスベースビューで使用したほうが楽なので、まずそちらから紹介します。手軽なのはListViewを継承する方法です。ちょうど、thread/views.pyのCategoryViewクラスがListViewを継承したクラスですのでこれにページネーションを適用することを考えてみましょう。

まずページネーションを表示するためのテンプレートを用意しましょう。templates/base/pagination.htmlを作成します。

temlates/base/pagination.html

{% if is_paginated %}
<div class="ui basic segment center aligned">
    <div class="ui pagination menu">
    <!--左矢印-->
    {% if page_obj.has_previous %}
        <a class="item" href="?p={{page_obj.previous_page_number}}"><i class="chevron left icon"></i></a>
    {% else %}
        <a class="disabled item"><i class="chevron left icon"></i></a>
    {% endif %}
    <!--//左矢印-->
    <!--ページ番号-->
    {% for link_page in page_obj.paginator.page_range %}
        {% if link_page == page_obj.number %}
            <a class="disabled item">{{link_page}}</a>
        {% else %}
            <a class="item" href="?p={{link_page}}">{{link_page}}</a>
        {% endif %}
    {% endfor %}
    <!--//ページ番号-->
    <!--右矢印-->
    {% if page_obj.has_next %}
        <a class="item" href="?p={{page_obj.next_page_number}}"><i class="chevron right icon"></i></a>
    {% else %}
        <a class="disabled item"><i class="chevron right icon"></i></a>
    {% endif %}
    <!--//右矢印-->
    </div>
</div>
{% endif %}

テンプレートに渡されたpage_objというパラメータで組み立てていきます。page_objはその名のとおりPageオブジェクトです。pageオブジェクトはpaginatorから生成されますが、ページングに関する様々な情報を備えています。詳細は公式ドキュメントのページネーションを一読することをオススメします。pageオブジェクトはインスタンス変数としてページ番号、pagenatorオブジェクト、オブジェクトリストを保有しており、この情報を基に、次にページがあるか、ないかなど情報を取得するインターフェースを備えています。今回はpageオブジェクトのインスタンス変数であるpagenateオブジェクトのpage_rangeを呼び出してループしています。

※実はListViewを使用した場合はpaginatorオブジェクトは別に渡されているので、そちらを使っても構わないのですが、次に扱う関数ベースのビューの場合のため今回はpageオブジェクトから情報を全て取得します。

この段階ではまだ良くわからないと思います。後半の関数ベースでの処理まで見て再度見直すとテンプレートの意味が何となく見えてくると思います。

次にこのpagination.htmlをcategory.htmlでインクルードしましょう。

templates/thread/category.html

  <div class="ui segment">
      <div class="content">
          <div class="header"><h3>{{category.name}}</h3></div>
+         {% include 'base/pagination.html' %}
          <div class="ui divided items">
              {% if topic_list %}
              {% for topic in topic_list %}

単順に見出しの下にインクルードしただけです。

ではthread/views.pyのCategoryViewを修正しましょう。

thread/views.py

  class CategoryView(ListView):
      template_name = 'thread/category.html'
      context_object_name = 'topic_list'
+     paginate_by = 1 # 1ページに表示するオブジェクト数 サンプルのため1にしています。
+     page_kwarg = 'p' # GETでページ数を受けるパラメータ名。指定しないと'page'がデフォルト

      def get_queryset(self):
          return Topic.objects.filter(category__url_code = self.kwargs['url_code'])

      def get_context_data(self):
          ctx = super().get_context_data()
          ctx['category'] = get_object_or_404(Category, url_code=self.kwargs['url_code'])
          return ctx

修正はこれだけです。実はListViewはBaseListViewを継承したクラスなのですが、このクラスはMultipleObjectMixinという複数のオブジェクトを表示する機能を持ったクラスを継承しており、MultipleObjectMixinがページネーション機能を有しているためクラス変数を指定するだけで使えたのです。尚、paginate_byには1ページに表示するオブジェクトの数、page_kwargsはGETで受けるページのパラメータ名でありデフォルトは’page’です。自作のクラスベースビューにListViewのようなページネーション機能を持たせる場合にはMultipleObjectMixinを継承させると機能を付与することができます。

では、表示して見ましょう。
image.png

image.png

関数ベースのビューでパージネーションを使う

クラスベースビューでは予め用意されたクラス変数をオーバライドすれば良かったので楽でした。ただし何が行われているのかが見えづらくて分かりづらい部分もあったと思います。そこで関数ベースのビューで同じ挙動をするビューを作成してみることにします。先程はMultipleObjectMixinが自動で行っていた部分を自分で書いていきます。

thread/view.py

from django.core.paginator import Paginator, PageNotAnInteger, EmptyPage
def show_catgegory(request, url_code):
    if request.method == 'GET':
        page_num = request.GET.get('p', 1)
        pagenator = Paginator(
            Topic.objects.filter(category__url_code=url_code),
            1 # 1ページに表示するオブジェクト数
        )
        try:
            page = pagenator.page(page_num)
        except PageNotAnInteger:
            page = pagenator.page(1)
        except EmptyPage:
            page = pagenator.page(pagenator.num_pages)

        ctx = {
            'category': get_object_or_404(Category, url_code=url_code),
            'page_obj': page,
            'topic_list': page.object_list, # pageでもOK
            'is_paginated': page.has_other_pages,
        }
        return render(request, 'thread/category.html', ctx)

thread/urls.pyも修正します。

thread/urls.py

  urlpatterns = [
      path('create_topic/', views.TopicCreateView.as_view(), name='create_topic'),
      path('/', views.TopicViewAndCommentCreateView.as_view(), name='topic'),
-     path('category/<str:url_code>/', views.CategoryView.as_view(), name='category'),
+     path('category/<str:url_code>/', views.show_catgegory, name='category'),
  ]

見た目は全く変わらないので画像は省略します。恐らくこちらの方が分かりやすいという方が多いと思います。先程見たListViewの継承クラスではpage_obj, is_paginatedは自動で渡されていたのです。また、topic_listも区切りの数字に併せて調整されていましたが、これもpageのobject_listを渡すことで対応しています。

おわりに

最近、朝早く起きることができています。
この投稿を始めた時から早起きをしているので、今日で27日目ですね。
早いと言っても、はじめの方は8時で最近は7時にシフトしている感じです。
朝がいいかは人それぞれですが、人間の大半は朝型と言われているので、朝に活動することはおすすめです。
直近の目標は必ず7:00に起きれるようになることです。
応援お願いしますw

それではまたまた

←前回:Day 26 検索画面の制作
→次回:Day 28 サイトマップを作成する

0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up