在我们还没有专门的后台的管理平台时,很多类型的应用程序都需要在程序发生特定事件发生时提醒管理员,而常用的通信方法是电子邮件,也是最稳定的途径。Python 标准库中的 smtplib 包可用在 Flask 程序中发送电子邮件,但包装了 smtplib 的Flask-Mail 扩展能更好地和 Flask 集成使用。
pip指令安装:
pip install flask-mail
pycharm中安装:
搜索并安装flask-mail:
Flask-Mail 连接到简单邮件传输协议(Simple Mail Transfer Protocol,简称SMTP)服务器,并把邮件交给这个服务器发送。如果不进行配置,Flask-Mail 会连接 localhost 上的端口 25,无需验证即可发送电子邮件。下表列出了可用来设置 SMTP 服务器的配置:
下面介绍如何配置发送邮件的程序:
注意:千万不要把账户密码和关键信息直接写入脚本,特别是计划开源自己的作品时。为了保护账户信息,需要让脚本从环境中导入敏感信息。
我们如果从本地环境中导入程序,可以这样设置:
在Mac OS X 中使用 bash,那么可以按照下面的方式设定这两个变量:
(venv) $ export MAIL_USERNAME=<邮箱账户名> (venv) $ export MAIL_PASSWORD=<邮箱服务器密匙>
微软 Windows 用户可按照下面的方式设定环境变量:
(venv) $ set MAIL_USERNAME=<邮箱账户名> (venv) $ set MAIL_PASSWORD=<邮箱服务器密匙>
本地环境变量设置时注意:
(1)MAIL_USERNAME或MAIL_PASSWORD等号右边的都是对应内容提示的字符串,如MAIL_PASSWORD=‘123’;
(2)邮箱服务器密匙是指在对应邮箱(qq或163)申请的密匙(像微软电脑系统的激活码一样),至于如何申请,这里放出一个方法链接:python-基于yagmail库开发自动邮件发送程序
,请详细阅读里面的邮箱设置步骤。
以下涉及本人的关键信息都用xxxx代替了,按对应内容要求填写自己的数据即可!
app.py中配置flask-mail:
import os app.config['MAIL_SERVER'] = 'smtp.163.com' app.config['MAIL_PORT'] = 465 app.config['MAIL_USE_TLS'] = False app.config['MAIL_USE_SSL'] = True app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME') app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD')
注意:'MAIL_USE_TLS’设置为False,'MAIL_USE_SSL’设置为True,这样设置发送邮件成功率会高,不然的默认设置的一般会导致邮箱连接失败或计算机积极拒绝的错误提示!
在app.py中初始化flask-mail:
from flask.ext.mail import Mail mail = Mail(app)
但是,微软系统环境的不稳定,这边不建议初学者使用本地环境变量,这样可能会有不少人系统内读取失败,这里建议直接配置!
另外,mail的实例化一定在配置完成后,下面会有直接配置的代码演示的!
app.py中直接配置:
app.config['MAIL_SERVER'] = 'smtp.163.com' app.config['MAIL_PORT'] = 465 app.config['MAIL_USERNAME'] = "xxxx@163.com" app.config['MAIL_PASSWORD'] = "xxxx" #为保障个人隐私,实际的用x代替,按自己所取得的信息设置填写就行 app.config['MAIL_USE_TLS'] = False app.config['MAIL_USE_SSL'] = True mail = Mail(app)#mail实例化一定要放在配置后
如果已经安装好flask_script的话,这里就不多说了,安装使用详见Flask Web开发-1.4Web程序与数据库处理的第十集成shell!
那么我们可以现在shell环境中测试我们的程序是否可以正确发送一封邮件:
我们可以依次输入以下代码:
(venv) $ python hello.py shell >>> from flask_mail import Message >>> from hello import mail >>> msg = Message('test subject', sender='发送者@example.com',recipients=['接收方@example.com']) >>> msg.body = 'text body' >>> msg.html = '<b>HTML</b> body' >>> with app.app_context(): ... mail.send(msg)
pycharm中操作如下:
经测试,能顺利接受到邮件,配置无误!
同样也可在app.py中,添加:
with app.app_context(): message = Message(subject='hello flask-mail', sender="xxxx@163.com", recipients=['xxxx@qq.com'], body='测试邮件') mail.send(message)
注意,Flask-Mail 中的 send() 函数使用 current_app,因此要在激活的程序上下文中执行。
为了避免每次都手动编写电子邮件消息,我们最好把程序发送电子邮件的通用部分抽象出来,定义成一个函数。另外,这么做还有个好处,即该函数可以使用 Jinja2 模板渲染邮件正文,灵活性极高。
在app.py中使用函数实现:
from flask_mail import Mail,Message app.config['FLASKY_MAIL_SUBJECT_PREFIX'] = '[Flasky]' app.config['FLASKY_MAIL_SENDER'] = 'xxxx@163.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函数用到了两个程序特定配置项,分别定义邮件主题的前缀’FLASKY_MAIL_SUBJECT_PREFIX’和发件人[‘FLASKY_MAIL_SENDER’]的地址。send_email 函数的参数依次为收件人地址、主题、渲染邮件正文的模板和关键字参数列表(关键字参数这里主要用来传入模板变量使用)。指定模板时不能包含扩展名,这样才能使用两个模板分别渲染纯文本正文(txt)和富文本正文(html等文件)。调用者将关键字参数传给 render_template() 函数,以便在模板中使用,进而生成电子邮件正文。
index() 视图函数很容易被扩展,这样每当表单接收新名字(即新用户访问登录)时,程序都会给管理员发送一封电子邮件。
在app.py中,index函数编写:
app.config['FLASKY_ADMIN'] = "xxxxx@163.com" @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_emails(app.config['FLASKY_ADMIN'], '新用户','mailnew_user', user=user) else: session['known'] = True session['name'] = form.name.data form.name.data = '' return redirect(url_for('index')) return render_template('home.html',form = form, name = session.get('name'),known = session.get('known', False))
电子邮件的收件人保存在环境变量 FLASKY_ADMIN 中,在程序启动过程中,它会加载到一个同名配置变量中。我们要创建两个模板文件,分别用于渲染纯文本和 HTML 版本的邮件正文。这两个模板文件都保存在 templates 文件夹下。 mail 电子邮件的模板中要有一个模板参数是用户,因此调用 send_mail() 函数时要以关键字参数的形式传入用户。
其他环境变量设置:
set 名称=<内容对应的字符串形式>
我们配置好,设置好,写好程序代码,成功的话,我们登录页面每登录一个新用户,管理者(接收方)会接到一份邮件提示!
如果像上面一样发送,mail.send() 函数在发送电子邮件时会停滞了几秒钟,为了避免处理请求过程中不必要的延迟,我们可以把发送电子邮件的函数移到后台线程中,利用threading库相关内容即可实现,注意:threading是本地库,不用另外下载安装!。
在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['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
提前打开待接受的邮箱,我们可以发现有专门线程的发送会比无专门线程的发送邮件速度要快!,这就是专门线程的优秀效果!不过要记住,程序要发送大量电子邮件时,使用专门发送电子邮件的作业要比给每封邮件都新建一个线程更合适。例如,我们可以把执行send_async_email() 函数的操作发给 Celery(http://www.celeryproject.org/)任务队列。
如果你想同时保存无线程与有线程的函数,等待另外使用也可以,只需把有线程的函数名与无线程的函数名区别下,分成两个不同的函数(其实主要参数都差不多),再决定index函数中使用有无线程的函数即可!
上述实现涉及一个有趣的问题。很多 Flask 扩展都假设已经存在激活的程序上下文和请求上下文。Flask-Mail 中的 send() 函数使用 current_app,因此必须激活程序上下文。不过,在不同线程中执行 mail.send() 函数时,程序上下文要使用 app.app_context() 人工创建。
pip命令失效
使用python flask_script.py --help出错:ModuleNotFoundError:no module named ‘flask._compat‘
仅展示对于本专栏前面的模块改动的部分,其他默认不变。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>{{ user }}登录你的网站!</h1> </body> </html>
注:mailnew_user.txt暂时为空的txt文本文件
from flask import Flask, render_template,url_for,redirect,session,flash from flask_bootstrap import Bootstrap from flask_moment import Moment from datetime import datetime from flask_wtf import FlaskForm from wtforms import StringField, SubmitField from wtforms.validators import Required from flask_sqlalchemy import SQLAlchemy from flask_script import Shell,Manager from flask_migrate import Migrate,MigrateCommand from flask_mail import Mail,Message from threading import Thread import pymysql #程序初始配置 pymysql.install_as_MySQLdb() app = Flask(__name__) bootstrap = Bootstrap(app) moment = Moment(app) manager = Manager(app) def make_shell_context(): return dict(app=app, db=db, User=User, Role=Role) manager.add_command("shell", Shell(make_context=make_shell_context)) #数据库连接配置 app.config['SECRET_KEY'] = 'hard to guess string' app.config['SQLALCHEMY_DATABASE_URI'] ='mysql://root:xxxxx@localhost/me' app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True db = SQLAlchemy(app) migrate = Migrate(app, db) manager.add_command('db', MigrateCommand) #邮箱配置 app.config['MAIL_SERVER'] = 'smtp.163.com' app.config['MAIL_PORT'] = 465 app.config['MAIL_USERNAME'] = "xxxxx@163.com" app.config['MAIL_PASSWORD'] = "xxxxx" app.config['MAIL_USE_TLS'] = False app.config['MAIL_USE_SSL'] = True mail = Mail(app) #下面是用来测试发送单个邮件是否成功的代码部分,已完成的可以删除或隐藏 '''with app.app_context(): message = Message(subject='hello flask-mail', sender="xxxxx@163.com", recipients=['xxxxx@qq.com'], body='测试邮件') mail.send(message)''' app.config['FLASKY_MAIL_SUBJECT_PREFIX'] = '[Flasky]' app.config['FLASKY_MAIL_SENDER'] = 'xxxxxxx@163.com' app.config['FLASKY_ADMIN'] = "xxxxx@163.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) def send_async_email(app, msg): with app.app_context(): mail.send(msg) def send_emails(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 class NameForm(FlaskForm): name = StringField('你叫什么名字?', validators=[Required()]) submit = SubmitField('提交') #数据库模型定义 class Role(db.Model): __tablename__ = 'roles' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64), unique=True) users = db.relationship('User', backref='role', lazy='dynamic') def __repr__(self): return '<Role %r>' % self.name class User(db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(64), unique=True, index=True) role_id = db.Column(db.Integer, db.ForeignKey('roles.id')) def __repr__(self): return '<User %r>' % self.username @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_emails(app.config['FLASKY_ADMIN'], '新用户','mailnew_user', user=user) else: session['known'] = True session['name'] = form.name.data form.name.data = '' return redirect(url_for('index')) return render_template('home.html',form = form, name = session.get('name'),known = session.get('known', False)) @app.route('/user/<name>') def users(name): return render_template('user.html',name=name,current_time=datetime.utcnow()) @app.errorhandler(404) def page_not_found(e): return render_template('404.html',current_time=datetime.utcnow()),404 @app.errorhandler(500) def internal_error(e): return render_template('500.html',current_time=datetime.utcnow()),500 @app.route('/form') def useform(): form=NameForm() return render_template('FORM.html',form=form) if __name__ == '__main__': manager.run()
最后,文中如有不足,欢迎批评指正!