如何在WTForms中有条件地选择字段?

时间:2011-12-11 10:08:33

标签: python google-app-engine validation wtforms

我的表单验证工作接近完成,我只有2个案例我不知道如何解决:1)当然应该要求密码字段,但我也提供了使用google或facebook帐户登录的可能性通过OAuth,然后名称被预先填写,但我从表单中完全删除密码字段是有用户(谷歌)或Facebook用户对象:

<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>

因此,对于已经登录且密码字段没有意义的用户,我需要一些逻辑来使该字段有条件地可选。我在想我可以为我的表单类中的logged_in +方法创建一个变量,例如:

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能否达到预期效果?还有另一种更好的方法吗?我能想到的另一种方法是拥有2个不同的表单类,在http post中我将表单类设置为应该是:

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

我还需要对类别字段进行条件验证,当选择某个类别时,会出现更多选项,这些选项应仅针对某个基类进行验证,例如。用户选择“汽车”,然后通过Ajax可以选择汽车的登记数据和里程数,这些字段是必需的,因为选择了汽车类别。

所以这可能是两个问题,但两个案例都涉及我如何使一个字段“有条件可选”或“有条件地要求”。

我的表单看起来像这样

enter image description here

对于已登录的用户,我预先填写姓名和电子邮件地址,并且简单地不使用密码字段,因此密码字段既不适合“可选”也不“必需”,它需要“条件可选”或“有条件地要求。”

enter image description here

感谢您的回答或评论

3 个答案:

答案 0 :(得分:63)

我不确定这完全符合您的需求,但我之前在字段上使用了RequiredIf自定义验证器,如果另一个字段在表单中有值,则需要字段...例如,在日期时区方案中,如果用户输入了日期时间,我可以使时区字段具有值。

class RequiredIf(Required):
    # 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')])

这可能是实现所需逻辑的良好起点。

答案 1 :(得分:5)

我发现这个问题很有帮助,根据@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)

答案 2 :(得分:3)

@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的当前行为。