Django forms.Formを用いたフォームの作成

# はじめに
　Djangoの初学者はまずDjango公式サイトやDjangogirlsなどのチュートリアルを取り組むのではないか。
　そこで共通するのは、フォームの事例が、forms.ModelFormを利用したものに偏っており、当たり前のようにforms.ModelFormを選択していた。しかし、フォームをオリジナルあるものにするには、forms.Formを使う手があるが、解説した記事が少ないと感じていた。事例を交えて記事に残そうと思う。

# 環境
Windows10
Python 3.6.0
Django 2.1.1

# models.pyの編集
ModelにBookモデルを作成する。タイトル、著者、所有者、貸出先、発刊日、購入日、ISBN、備考を属性として追記する。

```python:models.py
from django.db import models

class Book(models.Model):
    LIBRARY_CHOICES = (
        ('国立図書館', '国立図書館'),
        ('県立図書館', '県立図書館'),
        ('市立図書館', '市立図書館'),
    )

    title = models.CharField(max_length=200)
    author = models.CharField(max_length=50)
    holder = models.CharField(max_length=10, choices=LIBRARY_CHOICES)
    user = models.CharField(max_length=50, default='admin')
    published_date = models.DateField(blank=True, null=True)
    purchased_date = models.DateField(blank=True, null=True)
    isbn = models.BigIntegerField()
    comment = models.TextField(blank=True, null=True)

    def __str__(self):
        return self.title
```

# フォームの編集
ここでは、本の登録フォームを例とした。

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

LIBRARIES_CHOICES = (
    ('国立図書館', '国立図書館'),
    ('県立図書館', '県立図書館'),
    ('市立図書館', '市立図書館'),
)

class BooksForm(forms.Form):
    title = forms.CharField(
        label='タイトル',
        max_length=200,
        required=True,
    )

    author = forms.CharField(
        label='著者',
        max_length=50,
        required=True,
    )

    holder = forms.ChoiceField(
        label='所有者',
        widget=forms.Select,
        choices=LIBRARIES_CHOICES,
        required=True,
    )

    user = forms.CharField(
        label='貸出先',
        max_length=50,
        required=True,
    )

    published_date = forms.DateTimeField(
        label='発刊日',
        required=True,
        widget=forms.DateInput(attrs={"type": "date"}),
        input_formats=['%Y-%m-%d']
    )

    purchased_date = forms.DateTimeField(
        label='購入日',
        required=True,
        widget=forms.DateInput(attrs={"type": "date"}),
        input_formats=['%Y-%m-%d']
    )

    # 日付だけでなく、日時にする場合は以下
    # datetime = forms.DateTimeField(
    #    label='日時',
    #    required=True,
    #    widget=forms.DateTimeInput(attrs={"type": "datetime-local"}),
    #    input_formats=['%Y-%m-%dT%H:%M']
    #)

    isbn = forms.IntegerField(
        label='isbn',
        required=False,
    )

    comment = forms.CharField(
        label='備考',
        max_length=1000,
        required=False,
        widget=forms.TextInput()
    )

```

django.forms.ModelFormでforms.pyを作成すると、forms.ModelFormを利用すると以下4行程度だが、モデルに紐づいているため応用が効きにくい

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

 class BookForm(forms.ModelForm):
     class Meta:
         model = Book
         fields = ('title', 'author')
```

# コントローラ(Views)の編集
本の登録を行うコントローラを関数ベースで定義する。
フォームからPOSTがなければ、formをそのままbook_new.htmlに渡す。
フォームに入力があり、POSTを送信した場合、入力内容をバリデートする。バリデートが通った場合、bookオブジェクトをcreateする。
その後、本一覧ページにredirectしている。（redirect先である、book_list.htmlは省略）


```python:views.py
from django.shortcuts import render, get_object_or_404, redirect
from .models import Book
from .forms import BooksForm

def book_new(request):
    form = BooksForm(request.POST or None)
    if form.is_valid():
        book = Book()
        book.title = form.cleaned_data['title']
        book.author = form.cleaned_data['author']
        book.holder = form.cleaned_data['holder']
        book.user = form.cleaned_data['user']
        book.published_date = form.cleaned_data['published_date']
        book.purchased_date = form.cleaned_data['purchased_date']
        book.isbn = form.cleaned_data['isbn']
        book.comment = form.cleaned_data['comment']

        Book.objects.create(
            title=book.title,
            author=book.author,
            holder=book.holder,
            user=book.user,
            published_date=book.published_date,
            purchased_date=book.purchased_date,
            isbn=book.isbn,
            comment=book.comment,
        )
        return redirect('book:book_list')
    return render(request, 'book/book_new.html', {'form': form})
```

# テンプレートの編集
本の登録を行うテンプレートを準備
※継承元のテンプレートbook/base.htmlは省略

```python:book_new.html

{% extends 'book/base.html' %}

{% block content %}
    <h1>New book</h1>
  <table class="table">
    <form method="post" action="">
    {% csrf_token %}
    <tbody>
      <tr>
        <th>タイトル</th>
        <td>{{ form.title }}</td>
      </tr>
      <tr>
        <th>著者</th>
        <td>{{ form.author }}</td>
      </tr>
      <tr>
        <th>所有者</th>
        <td>{{ form.holder }}</td>
      </tr>
      <tr>
        <th>貸出先</th>
        <td>{{ form.user }}</td>
      </tr>
      <tr>
        <th>発刊日</th>
        <td>{{ form.published_date }}</td>
      </tr>
      <tr>
        <th>購入日</th>
        <td>{{ form.purchased_date }}</td>
      </tr>
      <tr>
        <th>ISBN</th>
        <td>{{ form.isbn }}</td>
      </tr>
      <tr>
        <th>備考</th>
        <td>{{ form.comment }}</td>
      </tr>
    </tbody>
      <tr>
      <td><input class="btn btn-primary" type="submit" value="Submit">
        <input class="btn btn-secondary" type="reset" value="Reset">
      </td>
      <td>
      </td>
      </tr>
    </form>
  </table>

{% endblock %}
```

画面はこんなイメージ
<img width="659" alt="new_book.PNG" src="https://qiita-image-store.s3.amazonaws.com/0/299159/c3f40a5b-904e-82d0-0278-7650ee3afd02.png">




# おわりに

django.forms.ModelFormでforms.pyを作成すると簡単に書くことができるが、モデルと紐づけしないフォームを作成する上で、django.forms.Formでのコーディングは必要となる。