【Django】009. CRUDをプログラムから行う

前回まででテーブルにあるレコードを表示することができました。

[前回記事：【Django】008. Managerクラスによるレコードの取得](https://qiita.com/ys_dirard/items/404ca660ebef4b6a77ed)

今回はテーブルへレコードをプログラムによって追加 / 編集 / 削除していきたいと思います。

今回も以下の本を参考にしています。

https://www.shuwasystem.co.jp/book/9784798062419.html


## CRUDとは？
データベースの基本的な機能で以下の頭文字です。

|名称|詳細|
|-|-|
|Create|新たにレコードを作成しテーブルに保存する|
|Read|テーブルからレコードを取得する ※前回やった内容|
|Update|既にテーブルにあるレコードの内容を変更し保存する|
|Delete|既にテーブルにあるレコードを削除する|

これらは必要最低限の機能で、これだけできれば完璧！とは言えないですがまず押さえておくべき機能です。

また、アプリによっては4つすべてを実装する必要はない場合もあります。

今回もテーブル(モデル)は前回同様以下のFriendモデルを使用します。

```python
class Friend(models.Model):
    name = models.CharField(max_length=100)
    mail = models.EmailField(max_length=200)
    age = models.IntegerField(default=0)
    birthday = models.DateField()
    
    def __str__(self):
        return '<Friend:id=' + str(self.id) + ', ' + \
            self.name + '(' + str(self.age) + ')>'

```

## Read
前回やった内容ですが、テーブルからレコードを取得して表示させます。

テンプレートは以下のようにread.htmlとしておきます。
```html:read.html
{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>{{ title }}</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">

</head>
<body class="container">
    <h1 class="display-4 text-primary">{{ title }}</h1>

    <table class="table">
        <tr>
            <th>data</th>
        </tr>
        {% for item in data %}
        <tr>
            <td>{{ item }}</td>
        </tr>
        {% endfor %}
    </table>
</body>
</html>

```

views.pyへ以下のread関数を追加します。
```python
def read(request):
    data = Friend.objects.all()
    params = {
        'title': 'Hello',
        'data': data
    }
    return render(request, 'hello/read.html', params)
    
```

urls.pyのurlpatternsに以下を追加する。
```python
urlpatterns = [
    path("read", views.read, name="read"), # 追加
]
```

アクセスすると以下のように表示されReadができていることが確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/73c020a3-1b45-b76c-e040-ea10b7064bda.png)

## Create
以下の流れでレコードを追加していきます。

1. レコード作成用のフォームを作成
1. フォームから入力して送信
1. 送られてきた情報をもとにレコードを作成

### レコード作成用のフォームの作成
forms.pyに以下を追加します。

```python:forms.py
from django import forms
from .models import Friend

class FriendForm(forms.ModelForm):
    class Meta:
        model = Friend
        fields = ['name', 'mail', 'gender', 'age', 'birthday']
        
```

ModelFormクラスを継承しています。

ModelFormでは内部に「Meta」というメタクラスを持っており、モデル用のフォームに関する情報が用意されています。

modelで使用するモデルクラスを、fieldsで用意するフィールドを指定しています。

### ビュー関数の準備
views.pyにビュー関数としてcreate関数を追加します。
```python:views.py
from django.shortcuts import render, redirect
from .forms import FriendForm
from .models import Friend

def create(request):
    if (request.method == 'POST'):
        obj = Friend()
        friend = FriendForm(request.POST, instance=obj)
        friend.save()
        return redirect(to='/hello/read')
    params = {
        'title': 'Hello',
        'form': FriendForm(),
    }
    return render(request, 'hello/create.html', params)
    
```

postならModelForm.save()によりレコードの作成、保存を行った後レコード一覧へとリダイレクトしています。

post以外なら、Create用の画面を表示します。


### テンプレートの準備
create.htmlを以下のように準備します。

```html
{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>{{ title }}</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">

</head>
<body class="container">
    <h1 class="display-4 text-primary">{{ title }}</h1>
    <form action="{% url 'create' %}" method="post">
        {% csrf_token %}
        <table class="table">
            {{ form.as_table }}
            <tr><th><td>
                <input type="submit" value="click" class="btn btn-primary mt-2">
            </td></th></tr>
        </table>
    </form>
</html>

```

createにアクセスすると以下のように表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/f78637a0-423f-403b-da2b-1df053e97e76.png)

