CakePHP使いがDjangoでサイトを作ってみた 〜フォームを作ってみよう〜

この記事はだいぶ前に書かれたものなので情報が古いかもしれません
フォーム? フロム?

この記事を三行にまとめると

今日はテキストボックスのみ
Djangoは同じname属性が複数ある場合にもしっかり対応してくれる
しかしformとfromって何度見てもよく似てるな
今日はフォームを使ってみましょう。僕自身、まだ使いこなせてない部分が多いんでCakePHPのフォームヘルパーほどに柔軟な使い方がまだできていないのが現状なんですが、分かる範囲で見ていきたいと思います。

すごーく長くなってしまうと思うので、今日はテキストボックスのみを対象に、フォームの設定をしてPOSTデータを受け取るところまでやりたいと思います。テキストボックスやラジオボタンなどの書き方、あるいはバリデーションの設定などは今度やります。



ファイルの作成

フォームを使うためにはフォームの設定用ファイルが必要です。ここでは仮に「forms.py」としましょう。アプリケーションフォルダの中(models.pyやviews.pyがあるところ)にforms.pyを作成します。

#forms.py
from django import forms

class SampleForm(forms.Form) :
  pass

とりあえずこれでオッケーです。こいつをビューでインポートすればフォームを使えるようになります。

#views.py
from django.shortcuts import render
from django.views.generic import TemplateView
from app.forms import SampleForm

class index(TemplateView) :
  def get(self, request, *args, **kwargs) :
    context = super().get_context_data(**kwargs)
    context['form'] = SampleForm() #フォームの読み込み
    return render(request, 'index.html', context)

contextという変数にフォームを入れているのはテンプレートに値を渡すためです。

基本はこんな感じです。あとは出力したいフォームの設定をSampleFormクラスの中に書いていくことになります。



テキストボックスを作成

例えばサイト名を入力するテキストボックスを出力してみましょう。

#forms.py
class SampleForm(forms.Form) :
  site = forms.CharField(label = 'サイト名')

#index.html
{{form.site.label}}:{{form.site}}

#出力されるHTML
サイト名:<input type="text" name="site" value="" id="id_site" required />

これだけでsiteのテキストボックスが作成できます。テンプレート側で「{{form.変数名}}」とするだけでテキストボックスを出力してくれます。この場合のformは先ほどビューでcontext[‘form’]にフォームを入れたからそうなっているだけで、context[‘akatsuki’]にフォームを入れたのであれば「{{akatsuki.site}}」と変わります。

余計な設定を書かなければ変数名がそのままname属性になるようです。それからidも自動的に「id_変数名」となります。あとデフォルトではrequiredがTrueになっているようなので、必須入力を外したい場合はrequiredをFalseにする必要があります。

#forms.py
class SampleForm(forms.Form) :
  site = forms.CharField(label = 'サイト名', required = False)



これらの値はビュー側で後から設定を変更することもできます。動的にlabelやrequiredを変化させたい時などはビューで設定すると良いでしょう。

#forms.py
class SampleForm(forms.Form) :
  site = forms.CharField()

#views.py
class index(TemplateView) :
  def get(self, request, *args, **kwargs) :
    context = super().get_context_data(**kwargs)

    #フォームの設定    
    form = SampleForm()
    form.fields['site'].label = 'サイト名'
    form.fields['site'].required = False
    context['form'] = form

    return render(request, 'index.html', context)

fields[変数名]にテキストボックスの設定が入っているので、そこを直接上書きすれば設定が書き換わります。



属性を追加

例えばclassやplaceholderなどの属性を追加する場合はwidgetという引数に辞書データを渡します。

#forms.py
class SampleForm(forms.Form) :
  site = forms.CharField(
    label = 'サイト名',
    required = False,
    widget = forms.TextInput(attr = {
      'class': 'form',
      'placeholder': 'サイト名を入力してください'
    })
  )

#出力
サイト名:<input type="text" name="site" value="" id="id_site" class="form" placeholder="サイト名を入力してください" />

もちろんビュー側で設定することも可能です。

#views.py
from django import forms

form = SampleForm()
form.fields['site'].widget = forms.TextInput(attrs = {'class': 'sample'})
context['form'] = form



初期値を設定

初期値を入れる方法はいくつかあります。

一つはinitialという引数に値を渡すやり方。

#forms.py
class SampleForm(forms.Form) :
  site = forms.CharField(
    label = 'サイト名',
    required = False,
    widget = forms.TextInput(attr = {
      'class': 'form',
      'placeholder': 'サイト名を入力してください'
    }),
    initial = 'あかつきのお宿'
  )

#出力
氏名:<input type="text" name="site" value="あかつきのお宿" id="id_site" class="form" placeholder="サイト名を入力してください" />

それから先ほどのlabelなどと同様にビュー側でinitialを上書きするやり方。

#views.py
form = SampleForm()
form.fields['site'].initial = 'あかつきのお宿'
context['form'] = form

そしてもう一つはインスタンスを作成する時に辞書データで渡すやり方です。

#views.py
context['form'] = SampleForm({'site': 'あかつきのお宿'})

