2-6. ページネーションを使う

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

※本ページは検索画面を作るまで読まれた方を対象としています。そのためサンプルソースコードが省略されている場合があります。

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

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

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

templates/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を継承させると機能を付与することができます。

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

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

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

thread/views.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)

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

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

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

最後に

クラスベースビューはコード量が少なく自動でいろいろやってくれる分、フレームワークで何をやっているのかが分かりづらい面もあります。時にはDjangoのソースを追って関数ベースビューで書き直してみてみると勉強にもなるんじゃないかな・・・と思っていますが忙しい方には大変ですね。このサイトがお役に立てるよう頑張ります。次は心機一転でサイトマップを作ってみます。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です