0. 前言
git客户端下载:http://git-scm.com/
git clone https://github.com/miguelgrinberg/flasky.git
1、把改动的文件还原成最初的状态
git reset --hard
2、git fetch命令根据GitHub上远程仓库更新本地仓库的提交历史和标签,但不会真正改动源文件。
git fetch --all
git fetch --tags
1. 安装
Flask有3个主要依赖:路由、调试和Web服务器网关接口(WSGI,Web server gateway interface)子系统由Werkzeug提供;模板系统由Jinja2提供;命令行集成由Click提供。
1.1 创建应用目录
git clone https://github.com/miguelgrinberg/flasky.git
cd flasky
git checkout 1a
# 1a代表一个标签(tag),是项目中某次提交历史的名称。
虚拟环境感觉在linux下比较合适,windows下用不上。
2. 应用的基本结构
所有Flask应用都必须创建一个应用实例。Web服务器使用一种名为Web服务器网关接口(WSGI,Web server gateway interface,读作"wiz-ghee")的协议,把接收自客户端的所有请求都转交给这个对象处理。
2.2 路由和视图函数
@app.route('/user/<name>')
def user(name):
return 'hello,{}'.format(name)
指定其他类型:
/user/<int:id>
string,int,float,path,
path类型是一种特殊的字符串,与string类型不同的是,它可以包括正斜线。
2.6 调试模式
重载器和调试器
2.8 请求-响应循环
2.8.1 应用和请求上下文
变量名 | 上下文 | 说明 |
---|---|---|
current_app | 应用上下文 | 当前应用的应用实例 |
g | 应用上下文 | 处理请求时用作临时存储的对象,每次请求都会重设这个变量 |
request | 请求上下文 | 请求对象, 封装了客户端发出的HTTP请求中的内容 |
session | 请求上下文 | 用户对话,值为一个字典,存储请求之间需要"记住"的值 |
>>> from hello import app
>>> from flask import current_app
>>> app_ctx = app.app_context()
>>> app_ctx.push()
>>> current_app.name
'hello'
>>> app_ctx.pop()
获取应用上下文的方法是在应用实例上调用app.app_context()
2.8.2 请求分派
>>> app.url_map
Map([<Rule '/static/<filename>' (OPTIONS, GET, HEAD) -> static>])
2,8.3 请求对象
request.values
属性或方法 | 说明 |
---|---|
form | 一个字典,存储请求提交的所有表单字段 |
args | 一个字典,存储通过URL查询字符串传递的所有参数 |
values | 一个字典,form和args的合集 |
cookies | 一个字典,存储请求的所有cookie |
headers | 一个字典,存储请求的所有HTTP首部 |
files | 一个字典,存储请求上传的所有文件 |
get_data() | 返回请求主体缓冲的数据 |
get_json() | 返回一个Python字典,包含解析请求主体后得到的json |
blueprint | 处理请求的Flask蓝本的名称 |
endpoint | 处理请求的Flask端点的名称;Flask把视图函数的名称用作路由端点的名称 |
method | HTTP请求方法,例如GET或POST |
scheme | URL方案(http或https) |
is_secure() | 通过安全的连接(HTTPS)发送请求时返回True |
host | 请求定义的主机名,如果客户端定义了端口号,还包括端口号 |
path | URL的路径部分 |
query_string | URL的查询字符串部分,返回原始二进制值 |
full_path | URL的路径和查询字符串部分 |
url | 客户端请求的完整URL |
base_url | 同url,但没有查询字符串部分 |
remote_addr | 客户端的IP地址 |
environ | 请求的原始WSGI环境字典 |
2.8.4 请求钩子
请求钩子通过装饰器实现。Flask支持以下4种钩子。
钩子 | 说明 |
---|---|
before_request | 注册一个函数,在每次请求之前运行。 |
before_first_request | 注册一个函数,只在处理第一个请求之前运行。可以通过这个钩子添加服务器初始化任务。 |
after_request | 注册一个函数,如果没有未处理的异常抛出,在每次请求之后运行。 |
teardown_request | 注册一个函数,即使有未处理的异常抛出,也在每次请求之后运行。 |
在请求钩子函数和视图函数之间共享数据一般使用上下文全局变量g。例如,before_request处理程序可以从数据库中加载已登录用户,并将其保存到g.user中。随后调用视图函数时,便可以通过g.user获取用户。
2.8.5 响应
方式一:return
数字代码作为第二个返回值,添加到响应文本之后
return '<h1>Bad Request</h1>',400
方式二:响应对象
from flask import make_response
response = make_reponse('<h1>flask</h1>')
response.set_cookie('answer','12')
响应对象最常使用的属性和方法如下所示:
属性或方法 | 说明 |
---|---|
status_code | HTTP数字状态码 |
headers | 一个类似字典的对象,包含随响应发送的所有首部 |
set_cookie() | 为响应添加一个cookie |
delete_cookie() | 删除一个cookie |
content_length | 响应主体的长度 |
content_type | 响应主体的媒体类型 |
set_data() | 使用字符串或字节值设定响应 |
get_data() | 获取响应主体 |
方式三:重定向
from flask import redirect
return redirect('http://baidu.com')
方式四:abort
from flask import abort
abort(404)
3. 模板
为了渲染模板,Flask使用Jinja2的模板引擎。
3.1 Jinja2模板引擎
3.1.2 变量
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>
变量值可以使用过滤器修改,过滤器加在变量名之后,二者之间以竖线分隔。
hello, {{ name|capitalize }}
Jinja2变量过滤器
过滤器名 | 说明 |
---|---|
safe | 渲染值时不转义 |
capitalize | 把值的首字母转换成大写,其他字母转换成小写 |
lower | 把值转换成小写形式 |
upper | 把值转换成大写形式 |
title | 把值中每个单词的首字母都转换成大写 |
trim | 把值的首尾空格删掉 |
striptags | 渲染之前把值中所有的HTML标签都删掉 |
3.1.3 控制结构
条件判断语句:
{% if user %}
hello,{{ user }}!
{% else %}
hello,stranger!
{% endif %}
循环:
<ul>
{% for comment in comments %}
<li>{{ comment }}</li>
{% endfor %}
</ul>
宏:略
模板继承:base.html
<html>
<head>
{% block head %}
<title>{% block title %}{% endblock %} - my application </title>
{% endblock %}
</head>
<body>
{% block body %}
{% endblock %}
</body>
</html>
{% extends "base.html" %}
3.2 使用Flask-Bootstrap集成Bootstrap(!)
bootstrap4 中文开发手册:https://www.php.cn/manual/view/34100.html
git checkout 3b
.navbar-inverse 表示反色的导航栏,是一个带有黑色背景白色文本的反色的导航栏。
.navbar-toggle来修饰button表示这是一个控制切换状态的button。
.collapse.navbar-collapse通过父断点来分组和隐藏导航栏内容。
icon-bar当屏幕宽度小于一定程度时,导航条缩小,变成可折叠形式
3.3 自定义错误页面
hello.py
from flask import Flask, render_template
from flask_bootstrap import Bootstrap
app = Flask(__name__)
bootstrap = Bootstrap(app)
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
@app.errorhandler(500)
def internal_server_error(e):
return render_template('500.html'), 500
@app.route('/')
def index():
return render_template('index.html')
@app.route('/user/<name>')
def user(name):
return render_template('user.html', name=name)
3.3.1 base.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 %}
</div>
{% endblock %}
3.3.2 404
{% extends "base.html" %}
{% block title %}Flasky - Page Not Found{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Not Found</h1>
</div>
{% endblock %}
3.4 链接(url_for)
Flask提供url_for()
辅助函数,它使用应用的URL映射中保存的信息生成URL。
url_for()函数的第一个且唯一必须指定的参数是端点名,即路由的内部名称。默认情况下,路由的端点是相应视图函数的名称。
url_for()函数最简单的用法是以视图函数名作为参数,返回对应的url
{{ url_for('static', filename='favicon.ico') }}
对于3.3的hello.py而言,
url_for('index') 得到的结果是/
url_for('index',_external = True) 返回的是绝对地址
使用url_for()生成动态url时,将动态部分作为关键字参数传入。例如:
url_for('user',name = 'john',_external = True) 返回结果http://localhost:5000/user/john
非动态的参数也可以添加到查询字符串。
url_for('user',name = 'john',page = 2,version = 1)
返回结果/user/john?page=2&version=1
3.5 静态文件
templates/base.html
插入head区块的末尾
{% 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 %}
为了保留基模板中这个区块里的原始内容,调用了super函数。
3.6 使用Flask-Moment本地化日期和时间
hello.py
#coding:utf-8
from datetime import datetime
from flask import Flask, render_template
from flask_bootstrap import Bootstrap
from flask_moment import Moment
app = Flask(__name__)
bootstrap = Bootstrap(app)
moment = Moment(app)
@app.route('/')
def index():
return render_template('index.html',current_time=datetime.utcnow())
# 协调世界时间(UTC,coordinated universal time)
if __name__ == '__main__':
app.run('127.0.0.1',port = '10110')
index.html
format(‘LLL’)函数根据客户端计算机中的时区和区域设置渲染日期和时间。参数决定了渲染的方式,从’L’到’LLLL’分别对应不同的复杂度。
fromNow()渲染相对时间戳,而且会随着时间的推移自动刷新显示的时间(refresh=True)。
{% extends "base.html" %}
{% block title %}Flasky{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>Hello World!</h1>
</div>
<p>The local date and time is {{ moment(current_time).format('LLL') }}.</p>
<p>That was {{ moment(current_time).fromNow(refresh=True) }}.</p>
{% endblock %}
base.html
{% extends "bootstrap/base.html" %}
{% block title %}Flasky{% endblock %}
{% 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 %}
{% 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 %}
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
{{ moment.include_moment() }}
{% endblock %}
4. Web表单
Flask-WTF扩展用于处理web表单。
from flask import Flask
app = Flask(__name__)
app.config['SECRET_KEY'] = 'hard to guess string'
Flask-WTF之所以要求应用配置一个密钥,是为了防止表单遭到跨站请求伪造(CSRF,cross-site request forgery)攻击。恶意网站把请求发送到被攻击者已登录的其他网站时,就会引发CSRF攻击。Flask-WTF为所有表单生成安全令牌,存储在用户会话中。令牌是一种加密签名,根据密钥生成。
4.2 表单类
#coding:utf-8
from flask_wtf import FlaskForm
from wtforms import StringField,SubmitField
from wtforms.validators import DataRequired
class NameForm(FlaskForm):
name = StringField('What is your name?',validators = [DataRequired()])
# StringField类表示属性为type="text"的HTML <input>元素
submit = SubmitField('Submit')
# SubmitField类表示属性为type="submit"的HTML <input>元素
WTForms支持的HTML标准字段
WTForms内建的验证函数
4.3 把表单渲染成HTML
Flask-Bootstrap扩展提供了一个高层级的辅助函数,可以使用Bootstrap预定义的表单样式渲染整个Flask-WTF表单,而这些操作只需一次调用即可完成。
import导入模板元素,在多个模板中使用。导入的bootstrap/wtf.html文件中定义了一个使用Bootstrap渲染Flask-WTF表单对象的辅助函数。wtf.quick_form()函数的参数为Flask-WTF表单对象,使用Bootstrap的默认样式渲染传入的表单。
index.html(截取部分,完整请查看4a)
{% import "bootstrap/wtf.html" as wtf %}
{{ wtf.quick_form(form) }}
hello.py(截取部分,完整请查看4a)
from flask import Flask, render_template
from flask_bootstrap import Bootstrap
from flask_moment import Moment
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
app = Flask(__name__)
app.config['SECRET_KEY'] = 'hard to guess string'
bootstrap = Bootstrap(app)
class NameForm(FlaskForm):
name = StringField('What is your name?', validators=[DataRequired()])
submit = SubmitField('Submit')
@app.route('/', methods=['GET', 'POST'])
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)
4.5 重定向和用户会话
git checkout 4b
@app.route('/', methods=['GET', 'POST'])
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'))
4.6 闪现消息
hello.py
@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'))
base.html
{% 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 }}
</div>
{% endfor %}
{% block page_content %}{% endblock %}
</div>
{% endblock %}
base.html
{% 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 }}
</div>
{% endfor %}
{% block page_content %}{% endblock %}
</div>
{% endblock %}