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

revision-up-to:11321 (1.1)
Django 1.0 で新たに登場しました: リリースノートを参照してください

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

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

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

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

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

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

使い方

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

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

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

フォームウィザード作成の最初のステップは、フォームクラスの作成です。 フォームの作成には django.formsForm クラスを使わねばなりません。 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() メソッドは、 全ての フォームが提出され、フォームデータが有効であった場合に何をするかを 決めるメソッドです。このメソッドには二つの引数が渡されます:

  • requestHttpRequest オブジェクトです。
  • form_listdjango.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 を全て表示せねばなりません。

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

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

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

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

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

class 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):
    # ...