はじめに
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オブジェクトの利用例
引用)【Django入門12】ページネーションの基本
しかし、ユーザーのプロフィールページに、そのユーザーが作成した記事一覧を併せて表示させたい場合などDetailViewを継承したビューでページネーションを実装する場合はListViewとは比べて少し複雑な手順が必要になります。
実装する方法
結論から述べると、MultipleObjectMixin
クラスを継承させたDetailViewを作成するか、MultipleObjectMixin
を使わずにget_context_data()
のオーバーライドでPaginator()
コントラクタの実行をする方法があります。
サンプルコード
今回は会員登録制の記事公開アプリで、ユーザーページ(DetailView)にそのユーザーが作成した記事一覧をページネーション表示するケースを想定します。
Modelはユーザー情報に関するCustomUser
モデルと記事情報に関するArticle
モデルを用意します。
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
また、テンプレートのページネーション処理部は下記のようにします。
<!--ページネーション処理-->
{% 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">←</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">→</span>
</a>
</li>
{% endif %}
</ul>
</nav>
</div>
</div>
{% endif %}
MultipleObjectMixinクラスを継承する場合
MultipleObjectMixin
クラスは、オブジェクトのリストを表示する時に使うMixinクラスです。特に重要なのが、DetailViewと一緒にこのクラスを継承することで、object_list
、is_paginated
、paginator
、page_obj
といったcontextを扱えるようになります。
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クラスを使わない場合
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種類の方法がある。
・前者の方法はコード量が少なく、後者の方法は直感的に何をやっているか分かりやすい