Django:CRUDを実装する【Read】DB登録データの一覧表示

Django:CRUDを実装する【Read】DB登録データの一覧表示

ReadにはList(一覧表示)とShow(個別データ表示)の2種があると思います。

ShowはUpdateと一緒に構築する事が多いと思いますので、この記事ではList(一覧表示)のみを扱いたいと思います。

プリセットクラスを利用したListの記載方法

DjangoのプリセットClassには【ListView】という一覧表示用のセットがあります。

■app/urls.py

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

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

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

{% 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

なので、こんなの書けばページネーションが完成してしまいます。

#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">&laquo;</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">&raquo;</span>
        </a>
      </li>
    {% endif %}
  </ul>
{% endif%}
<!-- Pagenation -->

プリセットクラスを利用する場合の問題点

私がプリセットクラスを使っていない理由でもあるのですが、困ったことがあります。

  1. 検索を考えるとプリセットクラスで完結できない ※ プリセットクラス内で関数(def)を使って構築する事になる
  2. Template側に複数の値を渡す時、プリセットクラスで完結できない ※同じくプリセットクラス内で関数を利用する

だったら【 def 】で書き始めてよくない?という奴ですね。
ListViewを使った検索の構築についてはこちらが綺麗にまとめてくれていました。

関数(def)を使って一覧表示を書く

同じものをプリセットクラスを使わないで書いてみます。urls.pyとviews.py が変わります。

■app/urls.py

from django.urls import path
from . import views

app_name = 'hoge'
urlpatterns = [
  path('<int:page>/', views.HogeListView, name="hoge_list"),
]

■app/views.py

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です。

  params = {
      'watasuzo': object_list.get_page(num)
  }

とすればTemplate側では【 {{ watasuzo }} 】で受け取れます。
また、一見なんも変わってなさそうなurls.py ですが、as_view()が消えてます。urls.pyの違いはこれだけです。

関数で書いた時に悩むところ

  1. ページネーションはどうすればいいか
  2. 検索情報の受け渡し方法をどうすればいいか
  3. 検索情報をページネーションの遷移で引き継ぐにはどうすればいいか

私が悩んだのはこの3項目です。2~3項目はプリセットClassでも同じかもしれません。というか同じだと思います。

ページネーションはどうすればいいか

Djangoで使える手段はいくつかあります。
理由はPagenatorプラグインが複数あるからです。

一番上の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で組む事が多いので今回もその流れで構築していきます。

#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を表示してみるとこんな感じのリストになっています。

<QueryDict: {'light_und': ['10'], 'date_field': ['2021-01-09']}>

なので、getのnameを指定して個別に取得していきます。

【検索】【GETの引き渡し】を含めたコード具体例

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をページネーションでどう使うか

こちらも具体的なコードを貼ってみます。

<!-- 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>&laquo;</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>&raquo;</span>
        </a>
      </li>
      {% endif %}
  </ul>
</div>
<!-- Pagenation -->

cssを変えた関係で前に記載したものとクラス名などが変わってますが本筋でないのでお気になさらず。

6行目/20行目/28行目

aタグのhref にて [ ?{{get_request}} ] として検索時と同じGETをURLに付け加えています。
これだけで「次ページ送りしたら抽出条件がリセットされた」なんて事態を回避できます。

日付検索にカレンダーを使いたい場合どうすればいいか

そこはHTML5を使いましょう。

<input type="date" >

これだけで簡単に実装できます。

まとめ

CRUDを実装していくと色々覚えますし、色々ハマりますよね。

とりあえずのRead編でした。

\ 最新情報をチェック /

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください