如何在WTForms中使字段条件性可选?
我的表单验证几乎完成了,但还有两个情况我不太清楚该怎么处理: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 个回答
来自@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
的行为一样。
我觉得这个问题很有帮助,基于@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)
我不太确定这是否完全符合你的需求,但我之前在一些字段上使用过一个叫做 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')])
这可能是实现你所需逻辑的一个不错的起点。