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.
Related
I have been trying to incorporate wtforms validators inside 2 forms I have on my index.html(this is a Uni assignment). Everything worked well before I did anything, but security was very bad. After I have inserted some wtform validators, everything works OK. But usability is quite bad. If I get an error whilst filling out one of the forms, I get error messages not only in the form I was writing stuff in, but also in the other separate form on index.html.
I suspect it has something with the way the assignment is set up. Like I said, I have 2 forms on my index.html. In a separate python file, where all the forms are configured, there is the following setup (where LoginForm and RegisterForm are the two forms on index.html):
class LoginForm(FlaskForm):
#more code here
class RegisterForm(FlaskForm):
#more code here
class IndexForm(FlaskForm):
login = FormField(LoginForm)
register = FormField(RegisterForm)
My first thought was to just delete the IndexForm code (and obviously update the code everywhere else where it was needed). I can´t see why the class IndexForm has to wrap the other 2. But that didn´t work out. So I suspect there is a reason why it´s set up the way it is. But how can I make RegisterForm and LoginForm behave such that a validation error in one of them, doesn´t trigger error messages in the other? I have inserted more verbose code below, if that could be of any help.
forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField, FormField, TextAreaField, FileField
from wtforms.fields.html5 import DateField
from wtforms.validators import InputRequired, Length, EqualTo, NoneOf
# defines all forms in the application, these will be instantiated by the template,
# and the routes.py will read the values of the fields
# TODO: Add validation, maybe use wtforms.validators??
# TODO: There was some important security feature that wtforms provides, but I don't remember what; implement it
#invalidInput = [ADD THINGS HERE]
class LoginForm(FlaskForm):
username = StringField('Username', render_kw={'placeholder': 'Username'}, validators=[InputRequired()])
password = PasswordField('Password', render_kw={'placeholder': 'Password'}, validators=[InputRequired()])
remember_me = BooleanField('Remember me') # TODO: It would be nice to have this feature implemented, probably by using cookies
submit = SubmitField('Sign In')
class RegisterForm(FlaskForm):
first_name = StringField('First Name', render_kw={'placeholder': 'First Name'}, validators=[InputRequired(), NoneOf(invalidInput, message="Invalid input")])
last_name = StringField('Last Name', render_kw={'placeholder': 'Last Name'}, validators=[InputRequired()])
username = StringField('Username', render_kw={'placeholder': 'Username'}, validators=[
InputRequired(), Length(min=5, max=50, message="Must be between 5 and 50 characters")])
password = PasswordField('Password', render_kw={'placeholder': 'Password'}, validators=[
InputRequired(), Length(min=8, max=50, message="Must be between 8 and 50 characters"), EqualTo('confirm_password', message='Passwords must match')])
confirm_password = PasswordField('Confirm Password', render_kw={'placeholder': 'Confirm Password'}, validators=[
InputRequired(), Length(min=8, max=50, message="Must be between 8 and 50 characters")])
submit = SubmitField('Sign Up')
class IndexForm(FlaskForm):
login = FormField(LoginForm)
register = FormField(RegisterForm)
routes.py
from app import app, query_db
from app.forms import IndexForm, PostForm, FriendsForm, ProfileForm, CommentsForm
from datetime import datetime
import os
#############################
from flask_wtf import FlaskForm
from wtforms import (StringField, PasswordField, SubmitField, TextAreaField, IntegerField, BooleanField, RadioField)
from wtforms.validators import InputRequired, Length
# this file contains all the different routes, and the logic for communicating with the database
# home page/login/registration
#app.route('/', methods=['GET', 'POST'])
#app.route('/index', methods=['GET', 'POST'])
def index():
form = IndexForm()
if form.login.validate_on_submit() and form.login.is_submitted() and form.login.submit.data:
user = query_db('SELECT * FROM Users WHERE username="{}";'.format(form.login.username.data), one=True)
print("user is ", user)
if user == None:
flash('Sorry, this user does not exist!')
elif user['password'] == form.login.password.data:
return redirect(url_for('stream', username=form.login.username.data))
else:
flash('Sorry, wrong password!')
elif form.register.validate_on_submit() and form.register.is_submitted() and form.register.submit.data:
flash("New user registered!")
query_db('INSERT INTO Users (username, first_name, last_name, password) VALUES("{}", "{}", "{}", "{}");'.format(form.register.username.data, form.register.first_name.data,
form.register.last_name.data, form.register.password.data))
print(form.register.username.data)
print(form.register.password.data)
return redirect(url_for('index'))
return render_template('index.html', title='Welcome', form=form)
index.html
{% import "bootstrap/wtf.html" as wtf %}
{% block content %}
<div class="jumbotron jumbotron-fluid">
<div class="container">
<h1 class="display-4">Social Insecurity</h1>
<p class="lead">The social network for the insecure™</p>
</div>
</div>
<div class="container-fluid">
<div class="row">
<div class="col-sm-12 col-lg-6">
<div class="card text-center">
<div class="card-header">
Sign In
</div>
<div class="card-body">
<h5 class="card-title">Access an existing profile</h5>
<form action="" method="POST" novalidate>
{{ wtf.form_field(form.login.username) }}
{{ wtf.form_field(form.login.password) }}
{{ wtf.form_field(form.login.remember_me) }}
{{ wtf.form_field(form.login.submit, class="btn btn-primary") }}
</form>
</div>
</div>
</div>
<div class="col-sm-12 col-lg-6">
<div class="card text-center">
<div class="card-header">
Register
</div>
<div class="card-body">
<h5 class="card-title">Create a new profile</h5>
<form action="", method="POST" novalidate>
{{ wtf.form_field(form.register.csrf_token) }}
{{ wtf.form_field(form.register.first_name) }}
{{ wtf.form_field(form.register.last_name) }}
{{ wtf.form_field(form.register.username) }}
{{ wtf.form_field(form.register.password) }}
{{ wtf.form_field(form.register.confirm_password) }}
{{ wtf.form_field(form.register.submit, class="btn btn-primary") }}
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}```
Solved it by removing the validators from LoginForm in forms.py. No need to validate these fields, if I have already validated them when creating a new user.
I have done a registration and login page registration page works fine but login page when i Click on create account i get object has no attribute.
application.py
from flask_wtf import FlaskForm
from wtforms import StringField,PasswordField,SubmitField
from wtforms.validators import Length,EqualTo,InputRequired,ValidationError
from models import User
#app.route('/login', methods=['GET', 'POST'])
def login():
login_form = LoginForm()
if login_form.validate_on_sumbit():
return "Logged in, finally!"
return render_template('login.html', form=login_form)
#wtform_fields.py
class LoginForm(FlaskForm):
"""Login Form """
username = StringField('username_label',validators=[InputRequired(message="username required")])
password = PasswordField('password_label',validators=[InputRequired(message="Password required"),invalid_credentials])
submit = SubmitField('Login')
login.html
{% from 'form_helper.html' import DisplayField %}
{% extends "prelogin-layout.html" %}
{% block title %} Registration {% endblock %}
{% block content %}
<h3>Create your account</h3>
<hr>
<form action="{{ url_for('index') }}", method="POST" >
{{DisplayField(form.username, 'Username', autocomplete='off',autofocus=true)}}
{{DisplayField(form.password, 'Password')}}
{{DisplayField(form.confirm, 'Confirm Password')}}
<div class="form-group">
<input type="submit" value="Create" >
</div>
{{form.csrf_token}}
</form>
{% endblock %}
ErrorLog
in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
File "C:\Users\Catalyst\Desktop\Python\chatAp\application.py", line 18, in login
if login_form.validate_on_sumbit():
AttributeError: 'LoginForm' object has no attribute 'validate_on_sumbit'
I'm new at flask can you direct e where I'm mistaken
PS I'm working with flaskwtf V1.0.1
Change the line that adds submit in login.html from
<div class="form-group">
<input type="submit" value="Create" >
</div>
to
<div class="form-group">
{{DisplayField(form.submit, 'Create')}}
</div>
If you are using Flask WTF all fields in the form must come from the library.
P.S.
I don't know where you got the confirm variable to check the password from.
It may not work for you either. If you want the user to enter the password twice, read here: wtforms.validators.EqualTo
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.
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.
I am trying to show an error when the user enters wrong login credentials using form_template. So far I tried the below approach but it is not working.
Forms.py:
class UserForm(forms.Form):
username=forms.CharField(max_length=50)
password=forms.CharField(widget=forms.PasswordInput)
model=User
fields=['username', 'password']
Views.py:
class loginform(View):
template_name='essay/Login.html'
form_class=UserForm
def get(self,request): # if the request is get then only view the function
form=self.form_class(None)
return render(request, self.template_name, {'form': form})
def post(self,request):
form=self.form_class(request.POST)
if form.is_valid():
#user = form.save(commit=False) # it doesnot save in database, it is used to et clean the values
# clean data
username = form.cleaned_data['username']
password = form.cleaned_data['password']
# authenticate user:
user = authenticate(username=username, password=password)
if user is not None:
login(request, user)
if(request.user.is_prof==True):
return redirect('essay:file', )
else:
return redirect('essay:stdprofile')
else:
return render(request,self.template_name, {
'error_message': ' Login Failed! Enter the username and password correctly', })
else:
msg = 'Errors: %s' % form.errors.as_text()
return HttpResponse(msg, status=400)
return render(request, self.template_name, {'form': form})
Form_template:
{% for field in form %}
<div class="form-group">
<label class="control-label col-sm-2">{{ field.label_tag }}</label>
<div class="col-sm-10">{{ field }}</div> <!-- inputs on the rigth -->
</div>
{% endfor %}
Login.html:
<body>
<div class="login-card">
<h1>Log-in</h1><br>
<form class="form-horizontal" action="" method="POST" enctype="multiport/form-data">
{% csrf_token %}
{% include 'essay/form_template.html' %}
<input type="submit" name="login" class="login login-submit" value="login">
</form>
{% error_message %}
</div>
</body>
The problem I got when I enter invalid credentials, username and password fields vanish and it also does not display the error message.
In your field in form page add,
{{field.errors}}
or under csrf tag add,
{{form.errors}}
This will show all your field errors, for non field errors add,
{{form.non_field_errors}}
Also, you can also use Django builtin messages to display your custom message.
The error is because you are using {% error_message %}in your template, when the correct is {{ error_message }}.
Plus,
why not use Django messages?
You also can easily include message on Class Based Views - see here