Flask Web 开发学习稿(三)

第六章 电子邮件

当我们需要在特定事件发生时提醒用户,包装了 smtplibFlask-Mail 扩展能更好的和 Flask 集成

安装 pip install flask-mail

Flask-Mail 连接到 SMTP 服务器,如果不进行配置,Flask-Mail 会连接 localhost 上的端口 25

配置 默认值 说明
MAIL_SERVER localhost Email服务器的ip地址或者主机名
MAIL_PORT 25 Email服务器端口
MAIL_USE_TLS False 启用传输层安全协议
MAIL_USE_SSL False 启用安全套接曾协议
MAIL_USERNAME None Email用户名
MAIL_PASSWORD None Email密码

使用外部 SMTP 服务器更加方便

from flask.ext.mail import Mail

app.config[‘MAIL_SERVER‘] = ‘mail.xxx.com‘
app.config[‘MAIL_PORT‘] = ‘587‘
app.config[‘MAIL_USE_TLS‘] = ‘True‘
app.config[‘MAIL_USERNAME‘] = ‘username‘
app.config[‘MAIL_PASSWORD‘] = ‘pwd‘
mail = Mail(app)

将账户和密码写在程序里太不安全了,为了保护敏感信息,需要让脚本从环境变量中导入这些信息

app.config[‘MAIL_USERNAME‘] = os.environ.get(‘MALI_USERNAME‘)
app.config[‘MAIL_PASSWORD‘] = os.environ.get(‘MALI_PASSWORD‘)

如何设置环境变量呢?

# Linux 或者 Mac OS X
export MALI_USERNAME=<YOU_USERNAME>
export MALI_PASSWORD=<YOU_PASSWORD>
# Windows
set MALI_USERNAME=<YOU_USERNAME>
set MALI_PASSWORD=<YOU_PASSWORD>

在程序中集成发送电子邮件功能

from flask.ext.mail import Message

app.config[‘FLASKY_MAIL_SUBJECT_PREFIX‘] = ‘[Flasky]‘
app.config[‘FLASKY_MAIL_SENDER‘] = ‘Flasky Admin <[email protected]>‘

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() 函数的参数分别为收件人地址,主题,渲染邮件正文的模版和关键字参数列表

指定模版时不能包含扩展名,这样才能使用两个模版分别渲染纯文本正文和富文本正文

调用者将关键字参数传给 render_template() 函数以便在模版中使用,进而生成电子邮件正文,下面修改视图函数

app.config[‘FLASKY_ADMIN‘] = os.environ.get(‘FLASKY_ADMIN‘)
#...
@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_email(app.config[‘FLASKY_ADMIN‘], ‘New User‘,
                           ‘mail/new_user‘, user=user)
        else:
            session[‘known‘] = True

        session[‘name‘] = form.name.data
        form.name.data = ‘‘

        return redirect(url_for(‘index‘))
    return render_template(‘index.html‘, form=form, name=session.get(‘name‘), known=session.get(‘known‘, False))

我们要创建两个模版文件,分别用于渲染纯文本和 HTML 版的邮件正文,这两个模版文件都保存在 tmplates 文件夹下的 mail 子文件夹中,以便和普通模版区分开来。电子邮件的模版中要有一个模版参数是用户,因此调用 send_mail() 函数时要以关键字参数的形式传入用户

这样的程序会在发送邮件的时候造成短暂阻塞,异步发送电子邮件来消除这种不必要的延迟

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

很多 Flask 扩展都假设已经存在激活的程序上下文和请求上下文,Flask-Mail 中的 send() 函数使用 current_app,因此必须激活程序上下文,不过不同线程中执行mail.send()函数时,程序上下文要使用 app.app_context() 人工创建

当你需要大量发送电子邮件时,使用 Celery 任务队列更合适

第七章 大型程序的结构

现在我们已经完成了很多功能的学习,但是随着程序越来越大,我们将学会如何组织大型程序的结构

7.1 项目结构

├─Flsky
│  │─app  # Flask 程序
│  |  ├─static
│  |  |─templates
│  |  |─main
│  |  |  │-__init__.py
│  │  |  |-errors.py
│  │  |  |-forms.py
│  │  |  |-views.py
│  │  |-__init__.py
│  │  |-email.py
│  │  |-models.py
|  |-migrations  # 数据库迁移脚本
|  |-tests  # 单元测试
|  |  |-__init__.py
|  |  |-test*.py
│  |-config.py  # 储存配置
│  |-manage.py  # 用于启动程序以及其他的程序任务
│  |-requirements.txt  # 列出全部依赖包
|  └─ venv # python虚拟环境

7.2 配置选项

从现在开始我们我们的配置不会再像之前那样用简单的字典结构,而是使用配置类

#config.py
import os
basedir = os.path.abspath(os.path.dirname(__file__))

