Django(ジャンゴ)には、ページ遷移を行うためのPaginatorクラスが用意されている。Paginatorクラスについては、以下のサイトを参照のこと。
https://en-junior.com/paginator/
今回は、作成済の「Django」を利用したアプリケーションに、Paginatorクラスを利用したページ遷移機能を追加してみたので、そのサンプルプログラムを共有する。
サンプルプログラムの作成
作成したサンプルプログラムの構成は、以下の通り。
なお、上記の赤枠は、前提条件のプログラムから変更したプログラムである。
demoフォルダ下、templatesフォルダ下の一覧画面(list.html)の内容は以下の通りで、ページ遷移を行うためのリンクを追加している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>一覧画面</title> <!-- BootStrap CSS/JSファイルの読み込み --> {% load static %} <link rel="stylesheet" href="{% static 'demo/css/bootstrap.min.css' %}"> <script src="{% static 'demo/js/bootstrap.bundle.min.js' %}"></script> </head> <body> <!-- 画面サイズを画面幅いっぱい(container-fluid)とし、marginを上下左右に設定 --> <div class="container-fluid m-4"> <!-- タイトル行の文字サイズを設定 --> <p class="fs-4">ユーザーデータテーブル(user_data)の検索データ</p> <!-- 表の幅を、画面横幅の11/12の長さに設定 --> <div class="col-11"> <!-- テーブルの1行おきに背景色を設定 --> <table class="table table-striped"> <!-- テーブルのタイトル行を水色に設定 --> <tr class="table-info"> <th>ID</th> <th>名前</th> <th>生年月日</th> <th>性別</th> <th></th> <th></th> </tr> <!-- 検索条件に合うデータを一覧画面に表示 --> {% for user in user_list %} <tr> <td>{{ user.id }}</td> <td>{{ user.name }}</td> <td>{{ user.birth_year }}年 {{ user.birth_month }}月 {{ user.birth_day }}日 </td> <td> {% if user.sex|stringformat:"s" == "1" %} 男 {% elif user.sex|stringformat:"s" == "2" %} 女 {% endif %} </td> <td> <a href="{% url 'update' user.id %}">更新</a> </td> <td> <a href="{% url 'delete' user.id %}">削除</a> </td> </tr> {% endfor %} </table> </div> <!-- 表の幅を、画面横幅の5/12の長さに設定し、marginを上に設定 --> <div class="col-5 mt-5"> <!-- ページ遷移用のリンクを設定 --> <!-- テーブルの枠線を非表示に設定 --> <table class="table table-borderless"> <tr> <td> <!-- 現在ページ数が1ページ目でない場合 --> {% if current_page_num != 1 %} <!-- 次ページ数に1を設定し、ページ遷移を行う --> <a href="{% url 'move_page' 1 %}">先頭へ</a> {% else %} 先頭へ {% endif %} </td> <td> <!-- 現在ページ数が1ページ目でない場合 --> {% if current_page_num != 1 %} <!-- 次ページ数に現在ページ数-1を設定し、ページ遷移を行う --> <a href="{% url 'move_page' current_page_num|add:'-1' %}">前へ</a> {% else %} 前へ {% endif %} </td> <td>{{ current_page_num }} / {{ all_page_num }}</td> <td> <!-- 現在ページ数が最終ページ数と一致しない場合 --> {% if current_page_num != all_page_num %} <!-- 次ページ数に現在ページ数+1を設定し、ページ遷移を行う --> <a href="{% url 'move_page' current_page_num|add:'1' %}">次へ</a> {% else %} 次へ {% endif %} </td> <td> <!-- 現在ページ数が最終ページ数と一致しない場合 --> {% if current_page_num != all_page_num %} <!-- 次ページ数に最終ページ数を設定し、ページ遷移を行う --> <a href="{% url 'move_page' all_page_num %}">最後へ</a> {% else %} 最後へ {% endif %} </td> </tr> </table> </div> <!-- action属性のURLで、(demoフォルダ内)urls.pyの画面遷移先のname属性の値を指定している --> <form action="{% url 'input' %}" method="post"> <!-- 下記csrf_tokenは、CSRF対策を行うことでform送信時エラーを防ぐために設定 --> {% csrf_token %} <!-- marginを上に設定 --> <div class="mt-5"> <!-- ボタンの色を青色に設定 --> <input type="submit" name="next" value="データ追加" class="btn btn-primary"/> <!-- marginを左に設定 --> <span class="ms-4"> <input type="button" name="back" value="検索画面に戻る" class="btn btn-primary" onclick="location.href={% url 'index' %}"/> </span> </div> </form> </div> </body> </html> |
なお、上記ソースコードでは、Djangoテンプレート内で加算を行うための「add」フィルターを利用している。Djangoテンプレート内で四則演算を行う方法については、以下のサイトを参照のこと。
https://freeheroblog.com/django-calculation/
また、demoフォルダ下、urls.pyの内容は以下の通りで、ページ遷移を行うためのURL(move_page)を追加している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | from django.urls import path from . import views urlpatterns = [ path('', views.IndexView.as_view(), name='index'), path('list', views.UserListView.as_view(), name='list'), path('move_page/<int:next_page>', views.MovePageView.as_view(), name='move_page'), path('search', views.SearchView.as_view(), name='search'), path('input', views.InputView.as_view(), name='input'), path('confirm', views.ConfirmView.as_view(), name='confirm'), path('regist', views.RegistView.as_view(), name='regist'), path('complete', views.CompleteView.as_view(), name='complete'), path('<int:pk>/update', views.UserUpdateView.as_view(), name='update'), path('<int:pk>/delete', views.UserDeleteView.as_view(), name='delete'), ] |
さらに、demoフォルダ下、view.pyの内容は以下の通りで、検索処理(SearchView)で検索後に、Paginatorオブジェクトを利用したページ遷移処理を行っている。また、ページ遷移処理(MovePageView)を追加すると共に、一覧画面に戻ってきた処理(UserListView)も修正している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 | from django.shortcuts import render from .forms import InputModelForm from .forms import SearchForm from .models import UserData from django.db.models import Max from datetime import datetime as dt from django.views.generic import TemplateView from django.views.generic import FormView from django.urls import reverse_lazy from django.views.generic import ListView from django.views.generic.edit import DeleteView from django.views.generic.edit import UpdateView from django.db.models import Q from django.core.cache import cache from django.core.paginator import Paginator # クラス定数を定義 SEX_LABEL_IDX = 1 # 性別のラベルを取得するためのインデックス PER_PAGE = 2 # 一覧画面に表示する1ページあたりのオブジェクト数 class InputView(TemplateView): """ 入力画面を表示するView """ template_name = 'demo/input.html' def get_context_data(self): """ 入力画面に渡すフォーム値を初期化 """ context = super().get_context_data() context['form'] = InputModelForm() return context def post(self, request): """ 入力画面にpostリクエストで遷移 """ return render(request, self.template_name, self.get_context_data()) class ConfirmView(FormView): """ 入力画面で確認ボタンが押下された時の処理を定義するView """ template_name = 'demo/input.html' # form_invalidメソッド実行時に表示する画面 form_class = InputModelForm # 利用するFormクラス success_url = 'demo/confirm.html' # form_validメソッド実行時の画面遷移先 def get_context_data(self, form): """ 入力チェックエラー時に、入力画面にuser_idの値を渡す """ context = super().get_context_data() context['user_id'] = self.request.POST['user_id'] return context def form_valid(self, form): """ フォームの入力チェックを行い、エラーがない場合 """ # 確認画面で表示するための入力値(性別)の値を生成 lbl_sex = UserData.sex_data[int(form.cleaned_data['sex']) - 1][SEX_LABEL_IDX] # 確認画面に渡す各変数を定義し、確認画面に遷移 context = { 'input_form': form, 'lbl_sex': lbl_sex, 'lbl_checked': '確認済', 'user_id': self.request.POST['user_id'] } return render(self.request, self.success_url, context) def form_invalid(self, form): """ フォームの入力チェックを行い、エラーがある場合 """ # フォーム値はそのままで入力画面に戻る return super().form_invalid(form) class RegistView(FormView): """ 確認画面でボタンが押下された時の処理を定義するView """ template_name = 'demo/input.html' # 戻るボタン押下時に表示する画面 form_class = InputModelForm # 利用するFormクラス success_url = reverse_lazy('complete') # 送信ボタン押下後の画面遷移先 def get_context_data(self, form): """ 戻るボタン押下時に、入力画面にフォーム値を渡す """ context = super().get_context_data() context['input_form'] = form context['user_id'] = self.request.POST['user_id'] return context def form_valid(self, form): # 送信ボタンが押下された場合 if "send" in self.request.POST: # 入力データを生成 user_data = UserData() # user_idの値をリクエストから取得 user_id = self.request.POST['user_id'] # user_idが設定済(更新)の場合 if user_id: user_data.id = int(user_id) # user_idが未設定(追加)の場合 else: # USER_DATAテーブルに登録されているidの最大値を取得 max_id_dict = UserData.objects.all().aggregate(Max('id')) # USER_DATAテーブルに登録されているデータが無い場合、id=1とする user_data.id = (max_id_dict['id__max'] or 0) + 1 # 入力データを保存 input_form = InputModelForm(self.request.POST, instance=user_data) input_form.save() # 完了画面に遷移 return super().form_valid(form) # 戻るボタンが押下された場合 elif "back" in self.request.POST: # 確認画面でのフォーム値を入力画面に渡す return super().form_invalid(form) class CompleteView(TemplateView): """ 完了画面を表示するView """ template_name = 'demo/complete.html' class UserDeleteView(DeleteView): """ 一覧画面で削除リンクが押下された時の処理を定義するView """ template_name = 'demo/delete_confirm.html' # 削除確認画面を表示 model = UserData # UserDataモデル(user_dataテーブルと紐づける) success_url = reverse_lazy('index') # 削除ボタン押下後の画面遷移先 class UserUpdateView(UpdateView): """ データ更新時の入力画面を表示するView """ template_name = 'demo/input.html' model = UserData # UserDataモデル(user_dataテーブルと紐づける) form_class = InputModelForm # 利用するFormクラス class IndexView(TemplateView): """ 検索画面を表示するView """ template_name = 'demo/search.html' def get_context_data(self): """ 検索画面に渡すフォーム値を設定 """ context = super().get_context_data() # キャッシュに検索条件データがあれば設定し、キャッシュをクリア context['form'] = SearchForm(cache.get('search_form')) cache.clear() return context class SearchView(FormView): """ 検索画面で検索ボタンが押下された時の処理を定義するView """ template_name = 'demo/search.html' # form_invalidメソッド実行時に表示する画面 form_class = SearchForm # 利用するFormクラス success_url = 'demo/list.html' # form_validメソッド実行時の画面遷移先 def get_context_data(self, form): context = super().get_context_data() return context def form_valid(self, form): """ フォームの入力チェックを行い、エラーがない場合、検索処理を行う """ search_name = Q() # 検索条件(名前) search_birthday_from = Q() # 検索条件(生年月日From) search_birthday_to = Q() # 検索条件(生年月日To) search_sex = Q() # 検索条件(性別) # 指定された名前を含むデータを検索 if form.cleaned_data['name']: search_name = Q(name__icontains=form.cleaned_data['name']) # 指定された生年月日(From)以降のデータを検索 if form.cleaned_data['birth_day_from_year']: search_birthday_from = Q(birth_year__gt=int(form.cleaned_data['birth_day_from_year'])) \ | Q(birth_year=int(form.cleaned_data['birth_day_from_year']) , birth_month__gt=int(form.cleaned_data['birth_day_from_month'])) \ | Q(birth_year=int(form.cleaned_data['birth_day_from_year']) , birth_month=int(form.cleaned_data['birth_day_from_month']) , birth_day__gte=int(form.cleaned_data['birth_day_from_day'])) # 指定された生年月日(To)以前のデータを検索 if form.cleaned_data['birth_day_to_year']: search_birthday_to = Q(birth_year__lt=int(form.cleaned_data['birth_day_to_year'])) \ | Q(birth_year=int(form.cleaned_data['birth_day_to_year']) , birth_month__lt=int(form.cleaned_data['birth_day_to_month'])) \ | Q(birth_year=int(form.cleaned_data['birth_day_to_year']) , birth_month=int(form.cleaned_data['birth_day_to_month']) , birth_day__lte=int(form.cleaned_data['birth_day_to_day'])) # 指定された性別と同じデータを検索 if form.cleaned_data['sex']: search_sex = Q(sex=form.cleaned_data['sex']) # 検索条件に合うデータを全件検索 all_user_list = UserData.objects.filter(search_name, search_birthday_from , search_birthday_to, search_sex) # Paginatorオブジェクトを利用して、一覧画面に表示するデータをcontextに設定 paginator_user_list = Paginator(all_user_list, PER_PAGE) context = { 'user_list': paginator_user_list.get_page(1), 'all_page_num': paginator_user_list.num_pages, 'current_page_num': 1 } # 検索条件・一覧表示データ(全ページ)・現在ページ数をキャッシュに格納し、一覧画面に遷移 cache.set('search_form', self.request.POST) # 検索条件 cache.set('paginator_user_list', paginator_user_list) # 一覧表示データ(全ページ) cache.set('current_page_num', 1) # 現在ページ数 return render(self.request, self.success_url, context) def form_invalid(self, form): """ フォームの入力チェックを行い、エラーがある場合 """ # フォーム値はそのままで検索画面に戻る return super().form_invalid(form) class UserListView(TemplateView): """ 入力画面・削除確認画面から一覧画面に戻ってきた時の処理を定義するView """ template_name = 'demo/list.html' # 一覧画面のHTML def get_context_data(self): """ 戻ってきた後の一覧画面に表示するページデータを取得し設定 """ # キャシュから一覧表示データ(全ページ)、現在ページ数を取得 paginator_user_list = cache.get('paginator_user_list') current_page_num = cache.get('current_page_num') # 一覧画面に表示するデータをcontextに設定 context = super().get_context_data() # 一覧表示結果 context['user_list'] = paginator_user_list.get_page(current_page_num) context['all_page_num'] = paginator_user_list.num_pages # 全ページ数 context['current_page_num'] = current_page_num # 現在ページ数 return context class MovePageView(TemplateView): """ 一覧画面で「先頭へ」「前へ」「次へ」「最後へ」リンクが押下された時の処理を定義するView """ template_name = 'demo/list.html' # 一覧画面のHTML def get_context_data(self, **kwargs): """ リンク押下後に一覧画面に表示するページデータを取得し設定 """ # キャシュから一覧表示データ(全ページ)を取得 paginator_user_list = cache.get('paginator_user_list') # 遷移先ページをリクエストパラメータから取得し、キャッシュに設定 next_page = int(self.kwargs['next_page']) cache.set('current_page_num', next_page) # 一覧画面に表示するデータをcontextに設定 context = super().get_context_data() context['user_list'] = paginator_user_list.get_page(next_page) # 一覧表示結果 context['all_page_num'] = paginator_user_list.num_pages # 全ページ数 context['current_page_num'] = next_page # 現在ページ数 return context |
その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/python/tree/master/django-paging/djangoApp
サンプルプログラムの実行
サンプルプログラムの実行結果は以下の通りで、ページ遷移処理を正常に行えることが確認できる。
1) 実行前のuser_dataテーブルの中身は、以下の通り。
1 | select * from user_data |
2) コマンドプロンプトでDjangoプロジェクト名のフォルダに移動し、コマンドプロンプトで「python manage.py runserver」コマンドを実行して、Webサーバーを起動する。
3) Webブラウザを起動し、「http://127.0.0.1:8000/demo/」にアクセスすると、以下のように、検索画面(search.html)が表示されることが確認できる。ここで、何も条件を入力せず「検索」ボタンを押下すると、以下のように、user_dataテーブルのうち2件が一覧画面(list.html)に表示され、ページ遷移のためのリンクが表示されることが確認できる。
4) 一覧画面(list.html)で「次へ」リンクを押下すると、以下のように、ページ遷移して次ページのデータが表示されることが確認できる。
5) 一覧画面(list.html)で「前へ」リンクを押下すると、以下のように、ページ遷移して前ページのデータが表示されることが確認できる。
6) 一覧画面(list.html)で「最後へ」リンクを押下すると、以下のように、ページ遷移して最終ページのデータが表示されることが確認できる。
7) 一覧画面(list.html)で「先頭へ」リンクを押下すると、以下のように、ページ遷移して先頭ページのデータが表示されることが確認できる。
要点まとめ
- Django(ジャンゴ)にはPaginatorクラスが用意されていて、ページ遷移機能で利用できる。
- Djangoテンプレート内で加算を行うためには、「add」フィルターを利用すればよい。