Письмо не отправлено в учетной записи Gmail с помощью Flask-Mail
Я пытаюсь добавить функцию сброса пароля пользователя, отправив электронное письмо на учетную запись Gmail. Чтобы добиться этого, я слежу за учебником CoreySchafer на YouTube и учебником Мигеля Гринберга.
Общая идея заключается в том, что пользователю будет предложено заполнить форму запроса на сброс пароля, где он будет вводить адрес электронной почты, для которого они хотят сбросить пароль. После нажатия кнопки "Запросить сброс пароля" отобразится сообщение о том, что на их учетную запись Gmail было отправлено электронное письмо. Щелкнув ссылку в письме, пользователь сможет сбросить свой пароль.
Коды с соответствующими именами файлов следующие:
Файл: routes.py
# Reset Password Request Route
@app.route("/reset_password", methods = ["GET", "POST"])
def reset_password_request():
if current_user.is_authenticated:
return redirect(url_for("dashboard"))
form = ResetPasswordRequestForm()
if form.validate_on_submit():
user = User.query.filter_by(email = form.email.data).first()
if user:
send_password_reset_email(user)
flash("Please check your email for the instructions to reset your password")
return redirect(url_for("login"))
return render_template("reset_password_request.html", title = "Request For Reset Password", form = form)
# Password Reset Route
@app.route("/reset_password/<token>", methods = ["GET", "POST"])
def reset_password(token):
if current_user.is_authenticated:
return redirect(url_for("dashboard"))
user = User.verify_reset_password_token(token)
if not user:
flash("Invalid Token")
return redirect(url_for("reset_password_request"))
form = ResetPasswordForm()
if form.validate_on_submit():
user.set_password(form.password.data)
db.session.commit()
flash("Congratulations! Your password has been reset successfully.")
return redirect(url_for("login"))
return render_template("reset_password.html", title = "Reset Password", form = form)
Файл: forms.py
# Reset Password Request Form
class ResetPasswordRequestForm(FlaskForm):
email = StringField("Email", validators = [DataRequired(message = "Email Address is required."), Email()], render_kw = {"placeholder": "Email Address"})
submit = SubmitField("Request Password Reset")
def validate_email(self, email):
user = User.query.filter_by(email = email.data).first()
if user is None:
raise ValidationError("There is no account with that email. You must register first.")
# Password Reset Form
class ResetPasswordForm(FlaskForm):
password = PasswordField("Password", validators = [DataRequired(message = "Password is required.")], render_kw = {"placeholder": "Password"})
confirm_password = PasswordField("Repeat Password", validators = [DataRequired(message = "Password Confirmation is required."), EqualTo("password")], render_kw = {"placeholder": "Confirm Password"})
submit = SubmitField("Reset Password")
Файл: email.py
from flask import render_template
from flask_mail import Message
from app import app, mail
from threading import Thread
# Sending Emails Asynchronously
def send_async_email(app, msg):
with app.app_context():
mail.send(msg)
# Email Sending Wrapper Function
def send_email(subject, sender, recipients, text_body, html_body):
msg = Message(subject, sender = sender, recipients = recipients)
msg.body = text_body
msg.html = html_body
Thread(target = send_async_email, args = (app, msg)).start()
# Send Password Reset Email Function
def send_password_reset_email(user):
token = user.get_reset_password_token()
send_email("【Task Manager】Reset Your Password",
sender = app.config["ADMINS"][0],
recipients = [user.email],
text_body = render_template("email/reset_password.txt", user = user, token = token),
html_body = render_template("email/reset_password.html", user = user, token = token))
Файл: models.py
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
# Reset Password Support in User Model
def get_reset_password_token(self, expires_sec = 1800):
s = Serializer(app.config["SECRET_KEY"], expires_sec)
return s.dumps({"user_id": self.id}).decode("utf-8")
# Verifying Reset Password Token in User Model
@staticmethod
def verify_reset_password_token(token):
s = Serializer(app.config["SECRET_KEY"])
try:
user_id = s.loads(token)["user_id"]
except:
return None
return User.query.get(user_id)
Файл: reset_password_request.html
{% extends "layout.html" %}
{% block content %}
<h1>Task Manager</h1>
<form action="" method="POST">
{{ form.hidden_tag() }}
<div>
{{ form.email(size=64) }}
</div>
{% for error in form.email.errors %}
<span style="color: red;">{{ error }}</span>
{% endfor %}
<div>
{{ form.submit() }}
</div>
</form>
{% endblock %}
Файл: reset_password.html
{% extends "layout.html" %}
{% block content %}
<h1>Task Manager</h1>
<form action="" method="POST" novalidate>
{{ form.hidden_tag() }}
<div>
{{ form.password(size=32) }}
</div>
{% for error in form.password.errors %}
<span style="color: red;">{{ error }}</span>
{% endfor %}
<div>
{{ form.confirm_password(size=32) }}
</div>
{% for error in form.confirm_password.errors %}
<span style="color: red;">{{ error }}</span>
{% endfor %}
<div>
{{ form.submit() }}
</div>
</form>
{% endblock %}
Я сохранил переменные среды в файле.env в корневом каталоге.
SECRET_KEY="simple-is-better-than-complex"
MAIL_SERVER="smtp.googlemail.com"
MAIL_PORT=587
MAIL_USE_TLS=True
MAIL_USERNAME="jeet.java.13"
MAIL_PASSWORD="pass123"
Также создал файл config.py в корневом каталоге проекта.
from dotenv import load_dotenv
load_dotenv(override=True)
import os
basedir = os.path.abspath(os.path.dirname(__file__))
# Application Configurations
class Config(object):
# Function: Getting Environment Variables
def get_env_var(name):
try:
return os.environ[name]
except KeyError:
message = "Expected Environment Variable '{}' Not Set!".format(name)
raise Exception(message)
# SECRET_KEY Configuration
SECRET_KEY = os.getenv("SECRET_KEY")
SQLALCHEMY_DATABASE_URI = "sqlite:///" + os.path.join(basedir, "tms.db")
SQLALCHEMY_TRACK_MODIFICATIONS = False
# EMAIL CONFIGURATION
MAIL_SERVER = os.getenv("MAIL_SERVER")
MAIL_PORT = int(os.getenv("MAIL_PORT")) or 25
MAIL_USE_TLS = os.getenv("MAIL_USE_TLS")
MAIL_USERNAME = os.getenv("MAIL_USERNAME")
MAIL_PASSWORD = os.getenv("MAIL_PASSWORD")
ADMINS = ["jeet.java.13@gmail.com"]
Конечный результат:
"POST /reset_password HTTP/1.1" 302 -
Я также включил "менее безопасные приложения" для своей учетной записи Gmail, но письмо по-прежнему не может быть отправлено. Во время выполнения приложения Flask в терминале нет ошибки.
Жду вашей самой доброй поддержки.
3 ответа
Задача решена. При определении переменных среды MAIL_USERNAME должен быть полным адресом электронной почты (Gmail); не только Gmail ID.
Google блокирует аутентификацию вашей учетной записи из небезопасных приложений. Вам нужно будет вручную разрешить доступ к вашей учетной записи с помощью менее безопасного диспетчера приложений из вашей учетной записи Google. Перейдите по этой ссылке и установите статус Разрешить менее безопасные приложения: ВЫКЛ на Разрешить менее безопасные приложения: ВКЛ, если он отключен с помощью переключателя:
У меня была аналогичная проблема в моем предыдущем приложении, что-то происходит с настройкой flask-mail. я считаю, что это MAIL_SUPPRESS_SEND или что-то в этом роде. проверьте здесь Flask-Mail не отправляет электронные письма, об ошибках не сообщается