Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validation failure in SelectField #804

Open
HK-Mattew opened this issue Oct 8, 2023 · 8 comments
Open

Validation failure in SelectField #804

HK-Mattew opened this issue Oct 8, 2023 · 8 comments

Comments

@HK-Mattew
Copy link

Even when inserting a value that is not in choices= of the SelectField, the validation error is not issued. (Even though validate_choice=True is set).

The error is in this line of code: Code Line
The else: is not being triggered and does not generate the validation error it should.

Environment

  • Python version: 3.8.18
  • wtforms version: 3.0.1
@azmeuk
Copy link
Member

azmeuk commented Oct 9, 2023

Hi. Can you describe your usecase?
Currently choices is not expected to be a dynamical list, but it can be a method that would be called when the Form object is created. Maybe you can do what you want with select = wtforms.SelectField(choices=lambda: ["foo", "bar"])?

@HK-Mattew
Copy link
Author

Hello,

See my code example:

from wtforms import Form, SelectField, validators



class MyForm(Form):
    def render_visitor_timer_choices():


        choices = [
            (1, 'One'),
            (2, 'Two'),
            (3, 'Three'),
            (4, 'Four')
        ]

        return choices

    visitor_timer = SelectField(
        label='Time in seconds',
        validators=[
            validators.Optional(),
            validators.NumberRange(
                min=1,
                max=4
            )
        ],
        choices=render_visitor_timer_choices,
        coerce=int,
        validate_choice=True,
        render_kw={
            'data-required': 'true'
        }
    )



form = MyForm(data={'visitor_timer': 5})

print(form.visitor_timer.choices) # Result: [(1, 'One'), (2, 'Two'), (3, 'Three'), (4, 'Four')]

print(form.validate()) # Result: True

print(form.visitor_timer.data) # Result: 5

"""
The response from *form.validate()* is True for the value 5 which is not in the SelectField's choices.

Shouldn't *form.validate()* be False?
"""

@azmeuk
Copy link
Member

azmeuk commented Oct 10, 2023

Sharing my investigation: the minimal form I could write to reproduce this is:

>>> from wtforms import Form, SelectField, validators
... from werkzeug.datastructures import ImmutableMultiDictMixin
...
... class MyForm(Form):
...     visitor_timer = SelectField(
...         validators=[validators.Optional()],
...         choices=[('1', 'One')],
...     )
...
... form = MyForm(data={'visitor_timer': '5'})
... print(form.validate())
True

The validation result is False as expected if the Optional validator is removed:

>>> from wtforms import Form, SelectField, validators
...
... class MyForm(Form):
...     visitor_timer = SelectField(
...         choices=[('1', 'One')],
...     )
...
... form = MyForm(data={'visitor_timer': '5'})
... print(form.validate())
False

Or if Optional is kept but the form is filled with formdata instead of data:

>>> from wtforms import Form, SelectField, validators
... from werkzeug.datastructures import ImmutableMultiDict
...
... class MyForm(Form):
...     visitor_timer = SelectField(
...         validators=[validators.Optional()],
...         choices=[('1', 'One')],
...     )
...
... form = MyForm(ImmutableMultiDict({'visitor_timer': '5'}))
... print(form.validate())
False

I think what is going on is: when the form is initialized with data instead of formdata, Optional looks for data in formdata, considers there is no data there, so it interrupts the validation chain:

https://github.com/wtforms/wtforms/blob/e205a991470f2054148734f6aa67ca49964b76ee/src/wtforms/validators.py#L249-L256

What is your real-world usecase? Do you really use form validation without passing form data, or was it just there to fill the bug report?

@HK-Mattew
Copy link
Author

Hello,

I use data= instead of formdata= because I'm using wtforms to validate data received through my api created with Flask-Restfull.

Then I receive the request data through:

data = request.get_json()
form = MyForm(data=data)

And I do validation and so on...

Based on your answer, my question now is:
If I use

form = MyForm(ImmutableMultiDict(data))

will this work correctly?

@Daverball
Copy link

Daverball commented Oct 12, 2023

@HK-Mattew For your example form this will work but it will not work for all the possible field types since formdata is inherently flat and consists of plain strings and uploaded files, while object data can be of an arbitrary type and can also be nested through use of things like FieldList and FormField. How the corresponding formdata should look like compared to the object data will depend on the specific field, but it's not a trivial conversion.

But I also don't really understand your use-case, how are you generating the JSON payload? Or is your goal to provide both a generic API interface and a renderable form from a single form definition and get shared validation regardless of input method? Something like pydantic should be better suited towards generic input validation. Generating a wtforms.Form class from a pydantic.Model should be a much easier thing to implement.

@HK-Mattew
Copy link
Author

Hi @Daverball ,

I'm generating the raunchy JSON payload from javascript. In this case I create the object. I already use Pydantic models, but I didn't think about putting them in the foreground to validate the data. But it's a good option you gave me.

@Daverball
Copy link

@HK-Mattew In that case couldn't you just submit the raw formdata, rather than try to create a JSON payload? It seems like a lot of work to juggle around the backend data representation in the frontend if ultimately you just want to validate the form input.

@HK-Mattew
Copy link
Author

@Daverball I can't send the raw data. Because I use the API to submit form data from my website and also to use the API externally (outside the browser).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

3 participants