How do I validate wtforms fields against one another?
Asked Answered
M

4

27

I have three identical SelectField inputs in a form, each with the same set of options. I can't use one multiple select.

I want to make sure that the user selects three different choices for these three fields.

In custom validation, it appears that you can only reference one field at a time, not compare the value of this field to others. How can I do that? Thanks!

Morehead answered 16/2, 2014 at 17:52 Comment(0)
R
35

You can override validate in your Form...

class MyForm(Form):
    select1 = SelectField('Select 1', ...)
    select2 = SelectField('Select 2', ...)
    select3 = SelectField('Select 3', ...)
    def validate(self):
        if not Form.validate(self):
            return False
        result = True
        seen = set()
        for field in [self.select1, self.select2, self.select3]:
            if field.data in seen:
                field.errors.append('Please select three distinct choices.')
                result = False
            else:
                seen.add(field.data)
        return result
Remodel answered 16/2, 2014 at 18:0 Comment(5)
Thanks - why the line if not Form.validate(self):? That keeps firing when I try your suggestion and the validation doesn't run.Morehead
That's to do the default validation (as if you hadn't overridden validate)Remodel
The other steps are to perform validation across multiple fields, as you asked.Remodel
I just want to add, that I was confused by overriding here. Referring to O'Reilly Learning Python, I would call this extending the validate method. As you're still invoking the default behavior when you call if not Form.validate(self) And then the custom validation code runs. When I read that, it cleared things up for me. Still @Remodel great answerLouise
If you're on Python 3, better to write super().validate() than Form.validate(self). If you're on Python 2, use super(MyForm, self).validate().Schnapp
G
8

You can use the form in your validation to get the value of other fields.

For example:

def validate_name(form, field):
    if form.other_variable.data == 'checked' and len(field.data) > 10:
        raise validation_error("say somgthing")
Galicia answered 3/7, 2021 at 3:43 Comment(0)
G
3

I wrote a small python library required to make cross-field validation like this easier. You can encode your validation logic declaratively as pairwise dependencies. So your form may look like:

from required import R, Requires, RequirementError

class MyForm(Form):

    VALIDATION = (
        Requires("select1", R("select1") != R("select2") +
        Requires("select2", R("select2") != R("select3") +
        Requires("select3", R("select3") != R("select1")
    )

    select1 = SelectField('Select 1', ...)
    select2 = SelectField('Select 2', ...)
    select3 = SelectField('Select 3', ...)

    def validate(self):
        data = {
            "select1": self.select1.data,
            "select2": self.select2.data,
            "select3": self.select3.data,
        }

        # you can catch the RequirementError
        # and append the error message to 
        # the form errors

        self.VALIDATION.validate(data)
        return result

You can take the VALIDATION object and append more validation rules or even put it in a separate module and import / reuse validation rules in different places.

Grafting answered 2/2, 2018 at 17:10 Comment(0)
G
0

Use FieldList like this:

def field_level(form, field):
    all_values = form.selects.data
    value = field.data

def list_level(form, field):
    all_values = field.data

class MyForm(Form):
    selects = FieldList(SelectField('label', validators=[field_level]), validators=[list_level])

form = MyForm()
form.append_entry()
form.append_entry()
form.append_entry()

You can have validators on Field level AND on FieldList level

Gisellegish answered 18/1, 2019 at 2:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.