【Django】004. フォームのさまざまなフィールド

前回はDjangoのフォームを使ったデータの送信について見ていきました。
[【Django】003. フォームでの送信](https://qiita.com/ys_dirard/items/dd85a5cd326e8e92ea26)

今回はDjangoのFormクラスを使ってフォームを作成する際に準備されている様々なフィールド (CharField, InteferField, ...) について見ていきます。

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

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

テンプレート、ビュー、urlpatternsは一旦以下のようにしておきます。

FormクラスはSampleFormクラスを以降作成していく想定にしています。

```html:form_sample.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_sample' %}" method="post">
        {% csrf_token %}
        {{ form.as_p }}
        <input type="submit" value="click" class="btn btn-primary">
    </form>
</body>
</html>

```

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

class SampleFormView(TemplateView):
    
    def __init__(self):
        self.params = {
            "title": "Hello",
            "message": "your data",
            "form": HelloForm(),
        }
        
    def get(self, request):
        return render(request, "hello/form_sample.html", self.params)
    
    def post(self, request):
        self.params["form"] = HelloForm(request.POST)
        return render(request, "hello/form_sample.html", self.params)
    
```
```python:urls.py
from django.urls import path
from .views import SampleFormView

urlpatterns = [
    path("form_sample", SampleFormView.as_view(), name="form_sample"),
]

```

## 様々な入力フィールド
<input>タグのシリーズ。

### CharField
一般的なテキスト入力用クラス。
```<input type="text">```が生成される。

```python:forms.py
class SampleForm(forms.Form):
    name = forms.CharField(label="name", widget=forms.TextInput(attrs={"class": "form-control"}))
```

引数：
|name|detail|
|-|-|
|label|ラベルの表示テキストの指定|
|widget|ウィジェットを設定できる|
|required|必須項目かどうかを指定 (True/False)|
|min_length, max_length|最小/最大文字数を指定(整数値)|

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/8063385b-b9eb-0e90-386e-272427565dc2.png)


### EmailField
CharFieldとほぼ変わらないが、メールアドレス形式のテキストしか受け付けない。
```<input type="email">```となる。

引数はCharField同様に設定できる。

```python:forms.py
class SampleForm(forms.Form):
    mail = forms.EmailField(label="mail", widget=forms.EmailInput(attrs={"class": "form-control"}))
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/15a7bd79-d3c1-1e5b-626f-7a75b2882f2e.png)


### IntegerField
整数値のみ入力可能。
```<input type=""number>```となる。

引数：
|name|detail|
|-|-|
|label|ラベルの表示テキストの指定|
|widget|ウィジェットを設定できる|
|required|必須項目かどうかを指定 (True/False)|
|min_value, max_value|最小/最大値を指定(整数値)、この範囲外の入力を受け付けなくできる。|

```python:forms.py
class SampleForm(forms.Form):
    age = forms.IntegerField(label="age", widget=forms.NumberInput(attrs={"class": "form-control"}))
    
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/9ab392b3-00f6-2eaf-c043-89a5d10a2536.png)


### FloatField
整数だけでなく実数も入力可能。
```<inpur type="number">```は変わらない。

引数もIntegerField同様。

```python:forms.py
class SampleForm(forms.Form):
    age = forms.FloatField(label="age", min_value=0, max_value=100, widget=forms.NumberInput(attrs={"class": "form-control"}))

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/372f1d3a-0d21-5e90-2885-ee59f4722f77.png)


### URLField
URLを入力するためのもの。
```<input type="url">```となる。

引数はCharFieldと同様。

```python:forms.py
class SampleForm(forms.Form):
    url = forms.CharField(label="url", widget=forms.URLInput(attrs={"class": "form-control"}))
    
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/65532809-d6a1-3a9c-83e9-17d178f66cdc.png)


## 日時に関するフィールド

### 種類
以下3種類がある。
|name|detail|
|-|-|
|DateField|日付|
|TimeField|時刻|
|DateTimeField|日付と時刻|

使い方は基本的にどれも同じ、requiredで必須項目かどうか設定可能。

```python:forms.py
class SampleForm(forms.Form):
    date_ = forms.DateField(label="date", required=True, widget=forms.DateInput(attrs={"class": "form-control"}))
    time_ = forms.TimeField(label="time", required=True, widget=forms.TimeInput(attrs={"class": "form-control"}))
    datetime_ = forms.DateTimeField(label="datetime", required=True, widget=forms.DateTimeInput(attrs={"class": "form-control"}))

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/b1baed7a-5efc-9ed5-6978-085f0aa3437c.png)

### 日時のフォーマット

日時は正しいフォーマットで入力しないと以下のようなエラーメッセージが出ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/5f3de757-c6ab-6589-7d8b-0e59f1469076.png)

フォーマットは以下のようなものが用意されています。

#### 日付(Date)
```
2024-07-25
07/25/2024
07/25/24
```

#### 時刻(Time)
```
12:34
12:34:56
```

#### 日付+時刻(DateTime)
とりあえず日付と時刻をスペースでつなげれば問題なさそう。(スペースは全角でも半角でも何個でも良い？)

無事にエラーメッセージが消えました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/123cf398-4b10-95fa-c037-1c5d10a1fb86.png)

## チェックボックス
BooleanFieldを使う。
required=TrueだとチェックをOFFにしたまま送信できないのでrequired=Falseとするのが無難。

