Djangoのフォーム機能

Formクラス

Djangoにはフォームをもっとスマートに書くための Formクラスがある。Formクラスは、フォームの内容をPythonのクラスとして定義するもの。Formクラスでフォームの内容を定義し、テンプレートに変数として渡すことで、クラスの内容をもとにフォームが自動生成される。

forms.pyを作る

helloアプリ直下にforms.pyを用意する。name,mail,ageの3つのフィールドを持つ。

from django import forms

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

Formクラスの書き方

Formクラスの書き方は簡単で、必要なフィールドを変数として用意しておくだけ。

class クラス名 (forms.Form):
  変数 = フィールド
  変数 = フィールド
  .......

forms.CharField : テキスト入力の一般的なフィールド

forms.IntegerField : 整数値入力のフィールド

書くフィールドにlabel='name'といった形で渡されるラベルは、フィールド手前に表示されるフィールド名のテキスト。

ビュー関数の作成

作ったFormクラスのHelloFormを利用するビュー関数をhello/views.pyに作る。

from django.shortcuts import render
from django.http import HttpResponse
from .forms import HelloForm

def index(request):
  params = {
    'title': 'Hello',
    'msg': '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/index.html', params)

index関数内にGETの処理とPOSTの処理を両方書いている。まず共通の処理(パラメータの用意)をし、if (request.method == 'POST'):でPOST送信であったときのみの処理を書いている。パラメータの定義、

params = {
  ......
  'form': HelloForm()
}

の中でformというキーの変数にHelloFormインスタンスを代入し、

POST送信の場合はparams['form'] = HelloForm(request.POST)で送られてきた値を元に作成したHelloFormインスタンスを再度キーformのパラメータに上書きしている。

HelloFormを表示する

hello/templates/hello/index.htmlテンプレートを書き換える。

{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{{title}}</title>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" crossorigin="anonymous">
</head>
<body class="container">
  <h1 class="display-4 text-primary">{{title}}</h1>
  <p class="h5 mt-4">{{message|safe}}</p>
  <form action="{% url 'index' %}" method="post">
    {% csrf_token %}
    {{ form }}
    <input type="submit" value="click" class="btn btn-primary">
  </form>
</body>
</html>

Formクラスを利用する場合は{{ form }}でフォームの具体的な内容が書き出されるので、記述が簡素化される。

safeフィルター

メッセージの表示部分を{{message|safe}}とし、safeフィルターを通した表示にすることでエスケープ処理をせずにHTMLタグはタグとして表示する。

urlpatternsの修正

hello/urls.pyを修正する。

urlpatterns = [
    path('', views.index, name='index'),
]

index関数にGET,POST両方の処理をまとめたので、今度はindexへのパスだけでよい。

表示結果

http://localhost:8000/hello/にアクセスしフォーム入力画面に値を入れて送信ボタンをクリックすると、

Hello

入力した値がフォームの上部に表示される。

Hello

名前: Foo
メール: foo@example.com
年齢: 35

何も入力せずにボタンを押すと「!このフィールドを入力して下さい」といった警告が自動的に出る。

フォームフィールドをタグで整理する

Formクラスには、フォームフィールドを整形するための機能がある。

<Form>.as_table : ラベルとフィールドをtrタグとtdタグで囲む

<Form>.as_p : ラベルとフィールドをpで囲む

<Form>.as_table : ラベルとフィールドをliタグで囲む

<Form>.as_tableを使ってindex.htmlテンプレートを修正する。

{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>{{title}}</title>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" crossorigin="anonymous">
</head>
<body class="container">
  <h1 class="display-4 text-primary">{{title}}</h1>
  <p class="h5 mt-4">{{message|safe}}</p>
  <form action="{% url 'index' %}" 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>

tableタグまでは生成してくれないのは一見面倒だが、実際はそこを手で書き入れるからこそ、手書きで挿入した送信ボタンも<table>の中に入れて整理できた。

Formクラスのwidgetの利用

Formクラスの定義で、引数にwidgetの値を用意すると、生成されるタグにカスタム属性を追加できる。hello/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'}))

forms.TextInput(attrs=属性値) forms.NumberInput(attrs=属性値)

といった形で指定する。

これに合わせてindex.htmlテンプレートも修正する。単純化するために<Form>.as_pを使う。

  <form action="{% url 'index' %}" method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="click" class="btn btn-primary my-2">
  </form>

出力されたHTMLにはclass名にform-controlが追加されている。

<p>
  <label for="id_name">name:</label> 
  <input type="text" name="name" value="Foo" class="form-control" required="" id="id_name">
</p>