通信方式嘛,我想...那就电子邮件吧

很多类型的应用程序都需要在特定事件发生时提醒用户,常用的通信方式是电子邮件,虽然Python标准库中的smtplib包可用在Flask程序中发送电子邮件,但包装了smtplib的Flask-Mail拓展能更好的和Flask集成

使用Flask-Mail提供电子邮件支持

老套路,先安装:

(venv) pip install flask-mail

Flask-Mail连接到SMTP(简单邮件传输协议)服务器,并把邮件交给这个服务器发送,如果不进行配置,Flask-Mail会连接localhost上的端口25,无需验证即可发送电子邮件
下表是可用来设置SMTP服务器的配置:

配置默认值说明
MAIL_SERVERlocalhost电子邮件服务器的主机名和IP地址
MAIL_PORT25电子邮件服务器的端口
MAIL_USE_TLSFlase启用传输层安全(TLS)协议
MAIL_USE_SSLFlase启用安全套接层(SSL)协议
MAIL_USERNAMENone邮件帐户的用户名
MAIL_PASSWORDNone邮件帐户的密码

在开发过程中,如果连接到外部SMTP服务器,则可能更方便
下例是配置程序,用Gmail账户发送电子邮件:

import os
#...
app.config['MAIL_SERVER'] = 'smtp.googlemail.com'
app.config['MAIL_PORT'] = 587
app.config['MAIL_USE_TLS'] = True
app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME')
app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD')

千万不要把账户密令直接写入脚本,特别是开源自己的作品的时候,为了保护账户信息,需要让脚本从环境中导入敏感信息

Flask-Mail的初始方法如下:

from flask_mail import Mail
mail = Mail(app)

保存电子邮件服务器用户名和密码的两个环境变量要在环境中定义:
Linux或MAC中使用bash,如下:

(venv) $ export MAIL_USERNAME=<Gmail username>
(venv) $ export MAIL_PASSWORD=<gmail password>

Windows用户,如下:

(venv) $ set MAIL_USERNAME=<Gmail username>
(venv) $ set MAIL_PASSWORD=<Gmail password>

在Python shell中发送电子邮件

你可以打开一个shell会话,发送一封测试邮件,以检查配置是否正确:


>>> from flask_mail import Message
>>> from hello import mail
>>> msg = Message('test subject', sender='you@163.com',
...     recipients=['you@163.com'])
>>> msg.body = 'text body'
>>> msg.html = '<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@example.com>'

def send_mail(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函数的参数分别为收件人地址、主题、渲染邮件正文的模板和关键字参数列表,指定模板时不能包含拓展名,这样才能使用两个模板分别渲染纯文本正文和富文本正文,调用者将关键字参数传给render_template()参数,以便在模板中使用,进而生成电子邮件正文

index()视图函数很容易被拓展,这样每当表单接受新名字时,程序都会给管理员发送一封电子邮件,修改方法如下:

# ...
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)
            session['known'] = False
            if app.config['FLASKY_ADMIN']:
                send_mail(app.config['FLASKY_ADMIN'], 'New User',
                          'mail/new_user', user=user)
        else:
            session['known'] = True
        session['name'] = form.name.data
        form.name.data = ''
        return redirect(url_for('index'))
    return render_template('index.html', form=form, name=session.get('name'),
                           known=session.get('known', False))

电子邮件的收件人保存在环境变量FLASKY_ADMIN中,在程序启动过程中,它会加载到一个同名配置变量中,我们要创建两个模板文件,分别用于渲染纯文本和HTML版本的邮件正文,这两个模板文件都保存在templates文件夹下的mail子文件夹中,以便和普通模板区分开来,电子邮件的模板中要有一个模板参数是用户,因此调用send_email()函数时要以关键字参数的形式传入用户

除了前面提到环境变量MAIL_USERNAMEMAIL_PASSWORD之外,这个版本的程序还需要使用环境变量FLASKY_ADMIN,下面是添加方式:
Linux和Mac OS:

(venv) $ export FLASKY_ADMIN=<your-email-address>

Windows:

(venv) $ set FLASKY_ADMIN=<your-email-address>

设置好这些环境变量后,我们就可以测试程序了,每次你在表单中填写新名字时,管理员都会收到一封电子邮件

异步发送电子邮件

如果你发送了几封测试邮件, 可能会注意到mail.send()函数在发送电子邮件时停滞了几秒,在这个过程中,浏览器就像无响应一样,为了避免处理请求过程中不必要的延迟,我们可以把发送电子邮件的函数移到后台线程中,具体方法如下:

from threading import Thread

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

def send_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(tamplate + '.txt', **kwargs)
    msg.html = render_template(template + '.html', **kwargs)
    thr = Thread(targer=send_async_email, args=[app, msg])
    thr.start()
    return thr

上述代码涉及了一个有趣的问题,很多Flask拓展都假设已经存在激活的程序上下文和请求上下文,Flask-Mail中的send()函数使用current_app,因此必须激活程序上下文,不过在不同线程中执行mail.send()函数时,程序上下文要使用app.app_context()人工创建

现在再运行程序会发现流畅多了,不过程序发送大量电子邮件时,使用专门发送电子邮件的作业要比给每封邮件都新建一个线程更合适,我们可以把执行send_async_email()函数的操作发给Celery任务队列

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值