DjangoのForm wizardを使ってみる

djangoでは複数ページにまたがるフォームを処理するためにForm wizardという仕組みが用意されてます。

練習のため、できるだけシンプルな使い方をして、動作を確認してみました。


まずFormが必要です。
適当に2つ用意します。

forms.py

class AddressForm(forms.Form):
    name = forms.CharField(max_length=255)
    prefecture = forms.ChoiceField(choices=JP_PREFECTURES, widget=JPPrefectureSelect())
    address = forms.CharField(max_length=255)


class PaymentForm(forms.Form):
    payment_methd = forms.ChoiceField(choices=((0, u'クレカ'),  (1, u'代引き')))


つぎにWizardViewを用意します。

views.py

class OrderWizard(SessionWizardView):
    form_list = [AuthenticationForm, AddressForm, PaymentForm]

    def done(self, form_list, **kwargs):
        # 入力値を使って、登録処理などを行い、
        # 実際はリダイレクトさせる
        return render_to_response('apps/done.html', {
            'form_data': [form.cleaned_data for form in form_list],
        })

やっぱ2つだと面白くないので、django標準のAuthenticationFormも使って3ページにまたがるフォームにしてみます。

done関数は、すべてのフォームの入力が済んだあとに呼び出される関数で、必ず実装する必要があります。


テンプレートを用意します。
フォームごとに別々のテンプレートを用意することができますが、最もシンプルな使い方として、1つのテンプレートだけで動かしてみます。

formtools/wizard/wizard_form.html

{% extends "base.html" %}
{% load i18n %}

{% block head %}
{{ wizard.form.media }}
{% endblock %}

{% block content %}
<p>Step {{ wizard.steps.step1 }} of {{ wizard.steps.count }}</p>
<form action="" method="post">{% csrf_token %}
<table>
{{ wizard.management_form }}
{% if wizard.form.forms %}
    {{ wizard.form.management_form }}
    {% for form in wizard.form.forms %}
        {{ form }}
    {% endfor %}
{% else %}
    {{ wizard.form }}
{% endif %}
</table>
{% if wizard.steps.prev %}
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.first }}">{% trans "first step" %}</button>
<button name="wizard_goto_step" type="submit" value="{{ wizard.steps.prev }}">{% trans "prev step" %}</button>
{% endif %}
<input type="submit" value="{% trans "submit" %}"/>
</form>
{% endblock %}

djangoのドキュメントからもってきました。
これを見るとwizard.management_formを表示させる必要があること、formsetに対応していることがわかります。
自前で用意するときは、これを参考にすればいいでしょう。


あとdone.htmlを適当に用意して、最後に入力値を確認できるようにしておきます。

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>
{{ form_data }}
</body>
</html>


urls.pyにルーティングの設定をします。
as_viewにFormのリストを渡します。
urls.py

    url(r'^order/$', OrderWizard.as_view([AuthenticationForm, AddressForm, PaymentForm]))


これで準備完了です。

動かしてみます。




順番にフォーム入力して、最後にdoneで全ての入力値がvalidな状態で渡されていることがわかります。
urlはすべて一緒です。

どのように順番を制御しているのか?

みてみると

{{ wizard.management_form }}

<input id="id_order_wizard-current_step" name="order_wizard-current_step" type="hidden" value="0">

このように表示されてます。このhidden入力値で表示するフォームを制御してるんですね。

ほかに入力値をhiddenで引き回したりはしていません。
ViewをSessionWizardViewを継承して作成しているので、入力値はSessionに保存されてます。

# 最初のフォーム表示時のセッションデータ
{'wizard_order_wizard': {'step_files': {}, 'step': u'0', 'extra_data': {}, 'step_data': {}}}
# 2つめのフォーム表示時のセッションデータ
{'wizard_order_wizard': {'step_files': {u'0': {}}, 'step': u'1', 'extra_data': {}, 'step_data': {u'0': {u'0-password': ['pass'], u'csrfmiddlewaretoken': [u'aIektqp3Q9vQBexwaUOSsuz809NWjPKa'], u'order_wizard-current_step': [u'0'], u'0-username': [u'user']}}}}
# 3つめのフォーム表示時のセッションデータ
{'wizard_order_wizard': {'step_files': {u'1': {}, u'0': {}}, 'step': u'2', 'extra_data': {}, 'step_data': {u'1': {u'csrfmiddlewaretoken': [u'aIektqp3Q9vQBexwaUOSsuz809NWjPKa'], u'1-prefecture': [u'hokkaido'], u'1-name': [u'brainstorm'], u'order_wizard-current_step': [u'1'], u'1-address': [u'test']}, u'0': {u'0-password': [u'pass'], u'csrfmiddlewaretoken': [u'aIektqp3Q9vQBexwaUOSsuz809NWjPKa'], u'order_wizard-current_step': [u'0'], u'0-username': [u'user']}}}}
# doneページの表示時のセッションデータ
{'wizard_order_wizard': {'step_files': {}, 'step': None, 'extra_data': {}, 'step_data': {}}}


他にCookieWizardViewというのもあるので今度試してみたいと思います。

ぱっと見ややこしそうな印象だったんですが、使ってみるとなんとなくわかりました。
フックポイントがいっぱいあるので、それもそのうち試したい。


参考 : Form wizard | Django documentation | Django