Custom validation for WTForm fileds in Flask - python

I have used the built-in validators, but none of them is printing a message on the page. Also, I want to create a custom validator to check duplicate username. I have written the function, as I am a beginner, I don't know how to use it. Pls resolve the problem.
from flask import Flask, app, render_template, request, url_for
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, PasswordField
from wtforms.validators import InputRequired, EqualTo, ValidationError
app = Flask(__name__)
app.config['SECRET_KEY'] = "PQrs12t46uvvrty567"
class MyForm(FlaskForm):
username = StringField('Username', validators=[InputRequired(message="This field is required.")])
password=PasswordField('Password', validators=[InputRequired(message=("enter the password"))])
confirm_password=PasswordField('Confirm Password', validators=[EqualTo('password')])
submit = SubmitField('Register')
def isDuplicate():
appdatabase={"Alis":"Smith","Mike":"Brown","Paul":"Miller"}
form = MyForm()
for user in appdatabase:
if form.username == appdatabase[user]:
raise ValidationError("Username already exists! Please choose another one.")
#app.route('/')
def base():
form = MyForm()
return render_template('customvalidator.html', form = form)
#app.route('/submitform', methods=["GET","POST"])
def submitform():
form = MyForm()
if form.validate_on_submit():
return 'Form accepted successfully.'
else:
return 'Incorrect form data'
if __name__=="__main__":
app.run(debug=True)
HTML file
<!DOCTYPE html>
<html>
<head><title>My website</title></head>
<body>
<h1>Registration form</h1>
<form action="{{ url_for('submitform') }}" method="post">
{{ form.csrf_token }}
{{ form.username.label }}
{{ form.username }}
<ul>
{% for error in form.username.errors %}
<li style="color: red;">{{ error }} </li>
{% endfor %}
</ul>
<br>
{{ form.password.label }}
{{ form.password }} <br><br>
{{ form.confirm_password.label }}
{{ form.confirm_password }} <br><br>
{{ form.submit}}
</form>
</body>
</html>

Try changing the isDuplicate function to this:
def isDuplicate(form, field):
appdatabase={"Alis":"Smith","Mike":"Brown","Paul":"Miller"}
form = MyForm()
for user in appdatabase:
if form.username.data == appdatabase[user]:
raise ValidationError("Username already exists! Please choose another one.")
Notice the added form and field parameters to the function to allow its use as a custom validator. form.username was also changed to form.username.data to access the data of the username field. (This whole function will need to be defined before the form.)
You will then need to add this custom validator to your list of validators for the username field in your form like so:
username = StringField('Username', validators=[InputRequired(message="This field is required."),isDuplicate])
With these changes, the custom validation should work, but we still haven't solved the issue of the custom error messages. To do this, add a novalidate attribute to your HTML form tag. Adding this will disable the default validation on forms and allow you to display custom messages.
With that in place, I believe the messages still won't work because of how you are handling the form submission. When the form does not validate, you will want to display the same form template instead of showing them whether it submitted or not.
My personal opinion is that merging your two form routes will be the simplest option. Have a route define the form, check for when it gets validated or not, and then render the form template. This will enable you to keep everything together and not render a different template when you only want to display the same template with a few added error messages.
Credits:
Custom validators (check the last point)
SO question on displaying validation messages
-which led me to this one on actually how to disable the default messages.

Related

Flask-WTF forcing to fill all the fields even tho they're not rendered in the template

I'm trying to send the data to the current URL with post method, but "FinalForm" is failing validation because not all the fields are filled when I've only rendered two fields into the template from the form. Is there any way to overcome this?
#...
class MotherboardForm(FlaskForm):
chipset_model = StringField("ჩიპსეტი", validators=[DataRequired()])
socket_type = StringField("სოკეტი", validators=[DataRequired()])
ram_technology = StringField("მეხსიერების ტიპი", validators=[DataRequired()])
class RamForm(FlaskForm):
frequency = StringField("სიხშირე", validators=[DataRequired()])
size = StringField("მეხსიერების მოცულობა", validators=[DataRequired()])
class FinalForm(ItemForm, BasicsForm, CpuForm, GpuForm, MotherboardForm, RamForm):
submit = SubmitField("done")
In html I'm only rendering these fields
<form action="" method="post" novalidate>
{{ form.hidden_tag() }}
{{ form.frequency.label }}
{{ form.frequency() }}
{{ form.size.label }}
{{ form.size() }}
{{ form.submit() }}
</form>
This is the view function:
#app.route("/list_item", methods=["GET", "POST"])
#login_required
def list_item():
item = FinalForm()
if item.validate_on_submit():
return redirect(url_for("list_cpu"))
return render_template("add_item.html", form=item)
Help would be appreciated
If a field is set with validator DataRequired, it will always asks for input whether the field is rendered or not. The option would be to remove DataRequired validator or if the error still arise, use Optional validator.
Or you could fill the formdata with dummy value before validating it.

