我的用例:
我有一个是/否问题,用户必须回答,但他们可能会回答是或否。我不想提供默认值,因为我需要用户积极选择他们的答案。如果他们没有选择答案,我会想要一个表单验证错误,例如“此字段是必需的”。
我知道可以将其作为CharField存储在数据库中,但是我更喜欢将其存储为必需的BooleanField。问题似乎是表单验证逻辑没有对布尔字段强制执行required = True,也没有在POST数据中返回值”时调用模型字段的自定义验证程序。
我的设置:
boolean_choices = (
(True, 'Yes'),
(False, 'No'),
)
def boolean_validator(value):
if value is not True and value is not False:
raise ValidationError("This field is required.")
class Item(models.Model):
accept = models.BooleanField(
choices=boolean_choices,
validators=[boolean_validator]
)
class ItemForm(ModelForm):
class Meta:
model = Item
fields = ['accept']
有关此问题的完整有效演示,请参见此处: https://repl.it/@powderflask/django-model-form-validators
再现问题
创建或编辑一个项目,并将'accept'值设置为None(“ -------”) ->保存将崩溃-通知form.is_valid()已通过,崩溃位于form.save()中 ->请注意,没有调用boolean_validator。
创建或编辑项目,然后选择“是”或“否” ->保存将正常工作,您可以在终端中看到boolean_validator DID被调用。
我的怀疑
我怀疑在表单验证逻辑的深处某个地方存在布尔字段的一种特殊情况,布尔字段通常是一个复选框(习惯于不检查时不返回POST数据中的任何内容)。我怀疑很难区分“复选框未打勾,所以为False”和“未返回任何值,因此引发RequiredField ValidationError),并且表单验证未正确处理我的用例。
我的问题 我在这里做蠢事吗?我想念什么吗?我应该放弃去洗个热水澡吗? 谢谢。
答案 0 :(得分:1)
查看表单字段类的validation documentation和source code可以发现问题并提出解决方案。
表单字段使用其clean
方法按此顺序处理输入:
to_python
将原始值转换为正确的类型validate
,以检查诸如required
之类的条件run_validators
,以检查是否违反了任何已注册的验证器 BooleanField
的{{1}}方法将任何输入转换为to_python
或True
。它不能返回False
或任何其他值:
None
这意味着没有验证者可以区分给字段class BooleanField(Field):
widget = CheckboxInput
def to_python(self, value):
"""Return a Python boolean object."""
# Explicitly check for the string 'False', which is what a hidden field
# will submit for False. Also check for '0', since this is what
# RadioSelect will provide. Because bool("True") == bool('1') == True,
# we don't need to handle that explicitly.
if isinstance(value, str) and value.lower() in ('false', '0'):
value = False
else:
value = bool(value)
return super().to_python(value)
作为值的情况与导致字段返回None
作为Python解析值的不同情况。如果需要,您可以使用False
或显式验证器来强制值必须为required=True
,但是您不能说出将值分开True
的不同原因。 / p>
为此,您需要一个不同的类-在将其转换为Python之前将False
以及您不希望接受的其他任何内容视为无效输入,或者将其转换为{{ 1}},然后可以使用验证器拒绝None
值。
对于第一个解决方案,您可以继承并覆盖None
。 None
类提供了一个很好的示例,但我们将对其进行修改以将空输入视为无效,而不是允许其返回to_python
:
NullBooleanField
对于第二个选项(之所以有吸引力,是因为它不需要自定义字段子类)是接受字段名和意图之间的冲突,并直接使用None
,注册将检测和验证的验证器。拒绝一个空值。像这样:
class StrictBooleanField(Field):
def to_python(self, value):
"""
Explicitly check for the string 'True' and 'False', which is what a
hidden field will submit for True and False, for 'true' and 'false',
which are likely to be returned by JavaScript serializations of forms,
and for '1' and '0', which is what a RadioField will submit. Unlike
the Booleanfield, this field must check for True because it doesn't
use the bool() function.
"""
if value in (True, 'True', 'true', '1'):
return True
elif value in (False, 'False', 'false', '0'):
return False
else:
raise ValidationError(_('Invalid boolean value %(value)s'), params={'value': value},', code='invalid')
在这里,我们利用NullBooleanField
来理解输入为def validate_non_null(value):
if value is None:
raise ValidationError(_('Value must not be None', code='invalid')
class ItemForm(ModelForm):
accept = NullBooleanField(validators=[validate_non_null])
class Meta:
model = Item
fields = ['accept']
,NullBooleanField
或True
,而不仅仅是False
或{ {1}},然后使用标准验证将None
视为已识别但输入错误。