use flask-wtf form validations without an actual form - python

I am trying to use flask-wtf form validators on some vaiables. All the examples for form validations uses actual form to do the validation. I want to use the validators on few variables without a form. Below are the snippets. I tried this way but it's of no use. I am getting False on form.validate() and I am getting [] lists for errors. Please guide me with the right process.
#this is my form_validations.py
#########################################################################################################
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField
from wtforms.validators import DataRequired, Email, Length
class SignUpForm(FlaskForm):
username = StringField('username', validators=[DataRequired(), Length(min=1, max=50)])
email = StringField('email', validators=[DataRequired(), Email()])
mobile = StringField('mobile', validators=[DataRequired(), Length(min=10, max=15)])
#########################################################################################################
#this is the function where I want to use the form validations.
def userSignUp():
username = request.form['username']
email = request.form['email']
mobile = request.form['mobile']
form = {
"username" : str(username),
"email": str(email),
"mobile": str(mobile)
}
formData = MultiDict(mapping=form)
print(formData)
form = SignUpForm(formData)
if form.validate():
output = {"result": "success"}
else:
output = {"result": "failed"}
# print(form.validate(),form.username.errors,form.email.errors,form.mobile.errors)
return jsonify(output)

Here is a simplified example.
The route:
#app.route("/signup", methods=["GET", "POST"])
def signup():
form = SignUpForm()
if form.validate_on_submit():
app.logger.info(f'Form validated - data: {form.data}')
# redirect somewhere upon success...
# return redirect('/somewhere')
return render_template("signup.html", form=form)
The class code:
class SignUpForm(FlaskForm):
username = StringField('username', validators=[validators.DataRequired(), validators.Length(min=1, max=50)])
email = StringField('email', validators=[validators.DataRequired(), validators.Email()])
mobile = StringField('mobile', validators=[validators.DataRequired(), validators.Length(min=10, max=15)])
Slight modification on my end (the validators), because I import like this:
from wtforms import (
Form, validators, widgets, FieldList,
BooleanField, TextField, IntegerField, SelectField, SelectMultipleField,
TextAreaField, StringField, SubmitField
)
The template:
{% extends "base.html" %}
{% block title %}Sign up{% endblock %}
{% block subtitle %}Sign up{% endblock %}
{% block content %}
<h1>Sign up page</h1>
{% if form.is_submitted() and form.errors|count > 0 %}
<div class="alert alert-danger alert-dismissible fade show" role="alert">
{{ form.errors|count }} error(s):
<ul>
{% for field_name, field_errors in form.errors|dictsort if field_errors %}
{% for error in field_errors %}
<li>{{ form[field_name].label }}: {{ error }}</li>
{% endfor %}
{% endfor %}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endif %}
<div class="row">
<div class="col">
<form class="row g-3" method="post" action="{{ url_for('signup') }}" class="form-inline">
{{ form.csrf_token }}
<div class="mb-3 col-md-4">
<label for="username" class="form-label">Username</label>
{{ form.username(class="form-control") }}
</div>
<div class="mb-3 col-md-4">
<label for="email" class="form-label">Email</label>
{{ form.email(class="form-control") }}
</div>
<div class="mb-3 col-md-4">
<label for="mobile" class="form-label">Mobile</label>
{{ form.mobile(class="form-control") }}
</div>
<div class="mb-3">
<button type="submit" id="save" name="save" class="btn btn-success" value="save">Save</button>
</div>
</form>
</div>
</div>
{% endblock %}
I am using Bootstrap, so you can see I am letting Flask_wtf automatically generate the form fields but apply certain classes. However, I have added a submit button manually but you include it in your form as an optional field. Also you can add all the form fields yourself, then make sure they keep their original names. NB: I added the hidden CRSF field too (populated automatically).
The key is to use validate_on_submit to force validation of the form and populate the list of errors. As you can see, the template can iterate on the list of errors and display them as list items.
Here is what the form looks like:

Related

Python Flask/WTForms Form Field Values Lost After Submit

