Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
197 views
in Technique[技术] by (71.8m points)

python - Mail Not Sent In Gmail Account Using Flask-Mail

I am trying to add a functionality of resetting user password by sending email to a Gmail account. I have been following CoreySchafer YouTube Tutorial and Miguel Grinberg Tutorial to achieve this.

The overall idea is user will be prompted with a Password Reset Request Form where they will enter the email address for which they want to reset the password. After clicking the "Request Reset Password", a message will be displayed that an email has been sent to their Gmail account. By clicking the link from the email, the user will be able to reset their password.

The codes with relevant file names are as follows:

File: 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) 

File: 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")

File: 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))

File: 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)

File: 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 %}

File: 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 %}

I have saved the environment variables in .env file in the root directory.

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"

Also created the config.py file in the project root directory.

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 = ["[email protected]"]

Terminal Result:

"POST /reset_password HTTP/1.1" 302 -

I have also turned ON the "Less Secure Apps" for my Gmail Account, but the email still can't be sent. There is no error in the terminal during the execution of the Flask Application.

Looking forward for your kindest support.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Google blocks authentication to your account from insecure apps. You will need to manually enable ta access to your account using less secure app manager from your google account. Go through this link and set the Allow less secure apps: OFF status to Allow less secure apps: ON if it is disabled using the toggle switch:

Less secure apps access manager


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...