在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解析文件示例
Jan 23 Python
简单的python后台管理程序
Apr 13 Python
JSONLINT:python的json数据验证库实例解析
Nov 28 Python
Python多进程原理与用法分析
Aug 21 Python
python打包生成的exe文件运行时提示缺少模块的解决方法
Oct 31 Python
Python+OpenCV采集本地摄像头的视频
Apr 25 Python
pyQT5 实现窗体之间传值的示例
Jun 20 Python
python射线法判断检测点是否位于区域外接矩形内
Jun 28 Python
python开头的coding设置方法
Aug 08 Python
Python解释器以及PyCharm的安装教程图文详解
Feb 26 Python
如何在mac版pycharm选择python版本
Jul 21 Python
Python类绑定方法及非绑定方法实例解析
Oct 09 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相关资料
2006/10/09 PHP
php关于array_multisort多维数组排序的使用说明
2011/01/04 PHP
php学习笔记 php中面向对象三大特性之一[封装性]的应用
2011/06/13 PHP
WordPress中转义HTML与过滤链接的相关PHP函数使用解析
2015/12/22 PHP
PHP利用正则表达式将相对路径转成绝对路径的方法示例
2017/02/28 PHP
PHP微信网页授权的配置文件操作分析
2019/05/29 PHP
javascript innerHTML、outerHTML、innerText、outerText的区别
2008/11/24 Javascript
javascript 密码强弱度检测万能插件
2009/02/25 Javascript
23个超流行的jQuery相册插件整理分享
2011/04/25 Javascript
jquery为页面增加快捷键示例
2014/01/31 Javascript
javascript常用经典算法实例详解
2015/11/25 Javascript
JavaScript中数组去除重复的三种方法
2016/04/22 Javascript
原生js仿jquery animate动画效果
2016/07/13 Javascript
Centos7 中安装 Node.js v4.4.4
2016/11/03 Javascript
Angular.js组件之input mask对input输入进行格式化详解
2017/07/10 Javascript
详解vue-element Tree树形控件填坑路
2019/03/26 Javascript
JavaScript使用面向对象实现的拖拽功能详解
2019/06/12 Javascript
[01:00:53]OG vs IG 2018国际邀请赛小组赛BO2 第一场 8.18
2018/08/19 DOTA
简单讲解Python中的闭包
2015/08/11 Python
Python2.x版本中基本的中文编码问题解决
2015/10/12 Python
Python循环语句之break与continue的用法
2015/10/14 Python
用Python爬取QQ音乐评论并制成词云图的实例
2019/08/24 Python
tensorflow 分类损失函数使用小记
2020/02/18 Python
python将unicode和str互相转化的实现
2020/05/11 Python
python 牛顿法实现逻辑回归(Logistic Regression)
2020/10/15 Python
销售经理工作职责
2014/02/03 职场文书
《九色鹿》教学反思
2014/02/27 职场文书
美术指导求职信
2014/03/17 职场文书
个人委托书范本
2014/04/02 职场文书
竞选班干部演讲稿600字
2014/08/20 职场文书
面试自我评价范文
2014/09/17 职场文书
飞屋环游记观后感
2015/06/08 职场文书
小学生安全教育主题班会
2015/08/12 职场文书
68句权威创业名言
2019/08/26 职场文书
vue使用Google Recaptcha验证的实现示例
2021/08/23 Vue.js
python实现简单的三子棋游戏
2022/04/28 Python