Flask Web DEvelopment 翻译5

第六章 Email

很多程序在一些特定事件触发时需要通知用户,最常见的方法就是email。虽然Python标准库中的smtplib就可以用来实现这一功能,但Flask-Email扩展更好的包装并集成了smtplib。

Flask-Email实现Email支持

通过pip安装Flask-Email:

(venv) $ pip install flask-mail

该扩展连接到一个smtp(简单邮件传输协议)服务器,并把email传递给它,由其进行分发。如果没有配置,Flask-Email将默认连接到localhost的25端口,并无需认证就发送。(<small>注:在开发服务器模式下,邮件将无法发送,因为localhost只是一个web服务器。</small>)
表6-1展示了配置smtp服务器时可用的配置键名。

Table 6-1. Flask-Mail SMTP server configuration keys
*键名*               *默认值*       *说明*
MAIL_HOSTNAME        localhost     email服务器的主机名或IP地址
MAIL_PORT           25            email服务器的端口号
MAIL_USE_TLS          False      使用安全传输层协议 (TLS)
MAIL_USE_SSL          False         使用安全套接字层(SSL)
MAIL_USERNAME         None          Mail帐户的帐户名
MAIL_PASSWORD         None          Mail帐户的密码

在开发过程中,连接到外部smtp服务器可能会更方便。例子6-1展示了如何配置程序来通过google的Gmail账户来发送邮件。

Example 6-1. hello.py: Flask-Mail configuration for 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可以如例子6-2这样初始化:

Example 6-2. hello.py: Flask-Mail initialization
from flask.ext.mail import Mail
mail = Mail(app)

你应该在环境变量中定义email服务器的用户名和密码。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中发送Email

为了测试配置,你可以启动shell会话并发送测试邮件:

(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-email的send()函数中使用了current_app,也就是说这一函数必须在活动的应用程序上下文中执行。

在程序中集成Email

为了避免每次都手动创建email消息,最好是把程序的邮件发送功能的通用部分抽出来独立成一个函数。附加好处就是,这个函数能以jinja2模板的方式更灵活地组织邮件内容。例子6-3实现了这一功能:

Example 6-3. hello.py: Email support
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函数携带了目标地址,主题,email正文模板一个列表参数。模板名字不带扩展名,这两个版本的模板分别用来对应纯文本和富文本的正文。调用者把关键字|键值参数(kwargs)传递给render_template(),以便模板用来生成邮件正文。
  经过简单扩展之后,index()视图函数一旦接收到一个新名字,就能给管理员发送邮件。例子6-4显示了这一变更。

Example 6-4. hello.py: Email example
# ...
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))

在程序启动的时候,邮件接收者——FLASK-ADMIN,从环境变量中被导入到配置的同名变量中。我们还需要创建纯文本和html两个邮件模板——它们保存在templates文件夹的mail子文件夹里,便于与其它模板区分开来。邮件模板需要把用户名做为模板参数传递过来,所以调用send_mail()时需要包含着那个关键字|键值参数。
  除了之前设置的MAIL_USERNAME和MAIL_PASSWORD环境变量之外,此处还需要再设置FLASMKY_ADMIN环境变量,Linux和MacOS用户可使用如下命令行:

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

windows用户则可如下操作:

(venv) $ set FLASKY_ADMIN=<Gmail username>

设置好后,你可以进行测试,每当你输入一个新用户名,你都会收到一封邮件。

异步发送邮件

发送几次测试邮件之后,你可能会注意到,在发送邮件时,mail.send()函数总是会阻塞几秒,这时候浏览器看上去就没有响应了。为了避免出现处理请求期间的不必要的延迟,应该把邮件发送函数转移到后台线程。请看例子6-5:

Example 6-5. hello.py: Asynchronous email support
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()来手工指定这一激活的上下文。
  如果你现在再进行测试,就会发现响应性得以改进了。但要记住一点,对于一个要发送大量邮件的程序来说,把发送邮件单独成一个工作比为每个邮件启动一个新线程更合理。例如:执行send_async_email()函数调用不如使用Celery任务队列来做。
  到本章为止,我们已经概略完成了大部分web应用具备的功能。现在的问题就是hello.py脚本已经开始变大,难以维护。下一章,我们将学习如何架构大型的程序。
<<第五章 Flask与数据库 第七章 大型程序架构>>

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,056评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,842评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,938评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,296评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,292评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,413评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,824评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,493评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,686评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,502评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,553评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,281评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,820评论 3 305
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,873评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,109评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,699评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,257评论 2 341

推荐阅读更多精彩内容