本笔记只是个人记录,非指导、解惑类博客
客户端(例如Web 浏览器)把请求发送给Web 服务器,Web 服务器再把请求发送给Flask程序实例。处理URL 和函数之间关系的程序称为路由。
from flask import Flask app = Flask(__name__) @app.route('/') def index(): return '<h1>Hello World!</h1>' @app.route('/user/<name>') def user(name): return '<h1>Hello, {}!</h1>'.format(name) if __name__ == "__main__": app.run(debug=True)
index
函数称为视图函数(view function)。视图函数返回的响应可以是包含
HTML 的简单字符串,也可以是复杂的表.这个函数的返回值称为响应,是客户端接收到的内容
'/user/<name>'
尖括号中的内容就是动态部分,任何能匹配静态部分的URL 都会映射到这个路由上
Flask 使用上下文临时把某些对象变为全局可访问。有了上下文,就可以写出下面的视图函数:
from flask import request app = Flask(__name__) @app.route('/') def index(): user_agent = request.headers.get('User-Agent') return '<p>Your browser is {}</p>'.format(user_agent)
在Flask 中有两种上下文:

/ 和/user/ 路由在程序中使用app.route 修饰器定义。/static/ 路由是
Flask 添加的特殊路由,用于访问静态文件。
在请求开始时,我们可能需要创建数据库连接或者认证发起请求的用户。为了避免在每个视图函数中都使用重复的代码,Flask提供了注册通用函数的功能,注册的函数可在请求被分发到视图函数之前或之后调用。
before_first_request
:注册一个函数,在处理第一个请求之前运行。before_request
:注册一个函数,在每次请求之前运行。after_request
:注册一个函数,如果没有未处理的异常抛出,在每次请求之后运行。teardown_request
:注册一个函数,即使有未处理的异常抛出,也在每次请求之后运行。
Flask 调用视图函数后,会将其返回值作为响应的内容。大多数情况下,响应就是一个简
单的字符串,作为HTML 页面回送客户端。
视图函数的作用很明确,即生成请求的响应,如第2 章中的示例所示。对最简单的请求来说,
这就足够了,但一般而言,请求会改变程序的状态,而这种变化也会在视图函数中产生。
例如,用户在网站中注册了一个新账户。用户在表单中输入电子邮件地址和密码,然后点
击提交按钮。服务器接收到包含用户输入数据的请求,然后Flask 把请求分发到处理注册
请求的视图函数。这个视图函数需要访问数据库,添加新用户,然后生成响应回送浏览
器。这两个过程分别称为业务逻辑和表现逻辑。
模板是一个包含响应文本的文件,其中包含用占位变量表示的动态部分,其具体值只在请
求的上下文中才能知道。使用真实值替换变量,再返回最终得到的响应字符串,这一过程
称为渲染。为了渲染模板,Flask 使用了一个名为Jinja2 的强大模板引擎。
<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>

