フォームウィザード (Form wizard)

revision-up-to:7392 (0.97pre SVN)

開発版の Django で新たに登場した機能です。

Django には、`フォーム`_ を複数のページに分割する「フォームウィザード」 アプリケーションがオプションで付いています。フォームウィザードは、フォーム の状態を HTML の <input type="hidden"> フィールドにハッシュ化して保存し、 最終的にフォームを提出するまで、サーバでフォームデータを処理させません。

フォームウィザードは、とても長いフォームを扱う必要があって、フォームを一つ のページに納めると不格好になってしまうような場合に使うとよいでしょう。 例えば、最初のページではユーザに重要な情報を尋ね、次のページでは比較的些細 な情報を訪ねるといったフォームで、フォームウィザードを使います。

「ウィザード (wizard)」という用語の意味は、 Wikipedia で解説されています

フォームウィザードの仕組み

ユーザがウィザードを使うときの基本的なワークフローは、以下の通りです:

  1. ユーザはウィザードの最初のページを訪問し、フォームを入力して内容を提 出 (submit) します。
  2. サーバは提出されたデータを検証します。データが有効でなければ、エラー メッセージつきでフォームを再表示します。データが有効なら、データのセ キュアなハッシュを計算して、ユーザに次のフォームを表示し、検証済みの データとハッシュを <input type="hidden"> フィールドに保存します。
  3. 以降のウィザード上の全フォームでステップ 1 と 2 を繰り返します。
  4. ユーザが全てのフォームを提出し、提出されたデータが全て有効であった場 合、ウィザードはデータを処理します。すなわち、データベースへの保存や 電子メールの送信といった、アプリケーションで必要な処理を実施します。

使い方

このアプリケーションは、可能な限り処理を自動化しています。基本的には、開発 者側で行う必要があるのは以下の 4 つの作業だけです:

  1. django.newforms のフォームクラスをウィザードの各ページごとに作成 します。
  2. FormWizard クラスを作成して、全てのフォームが提出され、フォーム データが有効だった場合の処理を記述します。同時に、ウィザードの挙動も いくつかオーバライドできます。
  3. フォームをレンダリングするためのテンプレートを作成します。全部のフォー ムの表示に使うテンプレートを一つだけ作成してもよいですし、各フォーム に対して固有のテンプレートを定義してもかまいません。
  4. URLconf が FormWizard クラスを指すように設定します。

フォームクラスを定義する

フォームウィザード作成の最初のステップは、フォームクラスの作成です。 フォームの作成には django.newformsForm クラスを使わねばなりませ ん。 Form クラスの開設は newforms のドキュメント を参照してください。

フォームクラスはコードベースのどこに置いてもかまいませんが、慣習的には アプリケーションフォルダの forms.py に置くことになっています。

例として、「コンタクトフォーム」のウィザードを作成してみましょう。このウィ ザードは、最初のページで送り手の e-mail アドレスとタイトルを入力させ、次の ページでメッセージ本体を入力させます。 forms.py は以下のようになるでしょ う:

from django import newforms as 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.newforms のフォームクラスからなるリストで す。

以下の簡単な例では、データベースの操作を行わずに、単に検証済みのデータをテ ンプレートに表示しています:

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 という 名前のテンプレートを使います。 (このテンプレート名は、後で説明する FormWizard.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 を全て表示せねばなりません。

ウィザードを URLconf に組み込む

最後に、 urls.py にフォームウィザードオブジェクトを追加します。 ウィザードはフォームオブジェクトを引数に取ります:

from django.conf.urls.defaults import *
from mysite.testapp.forms import ContactForm1, ContactForm2, ContactWizard

urlpatterns = patterns('',
    (r'^contact/$', ContactWizard([ContactForm1, ContactForm2])),
)

フォームウィザードの特殊なメソッド

done() メソッドの他にも、フォームウィザードは特殊なメソッドをいくつか提 供しており、ウィザードの動作をカスタマイズできます。

こうしたメソッドには step という引数をとるものがあります。 step は ゼロから始まるカウンタで、ウィザードの現在のステップを表しています。 (例えば、最初のフォームは 0 で、2 番目のフォームは 1 です。)

prefix_for_step

ステップを指定すると、フォームのプレフィクス文字列を返します。 デフォルトでは、単にステップ番号そのものを使います。詳しくは フォームプレフィクスのドキュメント を参照してください。

デフォルトの実装は以下の通りです:

def prefix_for_step(self, step):
    return str(step)

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.'})

security_hash

受け取ったリクエストオブジェクトとフォームインスタンスに対するセキュリティ ハッシュを計算します。

デフォルトでは、フォームデータの MD5 ハッシュと SECRET_KEY 設定 を使いま す。このメソッドをオーバライドする必要はほとんどありません。

実装例を示します:

def security_hash(self, request, form):
    return my_hash_function(request, form)

parse_params

リクエストオブジェクトと、 URLconf を使って URL からキャプチャした args / kwargs を元に、ウィザードの状態を保存するためのフックです。

デフォルトでは何もしません。

実装例を示します:

def parse_params(self, request, *args, **kwargs):
    self.my_state = args[0]

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']

render_template

指定したステップのテンプレートをレンダし、 HttpResponse オブジェクトを 返します。

カスタムのコンテキストを追加したり、 MIME タイプを変更したりしたければ、こ のメソッドをオーバライドしてください。テンプレート名をオーバライドしたいだ けなら、 get_template() を使ってください。

テンプレートは フォームのテンプレートを作成する で解説したコンテキストを 使ってレンダされます。

process_step

ウィザードの内部状態を変更するためのフックです。このフックには検証済みのフォー ムオブジェクトが渡されます。フォームには、必ずクリーニング済みで有効なデー タが入っています。

このメソッドでフォームデータの内容を変更しては なりません 。内容に変更を 加えたければ、 self.extra_context を設定するか、 self.form_list に 入っている提出済みフォームを入れ替えてください。

このメソッドは、フォームを提出する 全ての ステップでページのレンダリング 時に呼び出されるので注意してください。

関数シグネチャを以下に示します:

def process_step(self, request, form, step):
    # ...