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

## はじめに
Django 初心者が簡単なアプリをつくる3の続き。前回はCRUDのC、つまりCreate部分をClass-based-viewで作成した。今回はClass-based-viewからFunction-view(関数ビュー)に書き換えて、2つを比較しながらどうやってDjangoは動いているのかを追いかけてみたい。

### 初心者が簡単なアプリをつくるシリーズ目次
- [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つのコマンド実行済み

```bash

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

```

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


## 1. Class IndexView を def index に書き換える


```python:index_view

class IndexView(generic.ListView):
    template_name = 'myapp/index.html'
    context_object_name = 'movie_list'
    queryset = Movie.objects.all()

# 以下が Function-view

def index(request):
    movie_list = Movie.objects.all()
    return render(request, 'myapp/index.html', {'movie_list': movie_list})

```
実は2行で終わった… Function-view の方がコードが短い。やっていることは
1. model(データベース)の Movie からデータをすべて集めて movie_list というオブジェクト(インスタンス)にまとめる
2. movie_listを材料にしてindex.htmlにレンダリングする

### 学んだ知識

#### 1-1. requestって何？

- [リクエストとレスポンスのオブジェクト](https://docs.djangoproject.com/ja/3.0/ref/request-response/#quick-overview)

> あるページがリクエストされたとき、Django はリクエストに関するメタデータを含んだ HttpRequest オブジェクトを生成します。それから Django は HttpRequest をビュー関数の最初の関数として渡し、適切なビューを読み込みます。あらゆるビューは HttpResponse オブジェクトを返す必要があります。

引数のrequestって何だ？誰が何をリクエストしてんだ？と疑問だったが、**requestの正体はHttpRequestのオブジェクト(インスタンス)**だった。

#### 1-2. render()って何？

- [render()](https://docs.djangoproject.com/en/3.0/topics/http/shortcuts/#render)

> Combines a given template with a given context dictionary and returns an HttpResponse object with that rendered text.

render関数は辞書型のデータをHTMLファイルに整形して表示してくれると。**render()で最終的に出てくるのはHttpResponseのオブジェクト(インスタンス)。**

#### 1-3. objectsって何？

- [objects](https://docs.djangoproject.com/en/3.0/ref/models/class/#objects)

> Each non-abstract Model class must have a Manager instance added to it. Django ensures that in your model class you have at least a default Manager specified. If you don’t add your own Manager, Django will add an attribute objects containing default Manager instance.

**model(データベース)に入っているデータを引っ張ってくるためにManagerというモノがあって、その名前がobjects。**だから`Movie.objects.all()`というのは「Movieのobjects(Manager)よ、すべてのデータを持って来い！」っていう意味になる。


## 2. Class MovieDetailView を def moviedetail に書き換える

```python:detail_view

class MovieDetailView(generic.DetailView):
    model = Movie
    template_name = 'myapp/detail.html'

# ここからFunction-view

def moviedetail(request, pk):
    m = Movie.objects.get(pk=pk)
    return render(request, 'myapp/detail.html', {'movie': m})

```

ここも2行で済んだ。`(request, pk)`部分の`pk`はurls.pyに記述してある`path('movie/<int:pk>/',`の`<int:pk>`のこと。ここは変更可。もしもurlを`<int:movie_id>`に変えるならば、ここを`(request, movie_id)`に変える。この関数でやっていることは、
1. Movie(データベース)に入っている、pk(プライマリーキー)がリクエストと一致しているデータをgetしてmというオブジェクトにする
2. mをmovieとしてdetail.htmlにレンダリングする
ってことになるんだろう。

## 3. Class RegisterDirectorView を def registerdirector に書き換える

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

# ここからFunction-view

def registerdirector(request):
    if request.method == "POST":
        form = DirectorForm(request.POST)
        if form.is_valid():
            d = form.save(commit=False)
            d.save()
            return redirect('myapp:registermovie')
    else:
        form = DirectorForm()
        return render(request, 'myapp/register.html', {'form': form})
```
Class-based-viewとFunction-viewの大きな違いが現れるのはここCreateViewのところ。仕組みを把握するためにはFunction-viewの方が理解しやすい。

### 学んだ知識

#### 3-1. request.method == "POST" って何？

- [「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典](https://wa3.i-3-i.info/word11405.html)
- [HTTP リクエストメソッド](https://developer.mozilla.org/ja/docs/Web/HTTP/Methods)
- [djangoにおけるPOSTとGETの違いを解説【具体例もあります】](https://codor.co.jp/django/difference-post-get)

ブラウザはサーバとHTTPでやりとりしているけれども、そのやりとり(method)がPOSTということ。実はregister.htmlに`<form method="POST">`と記述があり、これはsaveボタンが押されたらPOSTとしてデータを送信するという意味で、この行と絡んでくる。

#### 3-2. form = DirectorForm(request.POST)って何？

- [はじめての Django アプリ作成、その 4](https://docs.djangoproject.com/ja/3.0/intro/tutorial04/)

> request.POST は辞書のようなオブジェクトです。キーを指定すると、送信したデータにアクセスできます。この場合、 request.POST['choice'] は、選択された選択肢の ID を文字列として返します。 request.POST の値は常に文字列です。

つまり`request.POST`に入力したデータが入っていると。それをDirectorFormにあてはめてformというオブジェクトを作っているということになる。

#### 3-3. form.is_valid()って何？

- [Using forms to validate data](https://docs.djangoproject.com/en/3.0/ref/forms/api/#using-forms-to-validate-data)

> The primary task of a Form object is to validate data. With a bound Form instance, call the is_valid() method to run validation and return a boolean designating whether the data was valid:

formというオブジェクトが有効であるかどうか。入力漏れ等あるとこれで弾いてくれる。バリデーション(入力したデータが間違ってないかチェックする）とかデータのクリーニング(データを使いやすい形に整形する)とかいう言葉を知らなかったので、はじめはちんぷんかんぷんだった。

#### 3-4. d = form.save(commit=False)って何？
- [Django、フォームセットでのcommit=Falseの注意点](https://narito.ninja/blog/detail/37/)
- [The save() method](https://docs.djangoproject.com/en/3.0/topics/forms/modelforms/#the-save-method)

> This save() method accepts an optional commit keyword argument, which accepts either True or False. If you call save() with commit=False, then it will return an object that hasn’t yet been saved to the database. In this case, it’s up to you to call save() on the resulting model instance. This is useful if you want to do custom processing on the object before saving it, or if you want to use one of the specialized model saving options. commit is True by default.

データを入れたformをsaveしてdというオブジェクトにするが、まだデータベースには入っていない状態。なんでわざわざこんなことをしてんだろうと思ったが、どうもこのformに後からデータを追加してあらためてデータベースに保存することがあるかららしい。一時保存みたいなものか。で、次行の`d.save()`でようやくデータベースに保存されると。

#### その他
redirect()は「()内のアドレスに飛んでいけ」、else以下は「空のformを作ってregister.htmlにレンダリングしろ」と。こんな風に学んだ知識を活用したらどんどんコードが読めるようになる。なんか自分が進化したようで嬉しい。

## 4. Class RegisterMovieView を def registermovie に書き換える

```python:register_movie_view

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 }) 

# ここからFunction-view

def registermovie(request):
    if request.method  == "POST":
        form = MovieForm(request.POST)
        if form.is_valid():
            m = form.save(commit=False)
            m.save()
            return redirect('myapp:movie_detail', pk=m.pk)
    else:
        form = MovieForm()
        return render(request, 'myapp/register.html', {'form': form})
```
コードはほぼ前回と一緒。あえて言うなら`redirect('myapp:movie_detail', pk=m.pk)`部分だが、「mというオブジェクトのpkをmovie_detailのpkとして飛んでいけ」という意味になるだろうか。

## 5. Class WritingLogView を def writinglog に書き換える

```python:writing_log_view

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 }) 

# ここからFunction-view

def writinglog(request):
    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:
        form = LogForm()
        return render(request, 'myapp/register.html', {'form': form})

```
コードはほぼ一緒。「読める！読めるぞ！！」(by ムスカ)



## あとがき
Class-based-Viewの方が最終的には楽に書けるんだろうけど、Function-viewの方が仕組みが把握しやすいし細かなところに手が届く感じ。オートマ車とマニュアル車みたいなものだろうか。そういやFunction-viewに書き換えたら、ちゃんとurls.pyも書き換えないと動かないので悪しからず。次回はCRUDのUDつまりUpdateとDeleteを実装予定。今回みたいに両方並べてコードを書いたほうがたぶんわかりやすいハズ。間違い等ありましたら、ご指摘ご指導よろしくお願いいたします。ゴールは近い…のか？