【Django】003. フォームでの送信

前回はDjangoのテンプレートについて見ていきました。
[【Django】002. テンプレートの利用](https://qiita.com/ys_dirard/items/6d26f32e0a7cfcaa08ea)

今回はDjangoでのフォームを使ったデータの送信についてみていきます。

今回も以下の本を参考にしています。

https://www.shuwasystem.co.jp/book/9784798062419.html

## フォームの準備

### テンプレート (ここではform.html) を準備する

```html:index.html
{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>{{ title }}</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">

</head>
<body class="container">
    <h1 class="display-4 text-primary mb-4">{{ title }}</h1>
    <p class="h6 my-3">{{ msg }}</p>
    
    <form action="{% url 'form' %}" method="post">
        {% csrf_token %}
        <label for="msg" class="form-label">message: </label>
        <input type="text" name="msg" id="msg" class="form-control">
        <input type="submit" value="click" class="btn btn-primary">
    </form>
</body>
</html>

```

普通にhtmlのformタグで作成しています。

### views.pyにform用の関数を追加する

```python:views.py
from django.shortcuts import render
from django.http import HttpResponse

def form(request):
    params = {
        "title": "Hello/Index",
        "msg": "this is sample page.",
    }
    return render(request, "hello/form.html", params)

```

### urls.pyのurlpatternsにpathを追加する

```python:urls.py
urlpatterns = [
    ...(略)...
    path("form", views.form, name="form"),
]
```

これでページにアクセスすると以下のようになる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/f9c729bb-69fa-a1d7-e6ba-6697a4075a8f.png)

urlpatternsにname="form"として追加したので、{% url form %}は自分自身に送信していることになります。なので現状は何も起きません。

### CSRF対策

formの中にある
```
{% csrf_token %}
```
はCSRF (Cross-Site Request Forgeries) 対策として必要なトークンを表示しています。
これにより利用者が意図したリクエスト(正しくフォームから送信されたアクセス)かどうかをチェックしています。

CSRFの詳細は以下を参照。

https://www.ipa.go.jp/security/vuln/websecurity/csrf.html

## フォームで送られた内容を処理する

views.pyのform関数を以下に変更する。
```python:views.py
from django.shortcuts import render
from django.http import HttpResponse

def form(request):
    if "msg" in request.POST:
        msg = "こんにちは、" + request.POST["msg"] + "さん。"
    else:
        msg = "お名前は？"
        
    params = {
        "title": "Hello/Form",
        "msg": msg,
    }
    return render(request, "hello/form.html", params)

```

ページにアクセスすると1枚目のように表示され、messageに入力してclickすると2枚目のように表示されます。
送信した内容を取得して表示できていることがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/089d84b1-e3f3-4fc9-ac47-4d3b99a6769a.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/af32451e-4ade-bf15-976b-d98e7d4045e0.png)

form.html内でformのactionを自分自身に設定していたので、データは自分自身に送信されます。

そのデータを
```python
request.POST["msg"]
```
のようにして取得することができます。

ここでの"msg"はform.htmlのform内で指定したname="msg"のことです。


## Djangoのフォーム機能

ここまでの実装だと送信すると値がクリアされるので不便な時も出てきます。

送られてきた値を変数に保存しておいて、それを表示することもできますが面倒。

そんな時にDjangoのFormクラスが便利らしい。

### forms.pyを作成する

アプリケーションフォルダ (プロジェクトフォルダではない) 直下に```forms.py```というファイルを作成します。

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

class HelloForm(forms.Form):
    name = forms.CharField(label="name")
    mail = forms.CharField(label="mail")
    age = forms.IntegerField(label="age")
    
```

上記のようにFormクラスは```forms.Form```クラスを継承して作成し、```変数 = フィールド```の形で準備します。


### views.pyの変更
views.pyのform関数を変更します。

```python:views.py
from django.shortcuts import render
from .forms import HelloForm

def form(request):
    params = {
        "title": "Hello",
        "message": "your data:",
        "form": HelloForm(),
    }
    if (request.method == "POST"):
        params["message"] = "名前:" + request.POST["name"] + \
            "<br>メール:" + request.POST["mail"] + \
            "<br>年齢:" + request.POST["age"]
        params["form"] = HelloForm(request.POST)
    return render(request, "hello/form.html", params)