I'm using Python with Flask with WTForms
I have two forms that are virtually identical called, login and register. However, the login form retains the email address after submission where register does not.
Troubleshooting:
I've compare the source code for both forms and I'm not finding any obvious difference.
I've searched online for potential solutions but everything looks like what I have coded.
I'm not seeing a reason why login works but register does not.
What am I missing? How do I get the field value to reload after submission on the registration page?
Login View:
#pages.route("/login", methods=["GET", "POST"])
def login():
if session.get("email"):
return redirect(url_for(".index"))
form = LoginForm()
if form.validate_on_submit():
user_data = current_app.db.user.find_one({"email": form.email.data})
if not user_data:
flash("Login credentials not correct", category="danger")
# print(form.email.data)
return redirect(url_for(".login"))
user = User(**user_data)
if user and pbkdf2_sha256.verify(form.password.data, user.password):
session["user_id"] = user._id
session["email"] = user.email
return redirect(url_for(".index"))
flash("Login credentials are incorrect", category="danger")
return render_template("login.html", title="Login", form=form)
Register View:
#pages.route("/register", methods=["GET", "POST"])
def register():
if session.get("email"):
return redirect(url_for(".index"))
form = RegisterForm()
if form.validate_on_submit():
#-------------------------------------------------
user_data = current_app.db.user.find_one({"email": form.email.data})
if user_data:
flash("Account already exists", category="danger")
print("In /register - Account already exists")
return redirect(url_for(".register"))
#--------------------------------------------------
user = User(
_id=uuid.uuid4().hex,
email=form.email.data,
password=pbkdf2_sha256.hash(form.password.data),
)
current_app.db.user.insert_one(asdict(user))
flash("User registered successfully", "success")
return redirect(url_for(".login"))
return render_template("register.html", title="Register", form=form)
forms.py
from flask_wtf import FlaskForm
from wtforms import (
IntegerField,
PasswordField,
StringField,
SubmitField,
TextAreaField,
URLField,
)
from wtforms.validators import (
InputRequired,
Email,
EqualTo,
Length,
NumberRange,
)
class LoginForm(FlaskForm):
email = StringField("Email", validators=[InputRequired(), Email()])
password = PasswordField("Password", validators=[InputRequired()])
submit = SubmitField("Login")
class RegisterForm(FlaskForm):
email = StringField("Email", validators=[InputRequired(), Email()])
password = PasswordField(
"Password",
validators=[
InputRequired(),
Length(
min=12,
message="Your password must be at least 12 characters long.",
),
],
)
submit = SubmitField("Register")
login.html snippet:
<form name="login" method="post" novalidate class="form">
{% with messages = get_flashed_messages(with_categories=true) %}
{%- for category, message in messages %}
<span class="form__flash form__flash--{{category}}"> {{ message }}</span>
{% endfor %}
{% endwith %}
<div class="form__container">
{{ form.hidden_tag() }}
{{ render_text_field(form.email) }}
{{ render_text_field(form.password) }}
<div>
{{ form.submit(class_="button button--form") }}
</div>
<div class="page-link">
Forgot password?
</div>
</div>
<section class="login-footer">
<div class="page-link">
Don't have an account? Register now
</div>
</section>
</form>
register.html snippet:
<form name="register" method="post" novalidate class="form">
{% with messages = get_flashed_messages(with_categories=true) %}
{%- for category, message in messages %}
<span class="form__flash form__flash--{{category}}"> {{ message }}</span>
{% endfor %}
{% endwith %}
<div class="form__container">
{{ form.hidden_tag() }}
{{ render_text_field(form.email) }}
{{ render_text_field(form.password) }}
<div>
{{ form.submit(class_="button button--form") }}
</div>
</div>
<section class="registration-footer">
<div class="page-link">
Already have an account? Log in here
</div>
</section>
</form>

WTForm validate_on_submit() returns False for selectField

