Flask学习(5)——电子邮件

只愿长相守 提交于 2020-02-02 01:52:43

在 Python 标准库中通常使用 smtplib 包发送电子邮件,而 Flask 中的 Flask-Mail 扩展不仅包装了 smtplib,且能更好的与 Flask 集成。首先在虚拟环境中安装此扩展:

pip install flask-mail

Flask-Mail文档:http://www.pythondoc.com/flask-mail/index.html

一、配置

Flask-Mail 连接到简单邮件传输协议(SMTP,simple mail transferprotocol)服务器,把邮件交给这个服务器发送。如果不进行配置,则 Flask-Mail 连接 localhost 上的 25 端口,无须验证身份即可发送电子邮件。

Flask-Mail SMTP 服务器配置:

配置 默认值 说明
MAIL_SERVER localhost 电子邮件服务器的主机名或 IP 地址
MAIL_PORT 25 电子邮件服务器的端口
MAIL_USE_TLS False 启用传输层安全(TLS,transport layer security)协议
MAIL_USE_SSL False 启用安全套接层(SSL,secure sockets layer)协议
MAIL_USERNAME None 邮件账户的用户名
MAIL_PASSWORD None 邮件账户的密码

实际中,连接到外部 SMTP 服务器更方便,如下例使用 qq邮箱的配置:

import os
# ...
app.config['MAIL_SERVER'] = 'smtp.qq.com'
app.config['MAIL_PORT'] = 465
app.config['MAIL_USE_SSL'] = True
app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME')
app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD')

注意qq邮箱需要先开启SMTP服务,并得到授权码:
在这里插入图片描述
MAIL_USERNAME为邮箱号,MAIL_PASSWORD 的值即为生成的授权码。

由于QQ邮箱不支持非加密的协议,那么使用加密协议,分为两种加密协议,选择其中之一即可

  • MAIL_USE_TLS:端口号是587
  • MAIL_USE_SSL:端口号是465

Flask-Mail在使用前也需要进行初始化:

from flask_mail import Mail
mail = Mail(app)

保存电子邮件服务器用户名和密码的两个环境变量要在环境中定义。如果你使用的是 Linux 或 macOS,可以按照下面的方式设定这两个变量:

export MAIL_USERNAME=<mail username>
export MAIL_PASSWORD=<mail password>

微软 Windows 用户可按照下面的方式设定环境变量:

# cmd终端
set MAIL_USERNAME=<mail username>
set MAIL_PASSWORD=<mail password>

# powershell终端
$env:MAIL_USERNAME='<mail username>'
$env:MAIL_PASSWORD='<mail password>'

二、在Python shell中发送电子邮件

打开一个 shell 会话(powershell),来发送一个测试邮件。

先配置一下环境变量,上面的方式定义的是临时环境变量,每个新shell都需要导入一次。

$env:FLASK_APP='.\hello.py'
$env:MAIL_USERNAME='your_email@qq.com'
$env:MAIL_PASSWORD='你的授权码'

然后打开 flask shell 进行测试:

>>> from flask_mail import Message
>>> from hello import mail
>>> msg = Message('test email', sender='your_email@qq.com',  recipients=['your_email@qq.com'])
>>> msg.body = 'This is the plain text body'
>>> msg.html = 'This is the <b>HTML</b> body'
>>> with app.app_context():
...     mail.send(msg)

注意:Flask-Mail 的 send() 函数使用 current_app ,因此要在激活的应用上下文中执行。

成功收到邮件:
在这里插入图片描述


三、在应用中集成邮件发送功能

一般把发送电子邮件的部分定义为一个函数,这样还可以使用 Jinja2 模板渲染邮件正文,灵活性高。

from flask_mail import Message

# 主题的前缀
app.config['FLASKY_MAIL_SUBJECT_PREFIX'] = '[Flasky]'
# 发件人地址
app.config['FLASKY_MAIL_SENDER'] = 'Flasky Admin <xxxx@qq.com>'

def sned_email(to, subject, template, **kwargs):
    msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX'] + subject,
                  sender=app.config['FLASKY_MAIL_SENDER'], recipients=[to])
    msg.body = render_template(template + '.txt', **kwargs)
    msg.html = render_template(template + '.html', **kwargs)
    mail.send(msg)
  • send_mail() 函数的参数分别为收件人地址(to)、主题(subject)、渲染邮件正文的模板(template)、关键字参数列表(**kwargs)。
  • 指定模板时不包含扩展名,这样才能使用两个模板分别渲染txt和HTML。
  • 调用者传入
    的关键字参数将传给 render_template() 函数,作为模板变量提供给模板使用,用于生成电子邮件正文。

下面我们修改视图函数 index(),使表单每接收到新的名字,应用就给管理员发送一封电子邮件,修改hello.py如下:

# ...
app.config['FLASKY_ADMIN'] = os.environ.get('FLASKY_ADMIN')
# ...
@app.route('/', methods=['GET', 'POST'])
def index():
    form = NameForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.name.data).first()
        if user is None:
            user = User(username=form.name.data)
            db.session.add(user)
            db.session.commit()
            session['known'] = False
            # 发送电子邮件
            if app.config['FLASKY_ADMIN']:
                sned_email(app.config['FLASKY_ADMIN'], 'New User',
                          'mail/new_user', user=user)
        else:
            session['known'] = True
        session['name'] = form.name.data
        session['message'] = user.message
        form.name.data = ''
        return redirect(url_for('index'))
    return render_template('index.html',
                           form=form, name=session.get('name'),
                           known=session.get('known', False),
                           message=session.get('message'))

templates/mai/new_user.txt:

User {{ user.username }} has joined.

templates/mai/new_user.html:

User <b>{{ user.username }}</b> has joined.
  • 电子邮件的收件人地址保存在环境变量 FLASKY_ADMIN 中,启动前需要导入此环境变量,方法和前面的相同。
  • 我们还需要创建两个模板文件,分别用于渲染纯文本和HTML版本的邮件正文。这两个模板文件都保存在 templates 目录下的 mail 子目录中。
  • 电子邮件的模板中有一个模板参数是用户,因此调用 send_email() 函数时要以关键字参数的形式传入用户。

现在每次你在表单中填写新名字(如email test),管理员(FLASKY_ADMIN)都会收到一封电子邮件。

在这里插入图片描述


四、异步发送电子邮件

在上面的例子中,我们发现在发送电子邮件的时候,网页会停滞一会,为了避免用户感觉到这样的延迟,可以把发送电子邮件的函数移到后台线程中,修改方法如下:

from threading import Thread

# ...
def send_async_email(app, msg):
    with app.app_context():
        mail.send(msg)


def sned_email(to, subject, template, **kwargs):
    msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX'] + subject,
                  sender=app.config['FLASKY_MAIL_SENDER'], recipients=[to])
    msg.body = render_template(template + '.txt', **kwargs)
    msg.html = render_template(template + '.html', **kwargs)
    thr = Thread(target=send_async_email, args=[app, msg])
    thr.start()
    return thr

很多 Flask 扩展都假设已经存在激活的应用上下文和(或)请求上下文。Flask-Mail 的 send() 函数使用 current_app ,因此必须激活应用上下文。

不过,上下文是与线程配套的,在不同的线程中执行 mail.send() 函数时,要使用 app.app_context() 人工创建应用上下文。app 实例作为参数传入线程,因此可以通过它来创建上下文。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!