```

リクエストがGETかPOSTかで処理が変わるようにしています。

今回作成したHelloFormはparamsの一つとしてそのまま渡すことができます。


### テンプレート (form.html) の変更

form.htmlを以下のように変更します。

```html:form.html
{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>{{ title }}</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">

</head>
<body class="container">
    <h1 class="display-4 text-primary mb-4">{{ title }}</h1>
    <p class="h6 my-3">{{ message|safe }}</p>
    
    <form action="{% url 'form' %}" method="post">
        {% csrf_token %}
        {{ form }}
        <input type="submit" value="click" class="btn btn-primary">
    </form>
</body>
</html>

```

```{{ message|safe }}```の```|safe```はフィルターという機能で、HTMLタグを書きだせるようにするためのものらしい。

Djangoでは{{ }}内にHTMLタグが含まれている場合は自動的にエスケープ処理 (ただのテキストとして処理) されてしまうところを```|safe```によりエスケープ処理を行わなくしているとのこと。

ページにアクセスすると以下のようになります。
送信後も値が残るようになっています。
値を入力せずに送信しようとすると警告メッセージが出ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/bb4b214d-14f3-2e7d-12de-8f0fa32d042e.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/2302698d-b942-174c-9c67-789ce311e8c3.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/2fce2f32-d53d-bf37-d798-9670740ae094.png)


### フィールドをタグで整える

先ほどまでで機能的にはOKなのですが、表示がきれいじゃない問題があります。

Formクラスは生成するフィールドのタグを他のタグでくくって出力する機能が用意されているとのこと。

例：
```<Form>.as_table```
ラベルとフィールドのタグを<tr>と<td>でくくる。

```<Form>.as_p```
ラベルとフィールド全体を<p>でくくる。

```<Form>.as_ul```
ラベルとフィールド全体を<li>でくくる。

form.htmlを以下のように変更します。
```html:form.html
{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>{{ title }}</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">

</head>
<body class="container">
    <h1 class="display-4 text-primary mb-4">{{ title }}</h1>
    <p class="h6 my-3">{{ message|safe }}</p>
    
    <form action="{% url 'form' %}" method="post">
        {% csrf_token %}
        <table>
            {{ form.as_table }}
            <tr><td></td><td>
                <input type="submit" value="click" class="btn btn-primary">
            </td></tr>
        </table>
    </form>
</body>
</html>

```

再びアクセスするといい感じになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/7abc6a05-d744-2be3-40fb-e9c5172b26bb.png)

### Bootstrapクラスを使う

これまでもなんやかんやBootstrapをロードしてはいたので勝手に使われているような気もしますが、Formクラスに明示的に使わせてみます。

使用方法はフィールドのインスタンス生成時にwidgetで指定します。

変更後のforms.pyは以下になります。
```python:forms.py
from django import forms

class HelloForm(forms.Form):
    name = forms.CharField(label="name", widget=forms.TextInput(attrs={"class": "form-control"}))
    mail = forms.CharField(label="mail", widget=forms.TextInput(attrs={"class": "form-control"}))
    age = forms.IntegerField(label="age", widget=forms.NumberInput(attrs={"class": "form-control"}))
    
```

form.htmlも以下のように修正してみます。
```html:form.html
{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>{{ title }}</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">

</head>
<body class="container">
    <h1 class="display-4 text-primary mb-4">{{ title }}</h1>
    <p class="h6 my-3">{{ message|safe }}</p>
    
    <form action="{% url 'form' %}" method="post">
        {% csrf_token %}
        {{ form.as_p }}
        <input type="submit" value="click" class="btn btn-primary">
    </form>
</body>
</html>

```

すると以下のようになります。
ちゃんと適用されていていい感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/714cf126-8965-5514-524e-f37c6323c4ff.png)

## HTMLタグかFormクラスか？
これまでで2種類のフォーム実装を見てきました。
* HTMLタグによる実装
* Formクラスによる実装

これらはどちらが良いのでしょうか？
答えはどちらでもよい！です。

とはいえ、フォームに必要な機能はFormクラスにもともと用意されているため自分で実装する必要はなく、組み込みやすいという話はあります。

## ビュー関数のクラス化

【Class-Based Views】
これまではviews.pyの中にビュー用の関数を定義して関数の中でGET時の処理とPOST時の処理を分岐していました。

【Function-Based Views】
Djangoではクラスによるビュー定義もあり、GET時の処理とPOST時の処理を別関数として分離できます。

これまでのviews.pyのform関数をクラスによるビューで書き直してみます。

```python:views.py
from django.shortcuts import render
from django.http import HttpResponse
from django.views.generic import TemplateView
from .forms import HelloForm

class FormView(TemplateView):
    
    def __init__(self):
        self.params = {
            "title": "Hello",
            "message": "your data",
            "form": HelloForm(),
        }
        
    def get(self, request):
        return render(request, "hello/form.html", self.params)
    
    def post(self, request):
        msg = "あなたは、<b>" + request.POST["name"] + \
            "(" + request.POST["age"] + ")</b>さんです。<br>メールアドレスは <b>" + \
            request.POST["mail"] + "</b>ですね。"
        self.params["message"] = msg
        self.params["form"] = HelloForm(request.POST)
        return render(request, "hello/form.html", self.params)
```

getとpostを別に定義することで同一関数の中での分岐を避けられています。

### urlpatternsの修正

クラスによるビューの場合はurls.pyのurlpatternsの指定の仕方が変わります。

```python:urls.py
from django.urls import path
# from . import views
from .views import FormView

urlpatterns = [
    # path("form", views.form, name="form"), # function-based view
    path("form", FormView.as_view(), name="form"), # class-based view
]

```

上記のようにクラス名.as_view()として指定することで使用可能です。

## クラス or 関数

Function-Based ViewとClass-Based Viewはどう使い分ければいいのでしょうか？

### 処理の複雑さ
関数による記載は直感的でわかりやすいのが特徴。
処理が複雑ではない場合は関数で書いた方が早い。

### GETやPOSTで共有できるクラス
GETとPOST等のHTTPメソッドによって関数を分ける場合はクラスによって一つ(のクラス)にまとめることができる。

これにより、GETとPOST間で値をやり取りしたい等の問題を解決したりできる。

### GETだけなら関数でOK
最初の「処理の複雑さ」にも関係しますが、ただページにアクセスして表示するだけなら関数だけで十分。