Edit, Solved: The validate() method which validate_on_submit() calls requires the values for SelectField options to be integers. Adding the coerce=int option to the SelectField solved the problem. The field now looks like this:
time_zone = SelectField('Time Zone', choices=[(-1, '--Select Time Zone--'),(0, 'PST'), (1, 'MST'), (2, 'EST')], coerce=int)
Original Post:
I am working on a registration form using WTForms (v2.2.1) in Flask(v1.1.1). When I have a SelectField in the form class form.validate_on_submit() automatically returns False
I followed the pattern in the documentation and looked through most of the "Similar questions" SO suggests to no avail. Many of the other posts are missing {{ form.csrf_token }} or {{ form.hidden_tag() }} below the form tag. I've tried both with the same outcome.
forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField, SelectField
from wtforms.validators import DataRequired, Email, EqualTo, ValidationError
class RegisterForm(FlaskForm):
first_name = StringField('First Name', validators=[DataRequired()])
last_name = StringField('Last Name', validators=[DataRequired()])
email = StringField('Email Address', validators=[DataRequired(), Email()])
phone = StringField('Phone Number', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()])
confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')])
time_zone = SelectField('Time Zone', choices=[(-1, '--Select Time Zone--'),(0, 'PST'), (1, 'MST'), (2, 'EST')])
submit = SubmitField('Register')
index.html (The form is rendered on the index.html page and processed in the /register route)
{% extends 'base.html' %}
{% block content %}
<div class="flex-item p-5 register">
<h2>Register</h2>
<form action="{{ url_for('register') }}" method="post" novalidate>
{{ register_form.hidden_tag() }}
<div class="form-row">
<div class="form-group col-md-6">
{{ register_form.first_name.label }}
{{ register_form.first_name(class="form-control") }}
</div>
<div class="form-group col-md-6">
{{ register_form.last_name.label }}
{{ register_form.last_name(class="form-control") }}
</div>
</div>
<div class="form-row">
<div class="form-group col">
{{ register_form.email.label }}
{{ register_form.email(class="form-control") }}
</div>
</div>
<div class="form-row">
<div class="form-group col">
{{ register_form.phone.label }}
{{ register_form.phone(class="form-control") }}
</div>
</div>
<div class="form-row">
<div class="form-group col">
{{ register_form.password.label }}
{{ register_form.password(class="form-control") }}
</div>
</div>
<div class="form-row">
<div class="form-group col">
{{ register_form.confirm_password.label }}
{{ register_form.confirm_password(class="form-control") }}
</div>
</div>
<div class="form-row">
<div class="form-group col">
{{ register_form.time_zone.label }}
{{ register_form.time_zone(class="form-control") }}
</div>
</div>
{{ register_form.submit(class="btn btn-primary") }}
</form>
</div>
{% endblock %}
routes.py
from flask import render_template, redirect, url_for, request, flash
from app import app
from app.forms import RegisterForm
#app.route('/', methods=['GET', 'POST'])
#app.route('/index', methods=['GET', 'POST'])
def index():
return render_template('index.html', register_form=RegisterForm())
#app.route('/register', methods=['POST'])
def register():
form = RegisterForm()
print(form.is_submitted(), form.validate())
# This is printing (True, False) when there is a SelectField
# and (True, True) when no SelectField is in the RegisterForm Class
if form.validate_on_submit():
print('validating register form')
return redirect(url_for('index'))
If I remove the SelectField from the RegisterForm class, validate_on_submit() works fine.
Any thoughts on what is causing this issue?

Why is the StringField always None?

I'm trying to read a form using Flask, WTForms and the macros that are made available via flask-Bootstrap. This works in some cases and in other - seemingly similar ones - it doesn't. When I check the content of a specific StringField it always evaluates to None, failing the InputRequired() validation.
forms.py:
from wtforms import Form, validators, \
StringField, PasswordField, SubmitField, \
SelectField, RadioField
from wtforms.fields.html5 import DateField
from wtforms.validators import ValidationError, InputRequired, NoneOf, \
EqualTo, Length, Optional
class TestForm(Form):
name = StringField('Name', [validators.InputRequired(message='Please enter name!')])
date = DateField('Date', format='%Y-%m-%d') #, default=date.today())
address = StringField('Address')
submit = SubmitField('Add')
test.py:
from flask import render_template, redirect, request
from flask_security import login_required
from application.routes import routes
from application.forms import TestForm
#routes.route('/test', methods=['POST', 'GET'])
#login_required
def test():
form = TestForm()
print(form.name.data)
if request.method == 'POST' and form.validate():
print("validated")
return redirect("/")
return render_template('test.html', title="Asset Manager", form=form)
test.html:
{% extends 'layout.html' %}
{% import "bootstrap/wtf.html" as wtf %}
{% block content %}
<div class="container-fluid">
<h3><u>Test:</u></h3>
<br>
<div class="container-fluid col-md-3">
<form action="" method="POST" class="form" role="form" onsubmit="/">
<div class="row">
<div class="col-md-12">
{{ wtf.form_field(form.name, form_type="inline", placeholder="Name") }}
</div>
</div>
<div class="row">
<div class="col-md-5">
{{ wtf.form_field(form.date) }}
</div>
</div>
<div class="row">
<div class="col-md-12">
{{ wtf.form_field(form.address) }}
</div>
</div>
{{ wtf.form_field(form.submit, class="btn btn-primary") }}
</form>
{% for message in get_flashed_messages() %}
<div class="alert alert-warning">
<button type="button" class="close" data-dismiss="alert">×</button>
{{ message }}
</div>
</div>
{% endfor %}
</div>
{% endblock %}
This always puts out None and consequently never gets past the validation check even when I enter a name and press the Add button.
I tried creating a minimal example (which still exhibits the same problem), but I left out large parts of the application, so if you need more information - please let me know and I'll be happy to provide them.
Thanks to Marc I found where I missed something. When instantiating the form I need to pass the request as an argument.
So the line
form = TestForm()
needs to be changed to
form = TestForm(request.form)
To make the application work.

