1

许多类型的应用程序都会在某些事件发生的时候通知用户,常用的沟通方法就是电子邮件。尽管在Flask应用程序中,可以使用Python标准库中的smtplib包来发送电子邮件,不过Flask-Mail扩展封装了smtplib且与Flask整合的非常好。

1、使用Flask-Mail作为邮件支持

使用pip安装Flask-Mail:

(venv) $ pip install flask-mail

扩展连接到一个简单邮件传输协议(SMTP)服务器并将邮件传递给它由它递送。如果没有给出配置,Flask-Mail则连接到localhost25端口并发送无验证的电子邮件。表6-1所示的配置键列表可以用来配置SMTP服务器。

表格6-1. Flask-Mail SMTP服务器配置键

在开发过程中如果能连接到一个外部SMTP服务器会更方便。示例6-1展示了如何配置应用程序通过谷歌的Gmail帐户发送电子邮件。

示例6-1. hello.py:Flask-Mail配置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')

建议:永远不要将账户证书直接写在你的脚本里面,尤其是如果你打算将你的的工作开源。为了保护你的帐户信息,必须让脚本从你的配置环境中导入敏感信息。

示例6-2展示了Flask-Mail的初始化。

示例6-2. hello.py:Flask-Mail初始化

from flask.ext.mail import Mail

mail = Mail(app)

持有email服务器用户名和密码的两个变量需要在环境中定义。如果你是使用Linux或Mac OS X上的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>
1.1、使用python shell发送邮件

为了测试配置,你可以开启一个shell会话并发送测试email:

(venv) $ python hello.py shell
>>> from flask.ext.mail import Message
>>> from hello import mail
>>> msg = Message('test subject', sender='you@example.com',
... recipients=['you@example.com'])
>>> msg.body = 'text body'
>>> msg.html = '<b>HTML</b> body'
>>> with app.app_context():
... mail.send(msg)
...

注意,Flask-Mail的send()函数使用current_app,所以它需要执行已激活的应用程序上下文。

1.2、集成邮件到应用程序

为了避免每次都手动创建电子邮件消息,将应用电子邮件发送功能的共同部分抽象到一个函数中是非常不错的做法。另一个好处是,这个函数可以用Jinja2模板尽情渲染。示例6-3展示了怎么实现。

示例6-3. hello.py:Email支持

from flask.ext.mail import Message 

app.config['FLASKY_MAIL_SUBJECT_PREFIX'] = '[Flasky]'
app.config['FLASKY_MAIL_SENDER'] = 'Flasky Admin <flasky@example.com>'

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(template + '.txt', **kwargs)
    msg.html = render_template(template + '.html', **kwargs)
    mail.send(msg)

为发件人的主题和地址定义前缀字符串的函数依赖于两个特定于应用程序的配置键。send_email函数携带目的地址、主题、邮件体模板和一组关键字参数。模板名不能有扩展,这样两个版本的模板可以使用纯文本或富文本。调用者传递关键字参数给render_template()调用,这样模板就可以生成email体。

index()视图函数可以非常容易的扩充用来发送一个email给管理员,当从表单中收到一个新的名字的时候。示例6-4展示所做的改动。

示例6-4. hello.py:Email示例

# ...
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_email(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环境变量中给出的email收件人会加载到同名的配置变量中。需要创建文本和HTML两个版本的email模板文件。这些文件都存储在template内的mail子目录中,让他们独立于普通模板。电子邮件模板需要给出用户作为该模板参数,因此send_email()调用包括用户来作为一个关键字参数。

建议:如果你有克隆在GitHub上的应用程序,你现在可以运行git checkout 6a来切换到这个版本的应用程序。

除了前面描述的MAIL_USERNAMEMAIL_PASSWORD环境变量,这个版本的应用程序需要FLASKY_ADMIN环境变量。对于Linux和Mac OS X用户来说,启动应用程序命令:

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

对于Windows用户,命令如下:

(venv) $ set FLASKY_ADMIN=<Gmail username>

使用这些环境变量设置,当你每次在表单中输入一个新的名字的时候都可以测试应用程序和接收电子邮件。

1.3、发送异步邮件

如果你发送一些测试邮件,你可能注意到mail.send()函数在发送电子邮件的时候会阻塞几秒钟,这段时间浏览器看起来没有响应。为了避免请求处理不必要的延误,可以将邮件发送功能移到一个后台线程去处理。示例6-5展示了以上改动。

示例6-5. hello.py:异步邮件支持

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(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()

建议:如果你有克隆在GitHub上的应用程序,你现在可以运行git checkout 6b来切换到这个版本的应用程序。

如果你现在运行应用程序,你会发现它响应更快了,但请记住,发送大量的电子邮件的应用程序,其拥有一个致力于发送电子邮件的服务比开启一个新的线程更合适。例如,执行send_async_email()函数可以将邮件发送到Celery的任务队列中。

本章完成的是大多数web应用程序必备的功能。现在的问题是,hello.py脚本开始大,这使得它变得更难管理。在下一章中,你将学习如何构建一个更大的应用程序。


wanyoung
2k 声望364 粉丝