一. 在应用中集成电子邮件发送功能
首先把应用发送电子邮件的部分抽象出来,作为单独的函数:
app.py 电子邮件支持:
# 添加发送电子邮件时应用层面的配置项
app.config['FLASK_MAIL_SUBJECT_PREFIX'] = '[FLASK APP]'
app.config['FLASK_MAIL_SENDER'] = 'Flask Admin <{}>'.format(os.environ.get('MAIL_USERNAME'))
app.config['FLASK_ADMIN'] = os.environ.get('FLASK_ADMIN')
def send_email(to, subject, template, **kwargs):
msg = Message(app.config['FLASK_MAIL_SUBJECT_PREFIX'] + subject,
sender=app.config['FLASK_MAIL_SENDER'], recipients=[to])
msg.body = render_template(template + '.txt', **kwargs)
msg.html = render_template(template + ".html", **kwargs)
mail.send(msg)
这个函数用到了3个应用层面的配置项:
- FLASK_MAIL_SUBJECT_PREFIX:邮件主题的前缀;
- FLASK_MAIL_SENDER:发件人的地址;
- FLASK_ADMIN:FLASK应用的管理员,用于接收邮件的email账号;
send_email的参数分别为:收件人地址、主题前缀、渲染邮件正文的模板以及渲染模板需要的关键字;模板的位置:
模板的内容:
# new_user.html
User <b>{{ user.username }}</b> has joined.
# new_user.txt
User {{ user.username }} has joined.
在flask shell中测试send_email函数:
打开收件箱查看:
现在可以扩展视图函数:每当表单收到新的名字,应用就给管理员发送一封电子邮件:
@app.route('/', methods=['POST', 'GET'])
def hello_world():
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['know'] = False
# 发送电子邮件给管理员
if app.config.get('FLASK_ADMIN') is not None:
send_email(app.config['FLASK_ADMIN'], 'New User', 'mail/new_user', user=user)
else:
session['know'] = True
old_name = session.get('name')
if old_name is not None and old_name != form.name.data:
flash('looks like you have changed your name!')
session['name'] = form.name.data
form.name.data = ''
return redirect(url_for('hello_world'))
return render_template('hello.html', form=form, name=session.get('name'), know=session.get('know'))
输入一个数据库中不存在的姓名:
二. 异步发送电子邮件
在上述的测试过程中,我们会发现mail.send()函数在发送电子邮件时停滞了几秒钟,在这个过程中,浏览器就像无响应一样。为了在处理请求的过程中避免不必要的延迟,我们可以把发送电子邮件的函数移到后台线程中。
app.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['FLASK_MAIL_SUBJECT_PREFIX'] + subject,
sender=app.config['FLASK_MAIL_SENDER'], recipients=[to])
msg.body = render_template(template + '.txt', **kwargs)
msg.html = render_template(template + ".html", **kwargs)
# mail.send(msg)
thr = Thread(target=send_async_email, args=[app, msg])
thr.start()
return thr
再次测试发现,客户端会0卡顿立即得到响应:
注意:
- 很多Flask扩展都假设已经存在激活的应用上下文(或请求上下文)。在使用终端测试Flask-Mail时,提到Flask-Mail的send函数使用current_app,因此必须激活应用的上下文。
- 上下文是与线程配套的,在不同的线程中执行mail.send()函数时,必须使用app.app_context()创建应用上下文。app实例作为参数传入线程,可以通过它来创建上下文。
- 应用要发送大量电子邮件时,使用专门发送电子邮件的作业要比给每封邮件都新建一个线程更合适。例如:我们可以把send_async_email()函数的操作发送给Celery任务队列。
目前为止,app.py已经变得越来越大,难以维护。原书的第一章将开始讲述组织Flask大型应用的架构。