SE(たぶん)の雑感記

一応SEやっている筆者の思ったことを書き連ねます。会計学もやってたので、両方を生かした記事を書きたいと考えています。 でもテーマが定まってない感がすごい。

Django - ジェネリックビューで楽してみた(DeleteView編)

数回前の記事で、CreateViewUpdateViewについて書きました。今回は更新ジェネリックビューの残り、DeleteViewについて書きます。CRUDDに相当します。

hiroronn.hatenablog.jp

hiroronn.hatenablog.jp

バージョン等

前回と同様です。

ツール バージョン
Python 3.7.2
Django 2.1.7
bootstrap 4.1.3

DeleteViewについて

前提: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を使います。

DeleteViewとは

DeleteViewは、名前の通り要素を削除するビューです。対象が存在したら削除します。が、削除は対象が無くても結果的には成功と言えるので…

DeleteViewでは、ビューで指定したモデルの1行を削除することを想定しています。

DeleteViewの定義

DeleteViewは、CreateViewUpdateViewと異なり、入力する必要が無いです。そのため、考え方か異なります。

説明のために、URLとビューを示します。

  • urls.py
from django.urls import path
from .views import table

app_name = 'inputs'

urlpatterns = [
    path('table', table.TableListView.as_view(), name='tables'),
    path('table/<int:pk>', table.TableDetailView.as_view(), name='table'),
    path('table/<int:pk>/delete', table.TableDeleteView.as_view(), name='table_del'),
]

UpdateViewの場合と同様、URLに主キーを含めます。

  • views/table.py
class TableDeleteView(DeleteView):
    """テーブル削除"""
    model = Table
    success_url = reverse_lazy('inputs:tables')
    template_name = 'inputs/delete.html'

単純な場合、上記の定義のみで良いです。

テンプレートについて

CreateView等のテンプレートは、入力項目をformとして配置し、そこにsubmit用のボタン等を置く、という形で作成します。登録するデータを入力するためのテンプレートを渡すことになります。

一方、DeleteViewが要求するテンプレートは、削除確認用途として使われます。ドキュメントにも

確認ページを表示して、現存するオブジェクトを削除するビューです。

という記述の通りです。共通のような形で作ると、以下のようになります。

  • templates/delete.html
<!-- body部のみ -->
<form method="post">
    {% csrf_token %}
    <p>削除します。よろしいですか?</p>
    <input class="btn btn-danger" type="submit" value="削除する">
</form>

削除確認のみ行います。

URLに対する役割

DeleteViewのURLに対してリクエストを送った場合、以下の挙動となります。

メソッド 挙動
GET 確認ページを返す
POST 指定された要素を削除し、指定したページにリダイレクト

POSTするには、フォームにsubmitのボタンを置けばいいです。

雑な図ですが、イメージはこんな感じです。

f:id:hiroronn:20190404084333p:plain
DeleteView

削除前に情報を表示する

削除する前に、削除されようとしている項目の情報を取得したいとします。

テンプレートにはobjectという名前で削除しようとしているモデルが格納されるので、そちらを使用できます。

<form method="post">
    {% csrf_token %}
    <p>このテーブルを削除しますか?</p>
    <table class="table table-sm table-bordered table-responsive">
        <tbody>
            <tr>
                <th>ID</th>
                <td>{{ object.id }}</td>
            </tr>
            <tr>
                <th>テーブル名</th>
                <td>{{ object.name }}</td>
            </tr>
        </tbody>
    </table>
    <input class="btn btn-danger" type="submit" value="削除する">
</form>

また、ここでは載せませんが、get_querysetget_context_dataをオーバーライドして、追加データを取得することもできます。

削除時の子要素

削除する際は、その要素の主キーを指定します。

今回、ColumnTableを外部キー要素として指定しています。もし、Tableを削除する場合、そのTableを参照しているColumnがあると、通常は先にColumnを消す必要があります。

今回は、親が消された場合、子も一緒に消えるように、

class Column(models.Model):
    """カラム定義(物理名格納)"""
    table = models.ForeignKey(Table, on_delete=models.CASCADE)
    name = models.CharField(max_length=100)

ForeignKeyを作成する際にon_delete=models.CASCADEを指定しています。なので気にせず消せます*1

on_deleteに関しては、こちらに記載があります。

公式ドキュメント:ForeignKey.on_delete

参考

公式:DeleteView

次回

ジェネリックビューを使わない場合について、書く予定です。今回書く予定でしたが、分量が多かったため、改めて書きます。

おわりに

Djangoは、単純なCRUDだけならとても簡単に作成できます。問題はそこから逸脱し始めるときです。そのあたりを次回書いてみます。

*1:場合によっては多くの行が消えるため、一応パフォーマンス上の考慮は必要