表單與欄位驗證¶
表單驗證發生在資料被清理時。如果您想自訂此過程,有許多地方可以進行變更,每個地方都有不同的用途。在表單處理期間會執行三種類型的清理方法。這些方法通常會在您呼叫表單上的 is_valid()
方法時執行。還有其他事情也可能會觸發清理和驗證(存取 errors
屬性或直接呼叫 full_clean()
),但通常不需要它們。
一般來說,如果正在處理的資料有問題,任何清理方法都可以引發 ValidationError
,並將相關資訊傳遞給 ValidationError
建構函式。請參見下方有關引發 ValidationError
的最佳實踐。如果沒有引發 ValidationError
,該方法應以 Python 物件的形式返回清理後的(標準化的)資料。
大多數驗證可以使用 驗證器 來完成,驗證器是可以重複使用的輔助程式。驗證器是函數(或可呼叫物件),它們接受單一引數,並在輸入無效時引發 ValidationError
。驗證器會在欄位的 to_python
和 validate
方法被呼叫之後執行。
表單的驗證分為幾個步驟,可以自訂或覆寫
Field
上的to_python()
方法是每個驗證的第一步。它會將值強制轉換為正確的資料類型,如果無法轉換,則會引發ValidationError
。此方法接受來自小部件的原始值,並返回轉換後的值。例如,FloatField
會將資料轉換為 Python 的float
或引發ValidationError
。Field
上的validate()
方法會處理不適合驗證器的特定欄位驗證。它會接收已強制轉換為正確資料類型的值,並在發生任何錯誤時引發ValidationError
。此方法不會返回任何內容,也不應更改該值。您應該覆寫它來處理無法或不想放入驗證器的驗證邏輯。Field
上的run_validators()
方法會執行所有欄位的驗證器,並將所有錯誤聚合到單一的ValidationError
中。您不需要覆寫此方法。Field
子類別上的clean()
方法負責以正確的順序執行to_python()
、validate()
和run_validators()
並傳播它們的錯誤。如果任何方法在任何時候引發ValidationError
,則驗證會停止並引發該錯誤。此方法會返回清理後的資料,然後將其插入表單的cleaned_data
字典中。clean_<fieldname>()
方法是在表單子類別上呼叫的 – 其中<fieldname>
會被表單欄位屬性的名稱取代。此方法會執行特定於該特定屬性的任何清理,與欄位的類型無關。此方法不會傳遞任何參數。您需要在self.cleaned_data
中查閱欄位的值,並記住此時它會是 Python 物件,而不是表單中提交的原始字串(它會在cleaned_data
中,因為上述的一般欄位clean()
方法已經清理過資料一次)。例如,如果您想要驗證名為
serialnumber
的CharField
的內容是唯一的,則clean_serialnumber()
將是執行此操作的正確位置。您不需要特定的欄位(它是CharField
),但您需要一個特定於表單欄位的驗證部分,並且可能需要清理/標準化資料。此方法的返回值會取代
cleaned_data
中的現有值,因此它必須是來自cleaned_data
的欄位值(即使此方法沒有變更它)或新的清理值。表單子類別的
clean()
方法可以執行需要存取多個表單欄位的驗證。您可以在這裡進行檢查,例如「如果提供了欄位A
,則欄位B
必須包含有效的電子郵件地址」。此方法可以返回一個完全不同的字典(如果需要),該字典將用作cleaned_data
。由於欄位驗證方法在呼叫
clean()
時已執行,因此您還可以存取表單的errors
屬性,其中包含個別欄位清理引發的所有錯誤。請注意,您的
Form.clean()
覆寫引發的任何錯誤都不會與任何特定的欄位相關聯。它們會進入一個特殊的「欄位」(稱為__all__
),如果您需要,可以透過non_field_errors()
方法來存取它。如果您想將錯誤附加到表單中的特定欄位,則需要呼叫add_error()
。另請注意,當覆寫
ModelForm
子類別的clean()
方法時,有一些特殊的考量。(如需更多資訊,請參閱ModelForm 文件)
這些方法會按照上面給定的順序,一次一個欄位地執行。也就是說,對於表單中的每個欄位(按照它們在表單定義中宣告的順序),會執行 Field.clean()
方法(或其覆寫),然後執行 clean_<fieldname>()
。最後,一旦對每個欄位執行了這兩個方法,就會執行 Form.clean()
方法或其覆寫,無論先前的方法是否引發了錯誤。
下面提供了每個方法的範例。
如前所述,這些方法中的任何一個都可以引發 ValidationError
。對於任何欄位,如果 Field.clean()
方法引發 ValidationError
,則不會呼叫任何特定於欄位的清理方法。但是,所有其餘欄位的清理方法仍會執行。
引發 ValidationError
¶
為了使錯誤訊息靈活且易於覆寫,請考慮以下準則
向建構函式提供描述性錯誤
code
# Good ValidationError(_("Invalid value"), code="invalid") # Bad ValidationError(_("Invalid value"))
不要將變數強制轉換為訊息;請使用佔位符和建構函式的
params
引數# Good ValidationError( _("Invalid value: %(value)s"), params={"value": "42"}, ) # Bad ValidationError(_("Invalid value: %s") % value)
請使用對應鍵而不是位置格式。這樣可以將變數以任何順序放置,或在重寫訊息時完全省略它們
# Good ValidationError( _("Invalid value: %(value)s"), params={"value": "42"}, ) # Bad ValidationError( _("Invalid value: %s"), params=("42",), )
用
gettext
包裝訊息以啟用翻譯# Good ValidationError(_("Invalid value")) # Bad ValidationError("Invalid value")
將它們全部放在一起
raise ValidationError(
_("Invalid value: %(value)s"),
code="invalid",
params={"value": "42"},
)
如果您編寫可重複使用的表單、表單欄位和模型欄位,則遵循這些準則尤其必要。
雖然不建議,但如果您位於驗證鏈的末端(即您的表單 clean()
方法)並且您知道您永遠不需要覆寫您的錯誤訊息,您仍然可以選擇不太詳細的方式
ValidationError(_("Invalid value: %s") % value)
# Good
raise ValidationError(
[
ValidationError(_("Error 1"), code="error1"),
ValidationError(_("Error 2"), code="error2"),
]
)
# Bad
raise ValidationError(
[
_("Error 1"),
_("Error 2"),
]
)
from django.core import validators
from django.forms import CharField
class SlugField(CharField):
default_validators = [validators.validate_slug]
slug = forms.SlugField()
slug = forms.CharField(validators=[validators.validate_slug])
from django import forms
from django.core.validators import validate_email
class MultiEmailField(forms.Field):
def to_python(self, value):
"""Normalize data to a list of strings."""
# Return an empty list if no input was given.
if not value:
return []
return value.split(",")
def validate(self, value):
"""Check if value consists only of valid emails."""
# Use the parent's handling of required fields, etc.
super().validate(value)
for email in value:
validate_email(email)
class ContactForm(forms.Form):
subject = forms.CharField(max_length=100)
message = forms.CharField()
sender = forms.EmailField()
recipients = MultiEmailField()
cc_myself = forms.BooleanField(required=False)
from django import forms
from django.core.exceptions import ValidationError
class ContactForm(forms.Form):
# Everything as before.
...
def clean_recipients(self):
data = self.cleaned_data["recipients"]
if "fred@example.com" not in data:
raise ValidationError("You have forgotten about Fred!")
# Always return a value to use as the new cleaned data, even if
# this method didn't change it.
return data
from django import forms
from django.core.exceptions import ValidationError
class ContactForm(forms.Form):
# Everything as before.
...
def clean(self):
cleaned_data = super().clean()
cc_myself = cleaned_data.get("cc_myself")
subject = cleaned_data.get("subject")
if cc_myself and subject:
# Only do something if both fields are valid so far.
if "help" not in subject:
raise ValidationError(
"Did not send for 'help' in the subject despite CC'ing yourself."
)
def clean(self):
super().clean()
cc_myself = self.cleaned_data.get("cc_myself")
...
from django import forms
class ContactForm(forms.Form):
# Everything as before.
...
def clean(self):
cleaned_data = super().clean()
cc_myself = cleaned_data.get("cc_myself")
subject = cleaned_data.get("subject")
if cc_myself and subject and "help" not in subject:
msg = "Must put 'help' in subject when cc'ing yourself."
self.add_error("cc_myself", msg)
self.add_error("subject", msg)