I am writing an application on Flask, I made authorization on flask-login, but an error appears during authorization itself. I use also Flask-WTF.
I have the simplest form of Flask WTF:
class LoginForm(FlaskForm):
email = StringField("Email: ", validators=[Email()])
password = PasswordField("Password: ", validators=[DataRequired(), Length(min=4, max=100)])
submit = SubmitField("Войти")
There is an HTML form:
<form action="/accounting" method="POST">
{{ form.csrf_token }}
{{ form.email.label() }} {{ form.email() }}
{{ form.password.label() }} {{ form.password() }}
{{ form.submit() }}
</form>
I tried to specify the HTML form {{ form.hidden_tag() }}
There is also a function:
#app.route('/login', methods=['POST', 'GET'])
def login():
if current_user.is_authenticated:
return redirect('/accounting')
form = LoginForm()
if form.validate_on_submit():
email = request.form['email']
user = UserModel.query.filter_by(email=email).first()
if user is not None and user.check_password(request.form['password']):
login_user(user)
return redirect('/accounting')
return render_template('login.html', form=form)
I couldn't find an answer on the Internet that fits my case, so I'm asking here.
Why does an error appear when submitting the form?
Bad Request
The CSRF session token is missing.
After rendering, the token is present:
<input id="csrf_token" name="csrf_token" type="hidden" value="IjhlMDBjMDhmYTIwOGUyNGQ5OGNiMTY0ZGZhOTU3Njc0ZDJhNjY4MDgi.YSW1_g.fhKQUYljjLKqUtl0OdcuOgJx02U">
I tried it both ways:
app.config['SECRET_KEY'] = SECRET_KEY
app.secret_key = "ff82b98ef8727e388ea8bff063"
There are also such lines:
csrf = CSRFProtect(app)
csrf.init_app(app)
Your error is in the html template. remove the action and add the hidden tags
<form method="POST">
{{ form.hidden_tag() }}
{{ form.email.label() }} {{ form.email() }}
{{ form.password.label() }} {{ form.password() }}
{{ form.submit() }}
</form>
Related
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>
Jinja Template Code
<form method="POST">
{{ form.name.label }} {{ form.name() }}
{{ form.submit() }}
</form>
Form Class
class NameForm(FlaskForm):
name = StringField('What is your name?', validators=[Required()])
submit = SubmitField('Submit')
pdb> request.form
ImmutableMultiDict([('name', 'rohit'), ('submit', 'Submit')])
form.validate_on_submit() is returning False instead of True.
Fr the above to work you also need to add protection against cross-site referencing in the HTML code. Adding this line
{{ form.hidden_tag() }}
might solve the problem.
I know that there are similar problems which have been answered. The csrf_enabled is not an issue now if the Form inheriting FlaskForm, and the template has the form.hidden_tag().
I have the following flask app.
## Filenname: app.py
from flask import Flask, render_template, redirect, url_for, flash, request
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, SelectField
from wtforms.validators import DataRequired
app = Flask(__name__)
app.config["SECRET_KEY"] = "secret"
class DataForm(FlaskForm):
name = StringField("Name", validators=[DataRequired()])
gender = SelectField("Gender", validators=None, choices=[(1, 'M'), (2, "F")])
submit = SubmitField("Submit", validators=None)
#app.route('/index', methods=["GET", "POST"])
def index():
form = DataForm(request.form)
print(form.validate_on_submit())
if form.validate_on_submit():
print(form.validate())
print(form.name)
flash("THIS IS FLASH")
title="hello"
return redirect(url_for('output'))
return render_template('index.html', form=form)
#app.route('/output', methods=["GET", "POST"])
def output():
title = "hello"
form = DataForm()
print(form.validate())
return render_template('output.html', title=title)
app.run(debug=False)
The following is index.html template:
<html>
<body>
{% with messages = get_flashed_messages() %}
{{ messages }}
{% endwith %}
<form action="" method="GET">
{{ form.hidden_tag() }}
{{ form.name.label }}
{{ form.name() }}
{% for error in form.name.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
<hr>
{{ form.gender.label }}
{{ form.gender() }}
{{ form.submit() }}
</form>
</body>
</html>
After clicking the submit button the execution never goes in the if form.validate_on_submit() block in the index function.
I also removed all the validators, the code inside validate_on_submit block is still unreachable. Printing form.validate_on_submit() is always false.
So there are multiple problems.
Change your choices to strings:
choices=[('1', 'M'), ('2', "F")]
Change your form method to POST, because validate_on_submit() requires it:
<form action="" method="POST">
Additionally, to debug other possible errors (like CSRF), add this to your template:
{% if form.errors %}
{{ form.errors }}
{% endif %}
That fixed your code for me.
just make form without
2.form = FlaskForm(meta={'csrf': False})
Just print csrf_token with jinja and it will return True.
<form method="POST" action="#">
{{ form.csrf_token }}
</form>
I made a flask_wtf Form with this field:
logo_image = FileField('logo_image', validators=[FileRequired(), FileAllowed(['jpg', 'png'], 'Images only!')])
My form looks like this:
<form action="" method="POST" name="app_branding" enctype="multipart/form-data">
{{ form.csrf_token }}
{{ form.brand.label }} {{ form.brand }}
{{ form.logo_image.label }} {{ form.logo_image }}
{{ form.title_text.label }} {{ form.title_text }}
{{ form.first_paragraph.label }} {{ form.first_paragraph }}
{{ form.faq.label }} {{ form.faq }}
{{ form.privacy_policy.label }} {{ form.privacy_policy }}
{{ form.success_message.label }} {{ form.success_message }}
{{ form.submit.label }} {{ form.submit }}
</form>
For debugging, in my view, I put:
#expose('/', methods=['GET', 'POST'])
def index(self):
form = BrandForm(request.form)
print(form.validate())
print(form.errors)
print("request.files")
print(request.files)
And in the console I get the message that logo_image is required, even though it is there in request.files:
False
{'logo_image': ['This field is required.']}
request.files
ImmutableMultiDict([('logo_image', <FileStorage: u'20140725_095232.jpg' ('image/jpeg')>)])
How do I get the FileRequired() method to detect the file?
request.form only contains form input data. request.files contains file upload data. You need to pass the combination of both to the form. Since your form inherits from Flask-WTF's Form (now called FlaskForm), it will handle this automatically if you don't pass anything to the form.
form = BrandForm()
if form.validate_on_submit():
...
Without Flask-WTF, use a CombinedMultiDict to combine the data and pass that to the form.
from werkzeug.datastructures import CombinedMultiDict
form = BrandForm(CombinedMultiDict((request.files, request.form)))
if request.method == 'POST' and form.validate():
...
I made a flask_wtf Form with this field:
logo_image = FileField('logo_image', validators=[FileRequired(), FileAllowed(['jpg', 'png'], 'Images only!')])
My form looks like this:
<form action="" method="POST" name="app_branding" enctype="multipart/form-data">
{{ form.csrf_token }}
{{ form.brand.label }} {{ form.brand }}
{{ form.logo_image.label }} {{ form.logo_image }}
{{ form.title_text.label }} {{ form.title_text }}
{{ form.first_paragraph.label }} {{ form.first_paragraph }}
{{ form.faq.label }} {{ form.faq }}
{{ form.privacy_policy.label }} {{ form.privacy_policy }}
{{ form.success_message.label }} {{ form.success_message }}
{{ form.submit.label }} {{ form.submit }}
</form>
For debugging, in my view, I put:
#expose('/', methods=['GET', 'POST'])
def index(self):
form = BrandForm(request.form)
print(form.validate())
print(form.errors)
print("request.files")
print(request.files)
And in the console I get the message that logo_image is required, even though it is there in request.files:
False
{'logo_image': ['This field is required.']}
request.files
ImmutableMultiDict([('logo_image', <FileStorage: u'20140725_095232.jpg' ('image/jpeg')>)])
How do I get the FileRequired() method to detect the file?
request.form only contains form input data. request.files contains file upload data. You need to pass the combination of both to the form. Since your form inherits from Flask-WTF's Form (now called FlaskForm), it will handle this automatically if you don't pass anything to the form.
form = BrandForm()
if form.validate_on_submit():
...
Without Flask-WTF, use a CombinedMultiDict to combine the data and pass that to the form.
from werkzeug.datastructures import CombinedMultiDict
form = BrandForm(CombinedMultiDict((request.files, request.form)))
if request.method == 'POST' and form.validate():
...