Question

I want to create a form with WTForms where a field is a SelectField with a special (custom) validation that (for example) checks something else if the current value is not valid (i.e. it is not an instance of the defined options).

I read in the documentation that the right way to do it is to override the functions pre_validate or post_validate according with the needs.

So my approach is the following:

1 - I create a new field called MySelectForm:

class MySelectField(SelectField):
    def post_validate(self, form, validation_stopped):
        """overrides post validation"""
        #Here is my custom validation

2- I use this field instead of the original one in my form

What I cannot understand is: if, like I read in the docs, post_validate takes in input "The form the field belongs to", how do I access the current value of the field that is an instance of MySelectField?

In other words, can someone make an example of how to override pre_validate or post_validate?

Was it helpful?

Solution

You are passing in self to the method and so you have your field object available. It inherits from WTForms' Field class and so you should have field.data available. This contains the "resulting (sanitized) value of calling either of the process methods."

OTHER TIPS

below is part of source code

# wtforms.fields.core.py  |  version: Flask-WTF 0.14.3

class Field(object):
    errors = tuple()
    ...

    def validate(self, form, extra_validators=tuple()):
        self.errors = list(self.process_errors)
        stop_validation = False

        self.check_validators(extra_validators)

        try:
            self.pre_validate(form)   # <---------- focus here
        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])

        # Run validators
        if not stop_validation:
            chain = itertools.chain(self.validators, extra_validators)
            stop_validation = self._run_validation_chain(form, chain)  # <-- It will run ``InputRequired, YourValidators1, YourValidators2, validate_username``  (see the example code)

        # Call post_validate
        try:
            self.post_validate(form, stop_validation)  # <---------- focus here
        except ValueError as e:
            self.errors.append(e.args[0])

        return len(self.errors) == 0

and I give you an example of post_validate (the pre_validate is very similar, and I believe you can do it by yourself)

from flask_wtf import FlaskForm, Form
from wtforms import StringField, ValidationError, StopValidation
from wtforms.validators import InputRequired
from typing import List


class StateStringField(StringField):
    ok_msg_list: List = None  # Because class ``wtforms.fields.core.Field`` are not existed any attributes I like, so I create one.

    def post_validate(self, form: Form, stop_validation: bool):
        if self.ok_msg_list is None:
            self.ok_msg_list = []
        if stop_validation:
            return
        if not 'errors':  # assume no errors...
            raise ValueError('...')
        else:
            self.ok_msg_list.append('OK')


class UserForm(FlaskForm):
    username = StateStringField('User',
                                validators=[InputRequired(),
                                            # YourValidators2, YourValidators3
                                            ])

    def validate_username(self, field: StringField):
        if len(field.data) > 50:
            raise StopValidation('Name must be less than 50 characters')
<!-- templates/auth/register.html-->
<form method="post">   
    {{ form.hidden_tag() }}
    <p>{{ form.username.label }} {{ form.username()|safe }}
      {% if form.username.errors %}
        {% for error in form.username.errors %}
          <p style="color:red;">{{ error }}<p>
        {% endfor %}
      {% endif %}

      {% if form.username['ok_msg_list'] %}  <!-- similar as python: if hasattr('ok_msg_list') -->
        {% for ok_msg in form.username.ok_msg_list %}
          <span style="color:blue;">{{ ok_msg|safe }}</span>
        {% endfor %}
      {% endif %}
    </p>
</form>

Summary

You can use post_validate when you want to perform another validation after all the validations have been performed. (Personally, I think post_validate is similar to finally.

In my example, when the user verifies that all the validation is correct, an OK message appears next to it.

enter image description here

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top