```python:forms.py
class SampleForm(forms.Form):
    check = forms.BooleanField(label="Checkbox", required=False)
```

### チェックボックスの値の取得方法

ビュー側で
```python
if ("check" in request.POST):
    ...
else:
    ...
```
のように処理可能です。

フィールドの変数名がPOSTリクエスト内に存在するかで判定できます。

今回は以下のようにしました。
```python
class SampleFormView(TemplateView):
    
    def __init__(self):
        self.params = {
            "title": "Hello",
            "message": "your data",
            "form": SampleForm(),
        }
        
    def get(self, request):
        return render(request, "hello/form_sample.html", self.params)
    
    def post(self, request):
        if ("check" in request.POST):
            self.params["message"] = "Checked!!"
        else:
            self.params["message"] = "not checked..."
        self.params["form"] = SampleForm(request.POST)
        return render(request, "hello/form_sample.html", self.params)
```

実際にチェックボックスの値を取得できていることが確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/f9c511ea-3ccf-1609-df62-0c10221122f6.png)


## 3択のNullBooleanField
3択のプルダウンはNullBooleanFieldで実現可能です。
デフォルトだと「Unknown」「Yes」「No」で表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/ab25b911-d44e-da76-cfe6-795651dcc780.png)

値は以下のように取得可能です。
```python
chk = request.POST["check"]
```
値は「unknown」「true」「false」となっているようでした。

## プルダウンメニュー
プルダウンメニューはChoiceFieldで実現可能です。

ChoiceFieldは引数のchoicesで項目を指定します。
```python
[
    (値, ラベル),
    (値, ラベル),
    (値, ラベル),
    ...必要な分を記載する...
]
```

今回は以下のようにしました。
```python:forms.py
class SampleForm(forms.Form):
    data = [
        ("one", "item 1"),
        ("two", "item 2"),
        ("three", "item 3"),
    ]
    choices = forms.ChoiceField(label="Choice", choices=data)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/3ce869b1-18a7-548d-6ade-1ed3ab69cb4f.png)

値は以下のように取得可能です。
```python
ch = request.POST["choices"]
```
取得した値はchoicesで設定した値となります。
※今回の例なら「item 1」を指定した場合は「one」となる。


## ラジオボタン
ChoiceFieldの引数のwidgetでRadioSelectを指定すると実現可能。

```python:forms.py
class SampleForm(forms.Form):
    data = [
        ("one", "radio 1"),
        ("two", "radio 2"),
        ("three", "radio 3"),
    ]
    choices = forms.ChoiceField(label="Choice", choices=data, widget=forms.RadioSelect())

```

値は以下のように取得可能です。
```python
ch = request.POST["choices"]
```

確認用にビューを以下のようにしておきます。
```python
class SampleFormView(TemplateView):
    
    def __init__(self):
        self.params = {
            "title": "Hello",
            "message": "your data",
            "form": SampleForm(),
        }
        
    def get(self, request):
        return render(request, "hello/form_sample.html", self.params)
    
    def post(self, request):
        ch = request.POST["choices"]
        self.params["message"] = "selected: '" + ch + "'."
        self.params["form"] = SampleForm(request.POST)
        return render(request, "hello/form_sample.html", self.params)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/3bedbf2b-6907-3e20-62f6-4aeb6f6d3471.png)


## 選択リスト
ChoiceFieldの引数のwidgetでSelectを指定すると実現可能。
Select作成時にattrs={"size": 項目数}とする。

```python:forms.py
class SampleForm(forms.Form):
    data = [
        ("one", "radio 1"),
        ("two", "radio 2"),
        ("three", "radio 3"),
    ]
    choices = forms.ChoiceField(label="Choice", choices=data, widget=forms.Select(attrs={"size": 3, "class": "form-select"}))
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/ebe26a72-f8a1-e63b-7a0d-93c41e20efac.png)

## 複数項目の選択
MultipleChoiceFieldを使用する。

```python
forms.MultipleChoiceField(choices=<値>, widget=forms.SelectMultiple(attrs={"size": 項目数}))
```

今回は以下のようにしました。
```python:forms.py
class SampleForm(forms.Form):
    data = [
        ("one", "radio 1"),
        ("two", "radio 2"),
        ("three", "radio 3"),
    ]
    choices = forms.MultipleChoiceField(label="Choice", choices=data, widget=forms.SelectMultiple(attrs={"size": 3, "class": "form-select"}))

```

複数の項目を表示するためにビューも変更します。
```python
class SampleFormView(TemplateView):
    
    def __init__(self):
        self.params = {
            "title": "Hello",
            "message": "your data",
            "form": SampleForm(),
        }
        
    def get(self, request):
        return render(request, "hello/form_sample.html", self.params)
    
    def post(self, request):
        ch = request.POST.getlist("choices")
        msg = '<ol class="list-group"><b>selected:</b>'
        for item in ch:
            msg += '<li class="list-group-item">' + item + '</li>'
        msg += '</ol>'
        self.params["message"] = msg
        self.params["form"] = SampleForm(request.POST)
        return render(request, "hello/form_sample.html", self.params)
```

```ch = request.POST.getlist("choices")```でリストとして取得し処理をします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/594896/e9f1590d-214c-96d5-96ef-b7c3c81aee41.png)