《FlaskWeb开发》阅读笔记
第一部分 Flask简介
程序基本结构
完整的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# test.py
from flask import Flask
app = Flask(__name__)
# 程序实例(Flask类的对象),Web服务器使用WSGI协议,把自客户端的所有请求都转交给这个对象处理
# __name__参数即程序主模块或包的名字,决定程序的根目录
def index(): # /对应的视图函数
return '<h1>Hello World!</h1>' # 返回值称为响应
# Flask支持在路由中使用int、float和path类型(也是字符串,但不把斜线视作分隔符)
def user(name): # /user/name对应的视图函数
return '<h1>Hello, %s!</h1>' % name
if __name__ == '__main__':
app.run(debug=True) # run函数的参数用于设置服务器的操作模式,但Flask提供的Web服务器不适合在生产环境中使用1
2
3
4
5
6
7
8
9$ python test.py
* Serving Flask app "test" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Restarting with stat
* Debugger is active!
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)Flask 没有内置的 manage 管理工具,一般目录结构都要自己创建,以下为一个项目的样例,该目录与后文无关。
1
2
3
4
5
6
7
8
9
10$ tree
├── test
│ ├── static
│ ├── templates
│ ├── __init__.py
│ ├── models.py
│ ├── views.py
├── config.py
├── run.py
└── tmp请求-响应循环
Flask 使用上下文临时把某些请求对象变为全局可访问——特定的变量在一个线程中全局可访问
1
2
3
4
5
6from flask import request
def index():
user_agent = request.headers.get('User-Agent')
return '<p>Your browser is %s</p>' % user_agent程序上下文被推送(激活)后,才可以在线程中使用 current_app 和 g 变量
同样的,请求上下文被推送(激活)后,才可以使用 request 和 session 变量
1
2
3
4
5
6
7
8# 在另一个newtest.py中测试以下代码
from test import app # app为之前test.py中Flask类的实例
from flask import current_app
# 获得一个程序的上下文 app_ctx = app.app_context()
app_ctx.push()
current_app.name
'test'
app_ctx.pop()
检查 hello.py 的 URL 映射:
app.url_map
请求钩子:用于在处理请求之前执行一些操作,如建立数据库链接或认证用户;通过注册为通用函数,在请求分发到视图函数之前或之后调用,以修饰器形式实现
- 修饰器类型:
- before_first_request:注册一个函数,在处理第一个请求之前运行
- before_request:注册一个函数,在每次请求之前运行
- after_request:注册一个函数,如果没有未处理的异常抛出,在每次请求之后运行
- teardown_request:注册一个函数,即使有未处理的异常抛出,也在每次请求之后运行
- 请求钩子函数和视图函数之间共享数据一般使用上下文全局变量 g,如 before_request 从数据库中加载已登录用户并保存到
g.user
,之后的视图函数从g.user
获取用户
- 修饰器类型:
视图函数的返回称为响应,一般响应就是一个字符串,作为HTML页面返回
响应状态码:Flask 默认为200,表明请求处理成功;可将数字代码作为第二个返回值
1
2
3
def index():
return '<h1>Bad Request</h1>', 400 # 返回一个400状态码,请求无效还可接收一个由 header 组成的字典作为第三个返回值
还可创建 response 对象,以进一步设置响应:
make_response()
的参数同上,返回 response 对象1
2
3
4
5from flask import make_response
...
response = make_response('<h1>This document carries a cookie!</h1>'python)
response.set_cookie('answer', '42') # 设置cookie
return response重定向响应:响应无页面文档,返回一个新的地址用于加载新的页面,常用于 Web 表单
使用状态码302,指向地址由 Location 首部提供
可使用第一种返回值生成,也可在 response 对象设定,也可用辅助函数
redirect()
1
2
3from flask import redirect
...
return redirect('http://www.example.com')
abort() 函数生成特殊响应,用于处理错误,并将控制权交给 Web 服务器
1
2
3
4
5
6
7
8from flask import abort
def get_user(id):
user = load_user(id)
if not user:
abort(404) # 动态参数id对应的用户不存在,返回状态码404
return '<h1>Hello, %s</h1>' % user.name
命令行参数扩展
Flask-Script 是一个 Flask 扩展,为 Flask 添加一个命令行解析器(
pip install flask-script
)使用例子:
1
2
3
4
5
6from flask.ext.script import Manager # 扩展都在flask.ext命名空间下
manager = Manager(app) # 程序实例作为参数
# ...
if __name__ == '__main__':
manager.run()1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19$ python hello.py
usage: hello.py [-h] {shell,runserver} ...
positional arguments:
{shell,runserver}
shell 在 Flask 应用上下文中运行 Python shell
runserver 运行 Flask 开发服务器:app.run()
optional arguments:
-h, --help 显示帮助信息并退出
$ python hello.py runserver --help
usage: hello.py runserver [-h] [-t HOST] [-p PORT] [--threaded]
[--processes PROCESSES] [--passthrough-errors] [-d]
[-r]
...
$ python hello.py runserver --host 0.0.0.0 # 设置监听客户端连接的网络接口
* Running on http://0.0.0.0:5000/
* Restarting with reloader
模板
- 模板是一个包含响应文本的文件,包含用占位变量表示的动态部分,在具体的上下文确定;真实值替换变量,再返回最终的响应,此过程称为渲染
- Flask 使用了一个 Jinja2 模板引擎渲染模板
Jinja2
Flask 在程序文件夹中的 templates 子文件夹中寻找模板(类似Django)
1
2
3
4
5
6
7
8
9
10
11from flask import Flask, render_template
# render_template集成了Jinja2模板引擎
# ...
def index():
return render_template('index.html')
def user(name):
return render_template('user.html', name=name) # 第一个参数是模板文件名,后面参数为键值对,对应模板变量的真实值模板中使用的
{{ name }}
结构表示一个变量,其他变量示例如下:1
2
3
4<p>A value from a dictionary: {{ mydict['key'] }}.</p>
<p>A value from a list: {{ mylist[3] }}.</p>
<p>A value from a list, with a variable index: {{ mylist[myintvar] }}.</p>
<p>A value from an object's method: {{ myobj.somemethod() }}.</p>过滤器修改变量:
{{ name|capitalize }}
- 如果要显示变量中存储的HTML代码,则需要用 safe 过滤器
- 不可信的值不能使用 safe 过滤器,如表单的输入文本
控制结构
条件控制
1
2
3
4
5{% if user %}
Hello, {{ user }}!
{% else %}
Hello, Stranger!
{% endif %}循环
1
2
3{% for comment in comments %}
<li>{{ comment }}</li>
{% endfor %}宏(类似于函数)
1
2
3
4
5
6
7
8
9{% macro render_comment(comment) %}
<li>{{ comment }}</li>
{% endmacro %}
<!--{% import 'macros.html' as macros %} 导入保存在单独文件的宏-->
<ul>
{% for comment in comments %}
{{ macros.render_comment(comment) }}
{% endfor %}
</ul>模板 include 与模板继承
1
{% include 'common.html' %}
1
2
3
4
5
6
7
8
9
10
11
12
13<!--base.html-->
<!--定义名为head、title和body的block,其元素可在衍生模板中修改-->
<html>
<head>
{% block head %}
<title>{% block title %}{% endblock %} - My Application</title>
{% endblock %}
</head>
<body>
{% block body %}
{% endblock %}
</body>
</html>1
2
3
4
5
6
7
8
9
10
11
12<!--衍生模板-->
{% extends "base.html" %}
<!--基模板中的 3 个块被重新定义,引擎将其插入基模板对应位置-->
{% block title %}Index{% endblock %}
{% block head %}
{{ super() }} <!--基模板中head的内容不是空的,因此需要super()获取原来的内容-->
<style>
</style>
{% endblock %}
{% block body %}
<h1>Hello, World!</h1>
{% endblock %}
Flask-Bootstrap
pip install flask-bootstrap
Bootstrap 是客户端框架,提供的用户界面组件可用于创建网页。服务器只需要提供引用了 Bootstrap 层叠样式表(CSS)和 JavaScript 文件的HTML响 应, 并 在 HTML、CSS 和 JavaScript 代码中实例化所需组件。
Flask 扩展一般都在创建程序实例时初始化,之后在程序中使用一个包含所有 Bootstrap 文件的基模板。
1 | from flask.ext.bootstrap import Bootstrap |
1 | {% extends "bootstrap/base.html" %} |
很多 Block 都是 Flask-Bootstrap 自用的,因此如果程序需要向已经有内容的块
中添加新内容,必须使用 Jinja2 提供的 super() 函数
1 | {% block scripts %} |
错误页面
最常见的错误代码:
- 404:客户端请求未知页面或路由
- 500:有未处理的异常
自定义处理程序(类似视图函数)
1
2
3
4
5
6
7
def page_not_found(e):
return render_template('404.html'), 404
def internal_server_error(e):
return render_template('500.html'), 500模板继承机制建立模板
- templates/base.html 继承 bootstrap/base.html
- 错误页面继承 templates/base.html
模板中的链接
url_for()
使用程序 URL 映射中保存的信息生成 URL,用于动态生成地址- 以视图函数名(或者
app.add_url_route()
定义路由时使用的端点名)作为参数,返回对应的 URL url_for('index')
得到的结果是/
url_for('index', _external=True)
返回绝对地址url_for('user', name='john', _external=True)
返回http://localhost:5000/user/john
- 还可将任何额外参数添加到查询字符串中,例如
url_for('index', page=2)
返回/?page=2
静态文件
对静态文件的引用被当成一个特殊的路由,即
/static/<filename>
默认设置下,Flask 在程序根目录中名为 static 的子目录中寻找静态文件
调用
url_for('static', filename='css/styles.css', _external=True)
得到http://localhost:5000/static/css/styles.css
1
2
3
4
5
6
7
8<!--templates/base.html:定义收藏夹图标-->
{% block head %}
{{ super() }}
<link rel="shortcut icon" href="{{ url_for('static', filename = 'favicon.ico') }}"
type="image/x-icon">
<link rel="icon" href="{{ url_for('static', filename = 'favicon.ico') }}"
type="image/x-icon">
{% endblock %}
本地化日期和时间
Flask-Moment 将 moment.js 集成到 Jinja2 模板,以获取客户端电脑的时区和时区设置。Flask-Moment 实现了 moment.js 中的format()
、fromNow()
、fromTime()
、calendar()
、valueOf()
和unix()
方法
1 | from flask.ext.moment import Moment |
1 | <!--templates/base.html:引入 moment.js 库--> |
Web表单
pip install flask-wtf
跨站请求伪造保护
Flask-WTF 能保护所有表单免受跨站请求伪造(CSRF)攻击;需要设置一个密钥,使用这个密钥生成加密令牌,用令牌验证请求中表单数据
1
2app = Flask(__name__)
app.config['SECRET_KEY'] = 'hard to guess string' # app.config 字典可用来存储框架、扩展和程序本身的配置变量,SECRET_KEY 配置变量是通用密钥(密钥不应该直接写入代码,而要保存在环境变量中)
表单类
每个 Web 表单都由一个继承自 Form 的类表示
每个字段都用对象表示,字段对象可附属一个或多个验证函数用来验证用户提交的输入值是否符合要求
1
2
3
4
5
6
7
8from flask.ext.wtf import Form
from wtforms import StringField, SubmitField
from wtforms.validators import Required
# 字段和验证函数直接从 WTForms 包中导入
class NameForm(Form):
# 有一个名为 name 的文本字段和一个名为 submit 的提交按钮
name = StringField('What is your name?', validators=[Required()]) # 表示type="text"的<input>元素;validators 指定一个由验证函数组成的列表, Required()确保提交的字段不为空
submit = SubmitField('Submit') # 表示属性为type="submit"的<input>元素
表单渲染
视图函数把上面的一个 NameForm 实例通过参数 form 传入模板
1
2
3
4
5<form method="POST">
{{ form.hidden_tag() }}
{{ form.name.label }} {{ form.name() }}
{{ form.submit() }}
</form>利用 Flask-Bootstrap 定义好的样式渲染整个表单
1
2{% import "bootstrap/wtf.html" as wtf %}
{{ wtf.quick_form(form) }}1
2
3
4
5
6
7
8
9
10
11
12<!--templates/index.html:使用 Flask-WTF 和 Flask-Bootstrap 渲染表单-->
{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1>
</div>
{{ wtf.quick_form(form) }}
{% endblock %}
视图函数处理表单
1 |
|
- 提交表单大多作为 POST 请求进行处理
重定向与用户会话
刷新页面时,浏览器会重新发送之前已经发送过的最后一个请求
使用重定向作为 POST 请求的响应,内容是 URL,而不是包含 HTML 代码的字符串,浏览器收到重定向响应时,会向重定向的 URL 发起 GET 请求——原先用
form.name.data
获取用户输入,但请求结束后数据便丢失,因此需要保存输入数据把数据存储在用户会话中,在请求之间”记住“数据(session,类似 Python 字典)
1
2
3
4
5
6
7
8
9from flask import Flask, render_template, session, redirect, url_for
def index():
form = NameForm()
if form.validate_on_submit():
session['name'] = form.name.data
return redirect(url_for('index'))
return render_template('index.html', form=form, name=session.get('name')) # 对于不存在的键,get()返回None
Flash消息
提交表单后,让用户知道变化——
flash()
函数1
2
3
4
5
6
7
8
9
10
11
12
13from flask import Flask, render_template, session, redirect, url_for, flash
def index():
form = NameForm()
if form.validate_on_submit():
old_name = session.get('name')
if old_name is not None and old_name != form.name.data: # 每次提交的名字都会和存储在用户会话中的名字进行比较
flash('Looks like you have changed your name!')
session['name'] = form.name.data
return redirect(url_for('index'))
return render_template('index.html',
form = form, name = session.get('name'))仅调用
flash()
不能把消息显示出来,需要在模板渲染(最好为基模板)——get_flashed_messages()
1
2
3
4
5
6
7
8
9
10
11
12
13<!--templates/base.html:渲染 Flash 消息-->
{% block content %}
<div class="container">
{% for message in get_flashed_messages() %}
<div class="alert alert-warning">
<button type="button" class="close" data-dismiss="alert">×</button>
{{ message }} <!--每次调用flash()都会生成一个消息,获取的消息在下次调用时不会再次返回-->
</div>
{% endfor %}
{% block page_content %}{% endblock %}
</div>
{% endblock %}
数据库
SQL数据库
关系数据库
主键:其值为表中各行的唯一标识符
外键:引用同一个表或不同表中某行的主键
NoSQL数据库
文档数据库和键值对数据库
使用集合代替表,使用文档代替记录
Python数据库框架
- 对象关系映射(Object-Relational Mapper,ORM)与对象文档映射(Object-Document Mapper,ODM)
- ORM 和 ODM 把对象业务转换成数据库业务,性能上有一定的损耗
- 数据库抽象层代码包:SQLAlchemy 和 MongoEngine 等,可以直接处理高等级的 Python 对象,而非表、文档、查询语言等数据库实体
Flask-SQLAlchemy
pip install flask-sqlalchemy
SQLAlchemy 是一个关系型数据库框架,提供高层 ORM,也提供使用数据库原生 SQL 的功能
数据库使用 URL 指定,保存到 Flask 配置对象的SQLALCHEMY_DATABASE_URI 键; SQLALCHEMY_COMMIT_ON_TEARDOWN 键设置为 true 时,每次请求结束后会自动提交数据库中的变动
hostname:MySQL 服务所在的主机,可以是本地主机(localhost)也可以是远程服务器
database:要使用的数据库名
username 和 password:数据库用户和口令
SQLite 数据库不需要服务器,不用指定 hostname、username 和
password,database 是硬盘上文件的文件名1
2
3
4
5
6
7
8
9from flask.ext.sqlalchemy import SQLAlchemy
basedir = os.path.abspath(os.path.dirname(__file__))
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'data.sqlite')
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
db = SQLAlchemy(app) # 程序使用的数据库,有Flask-SQLAlchemy的所有功能
定义模型
在 ORM 中,模型一般是一个 Python 类,类中的属性对应数据库表中的列;Flask-SQLAlchemy 要求每个模型都要定义主键
1 | class Role(db.Model): |
关系
关系型数据库使用关系把不同表中的行联系起来
1 | class Role(db.Model): |
- 一对一关系调用
db.relationship()
时要把参数 uselist 设为 False - 多对一关系也可使用一对多表示,对调两个表即可,或者把外键和
db.relationship()
都放在“多”这一侧
数据库操作
创建表:让 Flask-SQLAlchemy 根据模型类创建数据库
1
2
3(venv) $ python hello.py shell
>>> from hello import db
>>> db.create_all()插入行
1
2
3
4
5
6
7>>> from hello import Role, User
>>> admin_role = Role(name='Admin')
>>> mod_role = Role(name='Moderator')
>>> user_role = Role(name='User')
>>> user_john = User(username='john', role=admin_role)
>>> user_susan = User(username='susan', role=user_role)
>>> user_david = User(username='david', role=user_role)通过数据库会话管理对数据库所做的改动(数据库会话也称为事务)
1
2>>> db.session.add_all([admin_role, mod_role, user_role, user_john, user_susan, user_david])
>>> db.session.commit()修改行
1
2
3>>> admin_role.name = 'Administrator'
>>> db.session.add(admin_role)
>>> db.session.commit()删除行:删除与插入和更新一样,提交数据库会话后才会执行
1
2>>> db.session.delete(mod_role)
>>> db.session.commit()查询行:Flask-SQLAlchemy 为每个模型类都提供了 query 对象
Role.query.all()
使用过滤器可以配置 query 对象:
User.query.filter_by(role=user_role).all()
查看 SQL 语句:
str(User.query.filter_by(role=user_role))
查询关系:此处执行 user_role.users 表达式时,隐含的查询会调用 all() 返回一个用户列表;因此最好修改关系的设置,加入 lazy = ‘dynamic’ 参数,从而能指定更精确的查询过滤器
1
2
3
4
5
6
7
8
9
10>>> users = user_role.users
>>> users
[<User u'susan'>, <User u'david'>]
>>> users[0].role
<Role u'User'>
>>> user_role.users.order_by(User.username).all()
[<User u'david'>, <User u'susan'>]
>>> user_role.users.count()
2
结合视图函数
上面的操作可在视图函数中进行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
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))1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1>
{% if not known %}
<p>Pleased to meet you!</p>
{% else %}
<p>Happy to see you again!</p>
{% endif %}
</div>
{{ wtf.quick_form(form) }}
{% endblock %}
Python Shell 集成
Flask-Migrate
需要修改数据库模型
pip install flask-migrate
1
2
3
4
5
6from flask.ext.migrate import Migrate, MigrateCommand
# ...
migrate = Migrate(app, db)
manager.add_command('db', MigrateCommand)创建迁移仓库:创建 migrations 文件夹,所有迁移脚本都存放其中
1
(venv) $ python hello.py db init
数据库迁移用迁移脚本表示。脚本中有两个函数
upgrade()
函数把迁移中的改动应用到数据库中,downgrade()
函数则将改动删除自动创建的迁移会根据模型定义和数据库当前状态之间的差异,生成
upgrade()
和downgrade()
函数的内容1
(venv) $ python hello.py db migrate -m "initial migration"
更新数据库
1
(venv) $ python hello.py db upgrade
电子邮件
pip install flask-mail
Flask-Mail 连接到 SMTP 服务器,默认使用 localhost 端口25,不用验证即可发送
1
2
3
4
5
6
7
8
9
10from flask.ext.mail import Mail
import os
mail = Mail(app)
# ...
app.config['MAIL_SERVER'] = 'smtp.googlemail.com'
app.config['MAIL_PORT'] = 587
app.config['MAIL_USE_TLS'] = True
app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME')
app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD')需要预先设置环境变量
1
2(venv) $ set MAIL_USERNAME=<Gmail username>
(venv) $ set MAIL_PASSWORD=<Gmail password>实例
1
2
3
4
5
6
7
8
9
10
11from flask.ext.mail import Message
app.config['FLASKY_MAIL_SUBJECT_PREFIX'] = '[Flasky]' # 定义邮件主题的前缀
app.config['FLASKY_MAIL_SENDER'] = 'Flasky Admin <flasky@example.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()使用current_app,必须先激活程序上下文每当表单接收新的名字,就给管理员发送一个电子邮件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22# ...
app.config['FLASKY_ADMIN'] = os.environ.get('FLASKY_ADMIN') # 收件人保存在环境变量 FLASKY_ADMIN 中
# ...
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) # 模板文件都保存在templates下的mail子文件夹
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))异步发送
1
2
3
4
5
6
7
8
9
10
11
12
13
14from 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
项目结构
1 | |-app/ # Flask程序一般都保存在app包中 |
配置选项
使用层次结构的配置类。
基类 Config 中包含通用配置,子类分别定义专用的配置。
某些配置可以从环境变量中导入。
SQLALCHEMY_DATABASE_URI 变量被指定了不同的值,这样可在不同的配置环境中运行,每个环境都使用不同的数据库。
配置类可以定义 init_app() 类方法,其参数是程序实例。
1 | import os |
程序包(app)
数据库模型和电子邮件支持函数被移到了这个包中,分别保存为 app/models.py 和 app/email.py
__init__.py
:程序包的构造文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29from 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)
# 附加路由和自定义的错误页面
# 注册蓝本
from .main import main as main_blueprint
app.register_blueprint(main_blueprint)
return app蓝本中实现功能:蓝本和程序类似,也可以定义路由。但在蓝本中定义的路由处于休眠状态,需要蓝本注册到程序上
1
2
3
4
5
6# app/main/__init__.py:创建蓝本
from flask import Blueprint
main = Blueprint('main', __name__) # 参数:蓝本的名字和蓝本所在的包或模块
from . import views, errors # 程序的路由保存在包里的app/main/views.py中,错误处理程序保存在app/main/errors.py中。脚本的末尾导入,以避免循环导入依赖(views.py 和 errors.py 中还要导入蓝本 main)错误处理程序(app/main/errors.py)
1
2
3
4
5
6
7
8
9
10from flask import render_template
from . import main
def page_not_found(e):
return render_template('404.html'), 404
def internal_server_error(e):
return render_template('500.html'), 500程序路由(app/main/views.py)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20from 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
def index():
form = NameForm()
if form.validate_on_submit():
# ...
return redirect(url_for('.index')) # 蓝本中Flask会为蓝本中的全部路由端点加上一个命名空间,以在不同的蓝本中使用相同的端点名定义视图函数
# 命名空间就是蓝本的名字——Blueprint构造函数的第一个参数——index() 注册的端点名是main.index
# 同一蓝本中的重定向可以使用简写形式,跨蓝本的重定向必须使用带有命名空间的端点名
return render_template('index.html',
form=form, name=session.get('name'),
known=session.get('known', False),
current_time=datetime.utcnow())
启动脚本
manage.py 文件用于启动程序
加入了 shebang 声明,所以在基于 Unix 的操作系统中可以通过./manage. py
执行脚本
1 | #!/usr/bin/env python |
需求文件
requirements.txt 文件,用于记录所有依赖包及其精确的版本号
生成文件:pip freeze >requirements.txt
创建环境:pip install -r requirements.txt
单元测试
使用 Python 标准库中的 unittest 包编写,setUp()
和tearDown()
方法分别在各测试前后运行,并且名字以 test_ 开头的函数都作为测试执行
setUp()
方法尝试创建一个测试环境,并激活上下文,创建一个全新的数据库
数据库和程序上下文在tearDown()
中删除
1 | import unittest |
1 | # manage.py:启动单元测试的命令 |
1 | (venv) $ python manage.py test |