Django model.field验证器不会对必填的布尔字段执行

时间:2019-06-19 21:45:01

标签: django django-models

我的用例:

我有一个是/否问题,用户必须回答,但他们可能会回答是或否。我不想提供默认值,因为我需要用户积极选择他们的答案。如果他们没有选择答案,我会想要一个表单验证错误,例如“此字段是必需的”。

我知道可以将其作为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),并且表单验证未正确处理我的用例。

我的问题 我在这里做蠢事吗?我想念什么吗?我应该放弃去洗个热水澡吗? 谢谢。

1 个答案:

答案 0 :(得分:1)

查看表单字段类的validation documentationsource code可以发现问题并提出解决方案。

表单字段使用其clean方法按此顺序处理输入:

  1. 调用to_python将原始值转换为正确的类型
  2. 对转换后的值调用validate,以检查诸如required之类的条件
  3. 对转换后的值调用run_validators,以检查是否违反了任何已注册的验证器

BooleanField的{​​{1}}方法将任何输入转换为to_pythonTrue。它不能返回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值。

对于第一个解决方案,您可以继承并覆盖NoneNone类提供了一个很好的示例,但我们将对其进行修改以将空输入视为无效,而不是允许其返回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'] NullBooleanFieldTrue,而不仅仅是False或{ {1}},然后使用标准验证将None视为已识别但输入错误。