2021.06.27  

【Django】ページング処理をgeneric.ListViewを使わずに実装する(paginate_by)

Django,  Python    

Djangoでページング処理を実装するには、views.pyにgeneric.ListViewのpaginate_byを使用すれば簡単に実装できます。

しかし、新機能実装の都合上generic.ListViewが使えなかったため(※)、ListViewなしでページング処理を実装する方法をメモ書きします。

※ returnにrenderを使用したかった。今回の課題はpaginate_byとrenderの併用です。

ページング処理とは

テンプレートでリストに定義した一覧をfor文で表示する際に、全てのリストを同じページに表示したくない時などに利用します。(ブログの記事一覧など)

表示しなかったリストは、ページングの[ 1 2 3 4 5...]といった表示の数字をクリックすると後続のリストを表示できます。

実装例

generic.ListViewを利用した実装例

generic.ListViewを利用する場合は、views.pyにこれだけ定義すればページング処理を利用できるようになります。

views.py
from django.views import generic
from .models import Post

class IndexView(generic.ListView):
    model = Post
    paginate_by = 10  # ページング処理。リスト中の10行を表示する。


関連ファイルのコード

以下はページング処理を実装するために必要な関連ファイルのコードです。

generic.ListViewを使わずにページング処理を実装する場合、上記のviews.pyとurls.pyの内容を書き換えます。他のファイルの内容は変わりません。

urls.py
from django.urls import path
from . import views

app_name = 'app'

urlpatterns = [
 # URLにhttp://127.0.0.1:8000/と入力するとviews.pyのIndexView関数に遷移する
    path('', views.IndexView.as_view(), name='index'),
]
models.py
from django.db import models

class Post(models.Model):
    # 記事タイトルカラム。今回はこちらに90件分のタイトルを登録済み
    title = models.CharField('タイトル', max_length=255)
post_list.html
<!-- Models.pyで定義しているPOSTのカラムtitleをレコード分だけ取り出す -->
{% for post in post_list %}
  <div>{{ post.title }}</div>
{% endfor %}
<br>

<!-- ページング処理の部分 -->
<!-- 「前へ」の部分 -->
{% if page_obj.has_previous %}
    <a href="?page={{ page_obj.previous_page_number }}">前へ</a>
{% endif %}

<!-- 数字部分 -->
{% for num in page_obj.paginator.page_range %}
    {% if page_obj.number == num %}
        <span>{{ num }}</span>
    {% else %}
        <a href="?page={{ num }}">{{ num }}</a>
    {% endif %}
{% endfor %}

<!-- 「次へ」 の部分 -->
{% if page_obj.has_next %}
    <a href="?page={{ page_obj.next_page_number }}">次へ</a>
{% endif %}
上記コードをブラウザに表示

generic.ListViewを利用しない実装例

今度は本題のgeneric.ListViewを利用しない実装例です。

views.pyを次のように書き換えます。

views.py
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.shortcuts import render
from .models import Post

def index_view(request):
    # models.pyからPostのすべてのデータを取得
    queryset = Post.objects.all()

    # paginate_byの実装
    # 1ページに表示させるtitle数を指定する
    count = 10
    # pagenateの実行
    paginator = Paginator(queryset, count)
    page = request.GET.get('page')

    try:
        page_obj = paginator.page(page)
    except PageNotAnInteger:
        page_obj = paginator.page(1)
    except EmptyPage:
        page_obj = paginator.page(paginator.num_pages)

    return render(request,
                     'app/post_list.html',
                     {'post_list': page_obj.object_list,
                      'page_obj': page_obj}
                     )
urls.py
from django.urls import path
from . import views

app_name = 'app'

urlpatterns = [
 # path部分を次のように書き換える
    path('', views.index_view, name='index'),
    # path('', views.IndexView.as_view(), name='index'),
]

解説

views.py
def index_view(request):
    # models.pyからPostのすべてのデータを取得
    queryset = Post.objects.all()

Post.objects.all()でPostに格納されている全てのデータを取り出し、querysetに格納しています。

querysetとは、Djangoが準備した型のことです。

pythonにはstr型やint型などの型がありますが、querysetはDjangoオリジナルの型です。

参考: querysetについて分かりやすく解説

表示するデータの順番を変えたい場合は以下のように記述することで、昇順、降順に並び替えることができます。

昇順の場合

queryset = Post.objects.order_by('title')

降順の場合

queryset = Post.objects.order_by('-title')

    # paginate_byの実装
    # 1ページに表示させるtitle数を指定する
    count = 10
    # pagenateの実行
    paginator = Paginator(queryset, count)
    page = request.GET.get('page')

count = 10に、1ページに表示させたいtitleの数を指定します。

それを先ほど取得したquerysetのデータを含め、Paginator関数に渡します。

Paginator関数ページング処理の肝となる部分です。querysetとページ数(int)を引数として利用します。

page = request.GET.get('page')は現在ブラウザに表示しているページ数を取得する処理です。

ページング処理を使用すると、URLに次のようなパラメータが付与されます。

?page={{ num }}

現在のページが7ページなら次のようになります。

http://127.0.0.1:8000/?page=7

上記の例でいうと、 request.GET.get('page')は「7」を取得してpageに格納しています。

    try:
        page_obj = paginator.page(page)
    except PageNotAnInteger:
        page_obj = paginator.page(1)
    except EmptyPage:
        page_obj = paginator.page(paginator.num_pages)

Paginatorインスタンスのpage関数を利用し、pageオブジェクトを取得しています。

始めにページへアクセスするときはパラメータ?page={{ num }}が存在しない、つまりpageに現在のページ(int)が格納次の部分が実行されます。

except PageNotAnInteger:
 page_obj = paginator.page(1)

この場合、Postモデルの1ページ目のデータがpage_objに格納されます。

2回目以降はtry部分が実行され、テンプレートから受け付けたページ数がpageに格納されてpaginator.page(page)が実行されます。

return render(request,
                   'app/post_list.html',
                 {'post_list': page_obj.object_list,
                 'page_obj': page_obj}
                 )

最後にrenderでpage_objのデータをpost_list.htmlに渡しています。

page_obj.object_listは、Postモデルの1ページ目のtitleのリストです。

page_objは、テンプレート側でページングを制御するのに必要な情報が入っています。

render()の部分に任意の「キー:値」を設定することで、ページング以外の情報をテンプレートに渡すことが可能です。(今回著者が一番やりたかったこと)

定義例
return render(request,
                   'app/post_list.html',
                 {'post_list': page_obj.object_list,
                 'page_obj': page_obj,
       # 追加でhogeリストをテンプレートへ渡している
                        'hoge_list': hoge_list} 
                 )

解説は以上です。


参考にさせて頂いたサイト

Django、ページング処理まとめ

コメント
現在コメントはありません。
コメントする
コメント入力

名前 (※ 必須)

メールアドレス (※ 必須 画面には表示されません)

送信