文章轉自 :https://github.com/WapeYang/The-Flask-Mega-Tutorial/blob/master/email.rst
感謝原作者的付出
轉載時間爲:2014-05-06
郵件支持
回顧
在近來的幾篇教程中,我們一直在與數據庫打交道。
今天我們打算讓數據庫休息下,相反我們今天準備完成網頁應用程序中一項重要的功能:能夠給用戶發送郵件。
在我們小型 microblog 應用程序,我們將要實現一個與郵件有關的功能,我們將會給用戶發送一封郵件當他或者她被人關注的時候。實現郵件有很多方式,因此我們需要設計一個通用的框架,以便重用。
安裝 Flask-Mail
幸運地,Flask 已經存在處理郵件的擴展,儘管不是 100% 支持我們想要的功能,但是已經很好了。
在我們的虛擬環境上安裝 Flask-Mail 是相當簡單的。在 Windows 以外的其它系統上的用戶可以按照如下命令:
flask/bin/pip install flask-mail
Windows 上的用戶稍微有些不同,因爲 Flask-Mail 中使用的一個模塊不支持該系統,用戶需要按照如下命令:
flask\Scripts\pip install --no-deps lamson chardet flask-mail
配置
當我們在 :ref:`testing` 中,已經爲了能夠發送錯誤信息。配置了郵件相關的信息,實際上它也能用於發送用戶相關的郵件。
作爲提醒,我必須重複下配置中需要的信息:
- 用於發送郵件的郵件服務器
- 管理員的郵箱地址
下面就是 :ref:`testing` 中的配置(文件 config.py):
# 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 = ['[email protected]']
在開始發送應用程序的郵件之前,我們必須輸入實際的郵件服務器以及管理員的郵箱。比如,如果你想要應用程序通過你的 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 = ['[email protected]']
我們也需要初始化一個 Mail 對象,這個對象爲我們連接到 SMTP 服務器並且發送郵件(文件 app/__init__.py):
from flask.ext.mail import Mail mail = Mail(app)
讓我們發送郵件!
爲了學習 Flask-Mail 是如何工作的,我們將會在命令行中發送郵件。因此讓我們在虛擬環境中啓動 Python並且執行下面的代碼:
>>> from flask.ext.mail import Message >>> from app import app, mail >>> from config import ADMINS >>> msg = Message('test subject', sender = ADMINS[0], recipients = ADMINS) >>> msg.body = 'text body' >>> msg.html = '<b>HTML</b> body' >>> with app.app_context(): ... mail.send(msg) ....
上面的代碼塊將會向在配置 config.py 中的管理員列表所有成員發送郵件。發送者是管理員列表中的第一個管理員。郵件有文本和 HTML 版本,這取決你郵件客戶端的設置。注意我們需要創建一個 app_context 來發送郵件。Flask-Mail 最近的版本需要這個。當 Flask 處理請求的時候,應用內容就被自動地創建。因爲我們不是在請求中,必須手動地創建內容。
好了,是時候把代碼整合進我們的應用程序了。
簡單的郵件框架
我們現在編寫一個輔助方法用於發送郵件。這是上面使用的測試代碼通用的版本。我們將會把這個函數放入一個新文件,專門用於我們的應用程序的郵件支持(文件 app/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 = sender, recipients = recipients) msg.body = text_body msg.html = html_body mail.send(msg)
Flask-Mail 支持的應用超出了我們所用的。比如,抄送以及附件也是可用的,但是我們不準備在應用程序中使用。
關注提醒
現在我們已經有了發送郵件的基本框架,我們可以編寫發送關注提醒的函數(文件 app/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 居然出現在這裏。如果你還記得,我們用此函數來渲染視圖中所有的 HTML 模板。像視圖中的模板一樣,郵件的主體也是使用模板的理想候選者。
因此我們需要編寫我們的關注者提醒郵件的文本以及 HTML 版本的模板。這裏是文本版本(文件 app/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
對於 HTML 版本,我們可能會做得更好些,甚至會顯示出關注者的頭像和用戶信息(文件 app/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 函數生成的 URLs 是與當前頁面的域名相關的。例如,url_for("index") 返回值將會是 /index,但是在實際視圖函數中返回的是 http://localhost:5000/index。在郵件中是不存在域名的內容,因此我們必須要生成完全的包含域名的 URLs,_external 參數就是爲這個目的。
最後一步就是把發送郵件整合到實際的視圖函數中(文件 app/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 鏈接的時候,瀏覽器需要等到 2 到 3 秒的時間刷新頁面,儘管郵件是正常地收到了。以前這可是瞬間完成的。
這是怎麼回事了?
問題就是 Flask-Mail 發送郵件是同步的。網頁服務器是被阻塞了當發送郵件的時候,直到郵件已交付響應返回給瀏覽器。想象下,如果當我們試圖發送郵件的時候,郵件服務器是很慢,或者甚至更差,臨時斷線,會發生些什麼?這個解決方案並不完美。
這已經成爲了應用程序的一個瓶頸了,發送郵件應該是一個不會干擾到網頁服務器的後臺程序,所以讓我們看看如何解決這個問題。
在 Python 中異步調用
我們真正想要的就是 send_email 函數立即返回,發送郵件的工作移到後臺處理。
事實上 Python 已經支持運行異步任務,而且有不止一種方式。threading 以及 multiprocessing 模塊都可以達到這個目的。
每次我們需要發送郵件的時候啓動一個進程的資源遠遠小於啓動一個新的發送郵件的整個過程,因此把 mail.send(msg) 調用移入線程中(文件 app/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 = Thread(target = send_async_email, args = [msg]) thr.start()
如果現在測試點擊 follow 鏈接後的速度的話,瀏覽器會瞬間刷新頁面了。
既然異步的郵件發送功能已經實現了,如果將來我們需要實現其它異步的函數,還有什麼需要改進的嗎?我們需要爲每一個實現異步功能的函數拷貝多線程的代碼嗎?這並不好。
我們可以通過實現一個 裝飾器 來解決這個問題。有了裝飾器,上面的代碼可以修改爲:
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)
好的多了吧,對不對?
這個神奇的代碼其實很簡單。我們把它放入一個新文件(文件 app/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
而現在我們間接爲異步任務創建了一個有用的框架,我們可以說我們已經完成了!
作爲一個練習,大家可以考慮考慮如何用 multiprocessing 模塊來實現上面的功能。
結束語
代碼中更新了本文中的一些修改,如果你想要節省時間的話,你可以下載 microblog-0.11.zip。
我希望能在下一章繼續見到各位!