# 基类
class Config:
    SECRET_KEY = os.environ.get(‘SECRET_KEY‘) or ‘hard to guess string‘
    SQLALCHEMY_COMMIT_ON_TEARDOWN = True
    FLASKY_MAIL_SUBJECT_PREFIX = ‘[Flasky]‘
    FLASKY_MAIL_SENDER = ‘Flasky Admin <[email protected]>‘
    FLASKY_ADMIN = os.environ.get(‘FLASKY_ADMIN‘)

    @staticmethod
    def init_app(app):
        pass

class DevelopmentConfig(Config): DEBUG = True
    MAIL_SERVER = ‘smtp.googlemail.com‘
    MAIL_PORT = 587
    MAIL_USE_TLS = True
    MAIL_USERNAME = os.environ.get(‘MAIL_USERNAME‘)
    MAIL_PASSWORD = os.environ.get(‘MAIL_PASSWORD‘)
    SQLALCHEMY_DATABASE_URI = os.environ.get(‘DEV_DATABASE_URL‘) or         ‘sqlite:///‘ + os.path.join(basedir, ‘data-dev.sqlite‘)

class TestingConfig(Config):
    TESTING = True
    SQLALCHEMY_DATABASE_URI = os.environ.get(‘TEST_DATABASE_URL‘) or \ ‘sqlite:///‘ + os.path.join(basedir, ‘data-test.sqlite‘)

class ProductionConfig(Config):
    SQLALCHEMY_DATABASE_URI = os.environ.get(‘DATABASE_URL‘) or         ‘sqlite:///‘ + os.path.join(basedir, ‘data.sqlite‘)

config = {
    ‘development‘: DevelopmentConfig,
    ‘testing‘: TestingConfig,
    ‘production‘: ProductionConfig,
    ‘default‘: DevelopmentConfig
}

配置类可以定义init_app()方法,其参数是程序实例,在这个方法中,可以执行对当前环境的配置初始化,现在基类 Config 中的init_app()方法为空

7.3 程序包

程序包用来保存程序的所有代码、模版和静态文件。

7.3.1 使用程序工厂函数

把创建过程移到可显式调用的工厂函数中,程序的工厂函数在 app 包的构造文件中定义

构造文件导入了太多正在使用的 Flask 扩展,由于尚未初始化所需的程序实例,所以没有初始化扩展,创建扩展类时没有向构造函数传入参数

create_app() 函数就是程序的工厂函数,接受一个参数,是程序使用的配置名

配置类在 config.py 文件中定义,其中保存的配置可以使用 Flask app.config 配置对象提供的 from_object() 方法直接导入程序。至于扩展对象,则可以通过名字从 config 字典中选择。

程序创建配置好后,就能初始化扩展了,在之前创建的扩展对象上调用 init_app() 可以完成初始化过程

# app/__init__.py
from flask import Flask, render_template
from flask.ext.bootstrap import Bootstrap
from flask.ext.mail import Mail
from flask.ext.moment import Moment
from flask.ext.sqlalchemy import SQLAlchemy
from config import config

bootstrap = Bootstrap()
mail = Mail()
moment = Moment()
db = SQLAlchemy()

def create_app(config_name):
    app = Flask(__name__)
    app.config.from_object(config[config_name])
    config[config_name].init_app(app)
    bootstrap.init_app(app)
    mail.init_app(app)
    moment.init_app(app)
    db.init_app(app)
    # 附加路由和自定义错误页面

    return app

7.3.2 在蓝本中实现程序的功能

在单脚本程序中,程序实例存在于全局作用域中,路由可以直接使用 app.route 修饰器定义。但是现在应用程序实例是运行时创建的,app.route 只在在 create_app() 以后才存在,这时定义路由就太晚了

在蓝本中注册的路由都处于休眠状态,直到蓝本注册到程序上后,路由才真正成为程序的一部分。使用位于全局作用域的蓝本时,定义路由的方法几乎和单脚本程序一样

为了获得最大的灵活性,程序包中创建了一个子包用于保存蓝本

app/main/__init__.py
from flask import Blueprint
main = Blueprint(‘main‘, __name__)
from . import views, errors

通过实例化一个蓝本类对象可以创建蓝本,构造函数有两个参数:蓝本的名字和蓝本所在的模块或者包,大多数情况下,Python的 __name__变量就是第二个参数所需要的值

应用程序的路由放在app/main/views.py模块中, 错误处理放在app/main/errors.py中。导入这些模块以后,路由和错误处理就和蓝本关联起来了。

有一点要注意,路由和错误处理模块要在 app/__init__.py 的底部被导入,因为views.py 和 errors.py 要导入 main blueprint,所以为了避免循环依赖我们要等到 main 被创建出来才能够导入路由和错误处理。

