この記事を三行にまとめると
ありがたいありがたいリンクは自分で作る必要があります
可愛いを作るよりははるかに簡単な作業でしょう
ウェブサービスで何かしらの一覧ページを作る時、ページネーションを実装することがあると思います。これを自作しようと思うと結構めんどい。たぶん多くの人が一度は感じることだと思います。だからなのかCakePHPやDjangoではページネーションがわりと簡単に使えるようになっています。ありがたいありがたい。
そんなわけで今日はDjangoのページネーションの機能を使ってみたいと思います。
公式のドキュメントはこちら。
https://docs.djangoproject.com/ja/4.0/topics/pagination/
例えば今、blogsというテーブルにブログ記事の一覧データが入っているとします。この一覧をシンプルに全件取得したい場合はallを使います。
何かしら条件を指定したい場合はfilterを使います。
これはauthor_idが1のデータだけを取ってきたい場合ですね。author_idは記事の作成者のIDだと思ってください。
この記事データを作成日時が新しい順に取ってきたい場合はこうなります。
ここではcreatedに作成日時が入っているという前提です。フィールド名にマイナスをつけるとdescになります。
他にもいろんな書き方がありますが、だいたいこんな感じでデータを取ってくることができます。
まずページネーションを使用するには「Paginator」というモジュールが必要になります。使い方は簡単で、このモジュールにクエリと件数を突っ込むだけです。
クエリには通常の一覧を取得する時のallやfilterをそのままセットすればOKです。order_byなどもそのまま入れて大丈夫。
これでauthor_idが1のデータのうち、作成日時が新しいものを10件取ってきてくれます。
page変数には一覧だけじゃなくてページネーションに関する情報がいろいろ入っているので、そのままfor文などでデータを表示することはできません。
一覧を表示するには、まず一覧の何ページ目を表示したいかを指定します。上記の「page.page(1)」がそれに相当する部分です。ページを指定したら「object_list」に一覧データが入っているので、あとはそいつをfor文で回せばOKです。かっこの中を2にすれば11件目から20件目のデータを表示できます。
存在しないページ数を指定するとエラーになります。例えば今、ブログ記事が全部で80件ある場合。10件ずつ取得したら最後は8ページになります。この時、10ページとか100ページを指定すると「おめえに返すデータはねえ」と言われてしまいます。なのでマックスより多いページ数が指定されたら強制的に最後のページを表示したりエラーメッセージを出すなどの処理が必要になります。
全部で何ページあるかという情報はpage変数の中に入っています。
これでマックスのページ数が取れるので、それより大きいかどうかの判定をかませばエラーは回避できます。ちなみに取得した全データの件数は「page.count」に入っています。
例えばこんな感じでパラメータを渡してビュー側で受け取れるようにします。
current変数にGETパラメータの値を入れるようにしました。これでリンクに応じたページ数が表示できるようになります。パラメータがなければ1ページを表示してマックスよりも大きいページ数が入っていたらマックスページに修正する処理も入れてみました。まあ、もっと厳密にやるなら0以下の数字や数字以外の文字がパラメータに入ってきた場合の対策も必要ですが、今回はそこまではやらない方向で。
ページ情報が取得できたらテンプレートにデータを渡します。ここではobject_listで一覧データだけを変数に入れて渡さずにオブジェクトを丸ごと渡していますが、これは「page.page(current)」の中に前後のページに関する情報も入っているからです。リンクを作成する際にそれらの情報を使用します。
これでテンプレート側で記事の一覧と前後のページのリンクを表示できます。
「previous_page_number」に前のページの数字、「next_page_number」に次のページの数字が入っています。これらは前後にページが存在しないとエラーになってしまうので、「has_previous」や「has_next」でページが存在するか確かめる必要があります。
ちなみにこれらは全て値ではなく関数なのでビュー側で同じ処理をやる場合はかっこをお忘れなく。
こんな感じですね。100件のデータを10件ずつ取ってくる場合、1ページ目ならstart_indexに1、end_indexに10が入っています。2ページ目ならstart_indexに11、end_indexに20が入っていることになりますね。
このstart_indexとend_indexも関数なのでビューで使う場合はかっこが必要になります。
おおむねこんなとこでしょうか。他にも用意されている機能があるみたいですが、上記の関数が使えれば大半のページングは作れると思います。
使い方はそんなに難しくないと思います。どこに何の情報が入っているかさえ分かればリンクも簡単に作れる。可愛いを作るよりははるかに簡単な作業でしょう。
使い勝手の良さもCakePHPとさほど変わらないと思います。まあGETパラメータにページ数以外の情報(author_idとか)も入れてフィルタリングするような仕組みを考える場合は、パラメータ込みでリンクを生成してくれるCakePHPの方が楽かもしれませんが、僕の経験では検索条件が複雑な場合は手動で作成した方がやりやすい場合もたまにあるから、一概にどっちの方が優れているとは言えない感じですね。
そんなわけで今日はDjangoのページネーションの機能を使ってみたいと思います。
公式のドキュメントはこちら。
https://docs.djangoproject.com/ja/4.0/topics/pagination/
通常の一覧データ取得
前置きは不要かもしれませんが、一応ページネーションを使わずに一覧データを取ってくる方法を見ておきます。例えば今、blogsというテーブルにブログ記事の一覧データが入っているとします。この一覧をシンプルに全件取得したい場合はallを使います。
data = Blogs.objects.all()
何かしら条件を指定したい場合はfilterを使います。
data = Blogs.objects.filter(author_id = 1)
これはauthor_idが1のデータだけを取ってきたい場合ですね。author_idは記事の作成者のIDだと思ってください。
この記事データを作成日時が新しい順に取ってきたい場合はこうなります。
data = Blogs.objects.filter(author_id = 1).order_by('-created')
ここではcreatedに作成日時が入っているという前提です。フィールド名にマイナスをつけるとdescになります。
他にもいろんな書き方がありますが、だいたいこんな感じでデータを取ってくることができます。
ページネーション
では本題のページネーションの実装方法について。まずは絞り込みの条件を指定せず、全データを10件ずつに分けてみたいと思います。from django.core.paginator import Paginator
class index(TemplateView) :
def get(self, request, *args, **kwargs) :
query = Blogs.objects.all()
page = Paginator(query, 10)
data = page.page(1).object_list
まずページネーションを使用するには「Paginator」というモジュールが必要になります。使い方は簡単で、このモジュールにクエリと件数を突っ込むだけです。
クエリには通常の一覧を取得する時のallやfilterをそのままセットすればOKです。order_byなどもそのまま入れて大丈夫。
from django.core.paginator import Paginator
class index(TemplateView) :
def get(self, request, *args, **kwargs) :
query = Blogs.objects.filter(author_id = 1).order_by('-created')
page = Paginator(query, 10)
data = page.page(1).object_list
これでauthor_idが1のデータのうち、作成日時が新しいものを10件取ってきてくれます。
page変数には一覧だけじゃなくてページネーションに関する情報がいろいろ入っているので、そのままfor文などでデータを表示することはできません。
一覧を表示するには、まず一覧の何ページ目を表示したいかを指定します。上記の「page.page(1)」がそれに相当する部分です。ページを指定したら「object_list」に一覧データが入っているので、あとはそいつをfor文で回せばOKです。かっこの中を2にすれば11件目から20件目のデータを表示できます。
存在しないページ数を指定するとエラーになります。例えば今、ブログ記事が全部で80件ある場合。10件ずつ取得したら最後は8ページになります。この時、10ページとか100ページを指定すると「おめえに返すデータはねえ」と言われてしまいます。なのでマックスより多いページ数が指定されたら強制的に最後のページを表示したりエラーメッセージを出すなどの処理が必要になります。
全部で何ページあるかという情報はpage変数の中に入っています。
page.num_pages
これでマックスのページ数が取れるので、それより大きいかどうかの判定をかませばエラーは回避できます。ちなみに取得した全データの件数は「page.count」に入っています。
前後のページのリンク
CakePHPだとPaginatorヘルパーの中に前のページや次のページのリンクを生成してくれる関数があるのですが、たぶんDjangoにはそれがないので(あったらすまん)、リンクは自分で作る必要があります。といってもGETパラメータにページ数を渡すなどすれば良いだけなので、場合によってはCakePHPよりも楽かもしれない。<a href="/blogs?page=1">1ページ目</a>
例えばこんな感じでパラメータを渡してビュー側で受け取れるようにします。
from django.core.paginator import Paginator
class index(TemplateView) :
def get(self, request, *args, **kwargs) :
#パラメータからページ数を取得(パラメータがなければ1)
current = int(request.GET.get('page', 1))
query = Blogs.objects.filter(author_id = 1).order_by('-created')
page = Paginator(query, 10)
#マックスより大きい数字が指定されたら数字を入れ直す
if current <= page.num_pages :
current = page.num_pages
obj = page.page(current)
return render(request, 'index.html', {'obj': obj})
current変数にGETパラメータの値を入れるようにしました。これでリンクに応じたページ数が表示できるようになります。パラメータがなければ1ページを表示してマックスよりも大きいページ数が入っていたらマックスページに修正する処理も入れてみました。まあ、もっと厳密にやるなら0以下の数字や数字以外の文字がパラメータに入ってきた場合の対策も必要ですが、今回はそこまではやらない方向で。
ページ情報が取得できたらテンプレートにデータを渡します。ここではobject_listで一覧データだけを変数に入れて渡さずにオブジェクトを丸ごと渡していますが、これは「page.page(current)」の中に前後のページに関する情報も入っているからです。リンクを作成する際にそれらの情報を使用します。
{% for data in obj.object_list %}
一覧の記事データを表示
{% endfor %}
{% if obj.has_previous %}
<a href="blogs?page={{obj.previous_page_number}}">前のページ></a>
{% endif %}
{% if obj.has_next %}
<a href="blogs?page={{obj.next_page_number}}">次のページ></a>
{% endif %}
これでテンプレート側で記事の一覧と前後のページのリンクを表示できます。
「previous_page_number」に前のページの数字、「next_page_number」に次のページの数字が入っています。これらは前後にページが存在しないとエラーになってしまうので、「has_previous」や「has_next」でページが存在するか確かめる必要があります。
ちなみにこれらは全て値ではなく関数なのでビュー側で同じ処理をやる場合はかっこをお忘れなく。
if obj.has_previous() :
prev = obj.previous_page_number()
if obj.has_next() :
next = obj.next_page_number()
表示中のデータが何件目か
取得したデータが何件目〜何件目なのかという数字が欲しいときは「start_index」や「end_index」を使います。{{obj.start_index}}件目〜{{obj.end_index}}件目"
こんな感じですね。100件のデータを10件ずつ取ってくる場合、1ページ目ならstart_indexに1、end_indexに10が入っています。2ページ目ならstart_indexに11、end_indexに20が入っていることになりますね。
このstart_indexとend_indexも関数なのでビューで使う場合はかっこが必要になります。
start = obj.start_index()
end = obj.end_index()
おおむねこんなとこでしょうか。他にも用意されている機能があるみたいですが、上記の関数が使えれば大半のページングは作れると思います。
使い方はそんなに難しくないと思います。どこに何の情報が入っているかさえ分かればリンクも簡単に作れる。可愛いを作るよりははるかに簡単な作業でしょう。
使い勝手の良さもCakePHPとさほど変わらないと思います。まあGETパラメータにページ数以外の情報(author_idとか)も入れてフィルタリングするような仕組みを考える場合は、パラメータ込みでリンクを生成してくれるCakePHPの方が楽かもしれませんが、僕の経験では検索条件が複雑な場合は手動で作成した方がやりやすい場合もたまにあるから、一概にどっちの方が優れているとは言えない感じですね。