Python Django チュートリアル(5)


勉強会用資料です
今回は本家のチュートリアルから少し脇道にそれてFormについて説明していきます．
Formに入る前にdjangoの更新，urlのnamespaceについて少し紹介します．

[チュートリアルのチュートリアル](http://qiita.com/maisuto/items/d7e8cf0b5649f545ddb6)
[チュートリアル1](http://qiita.com/maisuto/items/d01e2cca63c8e5b2c18c)
[チュートリアル2](http://qiita.com/maisuto/items/357d13027e4fe5e10618)
[チュートリアル3](http://qiita.com/maisuto/items/eece9d880d94fd241a0d)
[チュートリアル4](http://qiita.com/maisuto/items/fef306bafc57f218794d)

→ [他のチュートリアル](http://qiita.com/maisuto/items/17653f344d1f64afa019)

[ソース（github）](https://github.com/usa-mimi/tutorial)

また，前回まではdjango1.8の説明でしたが，更新をさぼってる間にdjango1.9がリリースされてしまったので
以降のチュートリアルではdjango1.9について説明していきます．
なお，djangoのバージョンを1.8から1.9に変えてもチュートリアル1〜4までの内容に特別な違いはありません．

# django1.9への移行とrequirements.txtの紹介

ソース→`ee195d1`

## djangoの更新

コマンドラインで以下のコマンドを打つと最新のdjango (2016/2/20時点では1.9.2) にアップデートできます．
仮想環境を使用している人は `workon` で仮想環境をactivateするのを忘れないように．
`(tutorial)$ pip install --upgrade django`

```bash
(tutorial)$ pip install --upgrade django
Collecting django
  Using cached Django-1.9.2-py2.py3-none-any.whl
Installing collected packages: django
  Found existing installation: Django 1.8.6
    Uninstalling Django-1.8.6:
      Successfully uninstalled Django-1.8.6
Successfully installed django-1.9.2
```
正常に終了した場合は上記のように既にインスール済みのdjango(この場合は1.8.6)をアンインストールし，最新のdjangoをインストールしてくれます．

## requirements.txt
本チュートリアルでは今のところ使用している外部ライブラリはdjangoだけですが，今後使用する関連ライブラリはどんどん増えてきます．
そんな時に，どのライブラリを使用しているか？，そのライブラリのバージョンは同じか？ をいちいちチェックするのは大変です．
幸いにも，pythonには `pip` というパッケージ管理システムと， `virtualenv` という仮想環境があるため，
この問題に悩まされることは多くないでしょう．

現在の環境で使用しているライブラリ一覧を出すにはシェルで以下のコマンドを打ちます．

```bash
(tutorial)$ pip freeze
Django==1.9.2
wheel==0.24.0
```

pythonでは慣例的に，この内容を `requirements.txt` という名前で出力しておきます．
別の人はこのファイルを取り込むことで，必要なライブラリをインストールすることができます．
ご覧のように出力内容には1.9.2のようなバージョン番号もついているため，バージョンの違いによる動作不良が出る心配もありません．

> チュートリアルではすでに置いてたつもりでしたが，作成してなかったので commit: `ee195d1` で追加しています．

### 使用しているライブラリの出力 (requirements.txtの作成)

```bash
(tutorial)$ pip freeze > requirements.txt
```

`pip freeze` の内容をリダイレクトするだけです．
djangoの場合は `manage.py` と同じ階層に置くことを推奨します．

### ライブラリの取込

取込には `pip install -r` コマンドを使用します．
取り込みたい環境に `workon` されていることを確認した上で， `-r` の後ろに
取り込みたいファイル (`requirements.txt`) へのパスを記述しましょう．

```bash
(tutorial)$ pip install -r requirements.txt
```


> 既にinstall済みのライブラリがあり，そのバージョンを更新したい場合は
> `(tutorial)$ pip install -U -r requirements.txt`
> のように `-U` オプションが必要です．
> ただ，手元で試したところ，pipのバージョンが `8.0.2` の場合は付けなくても更新してくれるようです．

# urlにnamespaceの追加

ソース→`bba5e4f`

チュートリアル3ではurlのnamespace化の説明をスキップしてましたが，
のちのちの事を考えると分離しといたほうが都合がいいのでpollsのurlにnamespaceを設定します．
付け方は `include` 関数の引数に `namespace`を追加するだけです．


```python:tutorial/urls.py
urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
    url(r'^polls/', include('polls.urls')),
]
```

↓ pollsのincludeの引数に `namespace` を追加

```python:tutorial/urls.py
urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
    url(r'^polls/', include('polls.urls', namespace='polls')),
]
```
> 閉じカッコの位置を間違えてurl関数の引数にしないように注意

namespaceを設定すると，`namespace:hoge` の形でurlを引くことができるようになります．
namespaceは `api:polls:create` のようにさらに階層化して書くこともできます．
include先の `polls/urls.py` の名前には `poll_` というprefixを付けてましたが，
namespaceを使うと不要になるので削除しておきましょう．

```python:polls/urls.py
urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'(?P<pk>\d+)/$', views.detail, name='poll_detail'),
    url(r'(?P<pk>\d+)/vote$', views.vote, name='poll_vote'),
    url(r'(?P<pk>\d+)/results$', views.results, name='poll_results'),
]
```

↓ namespaceを切ったので `poll_` という名前のprefixが不要になる．

```python:polls/urls.py
urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'(?P<pk>\d+)/$', views.detail, name='detail'),
    url(r'(?P<pk>\d+)/vote$', views.vote, name='vote'),
    url(r'(?P<pk>\d+)/results$', views.results, name='results'),
]
```


>django1.9では `tutorial/urls.py` で includeの引数にnamespace名を書く代わりに，
>`polls/urls.py`に `app_name='polls'`を書くことでも設定できます．
>https://docs.djangoproject.com/en/1.9/releases/1.9/#passing-a-3-tuple-or-an-app-name-to-include


この変更により，名前からのURLの引き方が `poll_detail` などの代わりに `polls:detail` になりました．
修正箇所についてはソースの差分を確認してみてください．

名前空間を切ることで，`polls` アプリが `tutorial`プロジェクトからより分離され，他プロジェクトで使いやすくなります．
前回までのやり方では，同プロジェクト内の別アプリが `poll_detail` などの名前をURLにつけていた場合はエラーになるため，
プロジェクトが管理している全てのアプリケーションのURL名を気にする必要があります．
一方，namespaceを使うと，rootのurls.py (`tutorial/urls.py`) で `namespace` を衝突しないように
気をつけるだけでよくなります．

# Formとは

さて，やっとFormの話に入っていきます．
Formとは英訳の「書式，申込用紙」などの意味の通り，ある形式に合わせたデータをクライアントからサーバへ渡すのに使用します．
チュートリアル4ではテンプレート内で直にFormを記述し，view側で受け取り後の処理を書いていました．
しかし，テンプレート，viewはMVCモデルでいうView, Controllerに相当し，ここにロジックが入るのはよろしくありません．
また，今の形式ではラジオボタンの表示がテンプレートに記述されており，そのバリデーション（選択されたデータが正しいかの判定），
さらにその入力値を用いた処理（投票処理）がviewに記述されています．
しかし，入力項目とそれに対するバリデーション，そのデータを使用した処理は密接に関わっているため，
これらをまとめる扱いたいです．
djangoではFormクラスが用意されており，一般的な入力（テキスト，セレクトリスト，ラジオボタンなど）と，
そのバリデーター（入力チェック）を提供しています．

Formクラスを使えば[本家チュートリアル5](https://docs.djangoproject.com/en/1.9/intro/tutorial05/)で行うテストも書きやすくなりますし，
入力項目も簡単に増やせるようなりますし，別箇所での使い回しも簡単になります．
その上，クラスベース汎用Viewと連携することでviewの記述をさらに少なく，わかりやすくすることができます．


## Formクラス

### テキストフィールドの出力

ソース→`34698a1`

まずはともあれ，試しにFormを作ってみましょう．
アプリケーションフォルダ内に `forms.py`  というファイルを作り，その中にFormクラスを定義していきます．
Formクラスにはfieldクラスをメンバに設定していきます．
設定できるfieldクラスは公式ドキュメントを参考にしてください．
https://docs.djangoproject.com/en/1.9/ref/forms/fields/


とりあえず手始めに文字入力用のフィールドであるCharFieldを設定してみます．
CharFieldは必須引数として最長文字数（max_length）を指定する必要があるので，とりあえず100文字を設定します．

```python:polls/forms.py
from django import forms


class MyForm(forms.Form):
    text = forms.CharField(max_length=100)
```

こう書くとどうなるのか，出力を確認してみましょう．
`./manage.py shell` でpythonのシェルを起動させ，先ほど作ったFormクラスのインスタンスを作成し，
それをprintしてみます．

```bash
$ ./manage.py shell
(InteractiveConsole)
>>> from polls.forms import MyForm
>>> f = MyForm()
>>> print(f)
<tr><th><label for="id_text">Text:</label></th><td><input id="id_text" maxlength="100" name="text" type="text" /></td></tr>
```

出力としてtext型のinputタグが出力されているのがわかります．
フィールド名を `text` にしたため，labelとして `Text` という文字列が出ていることも確認できます．

### templateへの出力，ブラウザでの確認
ソース→`34f4914`

では次に，作ったFormクラスをhtmlさせましょう．
まずはviews.pyにFormの作成と，テンプレートへの受け渡しを書きます．

```python:polls/views.py
from .forms import MyForm


def form_test(request):
    form = MyForm()
    return render(request, 'polls/form.html', {
        'form': form,
    })
```

次にテンプレートを用意しましょう．
`polls/form.html` というテンプレートpathを指定したので，ファイルの場所は
`polls/templates/polls/form.html` です．

```html:polls/templates/polls/form.html
<html>
  <body>
    <form>
      {{ form }}
    </form>
  </body>
</html>
```
これで `{{ form }}` の中に先ほどシェルで確認した以下の文字列が入るはずです．
`<tr><th><label for="id_text">Text:</label></th><td><input id="id_text" maxlength="100" name="text" type="text" /></td></tr>`

最後に，form_test関数とurlを接続します．
`polls/urls.py` にurlを追加しましょう．

```python:polls/urls.py
urlpatterns = [
...
    url(r'^form$', views.form_test),
...
]
```

ここまで書いたら `./manage.py runserver` でテストサーバを起動させ，ブラウザで確認してみましょう．
urlは `http://localhost:8000/polls/form` です．

>![Kobito.HyYPSO.png](https://qiita-image-store.s3.amazonaws.com/0/58048/a8c7b65e-cbc0-5fed-d81d-55bcc905720d.png "Kobito.HyYPSO.png")
画面   

殺風景ですが，とりあえず入力用のボックスが1つ出ていることが確認できます．

>![Kobito.JxJa67.png](https://qiita-image-store.s3.amazonaws.com/0/58048/e7731abc-5c58-0544-fca8-a339ec2e8ca3.png "Kobito.JxJa67.png")
htmlソース

htmlのソースはこんな感じです．
目論見通り，`{{ form }}` の部分が置き換わってます．

### POST送信処理

ソース→`06c8422`

さて，入力ボックスはできましたが，サーバ側での受け取り処理がまだありません．
htmlでサーバへデータを送信する場合，`<form>`タグに **どこに** **どうやって** データを送るかを記述します．
それぞれ `action` と `method` というattributeを書きます．
また，送信するには `<form>` タグ内に `submit`ボタンを配置する必要があります．
同じviewで受け取る場合は`action`指定しなくても大丈夫です．
今回は空文字で記述だけしてます．

ここで注意して欲しいのですが，POSTでデータ送信をする場合，`csrf_token` が必要になります．
> csrfの説明 → [みんな大好きwikipedia](https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AD%E3%82%B9%E3%82%B5%E3%82%A4%E3%83%88%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88%E3%83%95%E3%82%A9%E3%83%BC%E3%82%B8%E3%82%A7%E3%83%AA)

POSTの通信はhtml的にはサーバへのデータ送信（＝サーバの情報変更）となるため，
どうやって入力されたかが重要になります．
djangoでは`csrf_token`タグを付けることで，そのデータが自分が用意したページで入力されたことを保証します．

修正したhtmlは以下のようになります．

```html:polls/templates/polls/form.html
<html>
  <body>
    <form action="" method="POST">
      {% csrf_token %}
      {{ form }}
      <input type="submit" value="送信"/>
    </form>
  </body>
</html>
```

>![Kobito.1nDDdU.png](https://qiita-image-store.s3.amazonaws.com/0/58048/4999351d-ed4f-46e9-167d-920bab94f6c7.png "Kobito.1nDDdU.png")
ブラウザでボタンが出ていることを確認

ボタンを押してもその後の処理を何も書いてないので何も起こりませんが，
`runsever`を実行している画面のログを見るとPOST通信がされていることが確認できます．

```bash
(tutorial)$ ./manage.py runserver 127.0.0.1:13000
Performing system checks...

System check identified no issues (0 silenced).
February 21, 2016 - 16:56:38
Django version 1.9.2, using settings 'tutorial.settings'
Starting development server at http://127.0.0.1:13000/
Quit the server with CONTROL-C.

[21/Feb/2016 17:34:28] "GET /polls/form HTTP/1.1" 200 343    # ← 画面の表示
[21/Feb/2016 17:34:30] "POST /polls/form HTTP/1.1" 200 343   # ←「送信」ボタンクリック
```

ちなみに，`{% csrf_token %}` タグをhtmlに書き忘れてるとこうなります．

>![Kobito.Ffr1Uf.png](https://qiita-image-store.s3.amazonaws.com/0/58048/569e120f-a67a-0942-40f3-99937d6772cb.png "Kobito.Ffr1Uf.png")

このチェック処理はMIDDLEWAREで行われており，settings.pyを見るとデフォルトで設定されていることが確認できます．

```python:tutorial/settings.py
MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',  # ←←←←←←←← csrfチェック処理
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'django.middleware.security.SecurityMiddleware',
)
```

### データ受信処理
ソース→`8ea545ce`

受け取ったPOSTデータはrequestクラスが`POST`という名前で持っています．
このデータをFormクラスに渡し，データの正当性を確認し，正しければそのデータを使ってなにか処理を行います．

修正したviewはこんな感じになります．

```python:polls/views.py
def form_test(request):
    if request.method == "POST":
        form = MyForm(data=request.POST)  # ← 受け取ったPOSTデータを渡す
        if form.is_valid():  # ← 受け取ったデータの正当性確認
            pass  # ← 正しいデータを受け取った場合の処理
    else:  # ← methodが'POST'ではない = 最初のページ表示時の処理
        form = MyForm()
    return render(request, 'polls/form.html', {
        'form': form,
    })
```

正しいデータを受け取った後はDBなどにデータを登録し，別のページにリダイレクトするなどの処理を行いますが，
今回はひとまず何もしていません．
この状態でブラウザから動作を確認してみましょう．
`Text`に何も入力せずに送信ボタンを押すとエラーメッセージが表示されることが確認できるはずです．

>![Kobito.Bgm3aT.png](https://qiita-image-store.s3.amazonaws.com/0/58048/e1b61a8e-d8dc-c1a4-1de6-315598571ca5.png "Kobito.Bgm3aT.png")



### フィールドのカスタマイズ
ソース→`5637b54`

フィールドは作成する時に引数を渡すことで動作や出力を変更することができます．
例えば，`required=False` と渡すとそのフィールドに入力値がなくてもエラーにならなくなります．
また，`label`引数を追加することで`Text`の文字を変更できるようになります．
指定できる引数は以下を参考にしてください．
https://docs.djangoproject.com/en/1.9/ref/forms/fields/#core-field-arguments

今回は先に説明した`required`と`label`を設定してみます．

```python:polls/forms.py
class MyForm(forms.Form):
    text = forms.CharField(max_length=100, required=False, label='テキスト')
```

>![Kobito.WXYnel.png](https://qiita-image-store.s3.amazonaws.com/0/58048/6c38f476-5f20-b5a4-da02-178f7d33f2d8.png "Kobito.WXYnel.png")

`Text`が`テキスト`になりました．
また，何も入力せずに送信を押してもエラーメッセージが出なくなったのが確認できたかと思います．


## 投票用Form作成

### VoteFormの作成
ソース→`9077adee`


チュートリアル4で作った投票フォームをFormクラスを使って書き換えてみましょう．
必要な入力は `question` モデルに紐付いた選択肢一覧を選択するためのラジオボタンです．
モデルの選択にはModelChoiceFieldが使えます．
このfieldにはモデルを選択するためのquerysetが必要ですが，fieldの定義時にはどの`question`に対する
フォームなのか確定しないため，`__init__`メソッドをオーバーライドし，引数で受け取ることにします．

```python:polls/forms.py
class VoteForm(forms.Form):
    choice = forms.ModelChoiceField(
        queryset=None,
        label='選択',
        widget=forms.RadioSelect(),
        empty_label=None,
        error_messages={
            'required': "You didn't select a choice.",
            'invalid_choice': "invalid choice.",
        },

    )

    def __init__(self, question, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['choice'].queryset = question.choice_set.all()
```
> ModelChoiceFieldのデフォルトwidgetはセレクトリストなので，widgetにRadioSelectを指定してあげます．
> querysetはひとまず `None` にしておき，`__init__` 内の引数で上書きします．
> error_messagesを設定すると様々な不正値が入力された際のエラーメッセージを指定できます．
> ここでは指定されなかった場合(required)と，選択肢にない値が入力された場合(invalid_choice)の
> エラーメッセージを設定しています．

続いてviews.py，templatesを書き換えて作成したFormを出してみましょう．

```python:polls/views.py
from .forms import VoteForm


def detail(request, pk):
    obj = get_object_or_404(Question, pk=pk)
    form = VoteForm(question=obj)
    return render(request, 'polls/detail.html', {
        'form': form,
        'question': obj,
    })
```

```html:polls/templates/polls/detail.html
<h1>{{ question.question_text }}</h1>

<form action="{% url 'polls:vote' question.id %}" method="post">
    {% csrf_token %}
    {{ form }}
    <input type="submit" value="Vote" />
</form>
```

>![Kobito.5NwxDg.png](https://qiita-image-store.s3.amazonaws.com/0/58048/c2e88255-daf1-6c43-11c5-84ce3c1d97c8.png "Kobito.5NwxDg.png")
ブラウザで確認

選択肢が`Choice object`になってます．
チュートリアル2で少し触れましたが，インスタンスの文字列表現はデフォルトで`ModelName object`のような形になっています．
models.pyを開き `__str__` メソッドをオーバーライドしましょう．

```python:polls/models.py
class Choice(models.Model):
...
    def __str__(self):
        return self.choice_text
```

![Kobito.cya0WW.png](https://qiita-image-store.s3.amazonaws.com/0/58048/3ce2f729-3b6f-feba-01bd-f31592d11185.png "Kobito.cya0WW.png")

ちゃんと変わりました．

>ちなみに，最初の画面と違って各選択肢の前に余計な黒ポチがついていますが，
>これはデフォルトで各選択肢を`<li>`タグで区切るようになっているためです．
>cssで黒ポチが出ないようにするほうが好ましいですが，
>htmlをどうしても変えたい場合はwidgetが握っているrendererのinner_htmlを書き換えるとできます．
> 
```python:polls/forms.py
class VoteForm(forms.Form):
    def __init__(self, question, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['choice'].queryset = question.choice_set.all()
        self.fields['choice'].widget.renderer.inner_html = '{choice_value}{sub_widgets}<br>'
```
>
> ![Kobito.j0CZTQ.png](https://qiita-image-store.s3.amazonaws.com/0/58048/e324e26e-daef-ebae-097c-e05a43f210e0.png "Kobito.j0CZTQ.png")

### 受け取り処理の記述

ソース→`56f2b498`

チュートリアル4では `vote` という投票受け取り用の関数を用意していましたが，分離させる必要はありません．
`form_test` で書いた時にように，`POST`された場合の分岐処理を記述しましょう．


```python:polls/views.py
def detail(request, pk):
    obj = get_object_or_404(Question, pk=pk)
    if request.method == "POST":
        form = VoteForm(question=obj, data=request.POST)
        if form.is_valid():
            # TODO: 投票処理
            return redirect('polls:results', pk)
    else:
        form = VoteForm(question=obj)
    return render(request, 'polls/detail.html', {
        'form': form,
        'question': obj,
    })
```

html側もvoteではなくdetailに飛ぶように直すのをお忘れなく．

```html:polls/templates/polls/detail.html
<h1>{{ question.question_text }}</h1>

<form action="" method="post">
  {% csrf_token %}
  {{ form }}
  <input type="submit" value="Vote" />
</form>
```
> action内を消去．
> ちなみに `error_message` もFormクラスが自動で出してくれるので消してます．


### 投稿処理の記述

ソース→`38eb2ec47`

いよいよ最後の作業です．
`views.vote` でやっていた投票処理（選択したchoiceのvotesを+1してsaveする処理）をFormクラスに移しましょう．

> やりたい処理
>
```python:polls/views.py
def vote(request, pk):
    question = get_object_or_404(Question, pk=pk)
    try:
        selected_choice = question.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        return render(request, 'polls/detail.html', {
            'question': question,
            'error_message': "You didn't select a choice.",
        })
    else:
        selected_choice.votes += 1
        selected_choice.save()
        return redirect('polls:results', pk)
```

ということで，Formクラスに選択されたChoiceインスタンスのvotesをインクリメントする処理を書きます．
Formクラスは`is_valid()`を実行すると`cleaned_data`の中に，入力値を適切な形式に変換したデータが入ります．
少しわかりにくい言い回しですが，htmlからラジオボタンで選択し，
サーバに渡されるデータは`"2"`や`"3"`など，Choiceインスタンスのpkの文字表現です．
`is_valid()`を実行すると，cleaned_dataの中に **Choiceインスタンスに変換して** 入れてくれます．

`cleaned_data`は各field名をkeyとする辞書形式になっているので，choiceインスタンスを取るには
`self.cleaned_data['choice']` のように書きます．
以上を踏まえた上で，選択されたChoiceインスタンスのvotesをインクリメントする`vote`メソッドを書くと以下のようになります．

```python:polls/forms.py
class VoteForm(forms.Form):
...
    def vote(self):
        assert(self.is_valid())
        choice = self.cleaned_data['choice']
        choice.votes += 1
        choice.save()
```
> assertは，is_valid()を実行済みで，かつ入力値が正常である必要があることを明確にするために書いてます．

voteメソッドができたら，view側から呼び出して上げましょう．

```python:polls/views.py
def detail(request, pk):
    obj = get_object_or_404(Question, pk=pk)
    if request.method == "POST":
        form = VoteForm(question=obj, data=request.POST)
        if form.is_valid():
            form.vote()  # ←←←←←←←←←←←← これを追記
            return redirect('polls:results', pk)
    else:
        form = VoteForm(question=obj)
    return render(request, 'polls/detail.html', {
        'form': form,
        'question': obj,
    })
```

これで無事完成です．
views.voteは必要なくなったので消しときましょう．
urls.pyからも消去するのをお忘れなく．

## fieldとwidget

fieldは内部で持つデータに相当します．
例えば`CharField`の場合は内部データ(cleaned_data)はテキスト型，
`IntegerField`なら数字型，今回のように`ModelChoiceField`なら選択したモデルのインスタンスになります．

一方widgetはGUIのことで，ブラウザにどういうパーツを出すかを指定します．
`ModelChoiceField`の場合，デフォルトではセレクトリストですが，今回のようにラジオボタンに変更することもできますし，
（入力としては最悪ですが，）テキストボックスにしてChoiceのpkを直に入力させることも可能です．
また，`SplitDateTimeWidget`のように，日付と時間をわけで入力できるようなwidgetもあります．

始めのうちは混乱しがちですが，内部で持つデータを変えたいのか，ブラウザ上の表示を変えたいのかを意識してどちらを変更すべきかを判断しましょう．

# クラスベース汎用Viewとの連携

## test_form関数の書き換え

ソース→`7d19e395`

djangoでは汎用ViewとしてFormViewが提供されています．
このクラスを用いて，まずはform_testから書き換えてみます．

さて，書き換え対象となるform_test関数ですが，短いながらもいくつかの処理にわかれています．

```python:polls.py
def form_test(request):
    if request.method == "POST":  # POST メソッドで受け取った場合の処理
        form = MyForm(data=request.POST)  # Form作成処理（ + 受け取ったデータをFormへ渡す）
        if form.is_valid():
            pass # formの入力値が正しい場合の処理
    else:  # GET メソッドで受け取った場合の処理
        form = MyForm()  # Form作成処理（データはなし）
    return render(request, 'polls/form.html', {  # templateのレンダリング
        'form': form,
    })
```

コメントでごちゃごちゃしちゃいましたが，まず大雑把にわけて`GET`で呼びだされたか`POST`で呼びだされたかに別れます．
`GET`の場合は最初にページを表示する場合の処理ですので，Formを作る場合の引数はありませんでした．
一方，`POST`の場合は一度ページを表示した後に，入力されたデータを受け取る処理があるため，
Formの引数に `data=request.POST` が渡されています．
さらに，このデータの正当性判定(`form.is_valid()`)を行い，正しければ何らかの処理を行うことになります．
djangoが提供するFormViewクラスはこれら一個一個の処理が全てメソッドになっており，
特定のメソッドをオーバーライドすることで該当箇所の処理を変えることができます．

以下に一例として動作変更をする際にオーバーライドするメソッドの一覧を示します．
>（この他にベースのViewが持っているメソッド（GETのときgetを呼び出すメソッド，など）があります）．

```python:FormViewでオーバーライドできるメソッド
    def get(self, request, *args, **kwargs):  # GETメソッドでアクセス時に呼ばれる
    def post(self, request, *args, **kwargs):  # POSTメソッドでアクセス時に呼ばれる
    def put(self, *args, **kwargs):  # PUTメソッドでアクセス時に呼ばれる
    def get_initial(self):  # Formの初期値を設定する
    def get_prefix(self):  # Formのprefixを設定する
    def get_form_class(self):  # 使用するFormクラスを取得する
    def get_form_kwargs(self):  # Formクラスに渡す引数を取得する
    def get_form(self, form_class=None):  # Formクラスのインスタンスを取得する
    def get_success_url(self):  # 正常終了時にリダイレクトするURL
    def form_valid(self, form):  # 正常時の処理
    def form_invalid(self, form):  # データが不正な場合の処理
    def get_context_data(self, **kwargs):  # templateに渡すcontextを取得する
    def render_to_response(self, context, **response_kwargs):  # responseを作成する
    def get_template_names(self):  # レンダリングに使用するテンプレート名を取得する
```

>使用するFormクラスやテンプレートの指定用に，`get_form_class`や`get_template_names`というメソッドがありますが，
このメソッドをオーバーライドする代わりに`form_class`，`template_name`などのフィールド値を指定するだけでも動作します．

単純なFormページの場合，指定するものは
`form_class`：使用するFormクラス
`template_name`：レンダリングに使用するテンプレート
`success_url`：成功時の飛び先URL
の3つだけです．
＃これに加えて，成功時になんらかの処理をしたくなると思うので`form_valid`をオーバーライドすることになります．

> success_urlは文字列の指定になります．urlで設定した名前を使いたい場合はresolverを使いましょう．
> class定義時にresolve_urlでフィールドをセットすることはできないので，`reverse_lazy`を使うか，
> `get_success_url`をオーバーライドすることになります．

書き換え後のform_testhは以下のようになります．

```python:polls/views.py
from django.views.generic import FormView
from django.core.urlresolvers import reverse_lazy

from .forms import MyForm
...
class FormTest(FormView):
    form_class = MyForm
    template_name = 'polls/form.html'
    success_url = reverse_lazy('polls:index')

form_test = FormTest.as_view()
```
>最初に作ってた`def form_test(request):`は不要なので削除してください．
>名前が衝突してエラーになります．

クラスベースViewを使う場合は，上記ソースのように
`view名 = クラス名.as_view()` をクラス定義後に実行します．

最初書いていた`form_test`関数はメソッドの判定，データの判定のif文が入っていましたが，
FormViewを使うとその判定はSuperクラスの中に押し込められるため，
if文（=ロジック）の全く無いviewが作成できました．

## detail関数の書き換え

ソース→`1efe74c527`

最後にdetail関数をクラスベースViewに書き換えてみます．
最初に言い訳しておきますが，チュートリアルには向いてない程度には複雑です．
理由が2つあって，
1. templateの中で`Question`インスタンスが必要
2. Formクラスの引数に`Question`インスタンスが必要

1だけなら話は簡単で，あるインスタンスの詳細ページを表示するための`DetailView`というものが用意されています．
このクラスに`FormView`と同じような感じで，使用するModel，テンプレートを指定するだけで表示が完成します．
\# viewで受け取る引数の名前（今はpk）やテンプレートに渡すオブジェクトの名前ももちろん変更できます．
\# また，テンプレートを指定しなくてもデフォルトで(*app_name* / *model_name* _detail.html)を使うようになっています．

また，2番も，Formクラスの引数を増やす自体は簡単（`get_form_kwargs`をオーバーライドするだけ）なのですが，
`Question`インスタンスが必要，という点が話をややこしくしています．

あるモデルのインスタンスを取得するViewクラスのmixinとして，`SingleObjectMixin`が用意されているので，
今回はこのMixinとFormViewを組み合わせて使用します．

>```python:polls/views.py
def detail(request, pk):
    obj = get_object_or_404(Question, pk=pk)
    if request.method == "POST":
        form = VoteForm(question=obj, data=request.POST)
        if form.is_valid():
            form.vote()
            return redirect('polls:results', pk)
    else:
        form = VoteForm(question=obj)
    return render(request, 'polls/detail.html', {
        'form': form,
        'question': obj,
    })
```
書き換え前のdetail

↓ クラスを使った書き換え

```python:polls/views.py
from django.shortcuts import resolve_url
from django.views.generic import FormView
from django.views.generic.detail import SingleObjectMixin

from .forms import VoteForm
from .models import Question

...
class Detail(SingleObjectMixin, FormView):
    model = Question
    form_class = VoteForm
    context_object_name = 'question'
    template_name = 'polls/detail.html'

    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        return super().get(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        return super().post(request, *args, **kwargs)

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        kwargs['question'] = self.object
        return kwargs

    def form_valid(self, form):
        form.vote()
        return super().form_valid(form)

    def get_success_url(self):
        return resolve_url('polls:results', self.kwargs['pk'])

detail = Detail.as_view()
```

まず，`GET`の場合も`POST`の場合も`Question`のインスタンスが必要なので
`self.object = self.get_object()`
を呼び出すようにしています．
このメソッドで取得するモデルクラスを `model = Question` で指定しています．
テンプレート内で`question`という名前で`Question`インスタンスを使っているので，
`context_object_name = 'question'` で名前を指定しています．

`VoteForm`への引数としてこのインスタンスが必要になるので
`get_form_kwargs`をオーバーライドし，`question`引数を追加しています．

データが正しい場合に投票処理を行いたいので，`form_valid`をオーバーライドし，
`form.vote`を呼び出すようにしています．

最後に，飛び先のURLを書き換えるために`get_success_url`をオーバーライドしています．
viewが受け取った引数は`self.kwargs`に入るため，`self.kwargs['pk']`でURLから受け取ったpkを結果URLに渡しています．


”最初の`detail`関数に比べて余りシンプルになってない，むしろ複雑になってる感じがするけどクラスにしてよかったの？”
と思っている方は，大事なことを忘れています．
クラスは **継承して使いまわせる** のです．
FormViewを継承してDetailクラスを作ったように，このクラスをスーパークラスとして別のViewクラスを作成すれば
同じような処理をするViewを書く際に，`model`などのフィールド4つを書き換えるだけで同じ動作をするviewが簡単に作れます．
viewを関数で書いてる人はこの機会にクラスベースViewへの乗り換えをぜひ検討してみてください．

<hr>

次はbootstrapを使ってデザインを綺麗にしていきます．

[次のチュートリアルへ](http://qiita.com/maisuto/items/86add9263a641899b1e3)

→ [他のチュートリアル](http://qiita.com/maisuto/items/17653f344d1f64afa019)