蓝本在工厂函数 create_app() 中注册到程序上

# app/__init__.py 注册蓝本
def create_app(config_name):
    # ...
    from main import main as main_blueprint
    app.register_blueprint(main_blueprint)
    return app

错误处理程序如下

#app/main/error.py
from flask import render_template
from . import main

@main.app_errorhandler(404)
def page_not_found(e):
    return render_template(‘404.html‘), 404

@main.app_errorhandler(500)
def internal_server_error(e):
    return render_template(‘500.html‘), 500

如果使用 errorhandler 修饰器,只有蓝本中的错误才能触发处理程序,要想注册程序全局的错误处理程序,必须使用 app_errorhandler

在蓝本中定义路由如下

# app/main/views.py
from datetime import datetime
from flask import render_template, session, redirect, url_for
from . import main
from .forms import NameForm
from .. import db
from ..models import User

@main.route(‘/‘, methods=[‘GET‘, ‘POST‘])
def index():
    form = NameForm()
    if form.validate_on_submit():
        # ...
        return redirect(url_for(‘.index‘))
    return render_template(‘index.html‘,
                           form=form, name=session.get(‘name‘),
                           known=session.get(‘known‘, False),
                           current_time=datetime.utcnow())

在蓝本中的视图函数的区别

  1. 路由修饰器由蓝本提供
  2. url_for() 的用法不同

Flask 会为蓝本中的全部端点都加上一个命名空间,这样就可以在不同的蓝本中使用相同的端点名定义视图函数,而不会产生冲突。命名空间就是蓝本的名字(蓝本构造函数的第一个参数),所以视图函数 index() 注册的端点名是 main.inedx 其 URL 使用 url_for(‘main.index‘) 获取

为了完全修改程序的页面,表单对象也要移到蓝本中,保存于 app/main/forms.py 模块

7.4 启动脚本

manage.py 文件用于启动程序

#!/usr/bin/env python
import os
from app import create_app, db
from app.models import User, Role
from flask.ext.script import Manager, Shell
from flask.ext.migrate import Migrate, MigrateCommand

app = create_app(os.getenv(‘FLASK_CONFIG‘) or ‘default‘)
manager = Manager(app)
migrate = Migrate(app, db)

def make_shell_context():
    return dict(app=app, db=db, User=User, Role=Role)

manager.add_command("shell", Shell(make_context=make_shell_context))
manager.add_command(‘db‘, MigrateCommand)

if __name__ == ‘__main__‘:
    manager.run()

该脚本首先创建应用程序实例,然后从系统环境中读取FLASK_CONFIG变量,如果该变量没有定义则使用默认值。然后初始化Flask-Script, Flask-Migrate和为 Python Shell 定义的上下文

7.5 需求文件

pip 可以使用如下命令自动生成这个文件

pip freeze >requirements.txt

当你在另一个环境中准备安装这些依赖时,执行如下命令

pip install -r requirements.txt

7.6 单元测试

import unittest
from flask import current_app
from app import create_app, db

class BasicsTestCase(unittest.TestCase):

    def setUp(self):
        self.app = create_app(‘testing‘)
        self.app_context = self.app.app_context()
        self.app_context.push()
        db.create_all()

    def tearDown(self):
        db.session.remove()
        db.drop_all()
        self.app_context.pop()

    def test_app_exists(self):
        self.assertFalse(current_app is None)

    def test_app_is_testing(self):
        self.assertTrue(current_app.config[‘TESTING‘])

测试是按照典型的单元测试的写法来构建的,类似于运行中的程序,首先使用测试配置创建程序,然后激活上下文。setUp()tearDown() 方法在每个测试方法执行前后都会运行,任何以test_ 开头的方法都会被当做测试方法来执行。setUp()方法创建了测试所需的环境, 他首先创建了应用程序实例用作测试的山下文环境,这样就能确保测试拿到current_app, 然后新建了一个全新的数据库。数据库和应用程序实例最后都会在tearDown() 方法被销毁。

第一个测试确保了应用程序实例是存在的,第二个测试应用程序实例在测试配置下运行。为了确保测试文件夹有正确的包结构,我们需要添加一个tests/__init__.py 文件,这样单元测试包就能扫描所有在测试文件夹中的模块了。

为了运行单元测试,我们可以在manage.py中添加一个自定义命令,

@manager.command
def test():
    """Run the unit tests."""
    import unittest
    tests = unittest.TestLoader().discover(‘tests‘)
    unittest.TextTestRunner(verbosity=2).run(tests)

运行方法如下

(venv) $ python manage.py test
test_app_exists (test_basics.BasicsTestCase) ... ok
test_app_is_testing (test_basics.BasicsTestCase) ... ok
.----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK

7.7 创建数据库