既存のデータを編集する時などはこのやり方を使うのが良いかもですね。データベースからデータを取ってきて、それを辞書データに変換してまとめてセットしてしまうみたいな。



inputのtypeを変更する

テキストボックスのtype属性は基本的にはtextですが、最近のHTMLにはnumberとかemailとかを使うこともできますね。スマホだとこのtypeによって表示されるキーボードの種類を制御できたりしますが、このtype属性もforms.pyの方で設定できます。もちろんfileやhiddenも。

#type="text"
forms.CharField()

#type="email"
forms.EmailField()

#type="url"
forms.URLField()

#type="number"
forms.IntegerField()

#type="tel"
forms.IntegerField(widget = forms.TextInput)

#type="file"
forms.FileField()

#type="hidden"
forms.CharField(widget = forms.HiddenInput)

#type="password"
forms.CharField(widget = forms.PasswordInput)

他にもいろんな設定ができます。詳しいことはこちら(↓)にて。

Form fields



POSTデータを受け取る

djangoのフォーム機能を使わずにテキストボックスなどをベタ書きした場合も同じなのですが、POSTデータはrequestという変数に入ってきます。

#views.py
class index(TemplateView) :
  def post(self, request, *args, **kwargs) :
    print(request.POST)

絶対にrequestという変数名でなくても良いんですけど、ようはpostメソッドの先頭の引数に値が入ってくるので、そこからデータを取得できます。

ちなみに今回作成したテキストボックスをフォームで送信するとPOSTデータの中身はこうなっています。

#index.html
氏名:<input type="text" name="site" value="あかつきのお宿" id="id_site" />

#views.py
class index(TemplateView) :
  def post(self, request, *args, **kwargs) :
    print(request.POST)

#POSTの中身
<QueryDict: {'site': ['あかつきのお宿']}>

nameというキーの中に入力されたデータが入っています。

ただ見てもらえば分かるのですが、入力データは文字列ではなくリストになっています。つまり単純に「request.POST[‘site’]」みたいにデータ取ろうとしても文字列としてのデータは取れません。

POSTデータを文字列で取るにはgetメソッドを使います。

#views.py
site = request.POST.get('site')
print(site)

#出力結果
あかつきのお宿



何でわざわざリストでデータをよこすんだこのヤロウって思うかもしれないけど、これにはちゃんと理由があって、Djangoは同じname属性が複数ある場合にもしっかり対応してくれるという利点(?)があるのです。

#index.html
サイト名:<input type="text" name="site" value="サイト1" />
サイト名:<input type="text" name="site" value="サイト2" />
サイト名:<input type="text" name="site" value="サイト3" />

例えばこんな風に同じsiteというname属性を持つテキストボックスを三つ用意する。通常、このデータをPOSTすると一番下にあるサイト3のデータだけしか取れないのですが、Djangoの場合は全部のデータを取得することができます。

#index.html
サイト名:<input type="text" name="site" value="サイト1" />
サイト名:<input type="text" name="site" value="サイト2" />
サイト名:<input type="text" name="site" value="サイト3" />

#views.py
class index(TemplateView) :
  def post(self, request, *args, **kwargs) :
    print(request.POST)

#POSTの中身
<QueryDict: {'site': ['サイト1', 'サイト2', 'サイト3']}>

このためのリストなわけです。チェックボックスの値なんかをまとめて受け取りたい場合なんかは便利かもしれないですね。

ただしこのデータを先ほどみたくget()を使って取得しようとすると、最後のサイト3だけしかデータが取れません。

#index.html
サイト名:<input type="text" name="site" value="サイト1" />
サイト名:<input type="text" name="site" value="サイト2" />
サイト名:<input type="text" name="site" value="サイト3" />

#views.py
class index(TemplateView) :
  def post(self, request, *args, **kwargs) :
    print(request.POST.get('site'))

#出力結果
サイト3

リストのデータをまとめて取得する場合はgetlist()を使います。

#views.py
class index(TemplateView) :
  def post(self, request, *args, **kwargs) :
    print(request.POST.getlist('site'))

#出力結果
['サイト1', 'サイト2', 'サイト3']

これでリストごと取得できます。ちなみにrequest.POST[‘site’]でも最後のサイト3しか取得できないので注意。必ずgetlist()を使うこと。






やっぱり長くなってしまいましたね。でもデータを取得するところまでできれば、あとはそれをデータベースに突っ込むなりなんなりするところはモデル側の仕事なので、フォームの基本に関してはこれで問題ないかと思うのですが、どうでしょうか。

冒頭でも言いましたが今回はテキストボックスのみの話になってしまったので、次回はテキストエリア、ラジオボタン、チェックボックス、プルダウン辺りの作り方を見て、さらにその次にバリデーションの書き方をさらってみたいと思います。

しかしformとfromって何度見てもよく似てるな。うっかり書き間違えていることに気づかずバグが発生する確率の高い単語ベスト5に確実に入るくらい似てるな。
 もしかしたら何か関連しているかも? 
 質問や感想などお気軽にコメントしてください