在Python的Flask框架下收发电子邮件的教程


Posted in Python onApril 21, 2015

 简述

在大多数此类教程中都会不遗余力的介绍如何使用数据库。今天我们对数据库暂且不表,而是来关注另一个在web应用中很重要的特性:如何推送邮件给用户。

在某个轻量级应用中我们可能会添加一个如下的邮件服务功能:当用户有了新的粉丝后,我们发送一封邮件通知用户。有很多方法可以实现这个特性,而我们希望提供出一种可复用的通用框架来处理。
 
Flask-Mail介绍

对于我们来说是幸运的,现在已经有很多外部插件来处理邮件,虽说不能百分百按照我们的想法去处理,但已经相当接近了。 

在虚拟环境中安装 Flask-Mail是相当简单的。Windows以外的用户可以利用以下命令来安装:
 

flask/bin/pip install flask-mail

Windows用户的安装稍有不同,因为Flask-Mail所使用的一些模块不能再Windows系统上运行,你可以使用以下命令: 
 

flask\Scripts\pip install --no-deps lamson chardet flask-mail

配置:

回想一下前文中单元测试部分的案例,我们通过添加配置支持了一个这样的功能:当应用的某个版本测试出错时可以邮件通知我们。从这个例子就可以看出如何配置使用邮件支持。

再次提醒大家,我们需要设置两个方面的内容:

  •     邮件服务器信息
  •     用户邮箱地址

如下正是前文中所用到的配置

 

# email server
MAIL_SERVER = 'your.mailserver.com'
MAIL_PORT = 25
MAIL_USE_TLS = False
MAIL_USE_SSL = False
MAIL_USERNAME = 'you'
MAIL_PASSWORD = 'your-password'
 
# administrator list
ADMINS = ['you@example.com']

其中并没有设置切实可用的邮件服务器和邮箱。现在我们通过一个例子来看如何使用gmail邮箱账户来发送邮件:

 

# email server
MAIL_SERVER = 'smtp.googlemail.com'
MAIL_PORT = 465
MAIL_USE_TLS = False
MAIL_USE_SSL = True
MAIL_USERNAME = 'your-gmail-username'
MAIL_PASSWORD = 'your-gmail-password'
 
# administrator list
ADMINS = ['your-gmail-username@gmail.com']

另外我们也可以初始化一个Mail对象来连接SMTP邮件服务器,发送邮件:
 

from flask.ext.mail import Mail
mail = Mail(app)

发个邮件试试!

为了了解flask-mail如何工作的,我们可以从命令行发一封邮件看看。进入python shell并执行如下的脚本:
 

>>> from flask.ext.mail import Message
>>> from app import mail
>>> from config import ADMINS
>>> msg = Message('test subject', sender = ADMINS[0], recipients = ADMINS)
>>> msg.body = 'text body'
>>> msg.html = '<b>HTML</b> body'
>>> mail.send(msg)

上面这段代码会根据inconfig.py中配置的邮箱地址列表,以首个邮箱作为发件人给所有邮箱发送一封邮件。邮件内容会以文本和html两种格式呈现,而你能看到哪种格式取决于你的邮件客户端。

多么简单小巧!你完全可以现在就把它集成到你的应用中。

邮件框架

我们现在可以编写一个帮助函数来发送邮件。这是以上测试中一个通用版的测试。我们把这个函数放进一个新的原文件中用作邮件支持(fileapp/emails.py):
 

from flask.ext.mail import Message
from app import mail
 
def send_email(subject, sender, recipients, text_body, html_body):
 msg = Message(subject, sender, recipients)
 msg.body = text_body
 msg.html = html_body
 mail.send(msg)

Flask-Mail的邮件支持超出了我们目前的使用范围,像密件抄送和附件的功能并不会在此应用中得以使用。

Follower 提醒

现在,我们已经有了发邮件的基本框架,我们可以写发送follower提醒的函数了 (fileapp/emails.py):
 

from flask import render_template
from config import ADMINS
 
def follower_notification(followed, follower):
 send_email("[microblog] %s is now following you!" % follower.nickname,
  ADMINS[0],
  [followed.email],
  render_template("follower_email.txt",
   user = followed, follower = follower),
  render_template("follower_email.html",
   user = followed, follower = follower))

你在这里找到任何惊喜了吗?我们的老朋友render_template函数有一次出现了。

如果你还记得,我们使用这个函数在views渲染模版. 就像在views里写html不好一样,使用邮件模版是理想的选择。我们要可能的将逻辑和表现分开,所以email模版也会和其它试图模版一起放到在模版文件夹里.

