LoginSignup
0
0

【Django】DetailViewでページネーションを実装する方法

Posted at

はじめに

Djangoでページネーション を実装する際、ListViewでは標準でページネーション機能がサポートされており、クラス変数paginate_byに1ページあたりに表示するデータの数を指定するだけで、テンプレートはページごとに分割されたデータを格納した変数を受け取ることができます。そして、テンプレートではpage_objオブジェクトの属性やメソッドを利用して前後のページのリンク生成や表示を行うことができます。

class DiaryListView(generic.Listview):
    model = Diary
    template_name = 'diary_list.html'
    paginate_by = 6 #1ページあたり6件のデータを表示

    #Diaryモデルの全件をクエリーセットに設定
    def get_queryset(self):
        diaries = Diary.objects.all()
        return diaries

page_objオブジェクトの利用例
スクリーンショット 2023-07-18 18.10.17.png
引用)【Django入門12】ページネーションの基本

しかし、ユーザーのプロフィールページに、そのユーザーが作成した記事一覧を併せて表示させたい場合などDetailViewを継承したビューでページネーションを実装する場合はListViewとは比べて少し複雑な手順が必要になります。

実装する方法

結論から述べると、MultipleObjectMixinクラスを継承させたDetailViewを作成するか、MultipleObjectMixinを使わずにget_context_data()のオーバーライドでPaginator()コントラクタの実行をする方法があります。

サンプルコード

今回は会員登録制の記事公開アプリで、ユーザーページ(DetailView)にそのユーザーが作成した記事一覧をページネーション表示するケースを想定します。

Modelはユーザー情報に関するCustomUserモデルと記事情報に関するArticleモデルを用意します。

models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.utils.translation import gettext_lazy as _

class CustomUser(AbstractUser):
    """拡張ユーザーモデル"""

    username = models.CharField(
        _("username"),
        max_length=30,
        help_text='Required 30 characters or fewer.',
        unique=True,
        error_messages={
            'unique': _("This Username already exists."),
        },)
    
    class Meta:
        verbose_name_plural = 'CustomUser'

class Article(models.Model):

    post_user = models.ForeignKey(CustomUser, verbose_name='Post User', on_delete=models.CASCADE, related_name='name',)
    title = models.CharField(verbose_name='title', max_length=50,)
    content = models.TextField(verbose_name='content', )
    created_at = models.DateField(verbose_name='created_at', auto_now_add=True,)
    updated_at = models.DateField(verbose_name='updated_at', auto_now=True)

    class Meta:
        verbose_name_plural = 'Article'

    def __str__(self):
        return self.title

また、テンプレートのページネーション処理部は下記のようにします。

user.html
<!--ページネーション処理-->
    {% if is_paginated %}
    <div class="row justify-content-between align-items-center mb-4">
      <div class="col-lg">
        <nav aria-label="Bootstrap Pagination Example">
          <ul class="pagination mb-0">
            <!--前ページへのリンク-->
            {% if page_obj.has_previous %}
            <li class="page-item">
              <a class="page-link" href="?page={{ page_obj.previous_page_number }}">
              <span class="mr-1 d-none d-sm-inline-block">&larr;</span> Previous
              </a>
            </li>
            {% endif %}

            <!--ページ数表示-->
            {% for page_num in page_obj.paginator.page_range %}
              {% if page_obj.number == page_num %}
                <li class="page-item active">
                  <a class="page-link" href="#">{{ page_num }}</a>
                </li>
              {% else %}
                <li class="page-item">
                  <a class="page-link" href="?page={{ page_num }}">{{ page_num }}</a>
                </li>
              {% endif %}
            {% endfor %}

            <!--次ページへのリンク-->
            {% if page_obj.has_next %}
              <li class="page-item">
                <a class="page-link" href="?page={{ page_obj.next_page_number }}">
                  Next<span class="ml-1 d-none d-sm-inline-block">&rarr;</span>
                </a>
              </li>
            {% endif %}
          </ul>
        </nav>
      </div>
    </div>
    {% endif %}

MultipleObjectMixinクラスを継承する場合

MultipleObjectMixinクラスは、オブジェクトのリストを表示する時に使うMixinクラスです。特に重要なのが、DetailViewと一緒にこのクラスを継承することで、object_listis_paginatedpaginatorpage_objといったcontextを扱えるようになります。

views.py
from django.views import generic
from django.views.generic.list import MultipleObjectMixin

class UserPageView(MultipleObjectMixin ,generic.DetailView):
    template_name = 'user.html'
    model = CustomUser
    paginate_by = 6
    
    def get_context_data(self, **kwargs):
        object_list = Article.objects.filter(post_user=self.get_object())
        context = super(UserPageView, self).get_context_data(object_list=object_list, **kwargs)

        return context

MultipleObjectMixinを継承したことで、ListViewと同様に**クラス変数paginate_byを指定することでPaginatorクラスやPageクラスを扱えるようになります。あとはobject_listに表示させたいクエリーセットを指定してコントラクタを立ち上げるだけです。

MultipleObjectMixinクラスを使わない場合

views.py
from django.views import generic
from django.core.paginator import Paginator

class UserPageView(generic.DetailView):
    template_name = 'user.html'
    model = CustomUser
    
    def get_context_data(self, **kwargs):
        context = super(UserPageView, self).get_context_data(**kwargs)
        activities = self.get_related_activities()
        context['article_list'] = activities
        context['page_obj'] = activities
        return context

    def get_related_activities(self):
        queryset  = Article.objects.select_related('post_user').filter(post_user=self.object).order_by('-created_at')
        paginator = Paginator(queryset, 6) #ページ分割
        page = self.request.GET.get('page') #クエリパラメータからページ番号取得
        activities = paginator.get_page(page) #取得したページ番号のページ取得

        return activities

大きな特徴はPaginator()コントラクタを立ち上げるために、独自のクラスメソッドget_related_activities()を定義している点です。

ここでは

1.クエリーセットの設定
2.Paginator()インスタンスを作成し、ページを分割
3.クエリパラメーターから分割されたページ番号を取得
4.取得した番号のページ取得

という順番で処理を行っています。そしてget_context_data()のオーバーライドでget_related_activities()を呼び出し、contextに設定することで、テンプレートでページング処理を行えるようになります。

まとめ

・DetailViewでページネーションを実装するにはMultipleObjectMixinを継承する方法と、継承せずに独自にページング処理を行うクラスメソッドを定義する2種類の方法がある。
・前者の方法はコード量が少なく、後者の方法は直感的に何をやっているか分かりやすい

参考

Stackoverflow

0
0
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
0
0