Flask WTForm not validating, no form errors reported

I'm trying to setup a flask webapp with some basic login/register system controlled by flask-WTF forms.
Here is my code:
html
<!-- Register form -->
<div class="form">
<div class="form-area">
<h2>Register</h2>
<form action="{{ url_for('register') }}">
{{ form.csrf_token() }}
{{ form.hidden_tag() }}
{{ form.name(placeholder='name') }}
{{ form.surname(placeholder='surname') }}
{{ form.email(placeholder='email') }}
{{ form.password(placeholder='password') }}
{{ form.confirm_password(placeholder='confirm password') }}
<input type="submit" value="Register">
</form>
<p>Already registered? Log in here</p>
</div>
<div class="error-area">
{% for error in form.confirm_password.errors %}
<p>{{ error }}</p>
{% endfor %}
</div>
</div>
class
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField
from wtforms.validators import InputRequired, Length, EqualTo
class RegisterForm(FlaskForm):
name = StringField('name', validators=[InputRequired()])
surname = StringField('surname', validators=[InputRequired()])
email = StringField('email', validators=[InputRequired()])
password = PasswordField('password', validators=[InputRequired(), Length(min=6)])
confirm_password = PasswordField('confirm passord', validators=[InputRequired(), Length(min=6), EqualTo(password)])
flask
#app.route('/register')
def register():
#declare a register form
form = RegisterForm()
#validate form
if form.validate_on_submit():
print('validate')
return '<h1>Success</h1>'
else:
print('not validated')
print(form.errors)
return render_template('register.html', form=form)
The problem with my code is that validation seems not to be working. Even if I fill the form with the "valid" input, form.validate_on_submit() always fail.
What I can't understand is that even when I try to print array errors, no error shows.
What am I missing?
There are a few issues here. Firstly, in your html, you haven't set a method attribute for the form. This means that it defaults to GET, which is why the form isn't validating. This can be changed like so:
<form action="{{ url_for('register') }}" method='POST'>
Incidentally, as the view that loads the form is the same as the target, you can leave out the action attribute, giving us:
<form method='POST'>
Secondly, in your class, you have a couple of issues with the confirm_password field. Firstly, you have a typo in PasswordField('confirm passord'. Secondly, the EqualTo() validator expects a string, not a field. We need to change this line in full to:
confirm_password = PasswordField('confirm password', validators=[InputRequired(), Length(min=6), EqualTo('password')])
Finally, in your flask, we need to accept POST requests to the view. This can be done by altering #app.route():
#app.route('/register', methods=['POST', 'GET'])
Having made these changes, the form should work as expected.

WTForms: Disable client-side validation on cancel

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?
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) }}
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)])
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())
To disable client-side form validation, add the 'novalidate' attribute to the HTML <form> element in your template:
<form method="POST" novalidate>

Flask app submit target="_blank" form only after wtforms validation

