Search code examples
flaskflask-wtformswtforms

Dynamically Add or Remove WTForms validators in Flask


Is there a way to do this?

Below is an example of what I'm trying to do:

class TestForm(FlaskForm):
  email = EmailField('Email', validators=[InputRequired('Email is required.')])
  start = SubmitField()

Then in a route:

del form.email.validators
# I also tried:
form.email.validators.remove

Basically I want to use stored data to determine if the field should be required or not for a predefined form.


Solution

  • Dynamically create an internal subclasses of the form within your view. Remove any validators from the fields of the internal subclass and then instance a form from the internal subclass. In code, something like:

    Define a form, the first_name field has two validators.

    class TestForm(FlaskForm):
        first_name = StringField(validators=[InputRequired(), Length(8)])
        submit = SubmitField()
    

    In your view:

    # Dynamic subclass
    class F(TestForm):
        pass
    
    # Remove the length validator.
    # Validators are in a dict called kwargs
    validators = F.first_name.kwargs.get('validators')
    for validator in validators:
        if isinstance(validator, Length):
            validators.remove(validator)
    
    # instance a form
    _form = F()
    # use the form ....
    

    Single file example app.py:

    from flask import Flask, render_template_string
    from flask_wtf import FlaskForm
    from wtforms import StringField, SubmitField
    from wtforms.validators import InputRequired, Length
    
    app = Flask(__name__)
    app.config['SECRET_KEY'] = '13332311ecd738748f27a992b6189d3f5f30852345a1d5261e3e9d5a96722fb9'
    
    
    class TestForm(FlaskForm):
        first_name = StringField(validators=[InputRequired(), Length(8)])
        submit = SubmitField()
    
    
    html_template = '''
        {% if form.first_name.errors %}
            <ul class="errors">
            {% for error in form.first_name.errors %}
                <li>{{ error }}</li>
            {% endfor %}
            </ul>
        {% endif %}
        <form role="form" method="post" action="" name="Form1">
            {{ form.hidden_tag() }}
            {{ form.first_name }}
            {{ form.submit() }}
        </form>      
    '''
    
    success_template = '''
        <h1>Success</h1>      
    '''
    
    
    @app.route('/', methods=['GET', 'POST'])
    def index():
    
        # Dynamic subclass
        class F(TestForm):
            pass
    
        # Remove the length validator.
        # Validators are in a dict called kwargs
        validators = F.first_name.kwargs.get('validators')
        for validator in validators:
            if isinstance(validator, Length):
                validators.remove(validator)
    
        # instance a form
        _form = F()
        # print the validators
        for field in _form:
            print(f"Field: {field.name} has {len(field.validators)} validator(s)")
            for validator in field.validators:
                print(validator)
    
        if _form.validate_on_submit():
            print('Validation passed')
            return render_template_string(success_template)
    
        return render_template_string(html_template, form=_form)
    
    
    if __name__ == '__main__':
        app.run()