revision-up-to: | 11321 (1.1) |
---|
Django には、 フォーム を複数のページに分割する
「フォームウィザード」アプリケーションがオプションで付いています。フォーム
ウィザードは、フォームの状態を HTML の <input type="hidden">
フィー
ルドにハッシュ化して保存し、最終的にフォームを提出するまで、サーバでフォー
ムデータを処理させません。
フォームウィザードは、とても長いフォームを扱う必要があって、フォームを一つ のページに納めると不格好になってしまうような場合に使うとよいでしょう。 例えば、最初のページではユーザに重要な情報を尋ね、次のページでは比較的些細 な情報を訪ねるといったフォームで、フォームウィザードを使います。
「ウィザード (wizard)」という用語の意味は、 Wikipedia で解説されています 。
ユーザがウィザードを使うときの基本的なワークフローは、以下の通りです:
- ユーザはウィザードの最初のページを訪問し、フォームを入力して内容を提 出 (submit) します。
- サーバは提出されたデータを検証します。データが有効でなければ、エラー メッセージつきでフォームを再表示します。データが有効なら、データのセ キュアなハッシュを計算して、ユーザに次のフォームを表示し、検証済みの データとハッシュを
<input type="hidden">
フィールドに保存し ます。- 以降のウィザード上の全フォームでステップ 1 と 2 を繰り返します。
- ユーザが全てのフォームを提出し、提出されたデータが全て有効であった場 合、ウィザードはデータを処理します。すなわち、データベースへの保存や 電子メールの送信といった、アプリケーションで必要な処理を実施します。
このアプリケーションは、可能な限り処理を自動化しています。基本的には、開発 者側で行う必要があるのは以下の 4 つの作業だけです:
django.forms
のフォームクラスをウィザードの各ページごとに作成 します。FormWizard
クラスを作成し て、全てのフォームが提出され、フォームデータが有効だった場合の処理を 記述します。同時に、ウィザードの挙動もいくつかオーバライドできます。- フォームをレンダリングするためのテンプレートを作成します。全部のフォー ムの表示に使うテンプレートを一つだけ作成してもよいですし、各フォーム に対して固有のテンプレートを定義してもかまいません。
- URLconf が
FormWizard
クラ スを指すように設定します。
フォームウィザード作成の最初のステップは、フォームクラスの作成です。
フォームの作成には django.forms
の Form
クラスを使わねばなりません。 Form
クラスの開設
は forms のドキュメント を参照してください。
フォームクラスはコードベースのどこに置いてもかまいませんが、慣習的にはアプ
リケーションフォルダの forms.py
に置くことになっています。
例として、「コンタクトフォーム」のウィザードを作成してみましょう。このウィ
ザードは、最初のページで送り手の e-mail アドレスとタイトルを入力させ、次の
ページでメッセージ本体を入力させます。 forms.py
は以下のようになる
でしょう:
from django import forms
class ContactForm1(forms.Form):
subject = forms.CharField(max_length=100)
sender = forms.EmailField()
class ContactForm2(forms.Form):
message = forms.CharField(widget=forms.Textarea)
ウィザードは、ページ間のデータの受け渡しに HTML の隠しフィールドを使うため、
最後のページに表示するフォーム以外では
FileField
を使えません。
次のステップはフォームウィザードクラスの作成です。 フォームウィザードクラス
は、 django.contrib.formtools.wizard.FormWizard
のサブクラスにせね
ばなりません。
フォームクラスと同様、フォームウィザードクラスはコードベースのどこに配置し
てもかまいませんが、慣習的には forms.py
の中に書きます。
サブクラスの定義で唯一必ず行わなければならないのは
done()
メソッドの実装で
す。 done()
メソッドは、
全ての フォームが提出され、フォームデータが有効であった場合に何をするかを
決めるメソッドです。このメソッドには二つの引数が渡されます:
request
–HttpRequest
オブジェクトです。form_list
–django.forms
のフォームクラスからなるリストで す。
以下の簡単な例では、データベースの操作を行わずに、単に検証済みのデータをテ ンプレートに表示しています:
from django.shortcuts import render_to_response
from django.contrib.formtools.wizard import FormWizard
class ContactWizard(FormWizard):
def done(self, request, form_list):
return render_to_response('done.html', {
'form_data': [form.cleaned_data for form in form_list],
})
このメソッドは POST
で送信されるので、よき Web 市民たるべく、データの処
理後にはリダイレクトすべきでしょう。というわけで、もう一つの例を示します:
from django.http import HttpResponseRedirect
from django.contrib.formtools.wizard import FormWizard
class ContactWizard(FormWizard):
def done(self, request, form_list):
do_something_with_the_form_data(form_list)
return HttpResponseRedirect('/page-to-redirect-to-when-done/')
フォームウィザードの提供しているフックを詳しく知りたければ、後の フォームウィザードの特殊なメソッド を参照してください。
次に、ウィザードのフォームをレンダリングするためのテンプレートを作成する必
要があります。デフォルトでは、全てのフォームは forms/wizard.html
と
いう名前のテンプレートを使います。 (このテンプレート名は、後で説明する
get_template()
をオーバライドして
変更できます。このフックを使えば、各フォームで別々のテンプレートを使えます。)
テンプレートには、以下のコンテキストが渡されます:
step_field
– ウィザードのステップ情報を格納している隠しフィール ドの名前です。step0
– 現在のステップ (ゼロから始まる数です)。step
– 現在のステップ (1 から始まる数です)。step_count
– 全ステップ数です。form
– 現在のステップのフォームインスタンスです (空の場合と、エ ラーつきの場合がありえます)。previous_fields
– 現在のステップよりも前のデータフィールドと、検 証済みフォームのハッシュ値の入ったフィールドを表現する文字列です。こ れらのフィールドは全て隠しフィールドです。フィールドの内容は生の HTML なので、内容を処理するにはsafe()
テンプレートフィルタを使っ て自動エスケープを抑制する必要があります。
辞書オブジェクト extra_context
にオブジェクトを渡せば、コンテキスト
に任意のオブジェクトを追加できます。 extra_context
の指定には以下の
二つの方法があります:
- フォームウィザードのサブクラスの
extra_context
属 性に辞書を指定します。- URLconf に追加のパラメタとして
extra_context
を 渡します。
テンプレート例の全体像は以下のようになります:
{% extends "base.html" %}
{% block content %}
<p>Step {{ step }} of {{ step_count }}</p>
<form action="." method="post">
<table>
{{ form }}
</table>
<input type="hidden" name="{{ step_field }}" value="{{ step0 }}" />
{{ previous_fields|safe }}
<input type="submit">
</form>
{% endblock %}
ウィザードを正しく動作させるには、 previous_fields
, step_field
およ
び step0
を全て表示せねばなりません。
最後に、 urls.py
にフォームウィザードオブジェクトを追加します。
ウィザードはフォームオブジェクトを引数に取ります:
from django.conf.urls.defaults import *
from mysite.testapp.forms import ContactForm1, ContactForm2, ContactWizard
urlpatterns = patterns('',
(r'^contact/$', ContactWizard([ContactForm1, ContactForm2])),
)
FormWizard
¶done()
メソッドの他
にも、フォームウィザードは特殊なメソッドをいくつか提供しており、ウィザー
ドの動作をカスタマイズできます。
こうしたメソッドには step
という引数をとるものがあります。 step
はゼロから始まるカウンタで、ウィザードの現在のステップを表しています。
(例えば、最初のフォームは 0
で、2 番目のフォームは 1
です。)
FormWizard.
prefix_for_step
()¶ステップを指定すると、フォームのプレフィクス文字列を返します。 デフォルトでは、単にステップ番号そのものを使います。詳しくは フォームプレフィクスのドキュメント <form-prefix> を参照してください。
デフォルトの実装は以下の通りです:
def prefix_for_step(self, step):
return str(step)
FormWizard.
render_hash_failure
()¶ハッシュチェックに失敗した際にテンプレートをレンダするためのメソッドで す。このメソッドをオーバライドする必要はほとんどありません。
デフォルトの実装は以下の通りです:
def render_hash_failure(self, request, step):
return self.render(self.get_form(step), request, step,
context={'wizard_error': 'We apologize, but your form has expired. Please continue filling out the form from this page.'})
FormWizard.
security_hash
()¶受け取ったリクエストオブジェクトとフォームインスタンスに対するセキュリ ティハッシュを計算します。
デフォルトでは、フォームデータの MD5 ハッシュと SECRET_KEY
を使います。このメソッドをオーバライドする必要はほとんどありません。
実装例を示します:
def security_hash(self, request, form):
return my_hash_function(request, form)
FormWizard.
parse_params
()¶リクエストオブジェクトと、 URLconf を使って URL からキャプチャした
args
/ kwargs
を元に、ウィザードの状態を保存するためのフックで
す。
デフォルトでは何もしません。
実装例を示します:
def parse_params(self, request, *args, **kwargs):
self.my_state = args[0]
FormWizard.
get_template
()¶指定されたステップのページ表示に使うテンプレート名を返します。
デフォルトでは、ステップに関係なく 'forms/wizard.html'
を返します。
実装例を示します:
def get_template(self, step):
return 'myapp/wizard_%s.html' % step
get_template()
が文字列のリストを返す場合、ウィザード
はテンプレートシステムの
select_template()
関数を使います。つま
り、リスト中から最初に見つかったテンプレートを使います。
select_template()
については
テンプレートのドキュメントで解説しています 。例えば:
def get_template(self, step):
return ['myapp/wizard_%s.html' % step, 'myapp/wizard.html']
FormWizard.
render_template
()¶指定したステップのテンプレートをレンダし、
HttpResponseRedirect
オブジェクトを返します。
カスタムのコンテキストを追加したり、 MIME タイプを変更したりしたければ、
このメソッドをオーバライドしてください。テンプレート名をオーバライドし
たいだけなら、 get_template()
を使ってください。
テンプレートは「フォームのテンプレートを作成する」で解説したコンテキス トを使ってレンダされます。
FormWizard.
process_step
()¶ウィザードの内部状態を変更するためのフックです。このフックには検証済み のフォームオブジェクトが渡されます。フォームには、必ずクリーニング済み で有効なデータが入っています。
このメソッドでフォームデータの内容を変更しては なりません 。内容に変
更を加えたければ、 self.extra_context
を設定するか、
self.form_list
に入っている提出済みフォームを入れ替えてください。
このメソッドは、フォームを提出する 全ての ステップでページのレンダリ ング時に呼び出されるので注意してください。
関数シグネチャを以下に示します:
def process_step(self, request, form, step):
# ...
Oct 26, 2017