In my Flask app I have a form generated with wtforms and jinja templates. If validation passes I want to redirect in a new tab, else I want to stay on the same page and display the errors. However if I set target="_blank" in my form, it opens a new tab without validation passing and shows the errors there. Removing target="_blank" will not open a new tab. Is there a way of achieving this without rewriting the whole validation in js? Thanks!
Code:
from wtforms import Form, TextAreaField, StringField, validators
class SubmitForm(Form):
field1 = StringField(u'field1', [validators.DataRequired()])
field2 = TextAreaField(u'field2', [validators.DataRequired()])
#app.route('/', methods=['POST'])
def sub():
form = SubmitForm(request.form)
if request.method == 'POST' and form.validate():
# great success
return redirect('/my_redirect_uri')
return render_template('index.html', form=form)
#app.route('/')
def layout():
return render_template('index.html', form=SubmitForm())
index.html:
{% from "_formhelpers.html" import render_field %}
<form method=post action="/" target="_blank">
<dl>
{{ render_field(form.field1) }}
{{ render_field(form.field2) }}
</dl>
<p><input type=submit value=Submit>
</form>
_formhelpers.html(not that relevant but for the sake of completness):
{% macro render_field(field) %}
<dt>{{ field.label }}
<dd>{{ field(**kwargs)|safe }}
{% if field.errors %}
<ul class=errors>
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</dd>
{% endmacro %}
Main issue
You must pass the form object as well as the variables representing your form fields namely field1 and field2.
Details
What is being stated above means that you need to change:
return redirect('/my_redirect_uri')
to
return redirect('/my_redirect_uri', form=form, field1=field1, field2=field2)
This also means that you have to adjustments to your view method:
#app.route('/', methods=['POST'])
def sub():
form = SubmitForm(request.form)
if request.method == 'POST' and form.validate():
# great success
field1 = form.field1.data
field2 = form.field2.data
return redirect('/my_redirect_uri', form=form, field1=field1, field2=field2)
return render_template('index.html', form=form)
Now there are some improvements to your program:
Code less principle:
Replace if request.method == 'POST' and form.validate(): by if form.validate_on_submit(): (you save one instruction)
Use url_for():
For the sake of scalability because in future versions and reedits of your program, any changes made to route names will be automatically available when using url_for() as it generates URLs using the URL map. You may use it in return redirect('/my_redirect_uri') (you may check the link I provided for further information)
Use sessions:
Sessions will make your application "able to remember" the form data being sent. In your example, you can use a session this way:
session['field1'] = form.field1.data
session['field2'] = form.field2.data
This means, for example, the above line:
return redirect('/my_redirect_uri', form=form, field1=field1, field2=field2)
must be changed to:
return redirect('my_redirect_uri', form=form, session.get('field1'), session.get('field2'))
Note that if you want to implement sessions, you will need to set a secret key because they are stored on the client side, so they need to be protected (cryptographically in Flask's philosophy). This mean you must configure your application this way:
app.config['SECRET_KEY'] = 'some secret phrase of your own'
Actually, the problem is not related to the browser tab you opened. The whole issue emanates from the redirect() statement.
More details on why it is good to use sessions? Check the next last section:
A word about redirect:
Your POST request is handled by redirect, consequently you loose access to the form data when the POST request ends.
The redirect statement is simply a response which, when received, the browser issues a GET request. This mechanism is there mainly for the following (and similar) situation:
If you try to refresh the same browser window on which you submitted the form data, the browser will prompt you a pop up window to confirm that you want to send the data (again) because the browser remembers, by design, the last request it performed. Of course, this is nasty for the user of your application. There we need sessions. This is also helpful for the browser tab to which you redirect.

Flask WTF (Python) Size-varying list of lists of fields not working (unbound error)

I'm trying to make a uncertainty calculator, where I need a variable number of Fields (this is the final idea). However, while testing for number of 1 field of fields, I came across a issue. Instead of the fields being rendered in the page, there is only some random code in its place:
I tried checking if the issue was related to them having the same name "values" or something, but it seems that is not the issue. I don't know what to try anymore.
forms.py
from flask.ext.wtf import Form
from wtforms import StringField, BooleanField, DecimalField
from wtforms.validators import DataRequired
class Receiver(Form):
expression = StringField('expression', validators=[DataRequired()])
# ve_list = [[StringField('expreson'), DecimalField('expression', places=10)], [StringField('expreson'), DecimalField('expression', places=10)]]
# remember_me = BooleanField('remember_me', default=False)
ve_list = [[DecimalField('value', validators=[DataRequired()]), DecimalField('value', validators=[DataRequired()])]]
The views.py:
from flask import render_template, flash, redirect
from app import app
from .forms import Receiver
#app.route('/login', methods=['GET', 'POST'])
def login():
form = Receiver()
return render_template('request.html',
title='Calculate',
form=form)
request.html:
{% block content %}
<h1>Error propagation calculator</h1>
<form action="" method="post" name="login">
{{ form.hidden_tag() }}
<p>
Please enter the expression:<br>
{{ form.expression }}<br>
</p>
Enter the value and respective error:<br>
{% for ve in form.ve_list %}
{{ ve[0] }} +/- {{ ve[1] }}<br>
{% endfor %}
<p><input type="submit" value="Calculate"></p>
</form>
{% endblock %}
Fields are classes and need to be called in order to run their call method which will render the html to your page.
Example 1:
{{ form.expression() }}
Your field is rendering but it is best to call the field properly.
Edited:
Your list of fields is not going to work because you need to have an instantiated class attached to the form class attribute. When you load up the field like this it is an UnboundField.
I recommend adding fields dynamically in your view. You can see an answer to that problem here.
The fields that are part of the form need to be class variables of the form class. If they are not, then WTForms is not going to find them, so they are never bound to the form.
If you want to add a list of fields, you can do so by setting the attributes on the Form class. Something like this:
class Receiver(Form):
expression = StringField('expression', validators=[DataRequired()])
setattr(Receiver, 've_list0', DecimalField('value', validators=[DataRequired()]))
setattr(Receiver, 've_list1', DecimalField('value', validators=[DataRequired()]))
Then in the template, you can iterate on the form fields to render them, instead of rendering them one by one.

Categories

Resources