DjangoのフォームをDjango shellでデバッグする

Djangoフレームワークには、ウェブ用のFormを作る機能があります。

フォームを使う | Django ドキュメント | Django

フォームを定義して実際にブラウザ上で表示するためには、ビューやテンプレートを用意する必要があるのですが、少しフォームを試したいだけであれば、毎回すべてを用意するのは少し手間です。

フォームクラスの動作を確認するだけであれば、Djangoのrequestオブジェクトには異存しないので、Django shell(manage.py shell)からでも手軽に試せます。

フォームを定義する

適当にmyappというDjangoアプリケーションを作成し、プロジェクトのsettings.LANGUAGEを 'ja' に変更してからフォームを作ってみます。

myapp/forms.py:

from django import forms

class ContactForm(forms.Form):
    name = forms.CharField(label="お名前", max_length=10)
    email = forms.EmailField(label="メールアドレス")
    content = forms.CharField(widget=forms.Textarea)

Django shellから試す

manage.py shell を起動して、定義しておいた ContactForm クラスをインポートします。

>>> from myapp.forms import ContactForm

これでContactFormクラスを試す準備ができました。

フォームのインスタンスを作る、メソッドを呼び出す

Djangoのフォームは引数を省略した場合、空のフォーム(新規入力相当)を生成します。

>>> form = ContactForm()
>>> form
<ContactForm bound=False, valid=Unknown, fields=(name;email;content)>
>>> form.as_p()  # pタグでフィールドをレンダリングしたHTMLを生成
'<p>\n    <label for="id_name">お名前:</label>\n    <input type="text" name="name" maxlength="10" required id="id_name">\n    \n    \n  </p>\n\n  \n  <p>\n    <label for="id_email">メールアドレス:</label>\n    <input type="email" name="email" maxlength="320" required id="id_email">\n    \n    \n  </p>\n\n  \n  <p>\n    <label for="id_content">Content:</label>\n    <textarea name="content" cols="40" rows="10" required id="id_content">\n</textarea>\n    \n    \n      \n    \n  </p>'

form.as_p() のようにして、formの各メソッドを試すことができます。

この例だと、pタグでフォームのHTMLを出力するメソッドを試しています。定義したフィールドに対応するHTMLの文字列が生成されていることを確認できます。

入力バリデーションを試す

Djangoのフォームでよく試したいのは、入力バリデーションだと思います。試していきましょう。

Djangoのフォームを使う際に、ビューでは request.POST のようなオブジェクトを引数で渡していることが多いですが、これは辞書ライクなオブジェクト(QueryDict)です。

実際、Djangoのフォームは入力値としてPythonの辞書を渡せば動かすことができる、シンプルな作りになっています。

formには空の辞書を渡した場合、bound=Trueの状態のフォームインスタンスが作られます。Unbound Form, Bound Formについては、ドキュメントを参照してください。

フォーム API | Django ドキュメント | Django

Bound Formの場合、errorsを参照すると、バリデーションが実行されるので、必須入力のチェックが実行されていることを確認できます。

>>> form = ContactForm({})
>>> form
<ContactForm bound=True, valid=Unknown, fields=(name;email;content)>
>>> form.errors
{'name': ['このフィールドは必須です。'], 'email': ['このフィールドは必須です。'], 'content': ['このフィールドは必須です。']}

nameフィールドの初期値だけを与えてテストしてみましょう。

>>> form = ContactForm({'name': 'おなまえおなまえおなまえ'})
>>> form.is_valid()
False
>>> form.errors
{'name': ['この値は 10 文字以下でなければなりません( 12 文字になっています)。'], 'email': ['このフィールドは必須です。'], 'content': ['このフィールドは必須です。']}

このように、Django shellで、フォームのバリデーションを試すことができます。

cleand_dataを試す

フォームの is_valid() がTrueを返す状態であれば、 cleaned_data プロパティでクリーニングしたフィールド毎のデータも取得できます。

フォームフィールドやウィジェットでデータ変換が発生するフォームの場合は、これもDjango shell上で試せるとデバッグがはかどると思います。

>>> form = ContactForm({'name': 'モンティ・パイソン', 'email': 'foo@example.com', 'content': 'お問い合わせ内容'})
>>> form.is_valid()
True
>>> form.cleaned_data
{'name': 'モンティ・パイソン', 'email': 'foo@example.com', 'content': 'お問い合わせ内容'}

IPythonのautoreload

フォームを頻繁に書き換えて試す場合、毎回shellを起動しなおして、インポートからやりなおすのは手間です。

IPythonのshellであれば、autoreloadという拡張を使って少し楽をできるかもしれません。

autoreload — IPython 3.2.1 documentation

IPythonをインストールした状態で、 manage.py shell を起動すると、IPythonのシェルになります。

%load_ext autoreload で自動リロードの拡張をロードし、 %autoreload 2 で自動リロードのモードを変更します。

モード2は、「Reload all modules」となっていて、コード実行前に毎回すべてのモジュールがリロードされます。

In [1]: %load_ext autoreload

In [2]: %autoreload 2

In [3]: from myapp.forms import ContactForm

In [4]: form = ContactForm({})

In [5]: form
Out[5]: <ContactForm bound=True, valid=Unknown, fields=(name;email;content)>

# ここで、ソースコード内のcontentの行をコメントアウト

In [6]: form = ContactForm({})

In [7]: form
Out[7]: <ContactForm bound=True, valid=Unknown, fields=(name;email)>  # 自動でリロードされたクラスが使われている

プロジェクトが大きいと、毎回全てのモジュールをリロードするのは遅い可能性があるので、その場合はドキュメントにあるように %aimport を使うなどして、部分的にリロードするだけでもいいかもしれません。

まとめ

Django shellでフォームをインポートして試せることを紹介しました。

最終的にはユニットテストのコードを書いて、保守しやすい状態にするのがよいですが、試行錯誤する段階では、このようにshell上でデバッグする方法も手軽なので、知っておくとよいかなと思います。