所以,我们需要为follower提醒邮件写纯文本和网页版的邮件模版,下面这个是纯文本的版本 (fileapp/templates/follower_email.txt):
 

Dear {{user.nickname}},
 
{{follower.nickname}} is now a follower. Click on the following link to visit {{follower.nickname}}'s profile page:
 
{{url_for("user", nickname = follower.nickname, _external = True)}}
 
Regards,
 
The microblog admin

下面这个是网页版的邮件,效果会更好(fileapp/templates/follower_email.html):
 

<p>Dear {{user.nickname}},</p>
<p><a href="{{url_for("user", nickname = follower.nickname, _external = True)}}">{{follower.nickname}}</a> is now a follower.</p>
<table>
 <tr valign="top">
  <td><img src="{{follower.avatar(50)}}"></td>
  <td>
   <a href="{{url_for('user', nickname = follower.nickname, _external = True)}}">{{follower.nickname}}</a><br />
   {{follower.about_me}}
  </td>
 </tr>
</table>
<p>Regards,</p>
<p>The <code>microblog</code> admin</p>

注解:模版中的url_for函数的 _external = True 参数的意义.默认情况下,url_for 函数生成url是相对我们的域名的。例如,url_for("index")函数返回值是/index, 但是,发邮件是我们想要http://localhost:5000/index这种url,email中是没有域上下文的,所以,我们必须强制生成带域名的url,_external argument就是干这个工作的。

最后一步是处理“follow”过程,即触发邮件提醒时的视图函数,(fileapp/views.py):
 

from emails import follower_notification
 
@app.route('/follow/<nickname>')
@login_required
def follow(nickname):
 user = User.query.filter_by(nickname = nickname).first()
 # ...
 follower_notification(user, g.user)
 return redirect(url_for('user', nickname = nickname))

现在你可以创建两个用户(如果还没有用户的话)尝试着用让一个用户follow另一个用户,理解邮件提醒是怎样工作的。

就是这样吗?我们做完了吗?

我们可能心底里很兴奋完成了这项工作并且把邮件提醒功能同未完成列表里删除。

但是,如果你现在测试下应用,你会发现当你单击follow链接的时候,页面会2到3秒才会响应,浏览器才会刷新,这在之前是没有的。 
 
发生了什么?

问题是,Flask-Mail 使用同步模式发送电子邮件。 从电子邮件发送开始,直到电子邮件交付后,给浏览器发回其响应,在整个过程中,Web服务器会一直阻塞。如果我们试图发送电子邮件到一个服务器是缓慢的,甚至更糟糕的,暂时处于脱机状态,你能想象会发生什么吗?很不好。

这是一个可怕的限制,发送电子邮件应该是后台任务且不会干扰Web服务器,让我们看看我们如何能够解决这个问题。

Python中执行异步调用

我们想send_email 函数发完邮件后立即返回,需要让发邮件移动到后台进程来异步执行。

事实上python已经对异步任务提供了支持,但实际上,还可以用其他的方式,比如线程和多进程模块也可以实现异步任务。

每当我们需要发邮件的时候,启动一个线程来处理,比启动一个全新的进程节省资源。所以,让我们将mail.send(msg)调用放到另一个线程中。(fileapp/emails.py):
 

from threading import Thread
 
def send_async_email(msg):
 mail.send(msg)
 
def send_email(subject, sender, recipients, text_body, html_body):
 msg = Message(subject, sender = sender, recipients = recipients)
 msg.body = text_body
 msg.html = html_body
 thr = threading.Thread(target = send_async_email, args = [msg])
 thr.start()

如果你测试‘follow‘函数,现在你会发现浏览器在发送邮件之前会刷新。

所以,我们已经实现了异步发送,但是,如果未来在别的需要异步功能的地方难道我们还需要在实现一遍吗?

过程都是一样的,这样就会在每一种情况下都有重复代码,这样非常不好。

我们可以通过 decorator改进代码。使用装饰器的代码是这样的: 
 

from decorators import async
 
@async
def send_async_email(msg):
 mail.send(msg)
 
def send_email(subject, sender, recipients, text_body, html_body):
 msg = Message(subject, sender = sender, recipients = recipients)
 msg.body = text_body
 msg.html = html_body
 send_async_email(msg)

更好了,对不对?

实现这种方式的代码实际上很简单,创建一个新源文件(fileapp/decorators.py):
 

from threading import Thread
 
def async(f):
 def wrapper(*args, **kwargs):
  thr = Thread(target = f, args = args, kwargs = kwargs)
  thr.start()
 return wrapper

现在我们对异步任务创建了个有用的框架(framework), 我们可以说已经完成了! 

