DjangoのListViewクラスを使って一覧ページを作る時、htmlファイルにページネーションを作成する事が多いと思います。今回は私がよく使う使い勝手の良いページネーション用のhtmlコードを紹介します。
サンプルコード
以下がサンプルコードです。※Bootstrapのclassを使用しています。
{% load core_tags %}
{% if page_obj.has_previous or page_obj.has_next %}
<nav>
<ul class="pagination">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href='{{ request.path }}?page=1{{ request.GET.items|get_search_query_param }}'>
<span>最初</span>
</a>
</li>
<li class="page-item">
<a class="page-link"
href='{{ request.path }}?page={{ page_obj.previous_page_number }}{{ request.GET.items|get_search_query_param }}'
>{{ page_obj.previous_page_number }}</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link">
<span>最初</span>
</a>
</li>
{% endif %}
<li class="page-item active">
<a class="page-link"
href='{{ request.path }}?page={{ page_obj.number }}{{ request.GET.items|get_search_query_param }}'>{{ page_obj.number }}</a>
</li>
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link"
href='{{ request.path }}?page={{ page_obj.next_page_number }}{{ request.GET.items|get_search_query_param }}'>{{ page_obj.next_page_number }}</a>
</li>
<li class="page-item">
<a class="page-link" href='{{ request.path }}?page={{ paginator.num_pages }}{{ request.GET.items|get_search_query_param }}'>
<span>最後</span>
</a>
</li>
{% else %}
<li class="page-item disabled">
<a class="page-link">
<span>最後</span>
</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
条件
このサンプルコードは以下の条件を満たすViewクラスを使用している必要があります。
- ListViewクラスを継承している
- paginate_byを定義している
読み込み方法
上記のコードをpagination.htmlとして保存します。ページネーションを追加したいhtmlファイルに、includeを使って以下のように読み込ませます。
{% include 'pagination.html' %}
‘pagination.html’部分はファイルパスに応じて適宜変更してください。
表示例
1ページしかない場合はページネーションを表示しません。
複数ページある場合は表示しているページにより表示が変わります。
1ページ目を表示している場合
最初のページを表示している場合は’最初’の部分はクリックできないようにしています。該当コードを削除すれば非表示にすることもできます。
中間のページを表示している場合
表示しているページの前後のページを表示しています。
最後のページを表示している場合
1ページ目を表示している場合と同様です。
解説
{% load core_tags %}
独自実装したテンプレートタグを読み込んでいます。今回はファイル名をcore_tags.pyとします。
{% if page_obj.has_previous or page_obj.has_next %}
1ページのみか複数ページあるかどうかを判定しています。
<a class="page-link" href='{{ request.path }}?page={{ page_obj.previous_page_number }}{{ request.GET.items|get_search_query_param }}'>
{{ request.path }}
現在のURLを取得しています。
{{ page_obj.previous_page_number }}
前のページ数を取得しています。他のaタグでは後ろのページ数や最後のページ数を取得する変数を指定しています。
{{ request.GET.items|get_search_query_param }}
request.GET.itemsにはクエリパラメーターが辞書形式で定義されています。検索条件を指定できるページの場合、クエリパラメータを指定する必要がありあます。
クエリパラメーターの中には現在のページ数を表す’page’パラメーターがあります。’page’はListViewクラスで表示したいページ数を取得するためのキーとして定義されています。
page_kwarg = "page"
ページネーションで’page’パラメーターを個別で指定したいため、’page’パラメーターを除くクエリパラメーターを取得するためのテンプレートタグを定義する必要があります。
テンプレートタグを定義するcore_tags.pyに以下の関数を作成します。
@register.filter
def get_search_query_param(value):
result = ''
for k, v in value:
if k != 'page':
result += f'&{k}={v}'
return result
処理速度を重視したい場合はViewクラス内で定義すると良いです(その場合はデコレーターは不要です)
応用編 表示数をクエリパラメーターで指定する
ユーザーが表示数を変更できるタイプのサイトはよくあります。
応用編としてデフォルトの表示数が10、指定可能な表示数が10~100のページに対応したページネーションを作成する方法を解説します。
表示数を動的に変更したい場合は次のようにget_paginate_by()をオーバーライドします。ここではpage_sizeというクエリパラメーターを使う想定です。
def get_paginate_by(self, queryset):
page_size = self.request.GET.get('page_size', 10)
try:
return page_size if 10 <= int(page_size) <= 100 else 10
except ValueError:
return 10
この場合、テンプレートタグは次のようにpage_sizeも除外するように改変します。
@register.filter
def get_search_query_param(value):
result = ''
for k, v in value:
if k not in ['page', 'page_size']:
result += f'&{k}={v}'
return result
aタグのhrefにpage_sizeのクエリパラメーターを追加します。{{paginator.per_page}}で現在の表示数を取得できます。
href='{{ request.path }}?page={{ page_obj.previous_page_number }}&page_size={{paginator.per_page}}{{ request.GET.items|get_search_query_param }}
まとめ
ListView関数やPaginationクラスは非常に便利ですが、ページネーション用のhtmlは独自実装する必要があります。
今回紹介したサンプルはどのURLでも対応しています。最初のページ、前後のページ、最後のページへのリンクの作成方法や、クエリパラメーターを保持する方法も紹介しているのでアレンジしやすいと思います。