首选从环境变量中读取数据库的 URL,同时还提供了一个默认的 SQLite 数据库做备用

可以使用如下命令创建数据表或者升级到最新修订版本

python mange.py db upgrade

时间: 2024-07-31 02:46:02

Flask Web 开发学习稿(三)的相关文章

Flask Web 开发学习稿(一)

好久没有更新过了,把这些日子搞的东西先放一波出来 这个学习笔记未必会包含全部章节,有时会将两个章节进行合并 第一章 安装 1.1 使用虚拟环境 虚拟环境是 Python 解释器的一个私有副本,可以安装私有包而不影响全局 Python 解释器 Python 3.3 通过 venv 模块原生支持虚拟环境,命令为 pyvenv .不过在 Python 3.3 中使用 pyvenv 命令创建的虚拟环境不包含 pip ,在 Python 3.4 中改进了这一缺陷. 我这边使用 Python 3.5.0 和

Flask Web 开发学习稿(二)

第四章 Web 表单 request.from 能获取 POST 请求中提交的表单数据 Flask-WTF 扩展可以把处理 Web 表单的过程变成一种愉悦的体验 4.1 跨站请求伪造保护 默认情况下,Flask-WTF 能保护所有表单免受跨站请求伪造的攻击,为了实现 CSRF 保护,Flask-WTF 需要程序设置一个密钥,会使用这个密钥生成加密令牌,再用令牌验证请求中表单数据的真伪 设置密钥的方法如示例所示 app = Flask(__name__) app.config['SECRET_KE

Flask web开发之路三

今天写一个URL传参.反转URL.页面跳转和重定向 URL传参 主app文件代码: from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello World!' @app.route('/article/<id>') def article(id): return '您请求的参数是: %s' %id if __name__ == '__main__': app.run

Flask之旅《Flask Web开发:基于Python的Web应用开发实战》学习笔记

<Flask Web开发:基于Python的Web应用开发实战> 点击上方的"目录"快速到达哦! 虽然简单的网站(Flask+Python+SAE)已经上线,但只是入门.开发大型网站,系统地学习一遍还是有必要的. 1 虚拟环境 2016-6-8 书上介绍了 virtualenv,每个venv都会拷贝一份packages到项目 /venv目录. virtualenv venv venv\Scripts\activate.bat (venv) $ pip freeze >

学习参考《Flask Web开发:基于Python的Web应用开发实战(第2版)》中文PDF+源代码

在学习python Web开发时,我们会选择使用Django.flask等框架. 在学习flask时,推荐学习看看<Flask Web开发:基于Python的Web应用开发实战(第2版)> 分三部分,全面介绍如何基于Python微框架Flask进行Web开发.第一部分是Flask简介,介绍使用Flask框架及扩展开发Web程序的必备基础知识.第二部分则给出一个实例,真正带领大家一步步开发完整的博客和社交应用Flasky,从而将前述知识融会贯通,付诸实践.第三部分介绍了发布应用之前必须考虑的事项

【web开发学习笔记】Structs2 Action学习笔记(三)action通配符的使用

action学习笔记3-有关于通配符的讨论 使用通配符,将配置量降到最低,不过,一定要遵守"约定优于配置"的原则. 一:前端htm <前端代码html> </head> <body> <a href="<%=context %>/actions/Studentadd">添加学生</a> <a href="<%=context %>/actions/Studentdel

【web开发学习笔记】Structs2 Result学习笔记(三)带参数的结果集

Result学习笔记(三)带参数的结果集 第一部分:代码 //前端 <head> <meta http-equiv="Content-Type" content="text/html; charset=GB18030" /> <title>Insert title here</title> </head> <body> <ol> <li><a href="

python web 开发学习路线

自己目前学习python web 开发, 经过两个月的摸索,目前对web开发有了浅显的认识,把自己的学习过程贴出来.1.python入门推荐老齐<从零开始学python>,<python简明教程>,这两本书很适合小白入门(像我一样长期徘徊在编程门外的人)2.python进阶推荐<python学习手册>,python学习手册的前半部分与在入门教程中的基础部分相重复,后面部分对python的介绍更细致,比如面向对象的这部分对于小白理解相对容易.还有一本<python

《Flask Web开发——基于Python的Web应用开发实践》一字一句上机实践(下)

目录 前言 第8章 用户认证 第9章 用户角色 第10章 用户资料 第11章 博客文章 第12章 关注者 第13章 用户评论 第14章 应用编程接口   前言 第1章-第7章学习实践记录请参见:<Flask Web开发——基于Python的Web应用开发实践>一字一句上机实践(上) 本文记录自己学习<Flask Web开发——基于Python的Web应用开发实践>的第8章-第14章内容.相比于刚开始学习第1-7章内容来说,本部分内容实战性更强,而且在书本上遇到的问题也相对较少,如果