Djnagoで、1ページあたりの件数が多くなった時にページングする方法を調べた時のメモです。
なお、ページングという用語については、
- ページング(Paging)
- ページネーション(Pagination)
- ページャー(Pager)
などの呼び方があるものの、今回は以下に従いページング
と呼ぶことにします。
一覧リストにおけるページングについて : circumstance evidence
環境
2018/8/26 追記
この記事で取り上げた django-pure-pagination
ですが、
- 現在メンテナンスされていない
- Django2.1以降に対応していない
の問題があるようです。
そのため、新規に導入する場合には、別のライブラリを使ったほうが良いです。
2018/8/26 追記 ここまで
作成するページングのタイプ
現在のページと、最大ページ数を表示するタイプ
Djangoドキュメントのサンプルのようなタイプです。
Pagination - Using Paginator in a view | Django documentation | Django
ページ番号をすべて表示するタイプ
ページ数が多い場合に、ページ番号の一部を省略するタイプ
Django Admin siteのようなものです。
準備
アプリ作成まで
d:\Sandbox\django_pagination_sample>virtualenv -p c:\python35-32\python.exe env d:\Sandbox\django_pagination_sample>env\Scripts\activate (env) d:\Sandbox\django_pagination_sample>pip install django (env) d:\Sandbox\django_pagination_sample>django-admin startproject myproject . (env) d:\Sandbox\django_pagination_sample>python manage.py startapp myapp
settings.py
INSTALLED_APPSにmyapp
アプリを追加します。
INSTALLED_APPS = [
...
'myapp',
]
myapp/models.py
ListViewに表示するModelを作成します。
from django.db import models class Article(models.Model): title = models.CharField('title', max_length=255) content = models.CharField('content', max_length=255)
myapp/fixtures/initial_data.json
ページングできるよう、15件ほどのデータをfixtureとして用意しておきます。
[ { "model": "myapp.Article", "pk": 1, "fields": { "title": "title-1", "content": "content-1" } }, ... ]
myproject/urls.py
Djangoアプリのurlsをincludeし、example.com/mysite/<DjangoアプリのurlpatternsによるURL>
という形のURLにします。
from django.conf.urls import url, include from django.contrib import admin urlpatterns = [ ... url(r'^mysite/', include('myapp.urls', namespace='my')), ]
マイグレーションとデータ投入
あとはマイグレーションとデータ投入を行うことで、準備ができました。
(env) d:\Sandbox\django_pagination_sample>python manage.py makemigrations (env) d:\Sandbox\django_pagination_sample>python manage.py migrate (env) d:\Sandbox\django_pagination_sample>python manage.py loaddata initial_data
現在のページと、最大ページ数を表示するタイプ
Djangoドキュメントのサンプル通りに実装してみます。
- Pagination | Django documentation | Django
- How do I use pagination with Django class based generic ListViews? - Stack Overflow
myapp/views.py
今回はclass-based viewのListViewを使います。クラスメンバpaginate_by
で1ページあたりの件数を指定します。
class PrevNextPaginatorView(ListView): model = models.Article template_name = 'myapp/prev_next.html' paginate_by = 1
myapp/urls.py
名前付URLパターンを指定します。
urlpatterns = [ url(r'^prev-next/$', views.PrevNextPaginatorView.as_view(), name='prev_next'), ]
myapp/templates/myapp/prev_next.html
表示するテンプレートを作成します。
プロジェクトのurls.pyでアプリのurls.pyをincludeしているため、example.com/prev-next/?page=
ではなくexample.com/mysite/prev-next/?page=
とする必要があります。
そのため、テンプレートタグのurl
+ 名前付URLパターンを使います。
Built-in template tags and filters - url | Django documentation | Django
{% if is_paginated %} <div class="pagination"> <span class="step-link"> {% if page_obj.has_previous %} <a href="{% url 'my:prev_next' %}?page={{ page_obj.previous_page_number }}">previous</a> {% endif %} <span class="page-current"> Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}. </span> {% if page_obj.has_next %} <a href="{% url 'my:prev_next' %}?page={{ page_obj.next_page_number }}">next</a> {% endif %} </span> </div> {% endif %}
動作確認
1ページ目
2ページ目以降
Modelのpkの降順でページング
デフォルトではModelのpkの昇順での表示となります。
pkの降順とするために、今回はDjango1.8よりViewに追加されたordering
を使ってみます。
Multiple object mixins | Django documentation | Django
class PrevNextPaginatorByDescView(ListView): model = models.Article template_name = 'myapp/prev_next_desc.html' paginate_by = 1 ordering = '-pk'
あとはテンプレートとurls.pyに設定することで、以下のように降順で表示できます。
ページ番号をすべて表示するタイプ
以下を参考に、ページ番号をすべて表示するページングを作成します。
Python Django入門 (5) - Qiita
必要なものは
- myapp/views.py
- myapp/urls.py
- myapp/templates/myapp/all.html
- myapp/static/myapp/css/pagination.css
- ページングで
<ul>
タグを使っていることから、CSSのinlineを使うため
- ページングで
ですが、viewsとurlsは特に変更がないため、ここでの記載は省略します。
myapp/templates/myapp/all.html
if文を書きたい時のテンプレートタグとして、
if hoge == fuga
を使うifequal hoge fuga
を使う
どちらを使うのが良いのかを調べてみたところ、公式ドキュメントに
The ifequal and ifnotequal tags will be deprecated in a future release.
Built-in template tags and filters - ifequal and ifnotequal | Django documentation | Django
との記載があったため、今回はif
を使うようにしました。
また、static
を使って、CSSファイルを読み込むようにします。
<head> ... {% load staticfiles %} <link href={% static 'myapp/css/pagination.css' %} rel='stylesheet'> </head> <body> ... {% if is_paginated %} <ul class="pagination"> {% if page_obj.has_previous %} <li><a href="{% url 'my:all' %}?page={{ page_obj.previous_page_number }}">previous</a></li> {% endif %} {% for link_page in page_obj.paginator.page_range %} {% if link_page == page_obj.number %} <li class="active">{{ link_page }}</li> {% else %} <li><a href="{% url 'my:all' %}?page={{ link_page }}">{{ link_page }}</a></li> {% endif %} {% endfor %} {% if page_obj.has_next %} <li><a href="{% url 'my:all' %}?page={{ page_obj.next_page_number }}">next</a></li> {% endif %} </ul> {% endif %} </body>
myapp/static/myapp/css/pagination.css
ul.pagination li { display: inline }
動作確認
ページ数が多い場合に、ページ番号の一部を省略するタイプ
Admin siteのページングを確認
DjangoのAdmin siteのページング方法が気になったため、Admin siteでArticleを表示できるようにします。
なお、Admin siteの1ページあたりの件数はデフォルトが100件のため、1ページ1件で表示できるように変更します。
Django admin pagination question - Stack Overflow
myapp/admin.py
from django.contrib import admin from . import models class ArticleAdmin(admin.ModelAdmin): list_per_page = 1 admin.site.register(models.Article, ArticleAdmin)
Admin siteにログインするため、superuserも作っておきます。
(env) d:\Sandbox\django_pagination_sample>python manage.py createsuperuser --username=admin --email=admin@example.com Password: Password (again): Superuser created successfully.
Admin siteにログインして動作確認しました。
今回のアプリもこんな感じの表示を目指します。
実装方法の調査
自作する方法は以下が参考になりましたが、いろいろと手間そうでした。
- 管理画面で使われているページング表示を通常のページで使う - 偏った言語信者の垂れ流し
- Yet another paginator (digg style) | blog.elsdoerfer.name
- Digg-style pagination in Django | Ryan Kanno: The diary of an Enginerd in Hawaii
一方、Djangoパッケージを調べてみたところ、いくつかありました。
Django Packages : Pagination
その中でも、django-pagination
を使う記事を見かけたものの、現在ではメンテナンスされてなさそうでした。
django-pagination 1.0.7 : Python Package Index
そのため、
- Python3に対応
- 最近メンテナンスされているもの
という点から、django-pure-pagination
を試してみることにました。
jamespacileo/django-pure-pagination - Python
2018/8/26 追記
再掲となりますが django-pure-pagination
は
- 現在メンテナンスされていない
- Django2.1以降に対応していない
の問題があるようです。
そのため、新規に導入する場合には、別のライブラリを使ったほうが良いです。
2018/8/26 追記 ここまで
django-pure-paginationを試す
pipでインストールします。
(env) d:\Sandbox\django_pagination_sample>pip install django-pure-pagination
django-pure-paginationのREADMEに従い、必要なファイルに追記します。
myproject/settings.py
INSTALLED_APPS
とPAGINATION_SETTINGS
の設定を行います。
INSTALLED_APPS = [ ... 'myapp', 'pure_pagination', # for django-pure-pagination ] ... # for django-pure-pagination settings PAGINATION_SETTINGS = { 'PAGE_RANGE_DISPLAYED': 2, 'MARGIN_PAGES_DISPLAYED': 2, 'SHOW_FIRST_PAGE_WHEN_INVALID': True, }
myapp/views.py
PaginationMixin
を使ったViewを作成します。
from django.views.generic import ListView from pure_pagination.mixins import PaginationMixin from . import models ... class PurePaginatorView(PaginationMixin, ListView): model = models.Article template_name = 'myapp/pure.html' paginate_by = 1
myapp/urls.py
urlpatterns = [ ... url(r'^pure/$', views.PurePaginatorView.as_view(), name='pure'), ]
myapp/templates/myapp/pure.html
README通りだとどこが省略された部分を表示しているのか分かりづらかったため、(省略)
という文字を入れるようにしました。
<head> ... {% load staticfiles %} <link href={% static 'myapp/css/pagination.css' %} rel='stylesheet'> </head> <body> ... {% if is_paginated %} <ul class="pagination"> {% if page_obj.has_previous %} <li><a href="{% url 'my:pure' %}?{{ page_obj.previous_page_number.querystring }}">previous</a></li> {% endif %} {% for link_page in page_obj.pages %} {% if link_page %} {% if link_page == page_obj.number %} <li class="active">{{ link_page }}</li> {% else %} <li><a href="{% url 'my:pure' %}?{{ link_page.querystring }}">{{ link_page }}</a></li> {% endif %} {% else %} {# 省略された部分の表示内容 #} <li>(省略)</li> {% endif %} {% endfor %} {% if page_obj.has_next %} <li><a href="{% url 'my:pure' %}?{{ page_obj.next_page_number.querystring }}">next</a></li> {% endif %} </ul> {% endif %} </body>
動作確認
1ページ目の表示
省略された時の表示
django-pure-paginationとBootstrapを組み合わせる
BootstrapにはPaginationのコンポーネントがありますので、それを組み合わせてみます。
といってもそれほど修正する部分は多くなく、上記のテンプレートに、
- Bootstrap用のCSSとJavaScriptを組み込む (今回はCDNを使用)
<li>
タグにclassを加える<li>
タグの中に<a>
タグを用意する
を加えるだけでBootstrapでの表示ができます。
myapp/templates/myapp/bootstrap_pure.html
<!DOCTYPE html> <html lang="ja" xmlns="http://www.w3.org/1999/xhtml"> <head> ... <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" integrity="sha256-7s5uDGW3AHqw6xtJmNNtr+OBRJUlgkNJEo78P4b0yRw= sha512-nNo+yCHEyn0smMxSswnf/OnX6/KwJuZTlNZBjauKhTK0c+zT+q5JOCx0UFhXQ6rJR9jg6Es8gPuD2uZcYDLqSw==" crossorigin="anonymous"> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha256-KXn5puMvxCw+dAYznun+drMdG1IFl3agK0p/pqT9KAo= sha512-2e8qq0ETcfWRI4HJBzQiA3UoyFk6tbNyG+qSaIBZLyW9Xf3sWZHN/lxe9fTh1U45DpPf07yj94KsUHHWe4Yk1A==" crossorigin="anonymous"></script> </head> <body> ... {% if is_paginated %} <ul class="pagination"> {% if page_obj.has_previous %} <li><a href="{% url 'my:bootstrap_pure' %}?{{ page_obj.previous_page_number.querystring }}">previous</a></li> {% endif %} {% for link_page in page_obj.pages %} {% if link_page %} {% if link_page == page_obj.number %} <li class="active"><a href="">{{ link_page }}</a></li> {% else %} <li><a href="{% url 'my:bootstrap_pure' %}?{{ link_page.querystring }}">{{ link_page }}</a></li> {% endif %} {% else %} {# 省略された部分の表示内容 #} <li class="disabled"><a href="#">(省略)</a></li> {% endif %} {% endfor %} {% if page_obj.has_next %} <li><a href="{% url 'my:bootstrap_pure' %}?{{ page_obj.next_page_number.querystring }}">next</a></li> {% endif %} </ul> {% endif %} </body> </html>
動作確認
ページング部分の共用
以下を参考に、ページング部分を別テンプレートとして切り出して、再利用できるようにしてみます。
Django のページネイトを使い回しできそうな感じにしてみるメモ - 牌語備忘録 -pygo
共用するために、
- ページング部分のテンプレート (share_include.html)
- ページング部分のテンプレートをincludeするテンプレート (share_base.html)
の2つを作成します。
myapp/templates/myapp/share_include.html
Bootstrapで使ったテンプレートを流用し、<a>
タグの中身を
- includeする側で引数
url_param
に、名前付きURLパラメータを渡してもらう - テンプレートタグの
url
にて、url_param
を使う
という内容で修正します。
{% if is_paginated %} <ul class="pagination"> {% if page_obj.has_previous %} <li><a href="{% url url_param %}?{{ page_obj.previous_page_number.querystring }}">previous</a></li> {% endif %} ... </ul> {% endif %}
myapp/templates/myapp/share_base.html
共用ページング部分を入れたい場所で、テンプレートタグのinclude
を使います。
また、with
を使って名前付URLパラメータを渡します。
Built-in template tags and filters - include | Django documentation | Django
今回は、tableの上下に共用のページングを表示してみます。
... <div id="main"> <h1>Shared pagination Page</h1> {% include "myapp/share_include.html" with url_param="my:share" %} <table> ... </table> {% include "myapp/share_include.html" with url_param="my:share" %} </div> ...
動作確認