項目を入力してclickを押すと、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/7a78542c-0444-fb66-15b7-dd130f0b416c.png)

無事に追加されリダイレクトされたことが確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/f070e348-7c0b-0704-329b-8b4542721999.png)

### forms.ModelFormではなくforms.Formを使うパターン
Create時に便利ということでforms.ModelFormを使ってみましたが、これまで使ってきたforms.Formを使っても実行できます。

※ModelFormの方が便利なので飛ばして良いです。

ModelFormとの差分は
* Formクラスでのフィールド
* レコードの保存の流れ
かと思います。

ざっくりと以下のようになります。(本当にざっくり)

Formクラスは以下のようにフィールドを持つクラスとなります。
```python
class FriendForm(forms.Form):
    name = forms.CharFirld(label='Name', widget=forms.TextInput(attrs={'class': 'form-control'}))
    mail = ...
    ...
```

レコードの作成/保存は以下のようになります。
```python
if (request.method == 'POST'):
    name = request.POST['name']
    mail = request.POST['mail']
    ...(全フィールドの値を取得)...

    friend = Friend(name=name, mail=mail, ...)
    friend.save()
    return redirect(to='hello/read')
```

全フィールドの値を取得後にFriendインスタンスを作成し保存を行っています。
全てのフィールドの値を1つずつ取り出さなければ去らないのが面倒くさいポイントです。

先ほどのModelFormによる方法では
```python
    obj = Friend()
    friend = FriendForm(request.POST, instance=obj)
    friend.save()
```
とFriendインスタンスを用意し、値はrequest.POSTで送られたものを使ってね！のような形になっているため1つずつフィールドの値を取り出す必要はありません。

そのため拡張性も高く感じるためこちらを使っていきたいです。

## Update
次にUpdate(更新)をしたいと思います。

更新時も保存は先のCreate同様にsaveで行います。

先ほどのCreateではFriendインスタンスを作成し、それと送信フォームの値を使ってFriendFormのインスタンスを作成し保存を行いました。

これを少し変更して、編集するFriendインスタンスを使用してFriendFormのインスタンスを作成することでUpdateを行うことができます。

以下の流れで行います。
1. 編集用のテンプレートの作成
1. read.htmlで表示されている各レコードから編集ページへのリンクを貼る
1. 編集用のビュー関数を作成する

### 編集用のテンプレートの作成
以下のedit.htmlを作成する。

```html:edit.html
{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>{{ title }}</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">

</head>
<body class="container">
    <h1 class="display-4 text-primary">{{ title }}</h1>
    <form action="{% url 'edit' id %}" method="post">
        {% csrf_token %}
        <table class="table">
            {{ form.as_table }}
            <tr><th><td>
                <input type="submit" value="click" class="btn btn-primary mt-2">
            </td></th></tr>
        </table>
    </form>
</body>
</html>

```


### read.htmlで表示されている各レコードから編集ページへのリンクを貼る

以下のようにread.htmlを編集する。
※編集ページへのリンクの追加

```html:edit.html
{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>{{ title }}</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">

</head>
<body class="container">
    <h1 class="display-4 text-primary">{{ title }}</h1>
    <table class="table">
        <tr>
            <th>data</th>
        </tr>
        {% for item in data %}
        <tr>
            <td>{{ item }}</td>
            <td><a href="{% url 'edit' item.id %}">Edit</a></td>
        </tr>
        {% endfor %}
    </table>
</body>
</html>

```

url 'edit'の後にitem.idを付けています。

これにより、/edit/1のようにeditの後にID番号を付けてアクセスできるようにしています。

### 編集用のビュー関数を作成する
views.pyにビュー関数として以下のedit関数を追加します。

```python
def edit(request, num):
    obj = Friend.objects.get(id=num)
    if (request.method == 'POST'):
        friend = FriendForm(request.POST, instance=obj)
        friend.save()
        return redirect(to='/hello/read')
    params = {
        'title': 'Hello',
        'id': num,
        'form': FriendForm(instance=obj),
    }
    return render(request, 'hello/edit.html', params)
    
```

まず、送信されたidをもとに対応するレコードを検索します。
そのレコードをもとにFriendFormを作成することで更新する対象として設定しています。

### 動作確認
read.htmlは以下のように編集画面へのリンクが追加されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/b513ae80-64f8-41eb-1cf3-9e2eefdef2e6.png)

