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."
How to override pre_validate and post_validate in wtforms?
-
23-06-2022 - |
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
?
Solution
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.