前回記事でCreateView
を扱いました。今回はUpdateView
について書きます。CRUD
のU
に相当します。
バージョン等
前回と同様です。
ツール | バージョン |
---|---|
Python | 3.7.2 |
Django | 2.1.7 |
bootstrap | 4.1.3 |
前提:Model層
- models.py
from django.db import models class Table(models.Model): """テーブル一覧""" name = models.CharField(max_length=100) class Column(models.Model): """カラム定義(物理名格納)""" table = models.ForeignKey(Table, on_delete=models.CASCADE) name = models.CharField(max_length=100)
前回と同様ですが、上記二つのModel
を使います。
UpdateViewとは
UpdateView
は、既に登録されているデータを開き、更新するためのビューです。DBでいうとSELECT
した後UPDATE
を行います。
基本的には、対象は1行のみです。
UpdateViewの効果
自分で作るとしたら
特定の1行を更新しようとする場合、以下のような作業(ロジック)が必要になります。
- 更新対象のレコードを何らかの形で選択する
- 指定レコードがあるかどうかチェックする
- レコードを取得する
- 値を表示するためのHTML(form等)を作る
- POSTが来たら入力された値を検証する(文字形式とか)
- 再び、指定レコードを取得する*1
- (アプリケーション要件に応じて)更新可否を判定する*2
- 取得レコードに入力値を反映
- 保存処理を呼び出す
- ブラウザの画面を遷移させる
作成時より、考慮事項が多いです。
UpdateViewを使う場合
最も単純な場合、
- 対象modelを指定する
- 入力したいフィールドを指定する
- URLに主キーを含める
- 描画用HTMLを指定する(省略可)
- 更新後の遷移先を指定する
'CreateView'とほとんど変わりませんが、主キーの指定が必要です。ただ本当にこれだけでよいです。
主キーの指定
UpdateView
を使う場合、URLに主キーを含めると楽です。以下のようになります。
- urls.py
urlpatterns = [ # table path('table/<int:pk>', table.TableDetailView.as_view(), name='table'), path('table/<int:pk>/update', table.TableUpdateView.as_view(), name='table_upd'), # UpdateView ]
主キーを受けるパラメータ名は、pk
またはslug
にしましょう*3。
根拠
UpdateView
の継承元、SingleObjectMixin
に、以下の記述があります。
- django/views/generic/detail.py
class SingleObjectMixin(ContextMixin): # 中略 def get_object(self, queryset=None): """ Return the object the view is displaying. Require `self.queryset` and a `pk` or `slug` argument in the URLconf. Subclasses can override this to return any object. """
ソースを見ると分かりますが、両方指定した場合はpk
が優先して使用されます。
ビューの作成
例1:単体で完結するモデル
URLは上記の通りです。
- views/table.py
class TableUpdateView(UpdateView): """テーブル更新""" model = Table fields = ('name', ) template_name = 'inputs/create.html' def get_success_url(self): return reverse('inputs:table', kwargs={'pk': self.object.id})
前回記事で書いたget_success_url
は、UpdateView
でも同じように利用できます。これにより、更新後に更新が終わった明細を開けます。
テンプレートを作る必要はある*4ものの、これだけ指定するとデータ取得から更新まですべて行えます。
例2:リレーションを持つモデル
今回でいうところのColumn
モデルです。
例えば、テンプレートにColumn
に紐づいているテーブル名を表示したい、とします。その場合、テンプレートで
<p> <span>{{ column.table.name }}</span> </p>
のような感じで書けばよいのですが、これだとテーブル名を取得する際に再度クエリが発行されてしまいます。
それを避けるために、前回も出てきたget_queryset
をオーバーライドする方法があります。
class ColumnUpdateView(UpdateView): """名称変更""" model = Column template_name = "inputs/create.html" fields = ('name',) def get_queryset(self): return super().get_queryset().select_related('table') def get_success_url(self): return reverse('inputs:col', kwargs={'pk': self.object.id})
継承元のget_queryset
で一行だけ取得するクエリが発行されるので、それと一緒に取得してしまおう、という考え方です。
まあ、たった一回増えるだけだしいいや、という考え方もあります。
リレーションの列を変える場合は、
class ColumnUpdateView(UpdateView): """名称変更""" model = Column template_name = "inputs/create.html" fields = ('name', 'table') def get_queryset(self): return super().get_queryset().select_related('table') def get_success_url(self): return reverse('inputs:col', kwargs={'pk': self.object.id})
と、fields
に追加すればよいです。テンプレートでselect
要素が作成され、選択できるようになります。
説明してみたものの、UpdateView
に関しては、単純である限りリレーションがあっても無くても、ほとんど同じように書けます。
疑問:レコードの有無はチェックされるのか
気になったので調べました。三点検証しています。
- ビューを開くとき、存在しないキーを指定したらどうなるのか
- 更新するとき、対象レコードが消されていたらどうなるのか
- 更新するとき、対象レコードが変更されていたらどうなるのか
結果として、画像の通り1.と2.はきちんと異常検知してくれます。3.は、そのまま更新されました。
他のクライアントが更新した場合のチェックは、必要なら実装する必要があります。更新日付列を持たせてチェックすると、多少堅牢になります。ただ、実装はアプリケーションによって異なるので、ここでは省略します。
おわりに
単一レコードの更新なら、UpdateView
を使うと実装が本当に楽です。ほとんど手組みする必要がありません。
Django
の機能で最低限のチェックは行ってくれるので、ささっと作るだけならあっという間です。慣れると10分ぐらいでできます。本当に。モデルもテンプレートも無いと、そこまで素早くはできませんが…
次はDeleteView
なのですが、たぶん短くなるので、きっと別のことも一緒に書きます。
*1:更新対象のレコードが、他のクライアントから削除されている可能性がある
*2:何もしない、楽観的、悲観的というもの。参考:https://docs.microsoft.com/ja-jp/dotnet/framework/data/adonet/optimistic-concurrency
*3:変えることもできるが、特別な事情が無い限りそのままのほうが楽
*4:見た目にこだわらなければ使いまわしできる