如何在WTForms中使字段条件性可选?

38 投票
4 回答
20183 浏览
提问于 2025-04-17 08:08

我的表单验证几乎完成了,但还有两个情况我不太清楚该怎么处理:1)密码字段当然是必填的,但我也提供了通过谷歌或脸书账号登录的选项,这样用户的名字会自动填入。如果用户是谷歌或脸书用户,我就完全去掉密码字段。

<tr><td>
  <br />        {% if user or current_user %}    {% else %} 

  <div class="labelform">
     {% filter capitalize %}{% trans %}password{% endtrans %}{% endfilter %}:
  </div>
      </td><td>  <div class="adinput">{{ form.password|safe }}{% trans %}Choose a password{% endtrans %}</div>{% endif %}

  </td></tr>

对于那些已经登录的用户来说,密码字段就没有意义了。我需要一些逻辑来让这个字段变成可选的。我在想可以用一个变量来表示用户是否登录,再加上我表单类中的一个方法,像这样:

class AdForm(Form):
    logged_in = False
    my_choices = [('1', _('VEHICLES')), ('2', _('Cars')), ('3', _('Bicycles'))]
    name = TextField(_('Name'), [validators.Required(message=_('Name is required'))], widget=MyTextInput())
    title = TextField(_('title'), [validators.Required(message=_('Subject is required'))], widget=MyTextInput())
    text = TextAreaField(_('Text'),[validators.Required(message=_('Text is required'))], widget=MyTextArea())
    phonenumber = TextField(_('Phone number'))
    phoneview = BooleanField(_('Display phone number on site'))
    price = TextField(_('Price'),[validators.Regexp('\d', message=_('This is not an integer number, please see the example and try again')),validators.Optional()] )
    password = PasswordField(_('Password'),[validators.Optional()], widget=PasswordInput())
    email = TextField(_('Email'), [validators.Required(message=_('Email is required')), validators.Email(message=_('Your email is invalid'))], widget=MyTextInput())
    category = SelectField(choices = my_choices, default = '1')

    def validate_name(form, field):
        if len(field.data) > 50:
            raise ValidationError(_('Name must be less than 50 characters'))

    def validate_email(form, field):
        if len(field.data) > 60:
            raise ValidationError(_('Email must be less than 60 characters'))

    def validate_price(form, field):
        if len(field.data) > 8:
            raise ValidationError(_('Price must be less than 9 integers'))

    def validate_password(form, field):
        if not logged_in and not field:
            raise ValidationError(_('Password is required'))

上面的validate_password方法能实现我想要的效果吗?有没有更好的方法?我还想到一种方法,就是用两个不同的表单类,在http post时实例化应该使用的表单类:

def post(self):
    if not current_user:
      form = AdForm(self.request.params)
    if current_user:
      form = AdUserForm(self.request.params)

我还需要对类别字段进行条件验证,当选择某个类别时,会出现更多选项,这些选项只有在选择了特定的基础类别时才需要验证。例如,用户选择“汽车”,然后通过Ajax可以选择汽车的注册信息和里程,这些字段在选择了“汽车”类别后是必填的。

所以这可能是两个问题,但这两种情况都与如何让一个字段变成“条件可选”或“条件必填”有关。

我的表单长这样:

在这里输入图片描述

对于已登录的用户,我会自动填入名字和邮箱地址,而密码字段根本不使用,所以密码字段既不适合“可选”也不适合“必填”,它需要类似“条件可选”或“条件必填”的东西。

在这里输入图片描述

谢谢任何回答或评论。

4 个回答

7

来自@dcrosta的回答很不错,但我觉得自从那时候以来,wtforms有了一些变化。从DataRequired继承会给表单字段添加一个required属性,这样条件验证器就不会被调用了。我对@dcrosta的类做了一个小改动,使其能在wtforms 2.1中正常工作。这只是覆盖了field_flags,这样就不会进行浏览器验证。

from wtforms.validators import DataRequired


class RequiredIf(DataRequired):
    """Validator which makes a field required if another field is set and has a truthy value.

    Sources:
        - http://wtforms.simplecodes.com/docs/1.0.1/validators.html
        - http://stackoverflow.com/questions/8463209/how-to-make-a-field-conditionally-optional-in-wtforms

    """
    field_flags = ('requiredif',)

    def __init__(self, other_field_name, message=None, *args, **kwargs):
        self.other_field_name = other_field_name
        self.message = message

    def __call__(self, form, field):
        other_field = form[self.other_field_name]
        if other_field is None:
            raise Exception('no field named "%s" in form' % self.other_field_name)
        if bool(other_field.data):
            super(RequiredIf, self).__call__(form, field)

一个更理想的解决方案是能够在浏览器中进行验证,就像现在DataRequired的行为一样。

9

我觉得这个问题很有帮助,基于@dcrosta的回答,我创建了一个可选的验证器。这个验证器的好处是可以和其他wtforms的验证器一起使用。下面是我写的这个可选验证器,它会检查另一个字段的值。因为我需要把另一个字段的值和某个特定的值进行比较,所以我添加了一个自定义的值检查:

class OptionalIfFieldEqualTo(wtf.validators.Optional):
    # a validator which makes a field optional if
    # another field has a desired value

    def __init__(self, other_field_name, value, *args, **kwargs):
        self.other_field_name = other_field_name
        self.value = value
        super(OptionalIfFieldEqualTo, self).__init__(*args, **kwargs)

    def __call__(self, form, field):
        other_field = form._fields.get(self.other_field_name)
        if other_field is None:
            raise Exception('no field named "%s" in form' % self.other_field_name)
        if other_field.data == self.value:
            super(OptionalIfFieldEqualTo, self).__call__(form, field)
78

我不太确定这是否完全符合你的需求,但我之前在一些字段上使用过一个叫做 RequiredIf 的自定义验证器,这个验证器的意思是,如果另一个字段有值,那么这个字段就变成必填的。例如,在处理日期时间和时区的情况下,如果用户输入了日期时间,那么我可以让时区字段变成必填。

更新为使用 "InputRequired" 而不是 "Required"

class RequiredIf(InputRequired):
    # a validator which makes a field required if
    # another field is set and has a truthy value

    def __init__(self, other_field_name, *args, **kwargs):
        self.other_field_name = other_field_name
        super(RequiredIf, self).__init__(*args, **kwargs)

    def __call__(self, form, field):
        other_field = form._fields.get(self.other_field_name)
        if other_field is None:
            raise Exception('no field named "%s" in form' % self.other_field_name)
        if bool(other_field.data):
            super(RequiredIf, self).__call__(form, field)

这个构造函数需要传入另一个字段的名字,这个字段的值会触发使当前字段变为必填,比如:

class DateTimeForm(Form):
    datetime = TextField()
    timezone = SelectField(choices=..., validators=[RequiredIf('datetime')])

这可能是实现你所需逻辑的一个不错的起点。

撰写回答