Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flask-WTForms RadioField custom validator message doesn't work

In Flask-WTForms, we can give a custom message for each validator for each field. But for RadioField it shows the default message only. Below is an example.

>>> from wtforms import Form, RadioField, TextField
>>> from wtforms.validators import *

TextField

>>> class MyForm(Form):
        x = TextField(u'Some text', validators = [Required(message="Hello")])

Error Message

>>> form = MyForm()
>>> form.x.data
>>> form.validate()
False
>>> form.errors
{'x': ['Hello']}

So for a TextField it shows the custom error message.

RadioField

>>> class MyForm(Form):
        x = RadioField(choices = [(1, '1'), (2, '2')], validators = [Required(message="Hello")])

Error Message

>>> form = MyForm()
>>> form.x.data
u'None'
>>> form.validate()
False
>>> form.errors
{'x': [u'Not a valid choice']}

The custom error message is not there. I guess, validation for TextField and RadioField will be different process and may be that's why it's showing the default message.

So my Question is How to show a custom message for the validation of a RadioField?

like image 926
RatDon Avatar asked Sep 26 '16 07:09

RatDon


1 Answers

You are right about that the process is different.

So if you go do source code there is base Field class with validate method. It's said

"""
Validates the field and returns True or False. `self.errors` will
contain any errors raised during validation. This is usually only
called by `Form.validate`.

Subfields shouldn't override this, but rather override either
`pre_validate`, `post_validate` or both, depending on needs.> 

:param form: The form the field belongs to.
:param extra_validators: A sequence of extra validators to run.
"""

And the procedure of validation is pre_validate() -> validate() -> post_validate()(Call pre_validate -> Run validators -> Call post_validate)

As you can guess, RadioField has it's own pre_validate() method, but basically SelectField's one. And then when RadioField is inherit from SelectField it has it too.

def pre_validate(self, form):
    for v, _ in self.choices:
        if self.data == v:
            break
    else:
        raise ValueError(self.gettext('Not a valid choice'))

So that's why you get 'Not a valid choice' error instead of your custom one, on wtforms.validators.Required() validator, because it just didn't go through the pre_validate() and it stops.

Note: Required validator is depracated and will be removed in WTForms 3.0, and in that pull request they already removed it's usage. Instead of Required() validator, use DataRequired()

UPDATE: Since you add your validator to field you still should be able to get your error. Because since pre_validate() raises only ValueError it doesn't stop validate()

# Call pre_validate
try:
    self.pre_validate(form)
except StopValidation as e:
    if e.args and e.args[0]:
        self.errors.append(e.args[0])
    stop_validation = True
except ValueError as e:
    self.errors.append(e.args[0])

And then it goes to

# Run validators
if not stop_validation:
    chain = itertools.chain(self.validators, extra_validators)
    stop_validation = self._run_validation_chain(form, chain)

And here is your validator exists and should add new error to the list of errors(form.x.error), however it converts None to 'None', so your form.x.data becomes 'None'(str type), and now it goes to __call__

def __call__(self, form, field):
    if not field.data or isinstance(field.data, string_types) and not field.data.strip():
        if self.message is None:
            message = field.gettext('This field is required.')
        else:
            message = self.message

        field.errors[:] = []
        raise StopValidation(message)

And the condition not field.data is False, because field.data is 'None'. Why data convert from None to 'None' for SelectField and its related ones is explained in Issue on GitHub and probably be fixed when Pull Request will be merged in master.

like image 163
vishes_shell Avatar answered Sep 25 '22 17:09

vishes_shell