Flask-WTForms RadioField custom validator message doesn't work
<p>In <code>Flask-WTForms</code>, we can give a custom message for each validator for each field. But for <code>RadioField</code> it shows the default message only. Below is an example.</p>

<pre><code>&gt;&gt;&gt; from wtforms import Form, RadioField, TextField
&gt;&gt;&gt; from wtforms.validators import *
</code></pre>

<p><strong>TextField</strong></p>

<pre><code>&gt;&gt;&gt; class MyForm(Form):
        x = TextField(u'Some text', validators = [Required(message="Hello")])
</code></pre>

<p><strong>Error Message</strong></p>

<pre><code>&gt;&gt;&gt; form = MyForm()
&gt;&gt;&gt; form.x.data
&gt;&gt;&gt; form.validate()
False
&gt;&gt;&gt; form.errors
{'x': ['Hello']}
</code></pre>

<p>So for a <code>TextField</code> it shows the custom error message.</p>

<p><strong>RadioField</strong></p>

<pre><code>&gt;&gt;&gt; class MyForm(Form):
        x = RadioField(choices = [(1, '1'), (2, '2')], validators = [Required(message="Hello")])
</code></pre>

<p><strong>Error Message</strong></p>

<pre><code>&gt;&gt;&gt; form = MyForm()
&gt;&gt;&gt; form.x.data
u'None'
&gt;&gt;&gt; form.validate()
False
&gt;&gt;&gt; form.errors
{'x': [u'Not a valid choice']}
</code></pre>

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

<p>So my Question is How to show a custom message for the validation of a <code>RadioField</code>?</p>

<p>You are right about that the process is different.</p>

<p>So if you go do <a href="https://github.com/wtforms/wtforms/" rel="nofollow">source code</a> there is base <a href="https://github.com/wtforms/wtforms/blob/master/wtforms/fields/core.py#L23" rel="nofollow"><code>Field</code></a> class with <a href="https://github.com/wtforms/wtforms/blob/master/wtforms/fields/core.py#L178" rel="nofollow"><code>validate</code></a> method. It's said</p>

<blockquote>
<pre><code>"""
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.&gt; 

:param form: The form the field belongs to.
:param extra_validators: A sequence of extra validators to run.
"""
</code></pre>
</blockquote>

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

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

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

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

<p><em>Note</em>: <code>Required</code> validator is depracated and will be removed in WTForms 3.0, and in <a href="https://github.com/wtforms/wtforms/pull/282" rel="nofollow">that</a> pull request they already removed it's usage. Instead of <code>Required()</code> validator, use <code>DataRequired()</code></p>

<p><strong>UPDATE</strong>: Since you add your validator to field you still should be able to get your error. Because since <a href="https://github.com/wtforms/wtforms/blob/master/wtforms/fields/core.py#L470-L475" rel="nofollow"><code>pre_validate()</code></a> raises only <code>ValueError</code> it doesn't stop <a href="https://github.com/wtforms/wtforms/blob/master/wtforms/fields/core.py#L178-L214" rel="nofollow"><code>validate()</code></a></p>

<blockquote>
<pre><code># 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])
</code></pre>
</blockquote>

<p>And then it goes to </p>

<blockquote>
<pre><code># Run validators
if not stop_validation:
    chain = itertools.chain(self.validators, extra_validators)
    stop_validation = self._run_validation_chain(form, chain)
</code></pre>
</blockquote>

<p>And here is your validator exists and should add new error to the list of errors(<code>form.x.error</code>), however it converts <code>None</code> to <code>'None'</code>, so your <code>form.x.data</code> becomes <code>'None'</code>(<code>str</code> type), and now it goes to <a href="https://github.com/wtforms/wtforms/blob/master/wtforms/validators.py#L200-L208" rel="nofollow"><code>__call__</code></a></p>

<blockquote>
<pre><code>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)
</code></pre>
</blockquote>

<p>And the condition <code>not field.data</code> is <code>False</code>, because <code>field.data</code> is <code>'None'</code>. 
Why data convert from <code>None</code> to <code>'None'</code> for <code>SelectField</code> and its related ones is explained in <a href="https://github.com/wtforms/wtforms/issues/289" rel="nofollow">Issue on GitHub</a> and probably be fixed when <a href="https://github.com/wtforms/wtforms/pull/288" rel="nofollow">Pull Request</a> will be merged in master.</p>

python flask flask-wtforms