I have a WTForm and I am trying to validate a custom multi-checkbox field,
class MultiCheckboxField(SelectMultipleField): widget = widgets.ListWidget(html_tag='ol', prefix_label=False) option_widget = widgets.CheckboxInput() class MultiCheckboxAtLeastOne(): def __init__(self, message=None): if not message: message = 'At least one option must be selected.' self.message = message self.field_flags = {"required": True} # Requires each checkbox to be true, even though that's not what __call__ checks for def __call__(self, form, field): if not field.data: raise StopValidation(self.message) class RegistrationForm(FlaskForm): language = MultiCheckboxField('Language', choices=get_languages(), validators=[MultiCheckboxAtLeastOne()])
When I set the required
flag to true, every checkbox needs to be checked. I just need at least one checkbox to be selected.
When I don’t set the required
flag, the native, and very elusive (is doesn’t appear in js, html, or css via developer tools), validation modal doesn’t appear, and ‘manually’ displaying errors is required with,
{% for error in form.language.errors %} <span>{{ error }}</span> {% endfor %}
How do I validate this field properly? I.e. just check if at least one box is true, if not, show the native modal. I also receive '1', '2', '3', '5', '4' are not valid choices for this field.
if I check all the boxes and submit the form. Why is that?
This is the “native modal” I mention.
Advertisement
Answer
You can’t control the native validation modal/popup using WTForms. You’ll have to use JavaScript, see here.
Should you want to avoid using JS, validation of the multiple select checkbox list with WTForms can be done using
from flask_wtf import FlaskForm from wtforms.fields import SelectMultipleField from wtforms.validators import StopValidation from wtforms import widgets def get_languages(): return [(1, 'English'), (2, 'French'), (3, 'Spanish'), (4, 'Mandarin'), (5, 'German'), (6, 'Other')] class MultiCheckboxField(SelectMultipleField): widget = widgets.ListWidget(html_tag='ol', prefix_label=False) option_widget = widgets.CheckboxInput() class MultiCheckboxAtLeastOne(): def __init__(self, message=None): if not message: message = 'At least one option must be selected.' self.message = message def __call__(self, form, field): if len(field.data) == 0: raise StopValidation(self.message) class RegistrationForm(FlaskForm): language = MultiCheckboxField('Language', choices=get_languages(), validators=[MultiCheckboxAtLeastOne()], coerce=int)
Notice the coerce=int
. This will cast the numeric strings into integers making the field’s data compatible with get_languages()
.
Remove the native validation by adding the attribute novalidate
to your html element.
You can skip writing the error conditions for each field by looping through the form fields,
<form method="POST" action="/post" novalidate> {% for field_name, value in form.data.items() %} {% if field_name != 'csrf_token' %} {{ form[field_name].label }} {% for error in form[field_name].errors %} {{ error }} {% endfor %} {{ form[field_name](size=40) }} {% endif %} {% endfor %} {{ form.csrf_token }} <input type="submit" value="Submit"> </form>