Why is my wtforms error handling not working? [duplicate] - python

This question already has answers here:
Flask view return error "View function did not return a response"
(3 answers)
Closed 1 year ago.
I am trying to build a flask login and registration system. Everything works except when the user gives an incorrect input to the forms then there will be an error since there is no working error handling yet. I am using wtforms and here is the registration form class:
class RegistrationForm(FlaskForm):
username = StringField('Username', validators=[DataRequired(), Length(min=2, max=20)])
email = StringField('Email', validators=[DataRequired(),Email()])
password = PasswordField('Password', validators=[DataRequired(), Length(min=4)])
confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), Length(min=4), EqualTo('password')])
submit = SubmitField('Sign Up')
And this is the registration route
#app.route('/register', methods=["GET", "POST"])
def register():
form = RegistrationForm()
if request.method == "POST":
if form.validate_on_submit():
return redirect(url_for('home'))
else:
return render_template('register.html', form=form)
I have tired to handle the errors in htmlbut every time the user gives the incorrect input then it show this error message "TypeError: The view function for 'register' did not return a valid response. The function either returned None or ended without a return statement." Here is my html form:
<form action="/register" method="POST">
<h1>Create Account</h1>
{{ form.hidden_tag() }} {% if form.username.errors %} {{
form.username(placeholder="Username", class="is-invalid") }} {% for
error in form.username.errors %}
<span>{{ error }}</span>
{% endfor %} {% else %} {{ form.username(placeholder="Username") }} {%
endif %} {% if form.email.errors %} {{ form.email(placeholder="Email",
class="is-invalid") }} {% for error in form.email.errors %}
<span>{{ error }}</span>
{% endfor %} {% else %} {{ form.email(placeholder="Email") }} {% endif
%} {% if form.password.errors %} {{
form.password(placeholder="Password", class="is-invalid") }} {% for
error in form.password.errors %}
<span>{{ error }}</span>
{% endfor %} {% else %} {{ form.password(placeholder="Password") }} {%
endif %} {% if form.confirm_password.errors %} {{
form.confirm_password(placeholder="Confirm Password",
class="is-invalid") }} {% for error in form.confirm_password.errors %}
<span>{{ error }}</span>
{% endfor %} {% else %} {{ form.confirm_password(placeholder="Confirm
Password") }} {% endif %}
<div>{{ form.submit(class="signup") }}</div>
</form>
Any response or help will be greatly appreciated. Thanks!

Every view must return something. Yours returns nothing if method is post but doesn't validate. It is a logical problem, not related to wtforms.
if request.method == "POST":
if form.validate_on_submit():
return redirect(url_for('home'))
else:
return 'oops'
else:
return render_template('register.html', form=form)

Related

Python Flask, displaying an input from one page on another

None of the other answers on this seem to solve my problem. I am trying to display a greeting that says "Welcome to the app [user]" when someone logs in. I am not sure how to get the greet page to display this message.
Here is my login function
#app.route("/login2", methods=["POST", "GET"])
def login():
if request.method == "POST":
session.permanent = True
user=request.form["nm"]
session["user"] = user
return render_template("greet.html")
else:
if "user" in session:
return redirect(url_for("greet"))
return render_template("login2.html")
Here is my login.html page
{% extends "base.html" %}
{% block title %}Log In Page{% endblock %}
{% block content %}
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for msg in messages %}
<p>{{msg}}</p>
{% endfor %}
{% endif %}
{% endwith %}
<form action="#" method="post">
<p>Name: </p>
<p><input type="text", name="nm" /></p>
<p><input type="submit", value="submit" /></p>
</form>
{% endblock %}
And here is greet.html, which should display the message that I want.
{% extends "base.html" %}
{% block content %} <h1>Welcome</h1>
<p>Hi, Welcome to the app {{}} .</p>
{% endblock %}
I believe that I need to pass something into the {{}} on the greet.html page, but I do not know what to pass in here.
In your login route when the method is POST pass the user object to the template.
#app.route("/login2", methods=["POST", "GET"])
def login():
if request.method == "POST":
session.permanent = True
user=request.form["nm"]
session["user"] = user
return render_template("greet.html", user=user)
else:
if "user" in session:
return redirect(url_for("greet"))
return render_template("login2.html")
Now you can use it in the template
<p>Hi, Welcome to the app {{user.name}} .</p>

Flask flashed messages not showing

I'm trying to flash messages in Flask using flash(), but the messages aren't appearing on the page. It isn't in the source code for the page either when looking at it with Dev Tools.
Here is the code that I have in my base.html It is outside of the blocks:
{% with messages = get_flashed_messages(with_categories=true) %}
<p>1</p>
{% if messages %}
<p>2</p>
{% for message, category in messages %}
<p>3</p>
{% if category == 'success' %}
<p>4</p>
<div class='message success'>
<p>{{ message }}</p>
</div>
{% elif category == 'error' %}
<div class='message error'>
<p>{{ message }}</p>
</div>
{% endif %}
{% endfor %}
{% endif %}
{% endwith %}
Python code(shortened to include relevant bits)
#app.route('/suggestion', methods=['GET', 'POST'])
def suggestion():
if request.method == 'POST':
...
content = f'Name: {name}\nEmail: {email}\nQuestion: {question}\nAnswer: {answer}\nType: {type}\nTopic: {topic}'
if sensible == 'Agreed' and accurate == 'Agreed':
email = Email()
sent = email.sendEmail(content)
if sent:
flash('Suggestion submitted successfully', category='success')
else:
flash('Error submitting suggestion. Please try again later',
category='error')
return redirect(url_for('suggest'))
Using the p tags to see where it's failing, I can tell that the {% if messages %} isn't working, but I don't know why.
I'm using flash('Message', category='success') to send the messages. I've looked at the Flask documentation and can't figure out what I'm doing wrong.
Please can someone help?
Have a look at this implementation
Routes.py
#auth.route("/login", methods=["GET", "POST"])
def login():
form = LoginForm()
if request.method == "POST":
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user is not None and user.confirm_password(form.password.data):
login_user(user)
flash("Login successful", "success")
return redirect(url_for("home.index"))
flash("Invalid email or password", "error")
return render_template("auth/login.html", form=form)
_message.html template
<div class="flash-message">
<div class="msg-wrapper">
{% with messages=get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category,message in messages %}
<p class="{{category}} message">{{message}}</p>
{% endfor %}
{% endif %}
{% endwith %}
</div>
</div>
Looping through the messages and categories in reverse

Flask wtf form not validating custom validators

So i have a login form and signup form on a single html page. I have custom validators for signup form that are not working.
The routes.py looks like this
#app.route("/", methods=['GET', 'POST'])
def home():
login_form = LoginForm(request.form, prefix="login-form")
regis_form = RegistrationForm(request.form, prefix="register-form")
return render_template('home.html', login_form=login_form, regis_form=regis_form)
#app.route("/register", methods=['POST'])
def register():
regis_form = RegistrationForm(request.form, prefix='register-form')
if regis_form.validate_on_submit():
hashed_password = bcrypt.generate_password_hash(regis_form.password_regis.data).decode('utf-8')
user = User(username=regis_form.username_regis.data, email=regis_form.email_regis.data, password=hashed_password)
db.session.add(user)
db.session.commit()
flash('Your account has been created! You are now able to log in', 'success')
return redirect(url_for('home'))
return redirect(url_for('home'))
#app.route("/login", methods=['POST'])
def login():
login_form = LoginForm(request.form, prefix='login-form')
if login_form.validate_on_submit():
user = User.query.filter_by(email=login_form.email_login.data).first()
if user and bcrypt.check_password_hash(user.password, login_form.password_login.data):
login_user(user, remember=login_form.remember.data)
next_page = request.args.get('next')
return redirect(next_page) if next_page else redirect(url_for('home'))
else:
flash('Login Unsuccessful. Please check email and password', 'danger')
return redirect(url_for('home'))
forms.py
class RegistrationForm(FlaskForm):
username_regis = StringField('Username',
validators=[DataRequired(), Length(min=5, max=20)])
email_regis = EmailField('Email',
validators=[DataRequired(), Email()])
password_regis = PasswordField('Password', validators=[DataRequired()])
submit_regis = SubmitField('Sign Up')
def validate_username_regis(self, username_regis):
user = User.query.filter_by(username=username_regis.data).first()
if user:
raise ValidationError('That username is taken. Please choose a different one.')
def validate_email_regis(self, email_regis):
user = User.query.filter_by(email=email_regis.data).first()
if user:
raise ValidationError('That email is taken. Please choose a different one.')
class LoginForm(FlaskForm):
email_login = EmailField('Email',
validators=[DataRequired(), Email()])
password_login = PasswordField('Password', validators=[DataRequired()])
remember = BooleanField('Remember Me')
submit_login = SubmitField('Login')
home.html
<div>
<form method="POST" action="{{ url_for('login') }}">
{{ login_form.hidden_tag() }}
<fieldset class="form-group">
<div class="form-group">
{% if login_form.email_login.errors %}
{{ login_form.email_login(class="form-control is-invalid") }}
<div class="invalid-feedback">
{% for error in login_form.email_login.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ login_form.email_login(class="form-control mb-2", placeholder="Email") }}
{% endif %}
</div>
<div class="form-group">
{% if login_form.password_login.errors %}
{{ login_form.password_login(class="form-control is-invalid") }}
<div class="invalid-feedback">
{% for error in login_form.password_login.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ login_form.password_login(class="form-control mb-2", placeholder="Password") }}
{% endif %}
</div>
</fieldset>
<div class="form-group">
{{ login_form.submit_login(class="btn btn-danger") }}
</div>
</form>
</div>
<div class="" id="regis">
<form method="POST" action="{{ url_for('register') }}">
{{ regis_form.hidden_tag() }}
<fieldset class="form-group">
<div class="form-group">
{% if regis_form.username_regis.errors %}
{{ regis_form.username_regis(class="form-control form-control-md is-invalid") }}
<div class="invalid-feedback">
{% for error in regis_form.username_regis.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ regis_form.username_regis(class="form-control form-control-md mb-2", placeholder="Username") }}
{% endif %}
</div>
<div class="form-group">
{% if regis_form.email_regis.errors %}
{{ regis_form.email_regis(class="form-control form-control-md is-invalid") }}
<div class="invalid-feedback">
{% for error in regis_form.email_regis.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ regis_form.email_regis(class="form-control form-control-md mb-2", placeholder="Email") }}
{% endif %}
</div>
<div class="form-group">
{% if regis_form.password_regis.errors %}
{{ regis_form.password_regis(class="form-control form-control-md is-invalid") }}
<div class="invalid-feedback">
{% for error in regis_form.password_regis.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ regis_form.password_regis(class="form-control form-control-md mb-2", placeholder="Password") }}
{% endif %}
</div>
</fieldset>
<div class="form-group">
{{ regis_form.submit_regis(class="btn btn-danger") }}
</div>
</form>
</div>
Previously I had both forms on the same home route, custom validators were working but if i submitted register form with errors purposefully, then the login form also shows error like input required. So to fix that I did this, but now signup form is not validating custom validators
After your data has failed validation, you execute return redirect(url_for('home')). That results into a GET on the home route. That populates a new instance of the form: regis_form = RegistrationForm(request.form, prefix="register-form"). Your error information will be lost, it is on a different instance.
To get the error information, your register() route will need to render the template using render_template('home.html', login_form=login_form, regis_form=regis_form), not do the redirect. Do not forget to create a new instance of LoginForm, otherwise your rendering will fail.

