Django 初心者が簡単なアプリをつくる5

## はじめに
Django 初心者が簡単なアプリをつくる4の続き。目的は複雑なことや面倒なことを極力削ぎ落として、単純化したWebアプリを作り、Djangoの仕組みを学ぶこと。バックエンド中心に何がどう繋がり動くのかだけを理解する。ひと通りCRUD(Create, Read, Update, Delete)を実装し、無事動けばゴール。前回はClass-based-viewからFunction-view(関数ビュー)に書き換えて、2つを比較しながらどうやってDjangoは動いているのかを追いかけた。今回はCRUDのUpdate部分とDelete部分を実装してみたい。で、実は完結です。

### 初心者が簡単なアプリをつくるシリーズ
- [Django 初心者が簡単なアプリをつくる1](https://qiita.com/soh506/items/4035650d2ea19e82d8fd)
    - 準備・Djangoの全体像・models.pyの作成
- [Django 初心者が簡単なアプリをつくる2](https://qiita.com/soh506/items/98239c6b1c6fbf737429)
    - Read部分の実装（urls.py・views.py・templateファイルの作成）
- [Django 初心者が簡単なアプリをつくる3](https://qiita.com/soh506/items/34cef1d386e1aa1b4496)
    - Create部分の実装（form.py等の作成)
- Django [初心者が簡単なアプリをつくる4](https://qiita.com/soh506/items/ac9b2914f64839edac3b)
    - Class-based-view と Function-view の比較
- Django 初心者が簡単なアプリをつくる5（今回で完結）
    - Update部分・Delete部分の実装


### 環境
Ubuntu 20.04 LTS
Python 3.8.2
Django 3.02

### 前提
プロジェクト名はconfig、アプリ名はmyapp とする。つまり以下2つのコマンド実行済み

```bash

(myenv)$ django-admin startproject config .
(myenv)$ python manage.py startapp myapp

```

templatesディレクトリはmanage.pyと同じ階層に作成、setting.pyも修正済み。(「[Django 初心者が簡単なアプリをつくる1](https://qiita.com/soh506/items/4035650d2ea19e82d8fd)を参照のこと)


## 1. Update部分の実装
### 大まかなイメージ
映画詳細画面のページでログが表示されていて、そのログをちょっと編集したいとき、ボタンを押したら編集できる、そんな感じ。

<img width="1158" alt="5FF00F76-C9CC-4796-B073-754EAA3173C2.jpeg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321085/d58ab7e2-d7fe-6359-aaa5-1bd28d18ec6f.jpeg">


新たなページを作成する場合、urls.py→views.py→templateという順番で作成する。こうして作り方を型にハメることで思考の漏れが少なくなると思う。まずはurls.pyにログ(感想)をupdateするときのアドレス`update/log/<int:pk>/`を記述する。

### urls.py(update部分)

```python:myapp/urls.py
from django.urls import path, include
from myapp import views

app_name = 'myapp'
urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
    path('movie/<int:pk>/', views.MovieDetailView.as_view(), name='movie_detail'),
    # (中略)
    path('update/log/<int:pk>/', views.UpdateLogView.as_view(), name='updatelog'), # この行を追加
]

```

### views.py(update部分)

次にviews.pyにClass-based-viewであるUpdateLogViewのコードを記述する。

```python:myapp/views.py

class UpdateLogView(generic.UpdateView):
    model = Log
    form_class = LogForm
    template_name = "myapp/register.html"
    def get_success_url(self):
        return reverse('myapp:movie_detail', kwargs={'pk': self.object.movie.pk })

# もしもFunction-viewで書くなら以下の通り

def updatelog(request, pk):
    obj = get_object_or_404(Log, id=pk)
    if request.method == "POST":
        form = LogForm(request.POST, instance=obj)
        if form.is_valid():
            form.save()
            return redirect('myapp:movie_detail', pk=obj.movie.pk)
    else:
        form = LogForm(instance=obj)
        return render(request, 'myapp/register.html', {'form': form})
```
`Class UpdateLogView`で指定すべきは４つの項目(前回のCreateViewと同じ)。
1. modelは何を使うか
2. formは何を使うか
3. 表示するtemplateは何を使うか
4. 成功したらどこへ飛ばすのか

で、実際にどう動いているのかを知るのはFunction-viewの`def updatelog`で理解する。
`(request, pk)`のrequest部分はdetail.htmlのeditボタンを押したらできるHttpRequestオブジェクト。pk部分にはそのログのidの数字が入る。でこのpkを使ってurls.pyの`update/log/<int:pk>/`の`<int:pk>`部分に数字が入る。以下覚え書き。

<img width="1201" alt="F87D58D8-8228-4E22-AB65-5682ABBC5233.jpeg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321085/256f2f7c-4c0e-5f95-914a-0ac11fac07ae.jpeg">

以上でUpdate実装が完了した。続いてDelete部分を実装する。


## 2. Delete部分の実装

### 大まかなイメージ
ログ(感想)を消す機能と、映画のデータを消す機能の2つが欲しいなと。ならばurls.pyに2つ、views.pyに2つまとまったコードを書く必要があるはず。

### urls.py(delete部分)

```python:myapp/urls.py

from django.urls import path, include
from myapp import views

app_name = 'myapp'
urlpatterns = [
    path('', views.index, name='index'),
    # (中略)
    path('delete/log/<int:pk>/', views.deletelog, name='deletelog'), # この行を追加
    path('delete/movie/<int:pk>/', views.deletemovie, name='deletemovie'), # この行を追加
]
```
ここで記述したアドレスは削除の確認画面。「これ消しちゃっていい？」って出てくるところ。次にviews.pyにコードを記述する。

### views.py(感想を消す部分)

```python:myapp/views.py

class DeleteLogView(generic.DeleteView):
    model = Log
    def get_success_url(self):
        return reverse('myapp:movie_detail', kwargs={'pk': self.object.movie.pk })

# ここからFunction-view

def deletelog(request, pk):
    obj = get_object_or_404(Log, id=pk)
    movie_id = obj.movie.pk
    if request.method =="POST": 
        obj.delete() 
        return redirect('myapp:movie_detail', pk=movie_id)
    context = {'obj':obj}
    return render(request, "myapp/delete.html", context)
```
Class-based-view ではmodelの指定と成功時に飛ばすページの指定だけで完了。ただこの2項目だけだと、削除確認ページの名前を「〇〇_confirm_delete.html」にする必要あり。〇〇にはmodelの名前が入る。今回だと「log_confirm_delete.html」となる。Function-viewでは「delete.html」を作成して削除確認ページとした。

Class-based-viewでは自動でやってくれることで、Function-viewでは工夫しなければならなかったことは、ログを削除したら、削除後の詳細画面を出すこと。ログを消す前に、そのログの映画のidを保存しておかないと、映画の詳細画面に戻ってこれない。以下覚え書き。

<img width="1298" alt="51EECC10-5523-49E7-A77E-1ACD0A0D133B.jpeg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321085/39cd5bbc-0b01-c7a0-6402-e1581de586d4.jpeg">

### views.py(映画データを消す部分)

```python:myapp/views.py

class DeleteMovieView(generic.DeleteView):
    model = Movie
    def get_success_url(self):
        return reverse('myapp:index')

# ここからFunction-view

def deletemovie(request, pk):
    obj = get_object_or_404(Movie, id=pk)
    if request.method == "POST":
        obj.delete()
        return redirect('myapp:index')
    context = {'obj':obj}
    return render(request, "myapp/delete.html", context)
```
コードはほぼdeletelogの繰り返し。違うのは削除後にindexに飛ぶくらい。


### template部分を書く

このtemplateは削除確認画面(delete.html)。ログを消しても映画のデータを消しても同じdelete.htmlで済ませる。表示は不格好(上下どちらかが空行になってしまう)になるけれども。

```python:myapp/templates/myapp/delete.html
<form method="POST">
    {% csrf_token %}
    {{obj.movie}}<br> # logを消すときこちらが表示される
    {{obj.text}}<br>
------------------------------<br>
    {{obj.title}}<br> # movieを消すときこちらが表示される
    {{obj.director}}<br>
    Do you want to delete it?<br>
    <button type="submit">OK</button>
</form>
```
で編集ボタンと削除ボタンをdetail.htmlに追加する

```html:templates/myapp/detail.html
{% for log in movie.log.all %}
    <li>
        <button onclick="location.href='{% url 'myapp:updatelog' log.id %}'">edit</button>
        <button onclick="location.href='{% url 'myapp:deletelog' log.id %}'">delete</button>
        {{ log.text }}
    </li>

{% endfor %}
```

以上でDelete部分の実装を完了した。


### その他微調整
- index.htmlに各ページに飛ぶようにリンクを貼る

```html:index.html
<a href="{% url 'myapp:registerdirector' %}">Register Director</a><br>
<a href="{% url 'myapp:registermovie' %}">Register Movie</a><br>
<a href="{% url 'myapp:writinglog' %}">Writing Log</a>
```

- detail.htmlからその映画のlogを入力できるように機能追加＋リンクを貼る

```python:views.py
def writingthismovielog(request, movie_id):
    obj = get_object_or_404(Movie, id=movie_id)
    form = LogForm({'movie':obj})
    if request.method == "POST":
        form = LogForm(request.POST)
        if form.is_valid():
            l = form.save(commit=False)
            l.save()
            return redirect('myapp:movie_detail', pk=l.movie.pk)
    else:
        return render(request, 'myapp/register.html', {'form': form})
```
ボタンを押したら映画のタイトルだけが先に入力済みになってて、あとは感想を書くだけみたいにしてみたかった。工夫のしどころは`form = LogForm({'movie':obj})`のところ。オブジェクトを作成し、辞書型でタイトル部分を指定してLogFormに入れたと。試すと上手くいった。こうやってこうなるはずだからこうだ！と自分の思った通りに動いてくれると楽しい。たったこの1行だけなんだけど、それがとてつもなく嬉しかったりする。

```html:detail.html
<a href="{% url 'myapp:writingthismovielog' movie.id %}">Write log of this movie data</a><br>
<a href="{% url 'myapp:deletemovie' movie.id %}">Delete this movie data</a><br>
<a href="{% url 'myapp:index' %}">To Index</a><br>
```
で上記のリンクをdetail.htmlに貼って微調整も完了。

## こんな感じで動いて・・・完成！！

| Update and Delete Log | Delete Movie data|
|:-----------------:|:------------------:|
|![editdelete.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321085/9c61f022-f90b-c1fa-b275-545285911f48.gif)| ![deletemovie.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321085/d0a559bf-f64c-54a4-1721-e828009e1467.gif) |

上記のgif動画はSimpleScreenRecordで画面を動画でキャプチャー、ffmpegで動画をgifに変換。簡単にできちゃって拍子抜け。なんか世の中進みすぎてない？

## あとがき
動くことだけを目指し、デプロイもせず見栄えも悪いモノだが、CRUD(Create, Read, Update, Delete)を実装できた。少なくとも動いている。というわけで、**Goooooooaaaaallll!** 最低限の基本は押さえることが出来たハズ。このアプリ(もどき)が、初めて「自力でProgrammingできた!」という実感をともなった体験となり、0から1をつくったというわずかな自信につながった。これを「movielogrecord」と名付け、githubにコードを置いておきます。無知も恥も公開。https://github.com/soh506/movielogrecord.git です。読んでくださった方ありがとうございました。少しでもお役に立てれば幸いです。さて次はフロントエンドまわりを勉強して見栄えを良くしようかな。永遠の初心者の勉強は続く・・・。