知識の枝

"All is well"

Django フォームのバリデーションルール作り

約258日前 2021年5月10日23:40
デジタル
Django

改訂履歴


2021/5/10 投稿

1. 背景


入力フォームのバリデーションって意外と躓き易いと思うんです。

現に私も躓きましたので、その解決法となぜ解決できたのかを解説したいと思います。


2. 目的


Djangoのバリデーションの仕組みを大まかに理解し、ルールを自分で作る。


3. はじめに


バリデーションとはどんなものか?

このページに来ている方はイメージが既に沸いていると思いますが念のため説明します。



フォームのバリデーションとは、フォームの入力内容に一定のルールを設け、ルールを満足するかしないかで条件分岐する機能です。

通常、views.pyからhtmlテンプレートにフォームクラスの変数を渡すと自動でバリデーション機能が付きます。
それは、「入力が必須のフィールドに値が入力されているかどうか」&「指定したフィールドの型とマッチしているか」の判定です。




今回解説したいのは上記ではなく、「自分でルールを追加したい」場合の方法です。
次項で解説致します。


4. フォームにルールを追加


forms.pyにルールを記載します。

元々「CreateURLForm」というURLを登録するフォームがあったとします。
(上述したURL登録フォームです)
forms.py
class CreateURLForm(forms.ModelForm):
"""Create Form"""
class Meta:
model = Star
fields = ('url',)
「Star」というお気に入り管理を行うモデルの「url」というフィールドを入力する設定になっています。

これを基本形として、入力ルールを定める関数を追加します。
forms.py
class CreateURLForm(forms.ModelForm):
"""Create Form"""
class Meta:
model = Star
fields = ('url',)

def clean_url(self):
url = self.cleaned_data.get('url')
if url.startswith(("http://","https://")):
return url
else:
raise forms.ValidationError('Please enter the URL in this field. (e.g. http://example.com)')
「clean_url()」という関数を定義しました。

1つ1つのフィールドにルールを設けるには、まず「clean_フィールド名(self):」と定義します。
def clean_url(self):
今回はフィールド名が「url」ですので、上記のようにしています。

if文の箇所にルールを書きます。
ルール(バリデーション・検証と言います)を通過した場合の分岐先には「return フィールド名」として下さい。
return url
views.pyの「is_valid()」以降に続きます。


ルールを満足しない場合の分岐先ではエラーを吐くようにします。
raise forms.ValidationError()
「ValidationError」の括弧内に表示するエラーメッセージを書きます。
今回は「Please enter the URL in this field. (e.g. http://example.com)」としています。


5. エラーメッセージの表示


先ほど設定したルールに基づいてviews.pyの「is_valid()」が行われます。

ルール(バリデーション)を通ったらPOST内容を保存する処理の例です。
views.py
if request.method == "POST":
create_url_form = CreateURLForm(request.POST)
if create_url_form.is_valid():
new_url.save()

return redirect('app_name:view_name')
「is_valid()」を通らなかった場合(=ルールNG)、モデルは保存されずif文から抜けます。

「is_valid()」でNGとなったとき、代わりにPOSTされたフォームに「エラーメッセージ」が保存されます。
抜けた後の処理を確認しましょう。

views.py
if文を抜けた後
context = {
'create_url_form': create_url_form,
}

return render(request, template, context)
エラーメッセージの入ったフォームオブジェクトをテンプレートに渡す為、「is_valid()」のif文よりも後でcontextの定義を行いましょう。

「if request.method == "POST":」よりも上でcontextの定義を行うと、エラーメッセージの入ったフォームオブジェクトをキャッチできないので気を付けましょう。


それではエラーメッセージをhtmlテンプレートで表示します。
html
<form method="POST" class="post-form" class="form-group">
<div class="row">
<div class="col-8 offset-2">
{% csrf_token %}
{% for field in create_url_form %}
{% if field.errors %}
{% render_field field class="form-control is-invalid" placeholder=" Enter URL here..." %}
{% for error in field.errors %}
<div class="invalid-feedback">
{{ error }}
</div>
{% endfor %}
{% else %}
{% render_field field class="form-control is-valid" placeholder=" Enter URL here..." %}
{% endif %}
{% endfor %}
</div>
<div class="col-2">
<button type="submit" class="save btn btn-success">Create Now</button>
</div>
</div>
</form>
BootstrapのCSSを適用させるために上記のような書き方になっています。
※widget_tweaksを使用しています。

こんなフォームになります。



フォームモデルの各フィールドをfor文で取り出しています。
(今回は「url」フィールドのみなので1つですが)
{% for field in create_url_form %}



フォームモデルにエラーメッセージが入っているかどうかでif分岐します。
{% if field.errors %}
「もしエラーメッセージがあったら」というif文です。


Bootstrapの「is_invalid」クラスを適用しています。
Server side
{% render_field field class="form-control is-invalid" placeholder=" Enter URL here..." %}



エラーメッセージを取り出し、画面に表示します。
{% for error in field.errors %}
<div class="invalid-feedback">
{{ error }}
</div>
{% endfor %}



エラーメッセージが入っていなければ通常のフォームを表示します。
{% else %}
{% render_field field class="form-control is-valid" placeholder=" Enter URL here..." %}
{% endif %}



これでエラーの表示ができるようになりました!


6. さいごに


Djangoには他にもバリデーション方法があります。
今回紹介した方法でもできますが、ほんの一部ですのでご承知おき下さい。

お疲れ様でした。