Django:CRUDを実装する【Read】DB登録データの一覧表示
Django:CRUDを実装する【Read】DB登録データの一覧表示
ReadにはList(一覧表示)とShow(個別データ表示)の2種があると思います。
ShowはUpdateと一緒に構築する事が多いと思いますので、この記事ではList(一覧表示)のみを扱いたいと思います。
プリセットクラスを利用したListの記載方法
DjangoのプリセットClassには【ListView】という一覧表示用のセットがあります。
■app/urls.py
1 2 3 4 5 6 7 |
from django.urls import path from . import views app_name = 'hoge' urlpatterns = [ path('<int:page>/', views.HogeDetailView,as_View() name="hoge_detail"), ] |
■app/models.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
from django.db import models from . import views class Hoge(models.Model): column01 = models.CharField(verbose_name='ほげ1', max_length=200) column02 = models.CharField(verbose_name='ほげ2', max_length=200) column03 = models.CharField(verbose_name='ほげ3', max_length=200) daylight_hours = models.FloatField(verbose_name='日照時間')#後半で検索の例に使います。 observation_date = models.DateField(verbose_name='観測日')#後半で検索の例に使います。 class Meta: verbose_name_plural = 'Hoge' def __str__(self): return self.column01 |
■app/views.py
1 2 3 4 5 6 7 8 |
from django.views import generic from .models import Hoge class HogeListView(generic.ListView): model = hoge template_name = 'hoge_list.html' paginate_by = 10 ordering = ['-created_at'] |
■app/template/hoge_list.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
{% extends "base_site.html" %} (中略) {% block content %} {% for Hogelist in object_list %} <table> <tr> <th>からむ1</th> <th>からむ2</th> <th>からむ3</th> </tr> <tr> <td>{{Hogelist.column01}}</td> <td>{{Hogelist.column02}}</td> <td>{{Hogelist.column03}}</td> </tr> </table> {% endfor %} {% endblock content %} (以下略) |
たったコレだけでDBに登録されたリストを抽出するんですけど、ページネーションまでついてるんだからね。
便利なもんだ。
チョット解説
プリセットクラスでは、Templateに送る値の名前は固定されます。ListViewsの場合は下の通り
- リスト(テーブルの値:DB) => object_list
- ページネーション前頁番号 => page_obj.previous_page_number
- ページネーション次頁番号 => page_obj.next_page_number
- ページネーション頁リスト => page_obj.paginator.page_range
- ページネーション前頁あるか => page_obj.has_previous
- ページネーション次頁あるか => ppage_obj.has_next
なので、こんなの書けばページネーションが完成してしまいます。
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 |
#app/template/hoge_list.html ※ul li でやるか div a でやるかは各々の嗜好で (前略) <!-- Pagenation --> {% if is_pagenated %} <ul class="pagenation"> {% if page_obj.has_previous %} <li class="page-item"> <a class="page-link" href="?page={{page_obj.previous_page_number}}"> <span aria-hidden="true">«</span> </a> </li> {% endif%} {% for page_num in page_obj.paginator.page_range %} {% if page_obj.number == page_num %} <li class="page-item active"> <a class="page-link" href="#">{{page_num}}</a> </li> {% else %} <li class="page-item"> <a class="page-link" href="?page={{page_num}}">{{page_num}}</a> </li> {% endif %} {% endfor%} {% if page_obj.has_next %} <li class="page-item"> <a class="page-link" href="?page={{page_obj.next_page_number}}"> <span aria-hidden="true">»</span> </a> </li> {% endif %} </ul> {% endif%} <!-- Pagenation --> |
プリセットクラスを利用する場合の問題点
私がプリセットクラスを使っていない理由でもあるのですが、困ったことがあります。
- 検索を考えるとプリセットクラスで完結できない ※ プリセットクラス内で関数(def)を使って構築する事になる
- Template側に複数の値を渡す時、プリセットクラスで完結できない ※同じくプリセットクラス内で関数を利用する
だったら【 def 】で書き始めてよくない?という奴ですね。
ListViewを使った検索の構築についてはこちらが綺麗にまとめてくれていました。
関数(def)を使って一覧表示を書く
同じものをプリセットクラスを使わないで書いてみます。urls.pyとviews.py が変わります。
■app/urls.py
1 2 3 4 5 6 7 |
from django.urls import path from . import views app_name = 'hoge' urlpatterns = [ path('<int:page>/', views.HogeListView, name="hoge_list"), ] |
■app/views.py
1 2 3 4 5 6 7 8 9 10 11 12 |
from django.views import generic from .models import hoge from django.core.paginator import Paginator def HogeListView(request,page=1): object_list = Hoge.objects.order_by('-created_at') object_list = Paginator(object_list, 10) params = { 'object_list': object_list.get_page(num) } return render(request, 'hoge_list.html', params) |
チョット解説
今回あえてTemplateに渡すオブジェクト名をプリセットクラスと同じにしましたが、全然違ってOKです。
1 2 3 |
params = { 'watasuzo': object_list.get_page(num) } |
とすればTemplate側では【 {{ watasuzo }} 】で受け取れます。
また、一見なんも変わってなさそうなurls.py ですが、as_view()が消えてます。urls.pyの違いはこれだけです。
関数で書いた時に悩むところ
- ページネーションはどうすればいいか
- 検索情報の受け渡し方法をどうすればいいか
- 検索情報をページネーションの遷移で引き継ぐにはどうすればいいか
私が悩んだのはこの3項目です。2~3項目はプリセットClassでも同じかもしれません。というか同じだと思います。
ページネーションはどうすればいいか
Djangoで使える手段はいくつかあります。
理由はPagenatorプラグインが複数あるからです。
- django.core.paginator
- django-bootstrap-pagination
- django-pure-pagination
- django-bootstrap-datepicker-plus
一番上のdjango.core.paginatorはDjango標準搭載。残りはpip(pip3)インストールするプラグインです。
インストールタイプのプラグインでは【項目3】のページネーションでの引数継承が楽になっているようです。
※詳細は試していない為わかりません。
と言う事で、このページ内では【django.core.paginator】で進めていきたいと思います。
django.core.paginatorでTemplateに値がどう渡るか
- リスト(テーブルの値:DB) => object_listで渡した時
- ページネーション前頁番号 => object_list.previous_page_number
- ページネーション次頁番号 => object_list.next_page_number
- ページネーション頁リスト => object_list.paginator.page_range
- ページネーション前頁あるか => object_list.has_previous
- ページネーション次頁あるか => object_list.has_next
- 現在のページネーション番号 => object_list.numbers
- 最終のページネーション番号 => object_list.paginator.num_pages
リスト名はその時つけた名前になりますが、以降はプリセットClassで渡されるのと同じ名称で引き渡されるんです。
なので、リストの名前を[ object_list ]で引き渡せばHTML側はプリセットClassと全く同じコードでページネーションが完成します。
検索はどう仕掛ければいいか
Djangoに限らずですが、まずPOSTで行うかGETで行うかの選択があります。
私の場合はList検索はGETで組む事が多いので今回もその流れで構築していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#app/template/hoge_list.html <form method="GET" action="" accept-charset="UTF-8"> <div class="col-md-7 col-md-offset-0"> <div class="col-md-3 col-md-offset-0">日照時間</div> <div class="col-md-4 col-md-offset-0"> <input type="number" name="light_under" class="form-control"> </div> </div> <div class="col-md-5 col-md-offset-0"> <div class="col-md-4 col-md-offset-0">観測日</div> <div class="col-md-8 col-md-offset-0"> <input type="date" name="date_field" class="form-control"> </div> <div class="col-md-offset-4 col-md-8"> <input class="btn btn-success col-md-12" type="submit" id="button" value="検索" style="width100%;"> </div> </div> </form> |
実際に書いたやつ貼ってみました。ごく普通のGETのFormです。
actionが””なのでGET値(?light_under=10&date_field=2021-02-09)を付けて自分に遷移します。※値は適当です
これが app/template/hoge_list.html で使うViewに渡る時【request.GET】に格納されます。
実際にrequest.GETを表示してみるとこんな感じのリストになっています。
1 |
<QueryDict: {'light_und': ['10'], 'date_field': ['2021-01-09']}> |
なので、getのnameを指定して個別に取得していきます。
【検索】【GETの引き渡し】を含めたコード具体例
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 |
from django.views import generic from .models import hoge from django.db.models import Q from django.core.paginator import Paginator from urllib.parse import urlencode def HogeListView(request,page=1): object_list = Hoge.objects.order_by('-created_at') #日照時間で検索 q_light_und = request.GET.get('light_under') if q_light_under: object_list = object_list.filter(Q(daylight_hours__gte=q_light_under)) #日付で検索※指定した日付の前後一週間を取得 get_date = request.GET.get('date_field') if get_date: tdate = datetime.datetime.strptime(get_date, '%Y-%m-%d') bdate = tdate + datetime.timedelta(weeks=-1) fdate = tdate + datetime.timedelta(weeks=1) b_date = datetime.date(bdate.year, bdate.month, bdate.day) f_date = datetime.date(fdate.year, fdate.month, fdate.day) object_list = object_list.filter(Q(observation_date__gte=b_date),Q(observation_date__lte=f_date)) #2週間分なので、ページネーションは14づつに設定 object_list = Paginator(object_list, 14) #ページネーションでのGET引継ぎ用 get_request = urlencode(request.GET) #params内にまとめてTemplateに渡す params = { 'object_list': object_list.get_page(num), 'get_request': get_request } return render(request, 'hoge_list.html', params) |
チョット解説します。
10行目 / 15行目
このパーツがGETの受け取りです。[ request.GET.get(‘name’) ]これだけなので簡単です。
11行目~12行目
GETの値があった時にQuery_SETにフィルターをかけます。
特徴的な書き方が [ カラム名__gte ]という抽出条件の書き方で、これになかなか慣れません。
なので、ここら辺の相対表は別記事で残しておこうと思います。※作成していたら長くなりすぎました。
17行目~22行目
GETで渡された日付をQuerySetで検索可能な日付データに変換しています。
今回は【指定した日の前後1週間】の検索なので、18行目で1週間前を、19行目で1週間後を計算。
それぞれ20行目と21行目で date( Y , m , d ) という構成に変換し22行目で比較演算をしています。
25行目
django.core.paginatorを使ったページネーションを仕掛けています。
これだけでobject_listにページネーションのリストが組み込まれ、object_listと一緒にTemplateに渡されます。
28行目
ページネーションのリンク作成時に一緒に渡すGET値を生成しています。
request.GETに投げられた値を再度urlencodeしているだけですが、これでGETの値を含めたURLが生成できます。
urlencodeしたGETをページネーションでどう使うか
こちらも具体的なコードを貼ってみます。
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 |
<!-- Pagenation --> <div class="pager"> <ul class="pagination"> {% if object_list.has_previous %} <li class="pre"> <a href="{% url 'Hoge:hoge_list' num=object_list.previous_page_number %}?{{get_request}}"> <span>«</span> </a> </li> {% endif %} {% for page_num in object_list.paginator.page_range %} {% if object_list.number == page_num %} <li> <a href="#" class="active"> <span>{{page_num}}</span> </a> </li> {% else %} <li> <a href="{% url 'Hoge:hoge_list' num=page_num %}?{{get_request}}"> <span>{{page_num}}</span> </a> </li> {% endif %} {% endfor%} {% if object_list.has_next %} <li class="next"> <a href="{% url 'HOge:hoge_list' object_list.next_page_number %}?{{get_request}}"> <span>»</span> </a> </li> {% endif %} </ul> </div> <!-- Pagenation --> |
cssを変えた関係で前に記載したものとクラス名などが変わってますが本筋でないのでお気になさらず。
6行目/20行目/28行目
aタグのhref にて [ ?{{get_request}} ] として検索時と同じGETをURLに付け加えています。
これだけで「次ページ送りしたら抽出条件がリセットされた」なんて事態を回避できます。
日付検索にカレンダーを使いたい場合どうすればいいか
そこはHTML5を使いましょう。
1 |
<input type="date" > |
これだけで簡単に実装できます。
まとめ
CRUDを実装していくと色々覚えますし、色々ハマりますよね。
とりあえずのRead編でした。
-
前の記事
Django:2タイプのCRUD記載方法について特徴と注意事項のまとめ 2021.02.08
-
次の記事
Django:ページネーションのページ番号を良い感じで縮める方法 2021.02.11