WTForms: Disable client-side validation on cancel
Asked Answered
A

4

5

What I'm asking for is actually quite simple. I want to create a form with some fields and a submit and a cancel button. I want to use the quick_form template function of Flask-Bootstrap to keep overhead in my template low. My form looks like this:

from flask_wtf import FlaskForm
from wtforms.validators import Required, Length


class SequenceForm(FlaskForm):
    name = StringField('Name:', validators=[Required(), Length(1, 128)])

    # some other fields here

    submit = SubmitField('submit')
    cancel = SubmitField('cancel')

The template:

{% extends 'base.html' %}
{% import 'bootstrap/wtf.html' as wtf %}

{% block content %}
  <div class="container">
    <form method="POST">
      <div class="row">
        <div class="col-xs-12">
          {{ wtf.quick_form(form, button_map={'submit': 'primary'}) }}
        </div>
      </div>
    </form>
  </div>
{% endblock %}

As one would suspect I want to validate and accept the input values on submit and skip validation on cancel. So my view function looks as expected.

@main.route('sequence/', methods=['GET', 'POST'])
def sequence():
    form = SequenceForm()
    if request.method == 'POST':
        if 'submit' in request.form:
            if form.validate_on_submit():
                print(form.duration.data)
        else:
            return redirect(url_for('main.index'))
    return render_template('sequence.html', form=form)

Now if cancel is pressed there should logically be no validation and the redirect should take place. However I run into the issue that my view function doesn't even get called if I press either submit or cancel due to the client-side validation.

<input class="form-control" id="name" name="name" required="" type="text" value="">

Is there a way to disable client-side validation on WTForms?

Autonomic answered 23/12, 2016 at 11:30 Comment(0)
A
4

The Required validator as well as the DataRequired and InputRequired which replace Required since version 3 of WTForms set the replace flag of the field. This flag is used to add the required attribute to the HTML representation of the field. My workaround is to manually create a validate function.

from wtforms.validators import ValidationError

def _required(form, field):
    if not field.raw_data or not field.raw_data[0]:
        raise ValidationError('Field is required')

class SequenceForm(FlaskForm):
    name = StringField('Name:', validators=[_required, Length(1, 128)])

    # some other fields here

    submit = SubmitField('submit')
    cancel = SubmitField('cancel')

This way there is no validation on the client-side and it is ensured that the view function is called on every submit or cancel.

Note

An even simpler solution is to subclass the InputRequired validator and overwrite the field_flags dictionary.

from wtforms.validators import InputRequired

class MyInputRequired(InputRequired):
    field_flags = ()

class SequenceForm(FlaskForm):
    name = StringField('Name:', validators=[MyInputRequired(), Length(1, 128)])
Autonomic answered 23/12, 2016 at 12:29 Comment(4)
You could simply reset the tuple of flags to be set: class MyInputRequired(wtforms.validators.InputRequired): next line: field_flags = () (can't get the formatting right....)Indenture
@Indenture That is a great solution. If you want to create an answer for it I would accept it.Autonomic
I think it is very similar to your solution. And you came first with the idea.Indenture
Actually it isn' t because validation is transfered from client-side to the view funciton. This way one can skip validation for "cancel".Autonomic
P
10

Since you use Flask-Bootstrap's quick_form() macro, you can just set novalidate parameter to True to disable client-side validation (it will set a novalidate attribute to your HTML <form> element):

{{ wtf.quick_form(form, novalidate=True) }}

If you are using Bootstrap-Flask, the method is similar:

{{ render_form(form, novalidate=True) }}
Perpetuate answered 12/4, 2020 at 3:49 Comment(0)
A
4

The Required validator as well as the DataRequired and InputRequired which replace Required since version 3 of WTForms set the replace flag of the field. This flag is used to add the required attribute to the HTML representation of the field. My workaround is to manually create a validate function.

from wtforms.validators import ValidationError

def _required(form, field):
    if not field.raw_data or not field.raw_data[0]:
        raise ValidationError('Field is required')

class SequenceForm(FlaskForm):
    name = StringField('Name:', validators=[_required, Length(1, 128)])

    # some other fields here

    submit = SubmitField('submit')
    cancel = SubmitField('cancel')

This way there is no validation on the client-side and it is ensured that the view function is called on every submit or cancel.

Note

An even simpler solution is to subclass the InputRequired validator and overwrite the field_flags dictionary.

from wtforms.validators import InputRequired

class MyInputRequired(InputRequired):
    field_flags = ()

class SequenceForm(FlaskForm):
    name = StringField('Name:', validators=[MyInputRequired(), Length(1, 128)])
Autonomic answered 23/12, 2016 at 12:29 Comment(4)
You could simply reset the tuple of flags to be set: class MyInputRequired(wtforms.validators.InputRequired): next line: field_flags = () (can't get the formatting right....)Indenture
@Indenture That is a great solution. If you want to create an answer for it I would accept it.Autonomic
I think it is very similar to your solution. And you came first with the idea.Indenture
Actually it isn' t because validation is transfered from client-side to the view funciton. This way one can skip validation for "cancel".Autonomic
I
1

You could forbid rendering of the required attr.

class MyTextInput(wtforms.widgets.TextInput):
    def __call__(self, field, **kwargs):
        kwargs['required'] = False
        return super().__call__(field, **kwargs)

For Python2 add args like this:super(MyTextInput, self)

and then:

name = StringField('Name:', validators=[Required(), Length(1, 128)], widget=MyTextInput())
Indenture answered 24/12, 2016 at 9:14 Comment(2)
This is a good solution for a specific field type. However if I got multiple field types in my form I need to create a separate widget for each of them.Autonomic
@Autonomic Yes, that's true. You just have to modify one of the items in the chain (validator, flags, widget, quick_form)Indenture
C
1

To disable client-side form validation, add the 'novalidate' attribute to the HTML <form> element in your template:

<form method="POST" novalidate>

Calcaneus answered 23/11, 2018 at 1:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.