Django Model Form validation doesn't show

I needed some validation on a Django ModelForm field. So I changed 2 lines in my models.py (just below). The validation is blocking as necessary, but I can't find the proper way to display the ValidationError. Maybe there is a cleaner way to do this in the model form ?
models.py
class Lexicon(models.Model):
[...]
alphanumeric = RegexValidator(r'^[0-9a-zA-Z]*$', _('Only alphanumeric characters are allowed'))
filename = models.CharField(_("Filename"), max_length=40, validators=[alphanumeric])
forms.py
class LexiconForm(forms.ModelForm):
class Meta:
model = Lexicon
fields = ['filename', 'language', 'comment', 'alphabet', 'case_sensitive', 'diacritics']
views.py
#login_required
def new_pls_view(request):
if request.method == 'POST':
form = LexiconForm(request.POST)
if form.is_valid():
obj = form.save(commit=False)
obj.user = request.user
obj.save()
return redirect('pls_edit')
else:
form = LexiconForm()
return render(request, 'main/new_pls.html', {
'form': form,
})
template.html
<form class="form-horizontal" method="post" action="{% url 'new_pls' %}">
{% csrf_token %}
{% if form.non_field_errors %}
<div class="alert alert-danger" role="alert">
{% for error in form.non_field_errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
[...]
{% if form.is_bound %}
{% if form.filename.errors %}
{% for error in form.filename.errors %}
<div class="invalid-feedback">
{{ error }}
</div>
{% endfor %}
{% endif %}
{% if form.filename.help_text %}
<small class="form-text text-muted">{{ form.filename.help_text }}</small>
{% endif %}
{% endif %}
{% render_field form.filename type="text" class+="form-control" id="plsFilename" placeholder=form.filename.label %}
Replacing my entire form by {{ form }} as #Alasdair suggested is working, so I guess something is wrong with my template rendering.
I've simply replaced my error printing by this and the error prints!
<form class="form-horizontal" method="post" action="{% url 'new_pls' %}">
{% csrf_token %}
{{ form.non_field_errors }}
[...]
{{ form.filename.errors }}
{% render_field form.filename type="text" class+="form-control" id="plsFilename" placeholder=form.filename.label %}

unable to show custom error message in Jinja2 template

I'm writing a register user function in ( using Flask, Python, Jinja2) which i'm checking if a username (or email) is already present and if so shows an error to below TextField.
register code is:
#app.route('/register', methods=['GET', 'POST'])
def register():
form = SignupForm()
error = None
if form.validate_on_submit():
user_by_name = Users.query.filter_by(username=form.username.data).first()
user_by_email = Users.query.filter_by(email=form.email.data).first()
if user_by_name:
error = 'Username already taken. Choose another'
return render_template('register.html', form=form, error = error)
elif user_by_email:
error = 'Email already registered. Login or register with another Email'
return render_template('register.html', form=form, error = error)
else:
#Add user details to DB logic
return redirect(url_for('index'))
return render_template('register.html', form=form, error = error)
I have a macro defined in a file util.html
{% macro render_field(field) %}
<div class="control-group {% if field.errors %}error{% endif %}">
{% if kwargs.get('label', True) %}
{{ field.label(class="control-label") }}
{% endif %}
<div class="controls">
{{ field(**kwargs) }}
{% for error in field.errors %}
<p class="help-block">{{ error }}</p>
{% endfor %}
</div>
</div>
{% endmacro %}
and using this macro in register.html as:
{% from "util.html" import render_field %}
{% extends "base.html" %}
{% block content %}
<form method="post">
{{ form.hidden_tag() }}
{{ render_field(form.username, label=True, class="input-xlarge", autofocus="autofocus", errors=error) }}
{{ render_field(form.password, label=True, class="input-xlarge", autofocus="autofocus") }}
{{ render_field(form.confirm, label=True, class="input-xlarge", autofocus="autofocus") }}
{{ render_field(form.email, label=True, class="input-xlarge", autofocus="autofocus", errors=error) }}
<button class="btn" type="submit">Register</button>
</form>
{% endblock %}
Now when i test the localhost:5000/register with duplicate username or email address it doesn't show any error at all (also doesn't add any user into DB which is okay).
But when i enter any wrong email or leave any field blank it shows respective error, but not showing the error which i want to pass using register view.
Is there any Jinja2 related logic missing?
And why its showing errors related to blank field or wrong email but not which i try to pass on duplicate username etc.
Please suggest.
Well, you doing it wrong :)
{% for error in field.errors %}
<p class="help-block">{{ error }}</p>
{% endfor %}
This macro searches for error in respective field.errors
But you pass error context variable, which is not attached to the field.
The right way to solve this will be to add def validate_user to your form definition and check for everything in there.
If it goes wrong -> raise ValidationError(message)
See example here:
WTForms documentation
With this approach, your validate_on_submit() should not return True if it is not validated.
You can just flash the error like this
sample code
file: views.py
#app.route('/')
def index():
# some checks which provided error
if error:
flash(error)
return render_template('template.html')
file template.html
{% for category, msg in get_flashed_messages(with_categories=true) %}
<div class="category">{{ msg|safe }}</div>
{% endfor %}

Categories

Resources