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

## はじめに
Django 初心者が簡単なアプリをつくる2の続き。前回はurls.py,views.py,templateを作成し、CRUDのR、つまりRead部分を作成した。今回はCreate部分をClass-based-viewで作成したい。

### 初心者が簡単なアプリをつくるシリーズ目次
- [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](https://qiita.com/soh506/items/fc6b00ba728ac41fa3d1)（完結）
    - Update部分・Delete部分の実装

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

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

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

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

## 0. 大まかな設計
本当は映画タイトルと監督名と視聴日と感想を1画面にで一気に入力できればと思ったのだが…。ForeignKeyで紐づけているとそう簡単にはいかないことが判明（こういうのって実際に作ってみてから不都合がわかるよね…）。「インラインフォームセット」なるもので実装はできるものの、初心者にはややハードルが高いので、監督名を入力、映画を入力、感想を入力、と別々に登録する方式を採用。イメージは下図。

<img width="1357" alt="08116AEF-A591-4655-A10C-14065C8E4BB8.jpeg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321085/b8934cbb-ee81-2de7-6686-baae1243c89d.jpeg">

## 1. Create の実装
今までは`127.0.0.1:8000/admin/`の管理サイトから各データを入力していたが、webから入力するためのページをつくりたい。それがCreate部分。つくる順番は以下の通り。

1. forms.py をつくる
2. urls.py でアドレスを決定する
3. views.py にどうやって監督名や映画の情報を入力するかを書く
4. 入力するページのHTMLファイルをつくる

このformというのはmodelというデータベースに入力するための入り口。というよりmodelとHTMLのフォームをつなぐインターフェースと思われる。Djangoをいじり始めた頃、DjangoのFormとHTMLのフォームを一緒のモノと考えていたために何をしているのかさっぱり分からなかった。**Formとフォームは別物です。**なんでformなんかがあるんだろうとずっと疑問に思っていたが、**洗濯した衣服（データ）が勝手にタンス（model)に入るわけがない。ちゃんと畳んで入れる人が必要で、それがform+viewなんだな**と自己解決。

<img width="1151" alt="E635ADCB-33A0-4A9A-9BFA-C7593213C02E.jpeg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321085/030cf63e-7247-8768-8c36-0d20b9497363.jpeg">


上図の真ん中部分のFormをform.pyに記述する。

### form.py のコード

```python:myapp/form.py
from django.forms import ModelForm
from myapp.models import Movie, Director, Log

class DirectorForm(ModelForm):
    class Meta:
        model = Director
        fields = ('name',)

class MovieForm(ModelForm):
    class Meta:
        model = Movie
        fields = ('title','watch_date', 'director')

class LogForm(ModelForm):
    class Meta:
        model = Log
        fields = ('movie','text')

```

Modelが3つあるのでFormも3つ記述。Formクラスには`forms.Form`と`forms.ModelForm`の2種類あり`ModelForm`の方が楽。formは入力されたデータの入り口なので、そのままmodelと直結するから。`model`(データベース)は何を使うか、どの`field`(項目)を使うか、を`class Meta`以下に記述するだけ。次に入力ページのアドレスをurls.pyに記述する。

### urls.py のコード

```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('register/director/', views.RegisterDirectorView.as_view(), name='registerdirector'), #これ追加
    path('register/movie/', views.RegisterMovieView.as_view(), name='registermovie'), #これ追加
    path('writing/log/', views.WritingLogView.as_view(), name='writinglog'), #これ追加
]
```
次にurls.pyで指定したように、Class-based-view (ここでは `RegisterDirectorView`と`RegisterMovieView`と`WritingLogView` のこと)を作成する。

### views.py のコード

まずは監督名を登録するためのView。

```python:myapp/views.py

class RegisterDirectorView(generic.CreateView):
    model = Director
    form_class = DirectorForm
    template_name = 'myapp/register.html'
    def get_success_url(self):
        return reverse('myapp:registermovie') 
```

次に映画の情報を登録するView。

```python:myapp/views.py

class RegisterMovieView(generic.CreateView):
    model = Movie
    form_class = MovieForm
    template_name = 'myapp/register.html'
    def get_success_url(self):
        return reverse('myapp:movie_detail', kwargs={'pk': self.object.pk }) 
```

最後に映画の感想を書くView。

```python:myapp/views.py
class WritingLogView(generic.CreateView):
    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 }) 
```

