DjangoのFormクラスを使う

# 背景

DjangoのFormクラス、あるいはFlaskのWTFormsは柔軟な故使いこなすのに苦労する割に情報も少ない。

そこでこちらの記事では実際に使ってみながら機能を確認していく。

今回はDjangoのFormクラスを利用する。

# フォームを文字列で出力してみる
## 実装
```python:form.py
from django import forms


class ContactForm(forms.Form):
    subject = forms.CharField()
```

```python:views.py
def index(request):
    return HttpResponse(str(ContactForm()))
```

## 表示
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79697/ddadb336-4ab8-3a06-62c9-bc0c013f04ec.png)

出力されるのはinput fieldのみでform要素やsubmit要素などは出力されない

```HTML
<input type="text" name="subject" required="" id="id_subject">
```

## 内部実装

https://docs.djangoproject.com/en/2.2/_modules/django/forms/forms

`BaseForm::__str__` を経て `BaseForm::as_table` (HTML table要素としてフォームを出力するメソッド)を呼び出している

# フォームインスタンスに格納されたデータを見てみる

## 実装
```python:form.py
from django import forms


class ContactForm(forms.Form):
    subject = forms.CharField()
    age = forms.IntegerField()
```

```python:views.py
def index(request):
    form = ContactForm()
    params={
        'form': form
    }
    return render(request, 'index.html', params)
```

```html:index.html
<div>
  {{form.data}}
</div>

<div>
  {{form.fields}}
</div>
```

## 表示
```
{}
OrderedDict([('subject', <django.forms.fields.CharField object at 0x104ab1f98>), ('age', <django.forms.fields.IntegerField object at 0x104aca080>)])
```

## 内部実装
 - dataは初期化時に引数を見て、無ければ空のdictが入る
 - fieldsは初期化時にOrderedDictとして作られる。引数で並び替えをすることもできる

# データを初期化して色々出してみる

## 実装
```python:views.py
data = {'subject': 'hello',
        'age': 21}

def index(request):
    form = ContactForm(data)
    params={
        'form': form
    }
    return render(request, 'index.html', params)
```

```html:index.html
<div>
  {{form.data}}
</div>

<div>
  {{form.fields}}
</div>

<div>
  {{form}}
</div>

<div>
  {{form.subject}}
</div>

<div>
  {{form.age}}
</div>
```

## 出力

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/79697/7a1785ff-edfc-4424-721e-8e62d91ea1bd.png)

## メモ

インタラクティブシェルでの動作

```
>>> form.fields['age']
<django.forms.fields.IntegerField object at 0x10d906e10>
>>> str(form.fields['age'])
'<django.forms.fields.IntegerField object at 0x10d906e10>'
>>> form['age']
<django.forms.boundfield.BoundField object at 0x10d906fd0>
>>> str(form['age'])
'<input type="number" name="age" value="21" required id="id_age">'
```

boundfieldは
https://docs.djangoproject.com/en/2.2/ref/forms/api/#more-granular-output

# POSTできるようにする

## ドキュメントより
以下のようにformタグとsubmit要素は自前で実装する

```
<form action="/your-name/" method="post">
    {% csrf_token %}
    {{ form }}
    <input type="submit" value="Submit">
</form>
```
https://docs.djangoproject.com/en/2.2/topics/forms/#the-template

## viewsの実装

Djangoの場合Viewクラスを使っていい感じに書くこともできるがミニマルに実装するならこんな風に出し分ける

```
def index(request):
  if request.method == 'POST':
    form = ContactForm(request.POST)
    # 中略
  else:
    form = ContactForm(request.GET or None)
    # 中略
```

### request.GET request.POSTの内容

```
def index(request):
    print(request.GET)
    print(request.POST)
```

```
GET時
<QueryDict: {}>
<QueryDict: {}>

POST時
<QueryDict: {}>
<QueryDict: {'csrfmiddlewaretoken': ['<ハッシュ>'], 'subject': ['hello'], 'age': ['21']}>
```

`request.GET` 型でリクエストした場合は `form.data` がQueryDict型で初期化される。

`request.POST` は要するにリクエストペイロードが含まれる

### QueryDict

dict型を継承した `MultiValueDict` をさらに継承したもの

https://docs.djangoproject.com/pl/2.2/ref/request-response/

immutableなので変更できない

```python:views.py
def index(request):
    if request.method == 'POST':
        form = ContactForm(request.POST)
        form.data['age'] = 103 # こういうことをするとエラーになる
```

Boundfieldのデータを書き換えることもできない

```
form['age'].data = 103 # これもエラー

form.fields['age'].label = '年齢' # これはOK

form.fields['age'].initial = 100 # エラーにはならないが値は反映されない
```

ラベルのアップデート: バウンドされたfieldに定義してもfieldsオブジェクトに紐づいたものでもOK。両方書いた場合はBoundFieldsが優先される。

```
def index(request):
    ...
    form['age'].label = '年齢1'
    form.fields['age'].label = '年齢2'
    ...

# 年齢1が表示される
```

# `form.fields` と `BoundField` の関係

`Boundfield` が `form.field` への参照を持っているような形。

```
>>> form['age'].field
<django.forms.fields.IntegerField object at 0x10d906e10>
>>> form.fields['age']
<django.forms.fields.IntegerField object at 0x10d906e10>
```

同じオブジェクトを参照していることを確認。

# まとめ的なもの

 - form: form周りのいろんな役割をするクラス
 - form.data: フォームのデータを格納。初期化時に定義し書き換え不可。
 - request.POST: リクエストデータが格納されて、これをformの引数として渡す。
 - form.fields: フィールドオブジェクトが格納された配列。
 - form.<キー名>: BoundField。formにバインドされたfield情報。加えてフィールドオブジェクトへの参照も持つ