Why is my registration form not updating on submit

I'm making a registration form using Flask-wtforms and sqlite as my database but when I try to submit it does not push to the database.
I have checked whether i had not put a method of 'post'in my registration form and also whether my db had been created when i configured it.
This my forms.py
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed
from flask_login import current_user
from wtforms import StringField, PasswordField, SubmitField, BooleanField
from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError
from app.models import User
from app import db
class RegistrationForm(FlaskForm):
username = StringField('Username',
validators=[DataRequired(), Length(min=2, max=20)])
email = StringField('Email',
validators=[DataRequired(), Email()])
picture=FileField('Profile picture',validators=[DataRequired(),FileAllowed(['jpg', 'png'])])
password = PasswordField('Password', validators=[DataRequired()])
confirm_password = PasswordField('Confirm Password',
validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('Sign Up')
def validate_username(self, username):
user = User.query.filter_by(username=username.data).first()
if user:
raise ValidationError('That username is taken. Please choose a different one.')
def validate_email(self, email):
user = User.query.filter_by(email=email.data).first()
if user:
raise ValidationError('That email is taken. Please choose a different one.')
Register.html
<form action="" method="POST" class='register-form'>
{{ form.hidden_tag() }}
<div class=''>
{{form.picture.label}}
{{form.picture}}
{%if error in form.picture.errors%}
<span style="color: red;">[{{ error }}]</span>
{% endif %}
</div>
<p >
{{ form.username.label }}<br>
{{ form.username(size=32) }}<br>
{% for error in form.username.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>
{{ form.email.label }}<br>
{{ form.email(size=32) }}<br>
{% for error in form.email.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>
{{ form.password.label }}<br>
{{ form.password(size=32) }}<br>
{% for error in form.password.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>
{{ form.confirm_password.label }}<br>
{{ form.confirm_password(size=32) }}<br>
{% for error in form.confirm_password.errors %}
<span style="color: red;"> [{{"Typo! Passwords didn't match"}}]</span>
{% endfor %}
</p>
<p>{{ form.submit(class="btn btn-primary") }}</p>
</form>
Where i have configured my sqlite db
from flask import Flask
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt
from flask_login import LoginManager
app = Flask(__name__)
app.config['SECRET_KEY'] = 'hidden secret key8e9808798709809'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS']=False
db = SQLAlchemy(app)
manager = Manager(app)
No error message i just know that it is not pushing new users to the db.
Got it!
Hope this helps someone out there ..lol...
adding to the register.html
<form method="POST" enctype="multipart/form-data">
This way the request.files won't be empty

In Django, form isn't rendered on browser

I have started learning django, but I can't understand how to render form.
This is form.py I have made.
from django import forms
class TweetForm(forms.Form):
text = forms.CharField(widget = forms.Textarea(attrs = {'rows' : 1, 'cols' : 85}), max_length = 160)
country = forms.CharField(widget = forms.HiddenInput())
This is code snippet in views.py.
from .forms import TweetForm
class Profile(View):
def get(self, request, username):
params = dict()
user = User.objects.get(username = username)
tweets = Tweet.objects.filter(user = user)
params["user"] = user
params["tweets"] = tweets
form = TweetForm
return render(request, 'profile.html', {'params' : params, 'form' : form})
This is html file and it must render form I have made.
{% extends "base.html" %}
{% block content %}
<div class="row clearfix">
<div class="col-md-12 column">
<form method="post" action="post/">
{% csrf_token %}
<div class="col-md-8 col-md-offset-2 fieldWrapper">
{{ form.text.errors }}
{{ form.text }}
</div>
{{ form.country.as_hidden }}
<div>
<input type="submit" value="post">
</div>
</form>
</div>
<h3> </h3>
<div class="col-md-12 column">
{% for tweet in tweets %}
<div class="well">
<span>{{ tweet.text }}</span>
</div>
{% endfor %}
</div>
</div>
{% endblock %}
When I issue command(run server), browser don't render form without any exception.
I think there is a problem in views.py file, But I can't find it.
How can I send form parameter to Template(.html file) and render form in result?
You didn't instantiate the form.
You need to instantiate the form, for rendering in a template, like this,
form = TweetForm()
Note:
The params variable is already a dict(), you could just add form into the params like this,
params['form'] = form
Then, render the template with context as params,
render(request, 'profile.html', params)

Categories

Resources