Django+Reactで学ぶプログラミング基礎(8): Djangoチュートリアル(投票アプリその4-1)

##### [前回] [Django+Reactで学ぶプログラミング基礎(7): Djangoチュートリアル(投票アプリその3)](https://qiita.com/mingchun_zhao/items/d352c9520337e86e0caa)

## はじめに

[Django公式チュートリアル](https://docs.djangoproject.com/ja/4.0/intro/tutorial01/)、その4-1です。
前回は、Djangoビューを深掘りしました。
今回は、フォームを作成してみます。

## Djangoアプリ作成(その4-1): 投票(poll)アプリ

### 今回の内容

- フォームを作成

### フォームを書く

- 投票詳細テンプレートにHTMLの`<form>`要素を追加
    - 各質問の選択肢のラジオボタンが表示される
        - 各ラジオボタンの`value`は、質問選択肢の`ID`
        - 各ラジオボタンの`name`は`choice`
        - 投票者がラジオボタンの1つを選択し、フォームを送信すると
            - `POST`データ`choice=#(#は選んだ選択肢のID)`が送信される
- クロスサイトリクエストフォージェリ(CSRF)によるデータ改ざんに、POSTフォームが悪用されないように
    - 自サイト内をURLに指定したPOSTフォームには、全て`{% csrf_token %}`テンプレートタグを使う
    - CSRFとは、Webアプリケーションに存在する脆弱性、もしくはその脆弱性を利用した攻撃方法
```polls/templates/polls/detail.html
<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
<fieldset>
    <legend><h1>{{ question.question_text }}</h1></legend>
    {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
    {% for choice in question.choice_set.all %}
        <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
        <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
    {% endfor %}
</fieldset>
<input type="submit" value="Vote">
</form>
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/ca85cef9-cee8-6eb4-cf24-d0e2bf28e06f.png)

### `vote()`ビューを作成し、フォームの送信データを処理

- `request.POST`
    - 辞書オブジェクトで、キーを指定すると、送信データにアクセスできる
    - `request.POST['choice']`は、選択された選択肢の`ID`を文字列として返す
        - request.POSTの値は常に文字列
        - POSTデータに`choice`がなければ、`KeyError`を返却
- `HttpResponseRedirect`
    - 別のURLにリダイレクトする関数
    - コンストラクタの中で`reverse()`関数を使用し、ビュー関数中でURLのハードコードを防ぐ
        - 関数の引数
            - 制御を渡したいビューの名前
            - ビューに与えるURLパターンの位置引数
        - 例では、reverse()を呼ぶと、`URLconf`指定とおり、次のような文字列が返される
            - `/polls/3/results/`(3は`question.id`の値)
    - リダイレクト先のURLは、`results`ビューを呼び出し、最終的なページにリダイレクト
```polls/views.py
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse

from .models import Choice, Question
# ...
def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = question.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        # Redisplay the question voting form.
        return render(request, 'polls/detail.html', {
            'question': question,
            'error_message': "You didn't select a choice.",
        })
    else:
        selected_choice.votes += 1
        selected_choice.save()
        # Always return an HttpResponseRedirect after successfully dealing
        # with POST data. This prevents data from being posted twice if a
        # user hits the Back button.
        return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
```

### 質問の結果`result()`ビューの作成

- 誰かが質問の投票すると、`vote()`ビューは質問の結果ページにリダイレクト
```polls/views.py
from django.shortcuts import get_object_or_404, render


def results(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/results.html', {'question': question})
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/e126c4ba-71af-24dd-e109-03c7fc354618.png)

- `result()`ビューのテンプレートを作成
```polls/templates/polls/results.html 
<h1>{{ question.question_text }}</h1>

<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>

<a href="{% url 'polls:detail' question.id %}">Vote again?</a>
```

### 管理サイトで質問の選択肢を登録

- 管理サイトに`Choice`モデルを登録
```polls/admin.py
from random import choices
from django.contrib import admin

from .models import Question,Choice

admin.site.register(Question)
admin.site.register(Choice)
```

- VS Codeターミナルで、仮想環境をアクティベート
```
C:\kanban\pollsite>..\venv\.venv\Scripts\activate
```

- 開発サーバーを起動
```
(venv) C:\kanban\pollsite>python manage.py runserver
```

- 管理サイト`http://127.0.0.1:8000/admin/`にログインし、質問1の選択肢を追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/b1dd237f-4d64-aad8-40a1-ab7490a786f6.png)

### 投票してみる

- ブラウザで`http://127.0.0.1:8000/polls/1/`にアクセス

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/5554f130-ff10-dd4e-3343-ff3a55aa7236.png)

- 票を入れるたびに、結果のページが更新される
    - 選択肢を選ばずにフォームを送信すると、エラーメッセージが表示される

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2523098/13a1a74a-8b5b-d214-5b34-6b760f3f2374.png)

## おわりに

Djangoアプリにフォームを追加し、投票を行いました。
次回も続きます。お楽しみに。

##### [次回] [Django+Reactで学ぶプログラミング基礎(9): Djangoチュートリアル(投票アプリその4-2)](https://qiita.com/mingchun_zhao/items/34018b7c89eb72b611d3)