Django ModelForm の clean の順番
更新日: 2024/02/19
概要
Django の Form の clean の順番を忘れていると誤っている情報を許容してしまう。
間違えない為の備忘録。
サンプルコード
顧客登録のフォームを想定。
フラグ is_xxxx_flag
が ON の場合、 xxxx_val
を入力必須としたい場面を考える。
モデル
from django.db import models
class Customer(models.Model):
first_name = models.CharField("姓", max_length=20)
last_name = models.CharField("名", max_length=20)
is_xxxx_flag = models.BooleanField("xxxxに加入する")
xxxx_val = models.CharField("xxxx特典名", max_length=20, default="", blank=True)
フォーム
from django import forms
from django.core.exceptions import ValidationError
class TestForm(forms.ModelForm):
class Meta:
model = Customer
fields = (
"first_name",
"last_name",
"is_xxxx_flag",
"xxxx_val",
)
def clean_last_name(self):
print("clean_last_name is called!")
return self.cleaned_data.get("last_name")
def clean_first_name(self):
print("clean_first_name is called!")
return self.cleaned_data.get("first_name")
def clean_xxxx_val(self):
print("clean_xxxx_val is called!")
xxxx_val = self.cleaned_data.get("xxxx_val")
is_xxxx_flag = self.cleaned_data.get("is_xxxx_flag")
print(f"cleaned_data xxxx_val:{xxxx_val}, is_xxxx_flag:{is_xxxx_flag}")
if is_xxxx_flag and not xxxx_val:
raise ValidationError("xxxxに加入される場合にはxxxx特典名を記入してください。")
return xxxx_val
def clean_is_xxxx_flag(self):
print("clean_is_xxxx_flag is called!")
return self.cleaned_data.get("is_xxxx_flag")
def clean(self):
print("clean is called!")
return self.cleaned_data
投入データ
is_xxxx_flag
が ON だが xxxx_val
が未記入なので弾きたいデータを想定。
test_data = {
"first_name": "テスト",
"last_name": "タロウ",
"is_xxxx_flag": True,
"xxxx_val": "",
}
実験
上記のコードのまま実行してみると想定通りの動きをした。
form = TestForm(test_data)
form.is_valid()
# clean_first_name is called!
# clean_last_name is called!
# clean_is_xxxx_flag is called!
# clean_xxxx_val is called!
# cleaned_data xxxx_val:, is_xxxx_flag:True
# clean is called!
# False <= 想定通り○
form.errors["xxxx_val"]
# ['xxxxに加入される場合にはxxxx特典名を記入してください。']
しかし、fields の順番を下記とすると。。
fields = (
"first_name",
"last_name",
"xxxx_val",
"is_xxxx_flag",
)
バリデーションを通過してしまいます。
form = TestForm(test_data)
form.is_valid()
# clean_first_name is called!
# clean_last_name is called!
# clean_xxxx_val is called!
# cleaned_data xxxx_val:, is_xxxx_flag:None
# clean_is_xxxx_flag is called!
# clean is called!
# True <= おかしい!
これは clean メソッドの呼ばれる順番が fields
で指定しているフィールド順になっているためです。(clean メソッドの定義順ではありません)
is_xxxx_flag:None
と後に呼ばれるフィールドはまだ値が取れていないことが見て取れます。
このように記載を間違えてしまうとエラーになってしまうので各フィールドの clean で他のフィールド情報を引っ張ってくるのはできるだけ避けた方が良いと思われます。
(わかったうえで利用するのはありだと思います。)
ただ、複数のフィールドを使ったバリデーションは clean
に書くと間違いがないと思います。
class TestForm(forms.ModelForm):
class Meta:
model = Customer
fields = (
"first_name",
"last_name",
"xxxx_val",
"is_xxxx_flag",
)
def clean(self):
xxxx_val = self.cleaned_data.get("xxxx_val")
is_xxxx_flag = self.cleaned_data.get("is_xxxx_flag")
if is_xxxx_flag and not xxxx_val:
raise ValidationError(
{"xxxx_val": ["xxxxに加入される場合にはxxxx特典名を記入してください。。"]}
)
return self.cleaned_data
ValidationError
を上記のように書くと各フィールドにエラーを出すことができます。
form = TestForm(test_data)
form.is_valid()
# False
form.errors["xxxx_val"]
# ['xxxxに加入される場合にはxxxx特典名を記入してください。。']
結果
fields
に設定したフィールドの順番で行われ、最後に clean
が呼ばれる。
修正
def clean_xxxx_val(self):
での print が誤っていましたので出力結果と合わせて修正しました。
print("clean_xxxx_val is called!") # ○
print("clean_is_xxxx_flag is called!") # ×