仅仅作为一个练习,让我们思考一下为什么这个方法会看上去使用了进程而不是线程。我们并不想每当我们需要发送一封邮件时就有一个进程被启动,所以我们能够使用thePoolclass而不用themultiprocessingmodule。这个类会创建指定数量的进程(这些都是主进程的子进程),并且这些子进程会通过theapply_asyncmethod送到进程池,等待接受任务去工作。这可能对于一个繁忙的网站会是一个有趣的途径,但是我们目前仍将维持现在线程的方式。
 
最后的话
这个 updatedmicroblogapplication 的源代码如下:

下载 microblog-0.11.zip 。

Python 相关文章推荐
Python实例分享:快速查找出被挂马的文件
Jun 08 Python
Python自动重试HTTP连接装饰器
Apr 28 Python
python嵌套字典比较值与取值的实现示例
Nov 03 Python
基于Django contrib Comments 评论模块(详解)
Dec 08 Python
解决python 输出是省略号的问题
Apr 19 Python
python多进程提取处理大量文本的关键词方法
Jun 05 Python
Python使用type关键字创建类步骤详解
Jul 23 Python
python hash每次调用结果不同的原因
Nov 21 Python
Pycharm学生免费专业版安装教程的方法步骤
Sep 24 Python
python爬虫 requests-html的使用
Nov 30 Python
python 实现客户端与服务端的通信
Dec 23 Python
详解Pycharm第三方库的安装及使用方法
Dec 29 Python
在Python的Flask框架中实现全文搜索功能
Apr 20 #Python
Python的Flask框架中实现分页功能的教程
Apr 20 #Python
在Python的Flask框架中实现单元测试的教程
Apr 20 #Python
Python的Flask框架中实现登录用户的个人资料和头像的教程
Apr 20 #Python
Python的Flask框架中实现简单的登录功能的教程
Apr 20 #Python
Python的Flask框架与数据库连接的教程
Apr 20 #Python
Python的Flask框架中web表单的教程
Apr 20 #Python
You might like
php实现文件编码批量转换
2014/03/10 PHP
PHP开发之用微信远程遥控服务器
2018/01/25 PHP
PHP操作Redis数据库常用方法示例
2018/08/25 PHP
javascript 动态table添加colspan\rowspan 参数的方法
2009/07/25 Javascript
基于jQuery的获得各种控件Value的方法
2010/11/19 Javascript
JavaScript去掉空格的方法集合
2010/12/28 Javascript
js特殊字符转义介绍
2013/11/05 Javascript
控制文字内容的显示与隐藏示例
2014/06/11 Javascript
js实现图片从左往右渐变切换效果的方法
2015/02/06 Javascript
分享两段简单的JS代码防止SQL注入
2016/04/12 Javascript
你有必要知道的10个JavaScript难点
2017/07/25 Javascript
jQuery实现可兼容IE6的滚动监听功能
2017/09/20 jQuery
移动端效果之Swiper详解
2017/10/09 Javascript
利用node.js如何创建子进程详解
2017/12/09 Javascript
nodejs的路径问题的解决
2018/06/30 NodeJs
Vue2.5学习笔记之如何在项目中使用和配置Vue
2018/09/26 Javascript
利用JavaScript将Excel转换为JSON示例代码
2019/06/14 Javascript
Vue.js自定义指令学习使用详解
2019/10/19 Javascript
Layui表格监听行单双击事件讲解
2019/11/14 Javascript
[54:27]TNC vs Serenity 2018国际邀请赛小组赛BO2 第一场 8.18
2018/08/19 DOTA
Python 文件重命名工具代码
2009/07/26 Python
用python实现面向对像的ASP程序实例
2014/11/10 Python
Python3实现的爬虫爬取数据并存入mysql数据库操作示例
2018/06/06 Python
numpy给array增加维度np.newaxis的实例
2018/11/01 Python
对Python3 pyc 文件的使用详解
2019/02/16 Python
Python中一般处理中文的几种方法
2019/03/06 Python
PYQT5实现控制台显示功能的方法
2019/06/25 Python
Python 分布式缓存之Reids数据类型操作详解
2020/06/24 Python
基于Python实现全自动下载抖音视频
2020/11/06 Python
美国电子产品主要品牌的授权在线零售商:DataVision
2019/03/23 全球购物
网站设计师的岗位职责
2013/11/21 职场文书
决心书标准格式
2014/03/11 职场文书
幼儿园小班教师寄语
2014/04/03 职场文书
师德师风建设整改措施思想汇报
2014/10/11 职场文书
酒吧七夕情人节宣传语
2015/11/24 职场文书
css position fixed 左右双定位的实现代码
2021/04/29 HTML / CSS