Django:CRUDを実装する【detail-update】DBの詳細表示と更新

Django:CRUDを実装する【detail-update】DBの詳細表示と更新

このパーツは多くの教本で「forms.pyを使ってTemplateで呼び出しましょう」という構成になっています。
確かにそっちの方が楽なんですけど、楽である以上なにがしかの不具合もあったりします。
forms.pyを使った時の具体的な問題点は『デザイン周り』です。
なので、ゴリゴリな方法も覚えておいて損はないかと思っています。

プリセットクラスを利用した詳細表示の記載方法

PresetClassは汎用ビューという呼ばれ方しているようですね。
この名称で検索した方が沢山の事例に出会えます。

(個人的に)面倒だと感じたのは【DetailView】【UpdateView】という2つが存在する事です。
Laravelと比較してみると参考にさせて頂いた情報ではこんな感じでした。

  • Laravel の edit 相当 = UpdateView
  • Laravel の show 相当 = DetaliView

そんなところを考慮しながらプリセットClassのScriptを見てみます。

■app/urls.py

from django.urls import path
from . import views

app_name = 'hoge'
urlpatterns = [
  path('', views.HogeListView,as_View() name="hoge_list"),
  path('<int=pk>/detail', views.HogeDetailView,as_View() name="hoge_detail"),
  path('<int=pk>/update', views.HogeUpdateView,as_View() name="hoge_update"),
]

■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)

    class Meta:
        verbose_name_plural = 'Hoge'
        def __str__(self):
            return self.column01

■app/views.py

from django.views import generic
from django.urls import reverse_lazy
from .models import Hoge

class HogeDetailView(generic.DetailView):
  model = hoge
  template_name = 'hoge_detail.html'
  ordering = ['-created_at']

class HogeUpdateView(generic.edit.UpdateView)
  model = hoge
  form_class = HogeCreateForm
  success_url = reverse_lazy("Hoge:hoge_list")

■app/template/hoge_detail.html

{% extends "base_site.html" %}
  {% block content %}
    <table>
      {{form}}
    </table>
  {% endblock content %}
  (以下略)

■app/template/hoge_update.html

{% extends "base_site.html" %}
  {% block content %}
    <form method="post">
      {% csrf_token%}
      <table>
        {{form}}
      </table>
      <input type="submit">
    </form>
  {% endblock content %}
  (以下略)

LaravelでのShowの扱いと同じですね、detail要らなくね?

PresetClass(汎用ビュー)を利用する場合の問題点

以下は私の詰まった点です。やり方はあるので知見のある方は難なくクリアできると思います。

  1. update成功時のリダイレクト先(success_url)にページネーションのあるListを設定する
  2. Template.html で呼び出す {{ form }} でレスポンシブデザインを作る
  3. update時のバリデーションエラーが発生する

特に[1]と[3]で沼りました。

update成功時のredirect先にページネーションを付ける方法

class HogeUpdateView(generic.edit.UpdateView)
  model = hoge 
  form_class = HogeCreateForm
  #success_url = reverse_lazy("Hoge:hoge_list")

  #detailに飛ばす時
  def get_success_url(self):
    return reverse(‘Hoge:hoge_detail’,kwargs={‘pk’:self.kwargs[‘pk’]})

  #listの1ページ目に飛ばす時
  def get_success_url(self):
    return reverse(‘Hoge:hoge_list’,kwargs={‘page’:1})

こんな形でClass内で関数を呼び出すとreverse関数を使えます。

reverse_lazyに対してpkを渡すとエラー。関数宣言をせずにreverse関数を使ってもImproperlyConfigurというエラーが返ってきます。クラス変数を設定する段階ではURLConf(=urls.py)をまだ読み込んでいない為に怒られているそうで、GET値を含めたページ指定は関数宣言をして処理を書く必要があります。

update時のバリデーションエラー

私の場合の原因は日付DATAとNULLで登録たい数値カラムでの’None’が原因でした。
特に日付についてはHTML5のDateTypeを使ってPOSTしたのですが、この送った日付がバリデーションエラーを出してました。

これは {{ Form }} として呼び出している場合には多分発生しない現象だと思います。
forms.pyでページ構成まで作成する方法についてはHTMLに慣れている私からするとちょっと面倒。
なので、forms.pyにはフィールド構成だけ載せていたのですが、こういう使い方が仇になったようです。

回避方法としては2通りあります。

  1. Template側でPOSTする前に撥ねられない形に整える。
  2. updateにforms.pyを参照しない ※必然的に汎用ビューを使わない方法(def 書出し)になります。

関数(def)を使ってUpdateを書く

PresetClassを使わない場合、Views.pyとurls.pyが変わります。

■app/urls.py

from django.urls import path
from . import views

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

■app/views.py

from django.views import generic
from django.contrib import messages
from django.shortcuts import render,redirec
from .models import hoge 
from django.core.paginator import Paginator

def HogeDetailView(request,pk):
    object = hoge.objects.get(pk=pk)

    if (request.method == 'POST'):
        object.column01 = request.POST['column01 ']
        object.column02 = request.POST['column02 '] 
        object.column03 = request.POST['column03 ']
        object.save()

        messages.success(request, 'レコードを新規追加しました。')
        return redirect(to='/hoge/1/')

    hoge_list = hoge.objects.order_by('-created_at')

    params = {
        'object': object,
        'list': hoge_list,
        'url': '/hoge/'+str(pk)+'/detail/'
    }
    return render(request, 'hoge_detail.html', params)

チョット解説

上に載せた views.pyは一番ゴリゴリやった例です。
例えば11行目~14行目は下のようにFormを使って書くこともできます。

Recode = HogeCreateForm(request.POST, instance=object)
Recode.save()

urls.pyからHogeUpdateViewが消えた理由

2つの実装でupdateViewを不要な姿にしています。

  1. POSTの送り先をdetail自身に設定
  2. 関数:HogeDitailViewにて【 if (request.method == 'POST'): 】とPOSTの値があるときの分岐を用意

こうすることで、POSTの値はdetail自身に渡され、POSTを持つ場合はsave()が走るようになっています。

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

  • NULLを入れたいのにNoneが登録される(場合によってはこれが原因でバリデーションエラーになる)
  • 上書きしたいのに新規追加してしまう

NULL登録の解決策:column01 などにNULLの記載をしたい場合はIfで分岐をする事で対処できます。

if (request.POST['column01'] == 'None'):
    object.columns01 = None
else:
    object.columns01 = request.POST['columns01']

新規追加してしまう時に見る場所:pkで抽出したobjectのカラムに対して処理を実行しているか確認しましょう

save()では基本的に INSERT SQL 文が実行されます。save()メソッドは値を返しません。
既にプライマリキーがあるデータの条件を指定した場合は、INSERTでなくUPDATE SQL 文が実行されます。

object = Contents.objects.get(pk=pk)
#これだと新規追加してしまう
if (request.method == 'POST'):
    model = Contents
    Recode = ContentsCreateForm(request.POST, instance=model)
    Recode.save()
    messages.success(request, 'レコードを更新しました。')
    return redirect(to='/managedcontents/contents/1/')

まとめ

実装していて一番悩んだパートがNullで上書きしたい時の処理でした。

DjangoだとNullではなくてNoneなんだと言う所から躓いたわけですけど、Noneを渡したつもりでいてもエラーが出てきました。 "None"という文字を渡していたとわかった時は思わず苦笑してしまいました。

こういった事って慣れれば大したことじゃないんですけどね。

\ 最新情報をチェック /

コメントを残す

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