このページでは、Django におけるページネーションについて解説していきます。
Contents
ページネーションとは
ウェブアプリにおけるページネーションとは、一覧表・一覧リストなどで表示するオブジェクトが多い場合、それを複数のページに分割して表示し、さらに各ページへのリンクを設置することになります。
例えば、Google の検索結果はページネーションの例の1つとなります。Google 検索でキーワードを入力すれば大量の検索結果が得られることになります。ですが、1つのページに全ての結果が表示されるのではなく、複数のページに分割して検索結果が表示されるようになっています。また、検索結果の他のページに簡単に遷移できるよう、他のページへのリンクも設置されています。これにより、検索結果が見やすくなってユーザーにとって使いやすいウェブアプリとなっています。
このように、量の多いオブジェクトを複数のページに分けて表示し、さらに他のページへのリンクを設置することをページネーションと呼びます。
特に、Django ではデータベースに保存されているレコードの一覧を表示するような際にページネーションが利用されることが多いです。Django においてレコードとはモデルのインスタンスのことになりますので、つまりは Django におけるページネーションとはモデルのインスタンスの一覧表・一覧リストを分割して表示する機能と考えて良いと思います。
このページネーションを実現する上では、まずページの分割が必要になります。また、複数のページに分割されるため、前後のページへの遷移を行うためのリンクの設置なども必要になります。そして、これらを実現するのが Django フレームワーク用意された Paginator
と Page
クラスになります。
ページネーションの実現方法
次は、ページネーションの実現方法・実現手順について解説していきます。
Django においては、前述の通りページネーションは Paginator
というクラスと Page
というクラスの2つを利用して実現することになります。
Paginator
のスペルに注意してください
4文字目は e
ではなく i
となっています
Page
の4文字目は e
となりますが、Paginator
や Pagination
の場合は4文字目が i
となります
基本的には Paginator
に関してはビューから、Page
に関してはテンプレートから利用することになります。
前回と前々回の連載の中で管理画面について説明しましたが、実はこの管理画面でも Paginator
や Page
が利用されてページネーションが実現されています。
簡単に言えば、Paginator
はページの分割や分割後の全ページを管理するクラスになります。そして、Page
は分割後の1つのページを管理するためのクラスになります。Page
からは前後のページ等の情報が取得できるため、この取得した情報を利用すれば前後のページへのリンクの設置なども簡単に実現できます。
スポンサーリンク
ページネーションを実現する流れ
続いて、ページネーションを実現する流れを解説しておきます。
ページネーションを利用しない場合の処理の流れ
まず、おさらいの意味でページネーションを利用しない場合の処理の流れについて説明しておきます。この場合の処理の流れは下の図の通りとなります。
ページネーションを利用しない場合、モデル(データベース)から取得したモデルクラスのインスタンスの集合を全てテンプレートに渡し、テンプレートに渡されたインスタンスの情報を全て表示することになります。そのため、基本的にはモデルから取得されたインスタンスの数が多くても、1つのページに全て表示されることになります。
ページネーションを利用する場合の処理の流れ
それに対し、ページネーションを利用する場合の処理の流れは下の図のようになります。
モデル(データベース)からモデルクラスのインスタンスの集合を取得するという点は同じですが、その集合がビューから Paginator
に渡されることで、それらのインスタンスの情報を表示するページの分割が行われ、各ページ(Page
)に表示するインスタンスの割り付けが行われます。
さらに、ビューから分割後の特定のページを取得し、そのページ(Page
)をテンプレートに渡すことで、そのページに割り付けられたインスタンスの情報のみが1つのページに表示されることになります。
また、前後のページへの遷移を実現するためにページ内へのリンクの設置が必要となりますが、その前後のページのページ番号等の情報はビューより受け取った Page
からテンプレートが取得し、テンプレートが取得したページ番号に基づいてリンクとして表示を行うことになります。Page
からは前後のページのページ番号だけでなく、様々な情報をが取得できるため、それを利用すればいろんなページ表示を実現することも可能です。
特に、今までのようにページネーションを利用していなかったときに比べて、ビューやテンプレートから Paginator
や Page
を利用して処理を行うところが新たに実装が必要になる部分となりますので、ここからはこれらの処理に焦点を当てて解説を行なっていきます。具体的には下記の3つについて説明していきます。
- ページを分割する
- 特定のページを取得する
- ページの情報を取得する
このページでは、上記で示した流れに基づいて解説を行なっていきますが、もっと別の実装でもページネーションを実現することは可能です。例えば、ビューから Page
の情報を取得し、その取得したデータをテンプレートに渡してやるような実装もあり得ます。
ページを分割する
まずは、ページを分割する処理について解説していきます。前述の通り、このページの分割はビューから Paginator
を利用して実現していくことになります。
Paginator
のコンストラクタの実行
Paginator
を利用する際には、まず Paginator
のコンストラクタを実行してインスタンスの生成を行います。Paginator
のコンストラクタには、下記のように4つの引数を指定することが可能であり、object_list
と per_page
の引数指定は必須となります。
from django.core.paginator import Paginator
paginator = Paginator(object_list, per_page, orphans, allow_empty_first_page)
object_list
引数には表示したい全てのモデルのインスタンスの集合を指定します。基本的には、データベースから取得したクエリーセットを指定することになると思います。
さらに、per_page
引数には1つのページに表示したいインスタンスの個数を整数で指定します。
これらを指定して Paginator
のコンストラクタを実行すれば、object_list
で与えられたクエリーセットの要素が per_page
個ごとに分割され、別々のページに割り付けられることになります。
例えば、object_list
引数に指定したクエリーセットのインスタンスの数が 32
で per_page
に 10
を指定した場合、下記のように各ページにインスタンスが分割されて割り付けられることになります。
- 1ページ目:
object_list[0:10]
(0
〜9
個目のインスタンス) - 2ページ目:
object_list[10:20]
(10
〜19
個目のインスタンス) - 3ページ目:
object_list[20:30]
(20
〜29
個目のインスタンス) - 4ページ目:
object_list[30:32]
(30
〜31
個目のインスタンス)
このように、Paginator
のコンストラクタを実行すれば、本来1ページに全て表示されていた object_list
が複数のページに割り付けられることになります。つまり、Paginator
のコンストラクタの実行によりページの分割が行われることになります。そして、返却値として得られる Paginator
のインスタンスが、その分割後のページの情報を持っていることになります。
orphans
引数
さて、先ほど Paginator
のコンストラクタの引数には object_list
と per_page
のみを指定しましたが、他にも orphans
引数と allow_empty_first_page
引数も指定可能ですので、これらの引数について説明をしておきます。
1つ目の orphans
引数はちょっとややこしいです。まず orphans
は孤児という意味の単語であり、orphans
引数で指定した整数以下の個数のインスタンスは孤児であるとみなされ、これらのインスタンスは他のページに合流させて表示されることになります。
もう少し具体的に説明すると、ページの分割を行うと最後のページだけ表示されるインスタンスの数が中途半端になることがあります。例えば、前述の例では最後のページに 2
つのインスタンスのみが表示されることになります。こういった最後のページに表示されるインスタンスの数が少ない場合、つまり孤児であるとみなされる場合に、それらのインスタンスを前のページにまとめて表示させるというのが orphans
引数の効果となります。
前述の例の場合、orphans
引数に 2
を指定しておけば、ページに表示されるインスタンスの数が 2
以下の場合にそれらのインスタンスは前のページに含めて表示されることになります。つまり、下記のようにページの表示が行われることになります。
- 1ページ目:
object_list[0:10]
(0
〜9
個目のインスタンス) - 2ページ目:
object_list[10:20]
(10
〜19
個目のインスタンス) - 3ページ目:
object_list[20:32]
(20
〜31
個目のインスタンス)
orphans
引数を指定しなかった場合、orphans
にはデフォルト値の 0
が設定されることになります。つまり、最後のページに表示されるインスタンスが 1
であったとしても、そのインスタンスは個別に最後のページに表示されることになります。
allow_empty_first_page
引数
allow_empty_first_page
引数は、その名の通り、最初のページに表示するインスタンスの数が 0
個であることを許可する(True
)or 許可しない(False
)を指定する引数になります。デフォルトは True
です。
False
を指定した場合、最初のページに表示するインスタンスの数が 0
個である場合に下記のような例外が発生することになります。
That page contains no results
ウェブアプリの公開初期や開発段階などの場合、表示するインスタンスの数が 0
であることも多いと思いますので、何らかの理由があって例外を発生させたい場合以外は、基本的には True
を指定しておけば良いと思います。
ページを取得する
前述の通り、Paginator
のコンストラクタを実行することでページの分割が行われることになります。
続いて行うことは、分割後のページの取得になります。このページの取得もビューから Paginator
を利用して実現していくことになります。
ページの取得
このページの取得は Paginator
のインスタンスに page
メソッドを実行させることで実現できます。page
メソッドの引数 num_page
には、取得したいページのページ番号を整数で指定します。ページ番号は 1
から始まる点に注意してください。
page_obj = paginator.page(num_page)
page
メソッドの返却値は Page
というクラスのインスタンスとなり、このインスタンスは num_page
で指定されたページの情報をデータ属性で持っていたり、ページの情報を取得するメソッドを持っていたりします。
ページ番号の取得
上記のように、ページを取得すること自体はページ番号を指定して page
メソッドを実行すれば良いだけなので簡単に思えます。
ですが、実はページ番号を取得するのに少し工夫が必要です。例えば、下の図のようなページ表示の場合、現在2ページ目を表示しているのですが、次へ
リンクがクリックされた際には3ページ目を表示する必要があります。
つまり、次へ
リンクがクリックされた際には、次のページの表示を行うために page
メソッドの引数にページ番号として 3
を指定する必要があります。このページ番号はどのようにして取得すれば良いでしょうか?
やり方はいろいろあって、URL パターンの仕組みを利用することもできるのですが、このページネーションを実現する際にはクエリパラメーターが利用されることが多いです。クエリパラメーターとは、URL の最後部分に下記の形式で指定されるパラメーターになります。
?変数名 = 値
そして、このクエリパラメーターはビューから取得することが可能です。具体的には、下記のように request
のデータ属性から取得することが可能です。
number = int(request.GET.get('変数名', 1))
get
の第2引数には 変数名
のクエリパラメーターが指定されていない場合のデフォルト値を指定します。つまり、上記の場合、クエリパラメーターが指定されなかった場合は1ページ目を表示することになります。クエリパラメーターが必ず指定されるとは限らないため、このデフォルト値は指定するようにしたほうが良いです。
また、上記の通り、ビューからはクエリパラメーターに指定された値を取得可能ですので、先ほど示した 次へ
リンクがクリックされた際に送信されるリクエストの URL に下記のようなクエリパラーメータが含まれるようにしてやれば、ビューから次に表示すべきページ番号を取得することができ、page
メソッドにそれを指定することで表示すべきページの Page
のインスタンスを取得することができることになります。
?p = ページ番号
上記では変数名を p
にしていますが、この部分はページ番号を表すことが分かりやすければ何でも良いです。要は、クエリパラメーターを取得する際に、その変数名の値を取得するように変数名を指定してやれば良いだけです。例えばクエリパラメーターに指定される変数名が p
であるのであれば、下記のように変数名 p
を指定してビューからページ番号を取得すれば良いことになります。
number = int(request.GET.get('p', 1))
じゃあ、このクエリパラメーターが URL に指定されるようにするためにはどうすれば良いでしょうか?単純ですが、そのようにテンプレートファイルを作れば良いことになります。
上の図のようなページのリンクはテンプレートファイルの記述によって表示されることになります。そして、このリンクには、クリックされた際に送信するリクエストの URL を指定することができます。ですので、その指定する URL にクエリパラメーターが含まれるようにしてやれば良いことになります。
HTML ではリンクの要素は a
タグによって表示することができ、さらにリンク先の URL は href
で指定できるため、下記のようなタグをテンプレートファイルに記述しておけば、リンククリック時に ?p=3
をクエリパラメーターとしたリクエストを送信できるようになります。
<a href="?p=3">next
ここで、3
は次のページ番号となるのですが、この「次のページ番号」は次に説明する Page
から取得してクエリパラメーターに指定することになります。
href="?p=3"
の ?p=3
は相対パス指定となるため、今表示しているページの URL に対してクエリパラメーターが追加されたリクエストが送信されることになります
href="/?p=3"
と最初に /
を付けるとルートパス指定になり、URL 自体が変化してしまうことになるので注意してください
前者の場合、例えば表示しているページの URL が http://localhost:8000/appli/
である場合、リンクのクリックによって送信されるリクエストの URL はクエリパラメーターを含めて http://localhost:8000/appli/?p=3
となります
それに対し、後者の場合は、http://localhost:8000/?p=3
となり、元々 URL に含まれていた /appli
の部分が消えてしまうことになります
スポンサーリンク
ページの情報を取得する
Page
のインスタンスが取得できれば、あとは、そのインスタンスからページの情報を取得してあなたの行いたい処理や表示を行なっていけば良いだけになります。特にページネーションで必要になるのが他のページへのリンクの設置になります。
ページに割り付けられたインスタンスの取得
まず、Page
のインスタンスからは、そのページに割り付けられたモデルクラスのインスタンスを取得することが可能です。Page
のインスタンスはイテラブルなオブジェクトであり、下記のように for
ループでページに割り付けられたモデルクラスのインスタンスを1つ1つ取得することが可能となります。
for obj in page_obj:
print(obj)
Page
のデータ属性
また、前述の通り、Page
のインスタンスからはデータ属性やメソッドによってページの情報を取得することができます。例えばデータ属性 number
からは、そのインスタンスに対応するページ番号を取得することができます。
また、Page
はデータ属性として Paginator
のインスタンスを持っており、Paginator
のデータ属性やメソッドを利用することも可能です。よく使うのが num_pages
で、これにより分割後のページのページ数を取得することができます。
print(page_obj.paginator.num_pages)
Page
のメソッド
さらに、Page
には下記のようなメソッドが存在し、これらのメソッドによって様々なページの情報を取得することができます。下記で示したメソッドは全て「引数なし」で実行することができます。
start_index
:そのページで表示する最初のインスタンスのインデックスを整数で取得end_index
:そのページで表示する最後のインスタンスのインデックスを整数で取得has_other_pages
:そのページ以外にページが存在するかどうかをブールで取得has_previous
:そのページの前のページが存在するかどうかをブールで取得has_next
:そのページの次のページが存在するかどうかをブールで取得previous_page_number
:そのページの前のページのページ番号を整数で取得next_page_number
:そのページの次のページのページ番号を整数で取得
例えば下記を実行すれば、前のページが存在する場合のみ前ページのページ番号が表示され、次のページが存在する場合のみ次ページのページ番号が表示されることになります。
if page_obj.has_previous():
print(page_obj.previous_page_number())
if page_obj.has_next():
print(page_obj.next_page_number())
テンプレートから Page
を利用する
上記でいくつか Page
のインスタンスを利用する例を示しましたが、どちらかというとこれらの例はビューから利用される例を示したものになります。ですが、実際には Page
はテンプレートファイルから利用されることの方が多いです。そして、テンプレートファイルから Page
に割り付けられているモデルのインスタンスの表示や、Page
のデータ属性やメソッドを利用して前後のページへのリンクの表示やページ番号の表示等を行います。
例えば、下の図のようなページを表示することを考えてみましょう。このページでは、モデルのインスタンスのリスト・表示されているページのページ番号(2
)、全ページ数(5
)、前のページのページ番号(1
)、次のページのページ番号(3
)が表示されています。
このような表示であれば、テンプレートに渡すコンテキストのデータは Page
のインスタンスのみでも十分実現可能です。前述の通り、Page
のインスタンスからは、そのページに割り付けられているモデルのインスタンスを取得することができますし、前のページや次のページが存在すること、それらのページ番号もメソッドから取得可能です。
例えば、テンプレートファイルが受け取る Page
のインスタンスを page_obj
とすれば、各項目は下の図のようなテンプレートのタグや変数表示の仕組みを利用して表示を行うことができます。全ての情報が page_obj
から取得できていることが確認できると思います。
このように、テンプレートファイルで Page
のインスタンスの情報を表示することで、モデルのインスタンスの一覧だけでなく、前後のページへ誘導するためのリンク等も設置することができるようになります。
ページネーションの利用例
続いて、ここまで説明してきた内容を踏まえながらページネーションの利用例を紹介していきます。
まずは、ページネーションを利用しない例を示し、それをページネーションを利用するアプリに変更していきたいと思います。
ページネーションを利用しないアプリ
まずはページネーションを利用しないアプリを開発しますので、いつも通りの手順でプロジェクトやアプリの作成等を行なっていきます。
プロジェクトの作成
まずは、適当な作業フォルダに移動し、その後下記コマンドを実行してプロジェクトを作成します。今回はプロジェクト名は paginationtest
としたいと思います。
% django-admin startproject paginationtest
上記コマンドの実行によって paginationtest
フォルダが作成されますので、下記コマンドで paginationtest
フォルダ内に移動します。
% cd paginationtest
アプリの作成
続いて、下記コマンドを実行してアプリの作成を行います。今回はアプリ名は appli
としたいと思います。
% python manage.py startapp appli
さらに、今いるフォルダの中に paginationtest
フォルダが存在するはずですので、そのフォルダの下にある settings.py
を開き、INSTALLED_APPS
の定義を下記のように変更します。1行目に 'appli',
を追加すれば良いだけです。
INSTALLED_APPS = [
'appli',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
これにより、アプリ appli
がプロジェクト paginationtest
に登録されることになります。
ビューの作成
次はビューを作成していきます。今いるフォルダの中に appli
フォルダが存在し、その中に views.py
が存在するはずですので、この views.py
を下記のように変更してください。
from django.shortcuts import render
from django.contrib.auth.models import User
def index(request):
users = User.objects.all()
context = {
'users' : users
}
return render(request, 'appli/users.html', context)
テンプレートの作成
次はテンプレートを作成していきます。まず、appli
フォルダの中に templates
フォルダ、さらに templates
フォルダの中に appli
フォルダを作成してください。
そして、最後の作成した appli
フォルダの中に users.html
を新規作成し、下記のように中身を変更してください。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<title>ユーザー一覧</title>
</head>
<body>
<main class="container my-5 bg-light">
<h2>ユーザー一覧</h2>
<table class="table table-hover">
<thead>
<tr><th>ユーザー名</th><th>登録日</th></tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.username }}</td>
<td>{{ user.date_joined|date }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</main>
</body>
</html>
ちょっとソースコードが長いので複雑にも思えますが、単にテーブルを作成し、ビューから渡された users
に含まれる各 User
のインスタンスの情報を users
に対する for
ループで1つ1つテーブルのセルの中に出力しているだけになります。ただ、見た目を整えるために、bootstrap
を読み込んだり class
の設定なども行っています。
User
のインスタンスの情報としては username
と date_joined
フィールドの値を出力するようにしており、date_joined
は User
のインスタンスが作成された日時となります。
URL のマッピング
次は先ほど作成したビューと URL のマッピングを行います。まず、paginationtest
フォルダの下にある urls.py
を下記のように変更してください。
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('appli/', include('appli.urls'))
]
これにより、リクエストされた URL がルートパス指定で /appli/
から始まる場合に appli
フォルダ内の urls.py
が読み込まれるようになります。が、現状 appli
フォルダ内に urls.py
は存在しないため、appli
フォルダ内に urls.py
を新規作成し、その urls.py
の中身を下記のように変更してください。
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index')
]
マイグレーションの実行
以上で、ページネーションを “利用しない” 例としてのソースコードの変更は完了となります。
ただし、ページネーションを “利用しない” 場合と “利用する” 場合の差を確認するためには、モデルのインスタンスが複数用意されている必要があります。その準備をここから行なっていきます。
今回は、views.py
の変更からも分かるように、モデルクラスとしては User
を利用します。この User
は auth
アプリにあらかじめ用意されたモデルクラスであり、別途 models.py
で定義する必要はありません。そして、データベースに保存された User
のインスタンスの一覧が表示されるように views.py
と users.html
が作成されています。なので、User
のインスタンスを複数作成しておけば、その一覧がウェブアプリから確認できることになります。
ただし、この User
のインスタンスの作成先は User
に対応するテーブルであるため、このテーブルを事前に作成しておく必要があります。そして、このテーブルの作成はマイグレーションによって実現することができ、マイグレーションは下記のコマンドで実行することができます。
% python manage.py migrate
スーパーユーザーの作成
続いてユーザーの作成、すなわち User
のインスタンスの作成を行なっていきます。
この作成はどんな手段で行っても良いのですが、ここでは管理画面からユーザーの作成を行なっていきたいと思っています。この管理画面でのユーザーの作成に関しては詳細を下記ページで解説していますので、詳しく知りたい方は下記ページをご参照いただければと思います。

まず、管理画面にログインするためにはスーパーユーザーが必要となりますので、下記のコマンドでスーパーユーザーの作成を行います。
% python manage.py createsuperuser
コマンドを実行すればユーザー名とメールアドレスとパスワード(確認用も含めて2回)の入力が促されますので、適当なものを入力してください。最後に Superuser created successfully.
と表示されればスーパーユーザーの作成に成功したことになります。メールアドレスやパスワードは出鱈目なものを入力するとエラーになる可能性があるので注意してください。メールアドレスやパスワードとして妥当なものを入力する必要があります。
これにより、スーパーユーザーとして管理画面にログインできるようになったことになります。
開発用サーバーの起動
次は、管理画面にログインするために下記コマンドで開発用サーバーを起動しておきたいと思います。開発用サーバーが起動することで、サーバーがリクエストを受信することが可能となり、受信したリクエストに応じてアプリが動作することになります。管理画面も利用可能となります。
% python manage.py runserver
ユーザーの作成
次は、管理画面でユーザーの作成を行なっていきます。
まず、ウェブブラウザから下記 URL を開いてください。
http://localhost:8000/admin/
管理画面へのログインフォームが表示されるはずですので、Username
と Password
に先ほど作成したスーパーユーザーのユーザー名とパスワードをそれぞれ入力して Log in
ボタンをクリックしてください。
ログインすれば下の図のような管理画面のトップページが表示されると思いますので、Users
の右側にある Add
リンクをクリックしてください。
そうすると、下の図のようなユーザー追加フォームが表示されます。このフォームでユーザーの追加を行うことができますので、適当なユーザー名とパスワードを入力して SAVE
ボタンのクリックを行ってください。
ポイントは、ユーザー名は重複は許されないという点と、パスワードは重複しても良いという点になります。なので、ユーザー名は個別に考える必要がありますが、パスワードに関しては毎回同じもので良いです。ですが、フォームに記載されている通り、ユーザー名と似ているパスワードや数字だけのパスワード等は許可されていないので注意してください。
SAVE
ボタンをクリックしてユーザーの追加に成功すれば、下の図のような画面が表示されます。ここではユーザーの詳細情報の変更を行うこともできるのですが、今回はこの変更は不要なので、画面左側にある Users
の右の Add
リンクをクリックしてください。
Add
リンクをクリックすれば再びユーザー追加フォームが表示されることになります。あとは、これらを繰り返してユーザーを複数作成しておいてください。最低でも、合計で5人程度のユーザーを作成しておいていただければと思います。ちなみに、最初にコマンドで作成したスーパーユーザーもユーザーの一人となるため、スーパーユーザーを含めて5人程度のユーザーを作成しておけばオーケーです。
動作確認
ちょっと準備に時間がかかりましたが、これで一旦ページネーションを “利用しない” 場合の動作確認が可能となったことになります。ということで、ウェブブラウザから下記 URL を開いてください。
http://localhost:8000/appli/
これにより、ウェブラウザから開発サーバーにリクエストが送信され、上記 URL にマッピングされた views.py
の index
関数が動作します。そして、その中で Users
のインスタンスが全て取得され、それがテンプレートファイル users.html
に埋め込まれてページとして表示されることになります。
その結果は、下の図のようなものになると思います。
今回の例ではページネーションを利用していないため、全インスタンスが1つのページに表示されることになります。これは、インスタンスの数が増えたとしても同様になります。
次は、ページネーションを利用し、インスタンスが複数のページに分割されて表示されるようにしていきたいと思います。
一旦ここで動作確認は終了となるため、開発用サーバーは終了しておいてください。開発用サーバーを起動したターミナルやコマンドプロンプト等で ctrl
+ c
を入力すれば開発用サーバーを強制終了できるはずです。
スポンサーリンク
ページネーションを利用するアプリ(ページの分割)
ということで、ここからは先ほど作成したページネーションを “利用しない” アプリを、ページネーションを “利用する” アプリに変更していきたいと思います。
ページネーションを利用するアプリに変更するため、先ほど作成した views.py
と users.html
の変更を行います。
views.py
の変更
まずは views.py
の変更を行なっていきます。結論として、ページネーションによりページの分割を行うためには下記のように views.py
を変更してやれば良いです。
from django.shortcuts import render
from django.contrib.auth.models import User
from django.core.paginator import Paginator
def index(request):
users = User.objects.all()
# ページの分割
paginator = Paginator(users, 2)
# クエリパラメーターからページ番号取得
number = int(request.GET.get('p', 1))
# 取得したページ番号のページを取得
page_obj = paginator.page(number)
context = {
'page_obj' : page_obj
}
return render(request, 'appli/users.html', context)
ページネーションを利用しない場合に比べて、ページの分割、ページ番号の取得、ページの取得の処理を追加しています。また、テンプレートに渡すコンテキストで、変数名を page_obj
に変更しているので、その点にも注意してください。
まず、ページの分割は Paginator()
の実行、すなわち Paginator
のコンストラクタの実行によって行なっています。第1引数にはデータベースから取得したクエリセット users
を指定しています。今回の場合は、User
のインスタンス全てがこのクエリセット users
に含まれていることになります。
さらに、第2引数には 2
を指定しているため、users
の各インスタンスが 2
つずつ各ページに割り付けられるようにページの分割が行われることになります。
次に行っているのがページ番号の取得で、このページ番号は変数名が p
のクエリパラメーターから取得するようにしています。変数名が p
のクエリパラメーターが指定されていない場合は、取得されるページ番号はデフォルトの 1
となります。
そして、その後に取得したページ番号を引数に指定して Paginator
の page
メソッドを実行して指定したページ番号に対応する Page
のインスタンス page_obj
の取得を行なっています。
そして、この page_obj
をコンテキストに設定してテンプレートに渡すようにしています。テンプレートには変数名 'page_obj'
として page_obj
が渡されることになります。
users.html
の変更
次にテンプレートファイルの変更を行なっていきます。今までテンプレートファイルには 'users'
という変数名で User
のインスタンスの集合が渡されていました。そして、この users
に対して for
ループを行うことで User
のインスタンスを1つ1つ取得して情報を表示していました。
ですが、先ほどのビューの変更により 'page_obj'
という変数名で Page
のインスタンスが渡されるようになったことになります。前述の通り、この Page
のインスタンスからは、その Page
のページに割り付けられたモデルクラスのインスタンスが取得可能です。今回の場合、User
のインスタンスの集合を Paginator
で分割して Page
に割り付けたのですから、Page
からは User
のインスタンスが取得可能であることになります。また、この User
のインスタンスは、Page
のインスタンスに対して for
ループを行うことで1つ1つ取得することが可能です。
つまり、users
に対して for
ループを行なっている部分を page_obj
に対して for
ループを行うようにすれば、ページネーションを利用しなかった場合と同様に page_obj
に割り付けられた User
のインスタンスの情報の表示が行えることになります。ただし、users
にはビューで取得された User
のインスタンス全てが含まれていたのに対し、page_obj
には page_obj
に割り付けられた User
のインスタンスのみが含まれることになります。そのため、users
に対する for
ループを page_obj
に対する for
ループに変更してやれば、自動的に page_obj
に割り付けられた User
のインスタンスの情報のみが表示されるようになります。
ということで、テンプレートファイル users.html
を下記のように変更したいと思います。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<title>ユーザー一覧</title>
</head>
<body>
<main class="container my-5 bg-light">
<h2>ユーザー一覧</h2>
<table class="table table-hover">
<thead>
<tr><th>ユーザー名</th><th>登録日</th></tr>
</thead>
<tbody>
{% for user in page_obj %}
<tr>
<td>{{ user.username }}</td>
<td>{{ user.date_joined|date }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</main>
</body>
</html>
ページネーションを利用しない場合に比べて変更しているのは下記部分のみです。元々 users
に対して for
ループを行なっていたところを、page_obj
に対する for
ループに変更しています。
{% for user in page_obj %}
動作確認
一旦ここで動作確認を行なっておきましょう!
先ほどと同様の手順で開発用サーバーを起動し、ウェブアプリから下記 URL を開いてください。
http://localhost:8000/appli/
すると、下の図のようなページが表示されると思います。先程は全ての User
のインスタンスが表示されていたのに対し、今回は 2
つ分のインスタンスのみが表示されていることが確認できます。これは、Paginator()
実行時に第2引数(per_page
引数)に 2
を指定しているためになります。
さて、先程ウェブブラウザに指定した URL にはクエリパラメータが指定されていません。そのため、views.py
の index
関数ではページ番号としてデフォルトの 1
が表示されるようになっています。
次は、クエリパラメーターを追加して再度ページの表示を行なってみましょう!ということで、次は下記の URL をウェブブラウザに指定して開いてください。
http://localhost:8000/appli/?p=2
すると、表示されるインタンスの情報が変化することを確認できると思います。これは、クエリパラーメーターの指定によって、2
ページ目に割り付けられたインスタンスが表示されるようになったためです。
ユーザーを5人以上作成している場合、クエリパラメーター部分を ?p=3
に変更した場合も、また今回とは異なるインスタンスが表示されることを確認することができます。
このように、Paginator
を利用することでページが分割され、各ページに自動的にモデルクラスのインスタンスが割り付けられます。また、クエリパラメーターを利用して表示するページ番号を取得し、ユーザーが表示したいページを表示することができます。
これでページの分割が実現できたことになるのですが、ユーザーが毎回 URL のクエリパラメーターを直接変更する必要があり、ユーザーにはちょっと不親切です。次のページや前のページへのリンクを表示し、リンクのクリックでユーザーが表示したいページに遷移するようにしてあげたほうが良いです。
また、今表示しているのが何ページ目なのか、全部で何ページあるのか、といった情報も表示した挙げたほうが親切です。
ページ分割を行う場合、こういったページの情報やリンクの設置はセットで行なったほうが良いです。
ページネーションを利用するアプリ(リンクの設置)
ということで、次はページの情報の表示や前後のページへのリンク等の設置の例を示していきたいと思います。
ここで変更するのは users.html
のみで、views.py
からはコンテキストの 'page_obj'
という変数名で Page
のインスタンスが渡されるようになっているため、この Page
のインスタンスから今表示しているページの情報や前後のページのページ番号等を取得し、それらを利用してよりユーザーに使いやすいページに変更していきます。
具体的には、下記のように users.html
を変更します。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<title>ユーザー一覧</title>
</head>
<body>
<main class="container my-5 bg-light">
<h2>ユーザー一覧</h2>
<table class="table table-hover">
<thead>
<tr><th>ユーザー名</th><th>登録日</th></tr>
</thead>
<tbody>
{% for user in page_obj %}
<tr>
<td>{{ user.username }}</td>
<td>{{ user.date_joined|date }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
<a href="?p=1">« first</a>
<a href="?p={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="?p={{ page_obj.next_page_number }}">next</a>
<a href="?p={{ page_obj.paginator.num_pages }}">last »</a>
{% endif %}
</span>
</div>
</main>
</body>
</html>
前述の users.html
から <table>
〜 </table>
の後ろ側にコードを追加し、今表示しているページの情報や前後のページへのリンクの表示を行うように変更しています。
で、この追加したコードは基本的に下記ページの Django 公式で紹介されているものの引用となります。
https://docs.djangoproject.com/en/4.2/topics/pagination/
また、この追加したコードで何をしているかは、コードと ページの情報を取得する の内容を確認していただければ大体わかるのではないかと思います。
例えば下記であれば、has_previous
で前のページが存在するかどうかを確認し、存在する場合は 1
ページ目と前のページへのリンクの設置を行なっています。存在しない場合、これらのリンクは表示されません。
{% if page_obj.has_previous %}
<a href="?p=1">« first</a>
<a href="?p={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
ポイントは、a
タグの href
に ?p=ページ番号
を指定するところになります。1
ページ目へのリンクを追加する場合は、単純に ページ番号
に 1
を指定してやれば良いことになります。
それに対し、前のページへのリンクを追加する場合、ページ番号
に指定する値は今表示しているページによって異なることになります。例えば、今 3
ページ目を表示しているのであれば、ページ番号
には 2
を指定する必要があります。そのため、このようなページ番号は今表示しているページに合わせて動的に変化するよう previous_page_number
を利用し、前のページを page_obj
から取得するようにしています。
ここでは前のページへのリンクについて説明しましたが、次のページへのリンクについても同様の仕組みで追加を行なっています。
また、page_obj
は paginator
をデータ属性として持っており、この paginator
からも情報の取得が可能となります。例えば、分割後の全ページ数を page_obj
は知りませんが、paginator
は知っているため、この情報は paginator
から取得することが可能です。上記においては、分割後の全ページ数を paginator.num_pages
から取得して表示するようにしており、この全ページ数の情報から最後のページへのリンクの設置を実現しています。
動作確認
これでコードの変更は完了したので動作確認を行なっていきます。
まず開発用サーバーを起動させ、続いて下記の URL をウェブブラウザで表示してください。
http://localhost:8000/appli/
すると、下の図のようなページが表示され、インスタンスの一覧の下側に次のページへのリンク(next
)と最後のページへのリンク(last
)が表示されていることが確認できると思います。また、全体のページ数と今表示しているページのページ番号(1 of 3
)も表示されていることが確認できると思います。
前のページへのリンクが表示されていないのは、今表示しているのが 1
ページ目になるからになります。
続いて next
リンクをクリックしてください。これにより、表示されるインスタンスが変化することが確認できるはずです。また、ユーザが5名以上登録されている場合、2
ページ目を表示すれば前のページへのリンク(previous
)と次のページへのリンク(next
)の両方が表示されます。
さらに、ウェブブラウザの URL バーを確認すると、下記のようにクエリパラメーター ?p=2
が表示されているはずです。このように、リンクがクリックされた際にクエリパラメーターを設定し、ビュー側でクエリパラメータからページ番号を取得するようにすることで、分割後のページの遷移が実現されることになります。ページの分割だけでなく、ページ番号を取得するという点も重要になるので覚えておいてください!
http://localhost:8000/appli/?p=2
掲示板アプリでページネーションを利用してみる
最後に、いつも通りの流れで、この Django 入門の連載の中で開発してきている掲示板アプリに対し、ページネーションを適用していきたいと思います。
この Django 入門に関しては連載形式となっており、ここでは以前に下記ページの 掲示板アプリで管理画面をカスタマイズしてみる で作成したウェブアプリに対してページネーションを実装していきたいと思います。

現状、このアプリにはモデルクラスのインスタンスの一覧を示すページとして「ユーザー一覧」「コメント一覧」があります。また、各ユーザーのコメント履歴が「ユーザーの詳細」ページで表示されるようになっています。
これらは1ページで全てのインスタンスが表示されるようになっているため、このページで紹介したページネーションを導入して複数のページの分割されるようにしていきたいと思います。
スポンサーリンク
コメント一覧の変更
アプリとしては既に動作するようになっているため、今回変更が必要なのはビューとテンプレートのみとなります。
ビューの変更
まず、コメント一覧の表示を実現しているのは view.py
における下記の comments_view
関数になります。
まずは、この comments_view
を例にページネーションを利用するように変更していきたいと思います。
from django.shortcuts import redirect, render, get_object_or_404
from .forms import RegisterForm, PostForm, LoginForm
from .models import Comment
from django.contrib.auth.decorators import login_required
from django.contrib.auth import login, logout
from django.contrib.auth import get_user_model
# 略
@login_required
def comments_view(request):
comments = Comment.objects.all()
context = {
'comments' : comments
}
return render(request, 'forum/comments.html', context)
といっても、変更内容は ページネーションを利用するアプリ(ページの分割) で示した通りで、インスタンスの集合を引数に指定して Paginator()
を実行することでページの分割を行い、クエリパラメーターからページ番号を取得、さらに page
メソッドで取得したページ番号のページの Page
インスタンスを取得し、その Page
インスタンスをコンテキストにセットしてやれば良いだけです。
ということで、変更後の comments_view
は下記のようなものになります。
from django.shortcuts import redirect, render, get_object_or_404
from .forms import RegisterForm, PostForm, LoginForm
from .models import Comment
from django.contrib.auth.decorators import login_required
from django.contrib.auth import login, logout
from django.contrib.auth import get_user_model
from django.core.paginator import Paginator
# 略
@login_required
def comments_view(request):
comments = Comment.objects.all()
paginator = Paginator(comments, 3)
number = int(request.GET.get('p', 1))
page_obj = paginator.page(number)
context = {
'page_obj' : page_obj
}
return render(request, 'forum/comments.html', context)
今回は、各ページに表示されるインスタンスの数(per_page
)を 3
に指定しています。
また、ページ番号の取得を行う request.GET.get('p', 1)
の第2引数には重要な意味合いがあるので指定を忘れないよう注意してください。この第2引数は、URL にクエリパラメーター p
が存在しなかった場合のデフォルト値として利用されます。第2引数が指定されなかった場合、URL にクエリパラメーター p
が存在しないと None
が返却されることになります。そして、None
を page
メソッドに指定して実行すると例外が発生します(そもそも上記の場合は int()
実行時に例外が発生します)。
掲示板アプリでは各ページにナビゲーションバーを表示してリンクからコメント一覧ページを表示されるようになっていますが、このリンククリック時にはクエリパラメーターは指定されません。そのため、第2引数を指定せずに request.GET.get('p')
を実行するようにしてしまうと、このリンククリック時に毎回例外が発生することになってしまいますので注意してください。
テンプレートの変更
続いて、テンプレートファイル側の変更を行っていきます。
コメント一覧ページの基になっているテンプレートファイルは comments.html
になっています。
変更前の comments.html
は下記になります。
{% extends "forum/base.html" %}
{% block title %}
コメント一覧
{% endblock %}
{% block main %}
<h2>コメント一覧(全{{ comments|length }}件)</h2>
<table class="table table-hover">
<thead>
<tr>
<th>本文</th><th>投稿者</th>
</tr>
</thead>
<tbody>
{% for comment in comments %}
<tr>
<td><a href="{% url 'comment' comment.id %}">{{ comment.text|truncatechars:20 }}</a></td>
<td>{{ comment.user.username }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
テンプレートファイルの変更に関しても、ページネーションを利用するアプリ(ページの分割) と ページネーションを利用するアプリ(リンクの設置) で説明した内容に基づいて変更を行えば良いです。特に、前後のページへのリンクの設置に関しては ページネーションを利用するアプリ(リンクの設置) で紹介したものをそのままコピペして利用できます。
ただし、テンプレートファイルからは comments
という名前で Comment
のインスタンスの集合が渡されるのではなく、page_obj
という名前で Page
のインスタンスが渡されるようになっていますので、この変化には注意が必要です。
特に、コメント一覧(全{{ comments|length }}件)
の部分の comments|length
は全てのコメントの件数を表示するための記述となっています。この部分を page_obj|length
にそのまま変更してしまうと、その page_obj
に割り付けられているインスタンスの数が表示されてしまうことになるため、全てのコメントの件数が表示されなくなってしまいます。全てのインスタンスの数は page_obj.paginator.count
によって取得できるため、comments|length
は page_obj.paginator.count
に書き換える必要があります。
これらを考慮すると、comments.html
を下記のように変更すればページネーションが実現できることになります。
{% extends "forum/base.html" %}
{% block title %}
コメント一覧
{% endblock %}
{% block main %}
<h2>コメント一覧(全{{ page_obj.paginator.count }}件)</h2>
<table class="table table-hover">
<thead>
<tr>
<th>本文</th><th>投稿者</th>
</tr>
</thead>
<tbody>
{% for comment in page_obj %}
<tr>
<td><a href="{% url 'comment' comment.id %}">{{ comment.text|truncatechars:20 }}</a></td>
<td>{{ comment.user.username }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
<a href="?p=1">« first</a>
<a href="?p={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="?p={{ page_obj.next_page_number }}">next</a>
<a href="?p={{ page_obj.paginator.num_pages }}">last »</a>
{% endif %}
</span>
</div>
{% endblock %}
ユーザー一覧の変更
続いてユーザー一覧の変更を行っていきますが、変更内容はコメント一覧の時と同様となりますので、ここでは変更後のビューの関数 users_view
と テンプレートファイル users.html
のみを示しておきます。
まず、変更後の users_view
の関数は下記のようになります。
@login_required
def users_view(request):
users = User.objects.all()
paginator = Paginator(users, 3)
number = int(request.GET.get('p', 1))
page_obj = paginator.page(number)
context = {
'page_obj' : page_obj
}
return render(request, 'forum/users.html', context)
続いて、変更後の users.html
は下記のようになります。
{% extends "forum/base.html" %}
{% block title %}
ユーザー一覧
{% endblock %}
{% block main %}
<h2>ユーザー一覧(全{{ page_obj.paginator.count }}人)</h2>
<table class="table table-hover">
<thead>
<tr>
<th>ユーザー</th><th>コメント数</th>
</tr>
</thead>
<tbody>
{% for user in page_obj %}
<tr>
<td><a href="{% url 'user' user.id %}">{{ user.username }}</a></td>
<td>{{ user.comments.all|length }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
<a href="?p=1">« first</a>
<a href="?p={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="?p={{ page_obj.next_page_number }}">next</a>
<a href="?p={{ page_obj.paginator.num_pages }}">last »</a>
{% endif %}
</span>
</div>
{% endblock %}
ユーザー詳細の変更
次はユーザー詳細ページの変更を行っていきます。
ユーザー詳細ページではユーザーの情報の表示とユーザーのコメント履歴を表示するようにしています。このコメント履歴部分のみをページ分割していきたいと思います。
ユーザーの詳細を表示するビューの関数は user_view
であり、変更前の user_vie
は下記のようになっています。
from django.shortcuts import redirect, render, get_object_or_404
from .forms import RegisterForm, PostForm, LoginForm
from .models import Comment
from django.contrib.auth.decorators import login_required
from django.contrib.auth import login, logout
from django.contrib.auth import get_user_model
User = get_user_model()
@login_required
def user_view(request, user_id):
user = get_object_or_404(User, id=user_id)
context = {
'user' : user
}
return render(request, 'forum/user.html', context)
ページネーションの利用手順は基本的には comments_view
の時と同じです。ですが、上記の通り、現在テンプレートに渡しているのは User
のインスタンスのみで(より正確に言えば User
ではなく CustomUser
ですが、説明を簡単にするため、ここでは User
と記します)、この User
のインスタンスからリレーションが構築されているコメントをテンプレートで取得するようにしています。そして、このコメントは Comment
のインスタンスであり、各ユーザーと複数のコメントとの間にリレーションが構築されていることになります。
このリレーションが構築されているコメントを全て表示するのであれば、現状のようにテンプレートに User
のインスタンスのみを渡すのでも良いのですが、ページ分割を行う場合、事前にビューでページを分割し、分割後のページ、つまり Page
のインスタンスをテンプレートに渡すようにする必要があります。
なので、まずはビューでユーザーの情報を表示する対象となる User
のインスタンスを取得し、次に、その User
とリレーションが構築されている Comment
のインスタンスの集合、すなわち、user
フィールドに「取得した User
のインスタンス」がセットされている Comment
のインスタンスの集合を取得するように変更します。さらに、その取得した Comment
インスタンスの集合に対してページ分割を行うようにします。そして、comments_view
等と同様に Page
のインスタンスを取得した後に、それをテンプレートに渡すように変更します。
この考え方に基づいて変更を行った user_view
関数は下記のようになります。
@login_required
def user_view(request, user_id):
user = get_object_or_404(User, id=user_id)
comments = Comment.objects.filter(user=user)
paginator = Paginator(comments, 3)
number = int(request.GET.get('p', 1))
page_obj = paginator.page(number)
context = {
'user' : user,
'page_obj' : page_obj
}
return render(request, 'forum/user.html', context)
comments
を取得した後は、基本的には comments_view
と同じような流れの処理になっています。ただし、テンプレートに渡すコンテキストには User
のインスタンスである user
と Page
のインスタンスである page_obj
の2つがセットされるようになっています。
User
のインスタンスと Page
のインスタンスの2つをコンテキストにセットしているのは、テンプレート側でユーザーの情報を表示するのに User
のインスタンスを利用し、ページに割り付けられたコメントの情報を表示するのに Page
のインスタンスを利用するようにしたいからになります。
テンプレートの変更
ということで、テンプレート側では、ユーザーの情報を表示する部分は User
のインスタンスを利用し、コメントの情報を表示するのに Page
のインスタンスを利用すれば良いことになります。
ユーザーの詳細ページのテンプレートファイルは user.html
であり、変更前の user.html
は下記のようになっています。
{% extends "forum/base.html" %}
{% block title %}
{{ user.username }}
{% endblock %}
{% block main %}
<h1>ユーザー({{ user.id }})</h1>
<h2>{{ user.username }}の情報</h2>
<table class="table table-hover">
<tbody>
<tr><th>名前</th><td>{{ user.username }}</td></tr>
<tr><th>連絡先</th><td>{{ user.email|urlize }}</td></tr>
<tr><th>年齢</th><td>{{ user.age }}</td></tr>
</tbody>
</table>
<h2>{{ user.username }}のコメント履歴</h2>
<table class="table table-hover">
<tbody>
{% for comment in user.comments.all %}
<tr>
<td><a href="{% url 'comment' comment.id %}">{{ comment.text|truncatechars:20 }}</a></td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
前半部分ではユーザーの情報の表示を行なっており、変更前でも User
のインスタンスを利用しているので、特に変更は必要ありません。それに対し、後半部分ではコメントの情報を表示するために、user.comments.all
に対する for
ループを行なっているため、この部分を page_obj
に対するループに変更する必要があります。この変更のみを行えば、今まで通りループの中で Comment
のインスタンスが comment
として取得されるようになるため、for
ループの中身の変更は不要となります。
また、前後のページへのリンクを追加するため、これに関しては comments.html
の変更時と同様に ページネーションを利用するアプリ(リンクの設置) で示したものをそのままコピペします。
これらを踏まえて変更を行った user.html
は下記のようになります。
{% extends "forum/base.html" %}
{% block title %}
{{ user.username }}
{% endblock %}
{% block main %}
<h1>ユーザー({{ user.id }})</h1>
<h2>{{ user.username }}の情報</h2>
<table class="table table-hover">
<tbody>
<tr><th>名前</th><td>{{ user.username }}</td></tr>
<tr><th>連絡先</th><td>{{ user.email|urlize }}</td></tr>
<tr><th>年齢</th><td>{{ user.age }}</td></tr>
</tbody>
</table>
<h2>{{ user.username }}のコメント履歴</h2>
<table class="table table-hover">
<tbody>
{% for comment in page_obj %}
<tr>
<td><a href="{% url 'comment' comment.id %}">{{ comment.text|truncatechars:20 }}</a></td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
<a href="?p=1">« first</a>
<a href="?p={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="?p={{ page_obj.next_page_number }}">next</a>
<a href="?p={{ page_obj.paginator.num_pages }}">last »</a>
{% endif %}
</span>
</div>
{% endblock %}
スポンサーリンク
動作確認
以上で、ページネーションを利用するための実装は完了したことになります。
最後に動作確認を行なっていきましょう!
開発用サーバーの起動
まずは前準備として、いつも通り開発用サーバーの起動を行います。manage.py
が存在するフォルダ(testproject
フォルダ)に移動し、下記コマンドを実行して開発用サーバーを起動してください。
python manage.py runserver
models.py
の変更は行なっていないため、マイグレーション等の実施は不要となります。
続いて、ウェブブラウザを起動し、下記 URL を開きます。
http://localhost:8000/forum/
上記 URL を開くとコメント一覧ページもしくはログインフォームが表示されることになると思います。
ログインフォームが表示された方はログアウトされている状態であるため、ログインフォームからまずはログインを行なってください。ログインすれば、ログインしたユーザーの詳細情報のページが表示されることになりますが、このページについては後から確認しますので、まずはナビゲーションバーの コメント一覧
をクリックしてコメント一覧ページを表示してください。
また、ログイン可能なユーザー名・パスワードを忘れてしまった方は、ユーザー登録
リンクをクリックしてユーザー登録を行なっていただければログインすることが可能です。この場合も、登録したユーザーの詳細情報のページが表示されることになりますが、ナビゲーションバーの コメント一覧
をクリックしてください。
ということで、これで皆さんがコメント一覧ページを見ている状態になっていると思いますので、まずはコメント一覧ページを確認してみましょう!
コメント一覧ページでは、今まで投稿済みのコメントが全てページ内に表示されるようになっていたのですが、今回の変更によって表示されるコメントの数は各ページで 3
以下となるようになっています。そして、投稿済みのコメントの数が 4
以上の場合、ページの分割が行われ、コメント一覧の下の方に次のページへのリンク(next
)や最後のページへのリンク(last
)が表示されるようになっています。
コメントの数が 4
未満の場合は次のページへのリンク(next
)や最後のページへのリンク(last
)が表示されないため、ナビゲーションバーの コメント投稿 をクリックし、コメントを投稿してコメント数を増やしてみてください。コメントの数が 4
以上になれば、コメント一覧の下にリンクが表示されるようになるはずです。
そして、これらのリンクをクリックすれば、次のページや最後のページを表示できることが確認できると思います。また、例えば次のページのリンクをクリックすれば、今度は前のページへのリンク(previous
)や最初のページへのリンク(first
)も表示されることが確認でき、これらをクリックすれば、前のページや最初のページに遷移することも確認できると思います。
このように、Paginator
を利用することでページの分割を実現することができ、さらにリンクを設置することで分割後の各ページへの遷移をマウス操作で実現することができるようになります。
また、上記ではコメント一覧を例に説明を行いましたが、ユーザー一覧やユーザーの詳細ページに表示されるコメント履歴に関しても同様のことが確認できると思います。
ナビゲーションバーの ユーザー一覧
をクリックすればユーザー一覧が表示され、ここでもページ分割が行われていることが確認できると思います。この場合もユーザー数が 4
未満の場合は1ページで全てのユーザーが表示されることになりますが、ナビゲーションバーの ユーザー登録
からユーザーの登録を行えばユーザー数を増やすことができ、ユーザー数が 4
以上になればページの分割が行われることが確認できると思います。
また、このユーザー一覧から特定のユーザーの名前をクリックすることで、そのユーザーの詳細ページが表示され、そこでコメント履歴も確認することができます。このコメント履歴に関しても同様のことが確認できると思います。
以上で動作確認は完了です!
まとめ
このページでは、Django のページネーションについて解説をしました!
Django では Paginator
が用意されており、これを利用することで簡単にページネーションを実現することができます。特にページ分割に関しては簡単に実現できることが、このページの解説からも理解していただけたのではないかと思います。また、分割後のページへの遷移をマウス操作で実現するためにはリンクの設置も必要で、これに関してはややこしいようにも思えますが、基本的に一度作ってしまえば、あとは基本的にはコピペだけで実装できると思います。
アプリの開発入門者にとってはあまり魅力的な機能に思えないかもしれないですが、ウェブアプリの利用者が増えると大量のレコードがデータベースに保存されることになり、それを一度に表示すると閲覧性の悪いページになってしまいます。そういったことを改善するのにページネーションは有効ですので、是非この機能についても覚えておいてください!
次の連載では、Django でのウェブアプリ開発時に陥りやすい N + 1
問題と、その解決方法について解説します。ちょっと地味な話題になりますが、ウェブアプリ開発者には是非知っておいてもらいたい内容の解説になりますので、是非次の連載も読んでみてください!