以上3つのCreateViewに共通しているのは以下の4項目。

1. 入力されたデータを格納するデータベースは何か(model = の部分)
2. 入力するformは何を使うのか(form_class = の部分）
3. 入力画面を表示するHTMLファイルは何か(template_name = の部分）
4. データの入力も、データベースへの格納も上手くいったら、どこのページを表示するか(def get_success_url の部分)

この4項目を決めていればあとは自動的に色々やってくれるのがCreateView。

### 3つのviewの最後の行を解説

#### 1. `return reverse('myapp:registermovie')`

RegisterDirectorView。reverse関数とは()に書かれたアドレスへ飛んでいけという意味。今回の`myapp:registermovie`はurls.pyに記述した`path('register/movie/', views.RegisterMovieView.as_view(), name='registermovie'),`の最後の部分に該当。つまり`127.0.0.1:8000/myapp/register/movie/`へ行け、ということ。

#### 2. `return reverse('myapp:movie_detail', kwargs={'pk': self.object.pk })`

RegisterMovieView。`kwargs={'pk': self.object.pk }`はキーワード引数ってやつで辞書型。自分の理解では…入力されたデータはインスタンス化されてオブジェクト（インスタンス）というモノになる。それがself.object。そのモノには自動的に`pk`(プライマリーキー:勝手につけられるidナンバー)がついていて、その`pk`(self.object.pk)はmovie_detailの`pk`(辞書のkey部分)に紐づけされる。それを使ってmovie_detailのページに行けと。つまりオブジェクト「ソナチネ:北野武:2020-05-01」のpkが1とすると、`127.0.0.1:8000/myapp/movie/1/`に飛んでいけという意味。

#### 3. `return reverse('myapp:movie_detail', kwargs={'pk': self.object.movie.pk })`

WritingLogView。キーワード引数部分。先程は`self.object.pk`で今回は`self.object.movie.pk`になっている。なぜか。RegisterMovieViewのself.objectは映画のオブジェクトだったが、WritingLogViewのself.objectは感想のオブジェクトだから。感想に紐付けられている映画のpkを引っ張って来るために`self.object.movie.pk`となる。ちなみにこの`movie`部分は、models.pyの`class Log(models.Model)`で記述したmovieを意味している。


### HTMLファイルをつくる
先程のコードに記述(template_name = 部分)した`retister.html`をつくる。templatesディレクトリ、その下のmyappディレクトリの中にregister.htmlファイルを作成し、以下のコードを記述する。

```html:templates/myapp/register.html
<form method="POST">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">save</button>
</form>
```
これがHTMLのフォーム。`{% csrf_token %}`はセキュリティ上必要なおまじないと思って絶対に書く。次の`{{ form.as_p }}`でformの内容が自動的に作られて表示される。監督名入力、映画情報入力、感想入力の3つのフォームがひとつのコードで済むという。すごい。むっちゃ賢い。

### register.htmlの表示結果
ブラウザに直接`http://127.0.0.1:8000/myapp/register/director/`を入力すると「監督名入力画面」へ。`http://127.0.0.1:8000/myapp/register/movie/`ならば「映画情報入力画面」へ。`http://127.0.0.1:8000/myapp/writing/log/`ならば「感想入力画面」へ飛ぶ。
「映画情報入力画面」の監督フィールドや「感想入力画面」のタイトルフィールドはドロップダウンの選択式になっている(ForeignKeyでひもづけされているため。)


|監督名入力画面|映画情報入力画面|感想入力画面|
|:-----------------:|:------------------:|:------------------:|
| ![Screenshot from 2020-05-20 20-09-30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321085/209194f2-d423-1b8c-569a-5897f27f1de0.png)|![Screenshot from 2020-05-20 20-08-22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321085/0af84c5a-9c27-8fc5-925a-09e7a8104bff.png)|![Screenshot from 2020-05-20 20-39-27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321085/e737248d-2503-453c-50f7-c8abc387e9ab.png)
|

以上でCreate部分が実装できた。

## あとがき
これでCRUDの半分まで来たわけで、残るはUPDATEとDELETE。でも次回はちょっと寄り道をして今までClass-based-viewで書いてきたコードをFunction-viewで書き直し、両方比較しながらDjangoの仕組みを眺める予定。間違い等ありましたら、ご指摘ご指導よろしくお願いいたします。