Skip to content
Advertisement

MultiCheckbox Field Validation with Flask WTForms

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.

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>
6 People found this is helpful
Advertisement