Editをクリックすると編集画面へ遷移し、編集前の情報が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/8de3c82c-cd34-6295-70d1-3b77e439dd7b.png)

内容を変更してClickを押すとUpdateされたのちread.htmlへリダイレクトされていることが確認できました。
※今回はageを20→23へと変更

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/3f2bde42-157c-1532-2b0b-f4efa5f753de.png)

## Delete
レコードの削除は対応するモデルのインスタンスに対し、deleteメソッドを呼ぶことで簡単にできます。

ただ、削除しますか？のような確認をすることが一般的な親切設計かと思うのでそうしてみます。

以下の流れで行います。
1. 削除(確認)用のテンプレートの作成
1. read.htmlで表示されている各レコードから削除(確認)ページへのリンクを貼る
1. 削除(確認)用のビュー関数を作成する

### 削除(確認)用のテンプレートの作成

削除(確認)用のテンプレートをdelete.htmlとして以下の内容で作成します。

```html:delete.html
{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>{{ title }}</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">

</head>
<body class="container">
    <h1 class="display-4 text-primary">{{ title }}</h1>
    <p>※以下のレコードを削除します。</p>
    <table class="table">
        <tr><th>ID</th><td>{{ obj.id }}</td></tr>
        <tr><th>Name</th><td>{{ obj.name }}</td></tr>
        <tr><th>Gender</th><td>
            {% if obj.gender == True %}male{% endif %}
            {% if obj.gender == False %}female{% endif %}
        </td></tr>
        <tr><th>Email</th><td>{{ obj.mail }}</td></tr>
        <tr><th>Age</th><td>{{ obj.age }}</td></tr>
        <tr><th>Birthday</th><td>{{ obj.birthday }}</td></tr>
        <form action="{% url 'delete' id %}" method="post">
            {% csrf_token %}
            <tr><th></th><td>
                <input type="submit" value="click" class="btn btn-primary mt-2">
            </td></tr>
        </form>
    </table>
</body>
</html>

```

### read.htmlで表示されている各レコードから削除(確認)ページへのリンクを貼る
read.htmlへ以下のように追加します。

```html:read.html
{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>{{ title }}</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">

</head>
<body class="container">
    <h1 class="display-4 text-primary">{{ title }}</h1>
    <table class="table">
        <tr>
            <th>data</th>
        </tr>
        {% for item in data %}
        <tr>
            <td>{{ item }}</td>
            <td><a href="{% url 'edit' item.id %}">Edit</a></td>
            <td><a href="{% url 'delete' item.id %}">Delete</a></td>
        </tr>
        {% endfor %}
    </table>
</body>
</html>

```
deleteへのリンクを追加しました。

deleteもupdate同様にidをパラメータとして渡します。

### 削除(確認)用のビュー関数を作成する
views.pyにビュー関数としてdelete関数を定義します。

```python
def delete(request, num):
    friend = Friend.objects.get(id=num)
    if (request.method == 'POST'):
        friend.delete()
        return redirect(to='/hello/read')
    params = {
        'title': 'Hello',
        'id': num,
        'obj': friend,
    }
    return render(request, 'hello/delete.html', params)
    
```

postで送信されたら、deleteを実行してread.htmlへリダイレクトするようにしています。

### 動作確認
read.htmlは以下のように削除(確認)画面へのリンクが追加されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/a6e38659-4c7e-d61c-ba0b-255c366912b6.png)

Deleteをクリックすると削除(確認)画面へ遷移し、削除前の情報が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/7f63b685-99ee-0dd9-060b-e6dd706d4e49.png)

Clickを押すと削除が実行され、read.htmlへリダイレクトされることが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/9b4cf165-e48b-a334-0b09-40f611b0e038.png)


## ジェネリックビュー
これまでのCRUD実装時にallやgetでレコードを抽出し表示するということをやりました。

この部分はごくごく一般的な機能で誰が作成しても大体同じビュー関数になると思われます。(少なくとも方針 / 方向性は概ね同じになるはず…)

同じような内容になるのであればもともとそれが可能なビュークラスを準備しておけば良い！ということで生まれた(らしい)のがジェネリックビューです。

ジェネリックビューには以下のようなものがあります。

* ジェネリック表示ビュー
    * DetailView
    * ListView
* ジェネリック編集ビュー
    * FormView
    * CreateView
    * UpdateView
    * DeleteView

