web-dev-qa-db-ja.com

WTFormsでフィールドを条件付きでオプションにする方法は?

フォームの検証はほぼ完了しています。解決方法が正確にわからない場合が2つあります。1)もちろんパスワードフィールドは必須ですが、OAuthすると、名前が事前に入力されますが、ユーザー(google)または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を介して車の登録データと走行距離を選択できます。これらのフィールドは、車のカテゴリが選択されている場合に必要です。

したがって、2つの質問になる可能性がありますが、どちらの場合も、フィールドを「条件付きでオプション」または「条件付きで必須」にする方法に関連しています。

私のフォームは次のようになります

enter image description here

また、ログインしたユーザーの場合、名前とメールアドレスを事前に入力しますが、パスワードフィールドは使用されないため、パスワードフィールドは「オプション」でも「必須」でもありません。「条件付きでオプション」や「条件付きで必須」などが必要になります。 。」

enter image description here

回答やコメントをありがとう

27
Niklas

これがあなたのニーズに完全に適合するかどうかはわかりませんが、以前はフィールドに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')])

これは、必要な種類のロジックを実装するための良い出発点になる可能性があります。

66
dcrosta

この質問は役に立ち、@ 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)
5
Mehdi Sadeghi

@dcrostaからの回答は素晴らしいですが、この回答以降、wtformsでいくつかの変更があったと思います。 DataRequiredから継承すると、フォームフィールドにrequired属性が追加されるため、条件付きバリデーターが呼び出されることはありません。 wtforms2.1で動作する@dcrostaからクラスに小さな変更を加えました。これは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の現在の動作のように、ブラウザで検証を行うことができます。

3
dennisobrien