Jinja2 提供了多种控制结构,可用来改变模板的渲染流程。
{% if user %} Hello, {{ user }}! {% else %} Hello, Stranger! {% endif %}
<ul> {% for comment in comments %} <li>{{ comment }}</li> {% endfor %} </ul>
{% macro render_comment(comment) %} <li>{{ comment }}</li> {% endmacro %} <ul> {% for comment in comments %} {{ render_comment(comment) }} {% endfor %} </ul>
重复使用宏,我们可以将其保存在单独的文件中,然后在需要使用的模板中导入
{% import 'macros.html' as macros %} <ul> {% for comment in comments %} {{ macros.render_comment(comment) }} {% endfor %} </ul>
- 基模板
<html> <head> {% block head %} <title>{% block title %}{% endblock %} - My Application</title> {% endblock %} </head> <body> {% block body %} {% endblock %} </body> </html>
- 衍生模板
{% extends "base.html" %} {% block title %}Index{% endblock %} {% block head %} {{ super() }} <style> </style> {% endblock %} {% block body %} <h1>Hello, World!</h1> {% endblock %}
Bootstrap 是客户端框架,因此不会直接涉及服务器。服务器需要做的只是提供引用了
Bootstrap 层叠样式表(CSS) 和JavaScript 文件的HTML 响应, 并在HTML、CSS 和
JavaScript 代码中实例化所需组件。
初始化Flask-Bootstrap 之后,就可以在程序中使用一个包含所有Bootstrap 文件的基模板。
下面是使用templates/user.html:使用Flask-Bootstrap 的模板:
{% extends "bootstrap/base.html" %}<!--extends 指令从Flask-Bootstrap 中导入bootstrap/base.html--> {% block title %}Flasky{% endblock %}<!--title 块--> {% block navbar %}<!--navbar 块--> <div class="navbar navbar-inverse" role="navigation"> <div class="container"> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="/">Flasky</a> </div> <div class="navbar-collapse collapse"> <ul class="nav navbar-nav"> <li><a href="/">Home</a></li> </ul> </div> </div> </div> {% endblock %} {% block content %}<!--content 块--> <div class="container"> <div class="page-header"> <h1>Hello, {{ name }}!</h1> </div> </div> {% endblock %}
- title 块的作用很明显,其中的内容会出现在渲染后的HTML 文档头部,放在 标签中。
- navbar 和content 这两个块分别表示页面中的导航条和主体内容。
如果程序需要向已经有内容的块中添加新内容,必须使用Jinja2 提供的super() 函数。例如,如果要在衍生模板中添加新的JavaScript 文件,需要这么定义scripts 块:
{% block scripts %} {{ super() }} <script type="text/javascript" src="my-script.js"></script> {% endblock %}
下面展示了templates/base.html 的内容,这是一个继承自bootstrap/base.html 的新模板,其中定义了导航条。这个模板本身也可作为其他模板的基模板,例如templates/user.html、templates/404.html 和templates/500.html。
templates/base.html:包含导航条的程序基模板
{% extends "bootstrap/base.html" %} {% block title %}Flasky{% endblock %} {% block navbar %} <div class="navbar navbar-inverse" role="navigation"> <div class="container"> <div class="navbar-header"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="/">Flasky</a> </div> <div class="navbar-collapse collapse"> <ul class="nav navbar-nav"> <li><a href="/">Home</a></li> </ul> </div> </div> </div> {% endblock %} {% block content %} <div class="container"> {% block page_content %}{% endblock %}<!--page_content 的新的空块,--> </div> {% endblock %}
使用模板继承机制自定义404 错误页面
{% extends "base.html" %} {% block title %}Flasky - Page Not Found{% endblock %} {% block page_content %} <div class="page-header"> <h1>Not Found</h1> </div> {% endblock %}
templates/user.html 现在可以通过继承这个基模板来简化内容
{% extends "base.html" %} {% block title %}Flasky{% endblock %} {% block page_content %} <div class="page-header"> <h1>Hello, {{ name }}!</h1> </div> {% endblock %}
Flask 提供了url_for() 辅助函数,它可以使用程序URL映射中保存的信息生成URL。
url_for('index') # / url_for('index', _external=True) # http://localhost:5000/ 返回绝对地址 url_for('user', name='john', _external=True) # http://localhost:5000/user/john
静态文件: HTML代码中引用的图片、JavaScript 源码文件和CSS。
默认设置下,Flask 在程序根目录中名为static的子目录中寻找静态文件,例如,调用 url_for('static', filename='css/styles.css', _external=True)
得到的结果是http://
localhost:5000/static/css/styles.css。
服务器需要统一时间单位,这和用户所在的地理位置无关,所以一般使用协调世界时
(Coordinated Universal Time,UTC)。
但是时间戳的表示方式很难一眼看出时间。解决方案是,把时间单位发送给Web浏览器,转换成当地时间,然后渲染。JavaScript开发的优秀客户端开源代码库,名为moment.js。根据书中的几个代码,可以得出如下图所示的时间戳,显示的时间会随时间而变化:


Flask-WTF 可以把轻松地处理Web表单
默认情况下,Flask-WTF 能保护所有表单免受跨站请求伪造(Cross-Site Request Forgery,CSRF)的攻击。为了实现CSRF 保护,Flask-WTF 需要程序设置一个密钥。Flask-WTF 使用这个密钥生成加密令牌,再用令牌验证请求中表单数据的真伪。
app = Flask(__name__) app.config['SECRET_KEY'] = 'hard to guess string' # app.config 字典可用来存储框架、扩展和程序本身的配置变量
下面是一个简单的Web 表单,包含一个文本字段和一个提交按钮。
from flask.ext.wtf import Form from wtforms import StringField, SubmitField from wtforms.validators import Required class NameForm(Form): """ StringField类表示属性为type="text" 的<input> 元素。 SubmitField 类表示属性为type="submit" 的<input> 元素。 """ name = StringField('What is your name?', validators=[Required()]) # 验证函数Required()确保提交的字段不为空。 submit = SubmitField('Submit')
Flask-Bootstrap 提供了一个非常高端的辅助函数,可以使用Bootstrap中预先定义好的表单样式渲染整个Flask-WTF表单,而这些操作只需一次调用即可完成。
{% import "bootstrap/wtf.html" as wtf %} {{ wtf.quick_form(form) }}
如上所示:导入的bootstrap/wtf.html文件中定义了一个使用Bootstrap渲染Falsk-WTF表单对象的辅助函数。wtf.quick_form() 函数的参数为Flask-WTF表单对象,使用Bootstrap的默认样式渲染传入的表单。
<--! 使用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 %}
新的视图函数index()
, 不仅要渲染表单,还要接收表单中的数据.
@app.route('/', methods=['GET', 'POST']) # 没指定methods 参数,默认为GET def index(): name = None form = NameForm() if form.validate_on_submit(): name = form.name.data form.name.data = '' return render_template('index.html', form=form, name=name)
刷新页面时浏览器会重新发送之前已经发送过的最后一个请求:


基于这个原因,最好别让Web 程序把POST请求作为浏览器发送的最后一个请求。解决方法是:使用重定向作为POST 请求的响应。重定向是一种特殊的响应,响应内容是URL,而不是包含HTML代码的字符串。

请求完成后,有时需要让用户知道状态发生了变化。这里可以使用确认消息、警告或者错误提醒。一个典型例子是,用户提交了有一项错误的登录表单后,服务器发回的响应重新渲染了登录表单,并在表单上面显示一个消息,提示用户用户名或密码错误。
from flask import Flask, render_template, session, redirect, url_for, flash @app.route('/', methods=['GET', 'POST']) 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'))

下面展示了如何初始化及配置一个简单的SQLite数据库。
# 配置数据库 from 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)
db
对象是SQLAlchemy
类的实例,表示程序使用的数据库,同时还获得了Flask-SQLAlchemy提供的所有功能。
模型这个术语表示程序使用的持久化实体。下面的代码定义了数据库的模型
class Role(db.Model): __tablename__ = 'roles' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64), unique=True) 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) def __repr__(self): return '<User %r>' % self.username
类变量
__tablename__
定义在数据库中使用的表名。db.Column
类构造函数的第一个参数是数据库列和模型属性的类型。
关系型数据库使用关系把不同表中的行联系起来。例如外键等,这里看书就行。书下面几节都是关于数据库的,对数据库有了解的都比较简单,这里就不记录了。
Flask-Mail 是链接到SMTP服务器的,默认会使用本地localhost的25端口。配置示例如下:
import os # ... 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')
|-flasky |-app/ |-templates/ |-static/ |-main/ |-__init__.py |-errors.py |-forms.py |-views.py |-__init__.py |-email.py |-models.py |-migrations/ |-tests/ |-__init__.py |-test*.py |-venv/ |-requirements.txt |-config.py |-manage.py
- Flask程序一般都保存在名为app 的包中;
- 和之前一样,migrations文件夹包含数据库迁移脚本;
- 单元测试编写在tests包中;
- 和之前一样,venv 文件夹包含Python 虚拟环境。