他にもジェネリック日付ビューというのもありそうでした。

今回はジェネリック表示ビューのListViewとDetailViewを使ってみます。

### ListView
ListViewは以下のような形で作成します。

```python
from django.views.generic import ListView

class クラス名(ListView):
    model = モデル
    
```

クラスには「model」という値を1つ用意して終わりです。

モデルの箇所にモデルのクラスを指定するだけで、そのモデルの全レコードが取得されます。

取り出されたレコードは「object_list」という名前の変数としてテンプレート側に渡されます。

使用されるテンプレートは「モデル_list.html」という名前のファイルになります。

この名前のテンプレートファイルを用意しておき、その中でobject_listの値を順に取り出して表示すればよいです。

### DetailView
DetailViewは以下のような形で作成します。

```python
from django.views.generic import DetailView

class クラス名(DetailView):
    model = モデル
    
```

継承するクラスが違うだけでListViewの時と同じです。

DetailViewは特定のレコードだけを取り出すためのものです。

取り出されるレコードは「pk」というパラメータでプライマリキーの値を渡すようになっています。

取り出された値は「object」という変数としてテンプレート側に渡されます。

利用されるテンプレートは「モデル_detail.html」という名前のファイルになります。

## Friendモデルをジェネリックビューで表示する
では実際にFriendモデルに対しListViewとDetailViewを使用し表示させてみます。

### ジェネリックビューの定義
views.pyに以下を追加します。

```python:views.py
from django.views.generic import ListView, DetailView
from .models import Friend

class FriendList(ListView):
    model = Friend
    
class FriendDetail(DetailView):
    model = Friend
    
```

### urlpatternsの登録
続いてurls.pyのurlpatternsに追加します。
```python:urls.py
from .views import FriendList, FriendDetail

urlpatterns = [
    ...(略)...
    path('list', FriendList.as_view(), name='list'),
    path('detail/<int:pk>', FriendDetail.as_view(), name='detail'),
]

```

### テンプレートの追加
friend_list.htmlとfriend_detail.htmlを以下のように追加します。

```html:friend_list.html
{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>{{ title }}</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">

</head>
<body class="container">
    <h1 class="display-4 text-primary">Friends List</h1>
    <table class="table">
        <tr>
            <th>id</th>
            <th>name</th>
            <th></th>
        </tr>
        {% for item in object_list %}
        <tr>
            <td>{{ item.id }}</td>
            <td>{{ item.name }}</td>
            <td><a href="/hello/detail/{{ item.id }}">detail</a></td>
        </tr>
        {% endfor %}
    </table>
</body>
</html>

```

object_listから順に取り出して表示をしています。

```html:friend_detail.html
{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>{{ title }}</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">

</head>
<body class="container">
    <h1 class="display-4 text-primary">{{ title }}</h1>
    <table class="table">
        <tr><th>ID</th><td>{{ object.id }}</td></tr>
        <tr><th>Name</th><td>{{ object.name }}</td></tr>
        <tr><th>Gender</th><td>
            {% if object.gender == True %}male{% endif %}
            {% if object.gender == False %}female{% endif %}
        </td></tr>
        <tr><th>Email</th><td>{{ object.mail }}</td></tr>
        <tr><th>Age</th><td>{{ object.age }}</td></tr>
        <tr><th>Birthday</th><td>{{ object.birthday }}</td></tr>
        <form action="{% url 'list' %}" method="get">
            {% csrf_token %}
            <tr><th></th><td>
                <input type="submit" value="click" class="btn btn-primary mt-2">
            </td></tr>
        </form>
    </table>
</body>
</html>

```
object変数から要素を取り出して表示しています。

### 動作確認

```localhost:8000/hello/list```へアクセスすると以下のようにFriendモデルの全レコードは表示されていることが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/8f649f15-988f-ecd9-31cc-5468e2690706.png)

「detail」をクリックすると選択したレコードのdetail画面へと遷移できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/a6c5a4f4-8a04-f31b-2bee-b56940e79a04.png)

ジェネリックビューを使えると車輪の再開発は防げそうですが、ジェネリックビュー固有の変数名を覚えるまで苦労しそうな印象です…。

※一度覚えたら強い

## まとめ
今回は
* プログラムからのCRUDの実行
* ジェネリックビューで少しサボる

という内容でした。
次回はデータベースの検索周りを行います。