HTTP请求
请求报文
请求的实质是发送到服务器上的一些数据,浏览器与服务器之间交互的数据称之为报文.
报文由报文首部和报文主体组成,两者由空行分隔,请求报文的主体一般为空
报文首部包含了请求的各种信息和设置,比如客户端的类型,是否设置缓存,语言偏好等等
Request对象
请求对象Request封装了从客户端发来的请求报文,我们能从它获取请求报文中的所有数据
属性/方法 | 说明 |
---|---|
args | 存储解析后的查询字符串,可以通过字典方式获取键值 |
data | 包含字符串形式的请求数据 |
method | 请求的HTTP方法 |
json | 包含解析后的json数据,内部调用get_json,可通过字典方式获取键值 |
获取请求的查询字符串
from flask import Flask,request
app = Flask(__name__)
URL处理
flask内置的URL变量转换器
转换器 | 说明 |
---|---|
string | 不包含斜线的字符串 |
int | 整型 |
fioat | 浮点型 |
path | 包含斜线的字符串 |
any | 匹配一系列给定值中的一个元素 |
uuid | UUID字符串 |
请求钩子
我们需要对请求进行预处理和后处理,这时可以使用Flask提供的一些请求钩子,他们可以注册在请求处理不同阶段执行的处理函数(或称回调函数)
钩子 | 说明 |
---|---|
before_first_request | 注册一个函数,在处理第一个请求前运行 |
before_request | 注册一个函数,在处理每个请求前运行 |
after_this_request | 在视图函数中注册一个函数,会在这个请求结束后运行 |
每个钩子可以注册任意多个处理函数,函数名并不是必须和钩子名称相同
HTTP响应
响应报文主要由协议版本,状态码,原因短语,响应首部和响应主体组成
错误响应
如果想手动返回错误响应,方便的方法是使用Flask提供的abort()函数
在abort()函数中传入状态码即可返回对应的错误响应
from flask import Flask,request,abort
app = Flask(__name__)
响应格式
在HTTP响应中,数据可以通过多种格式传输,不同的响应数据格式需要设置不同的MIME类型,MIME类型在首部的Content-Type字段中定义.
如果使用其他的MIME类型,通过Flask提供的make_response()方法生成响应对象,传入响应的主体作为参数,然后使用响应对象的mimetype属性设置MIME类型
from flask import Flask,request,abort,make_response
-
纯文本: text/plain
-
HTML: text/html
-
XML: application/xml
-
JSON: application/json
jsonify()函数
借助jsonify()函数,我们仅需要数据或参数,它会对我们传入的参数进行序列化,转换为JSON字符串最为响应的主体,然后生成一个响应对象,并且设置正确的MIME类型
from flask import jsonify
cookie
在Flask中,如果想要在响应中添加一个cookie,使用Response类提供的set_cookie()方法,使用这个方法,需要先使用make_response()方法手动生成一个响应对象,传入响应主体作为参数,
Response类的常用属性和方法
方法/属性 | 说明 |
---|---|
headers | 表示响应首部,可以像字典一样操作 |
status | 状态码,文本类型 |
status_code | 状态码,整型 |
mimetype | MIME类型 |
set_cookie | 可以用来设置一个cookie |
set_cookie方法的参数
属性 | 说明 |
---|---|
key | cookie的键 |
value | cookie的值 |
max_age | cookie被保存的时间数,单位为秒 |
expires | 具体的过期时间 |
path | 限制cookie只在给定的路径可用,默认为整个域名 |
domain | 设置cookie可用的域名 |
secure | 如果设置为True,只有通过HTTPS才可以使用 |
httponly | 如果设置为True,禁止客户端JavaScript获取cookie |
设置程序秘钥
session通过秘钥对数据进行签名以加密数据,因此得先设置一个秘钥,秘钥就是一个具有一定复杂度和随机性的字符串.
程序的秘钥可以通过Flask.secret_key属性或配置变量SECRET_KEY设置
更安全的做法是把秘钥写进系统环境变量,或者保存在.env文件中
登出用户
Flask上下文
flask有两种上下文,程序上下文和请求上下文
程序上下文存储了程序运行所必须的信息
请求上下文包含了请求的各种信息,比如请求的URL,请求的HTTP方法
Flask的上下文变量
变量名 | 上下文类别 | 说明 |
---|---|---|
current_app | 程序上下文 | 指向处理请求的当前程序实例 |
g | 程序上下文 | 用于存储全局数据,每次请求都会重设 |
request | 请求上下文 | 封装客户端发出的请求报文数据 |
session | 请求上下文 | 用于记住请求之间的数据,通过签名的cookie实现 |
在hello视图函数中从查询字符串获取name值,如果每一个视图都需要这个值,那么就要在每个视图重复这行代码,借助g我们可以将这个操作移动到before_request处理函数中执行,然后保存在g的任意属性上
from flask import g
设置这个函数后,其他视图中可以直接使用g.name获取对应的值,另外,g也支持使用类似字典的get(),pop(),以及setdefault()方法进行操作
模板
模板引擎的作用就是读取并执行模板中的特殊语法标记,根据传入的数据将变量替换为实际值,输出最终的HTML页面,这个过程呗称之为渲染
上下文
内置上下文变量
Flask在模板上下文中提供了一些内置变量,可以在模板中直接使用
标准模板全局变量
变量 | 说明 |
---|---|
config | 当前的配置对象 |
request | 当前的请求对象,在已激活的请求环境下可用 |
session | 当前的会话对象,在已激活的请求环境下可用 |
g | 与请求绑定的全局变量,在已激活的请求环境下可用 |
自定义上下文
如果多个模板都要使用同一变量,更好的办法是设置一个模板全局变量,Flask提供了一个app.context_process装饰器,可以用来注册模板上下文处理函数
当我们渲染任意一个模板时,所有使用qpp.context_process装饰器注册的模板上下文处理函数都会被执行,这些函数的返回值会被添加到模板中,因此我们可以在模板中直接使用foo变量
全局对象
全局对象是指在所有模板中都可以直接使用的对象
jinja2内置全局函数
函数 | 说明 |
---|---|
range([start,]stop[,step]) | 和python中的用法相同 |
lipsum(n=5,html=True,min=20,max=100) | 生成随机样本,可以在测试的时候用来填充页面,默认生成5段HTML文本,每段包含20-100个单词 |
dict(**item) | 和python的dict()用法相同 |
自定义全局函数
默认使用函数的原名称传入模板,app.template_global()仅能注册全局函数
其他模板可以直接使用:
如<h2>{{bar()}}</h2>
过滤器
内置过滤器
过滤器 | 说明 |
---|---|
default | 设置默认值,默认值作为参数传入,别名为d |
escape(s) | 转义HTML文本,别名为e |
first(seq) | 返回序列的第一个元素 |
last(seq) | 返回序列的最后一个元素 |
random(seq) | 返回序列的随机元素 |
safe(value) | 将变量标记为安全,避免转义 |
trim(value) | 清除变量值前后的空格 |
测试器
内置测试器
测试器 | 说明 |
---|---|
callable(object) | 判断对象是否可调用 |
define(value) | 判断变量是否已定义 |
none(value) | 判断变量是否为none |
number(value) | 判断变量是否是数字 |
sequence(value) | 判断变量是否是序列 |
sameas(value,other) | 判断变量与other是否指向相同的内存地址 |
如:
{% if foo is sameas bar %}
模板环境对象
配置选项,上下文变量,全局函数,过滤器和测试器存储在app.jinja_env
模板环境中的全局函数,过滤器,测试器分别存储在Environment对象的globals,filters和test属性中.
添加自定义全局对象
和app.template_global装饰器不同,直接操作globals字典允许我们传入任意python对象,
def bar():
return 'i am bar'
foo = 'i am foo'
app.jinja_env.globals['bar'] = bar
app.jinja_env.globals['foo'] = foo
添加自定义过滤器
def smiling(s):
return s + ':)'
app.jinja_env.filters["smiling"] = smiling
宏:
模板中的宏和python中的函数类似,可以传递参数,但是不能有返回值,可以将一些经常用到的代码片段放到宏中,把一些不固定的值抽取出来当成一个变量,使用宏的时候,参数可以设置为默认值
为了方便管理,把宏存储在单独的文件中,这个文件通常命名为macros.html 或_macros.html使用macro和endmacro标签声明宏的开始和结束,在开始标签中定义宏的名称和接受的参数.
{% macro qux(amount=1) %}
{% if amount == 1 %}
I am qux
{% elif amount > 1 %}
We are quxs.
{% endif %}
{% endmacro %}
使用时,使用import语句导入它,然后作为函数调用,传入必要的参数
{% from 'macros.html' import qux %}
...
{{ qux(amoubt=5)}}
空白控制
如果想在渲染时自动去掉空行,可以在定界符内侧添加减号
<div>
{% if True -%}
<p>Hello !</p>
{%- endif %}
</div>
我们也可以使用模板环境对象提供的trim_blocks,lstrip_blocks属性设置,前者删除jinja2语句的第一个空行,后者用来删除jinja2语句所在行之前的空格和制表符.
app.jinja_env.trim_blocks = True
app.jinja_env.lstrip_blocks = True
宏内的空白控制行不受trim_blocks,和lstrip_blocks属性控制,需要进行手动控制
{% macro qux(amount=1) %}
{% if amount == 1 -%}
I am qux
{% elif amount > 1 -%}
We are quxs.
{%- endif %}
{% endmacro %}
加载静态文件
<img src="{{ url_for('static', filename='avatar.jpg') }}" width="50"> {{ user.username }}
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css' ) }}">
Favicon
是一个在浏览器标签页地址栏书签收藏夹显示的小图标
使用宏加载静态资源
为了方便加载静态资源,我们可以创建一个专门用于加载静态资源的宏
{% macro stacic_file(type,filename_or_url,local=True) %}
{% if local %}
{% set filename_or_url=url_for('static',filename=filename_or_url) %}
{% endif %}
{% if type == 'css' %}
<link rel="stylesheet" type="text/css" href="{{ filename_or_url }}">
{% endif %}
{% if type == 'js' %}
<link type="text/javascript" href="{{ filename_or_url }}">
{% endif %}
{% if type=='icon' %}
<link rel="icon" href="{{ filename_or_url }}">
{% endif %}
{% endmacro %}
在模板中导入宏后,只需要在调用的时候传入静态资源的类别和文件路径就会获得完整的资源加载语句,如:
static_file('css','css/bootstrap.min.css')
使用它也可以从CDN中加载资源,只需要将关键字参数设置为False
消息闪现
使用功能flash()函数发送的消息会存储在session中,我们需要在模板中使用全局函数get_flashed_message()获取消息并将其展示出来
在基模板中渲染flash消息
<main>
{% for message in get_flashed_message() %}
<div class="alert">{{ message }}</div>
{% endfor %}
{% block content %}
{% endblock %}
</main>
表单
定义表单类
from flask_wtf import FlaskFormfrom wtforms import SubmitField,StringField,PasswordField,BooleanFieldfrom wtforms.validators import DataRequired,Lengthclass Login(FlaskForm): username = StringField('Username',validators=[DataRequired()]) password = PasswordField('Password',validators=[DataRequired,Length(8,128)]) remember = BooleanField('Remember me') submit = SubmitField('Log in')
常用的WTForms
字段类 | 说明 | 对应的HTML显示 |
---|---|---|
BooleanField | 复选框,值被处理为True或False | <input type = "checkbox"> |
DateField | 文本字段,值会被处理为datetime.date | <input type = "text"> |
DateTimeField | 文本字段,值会被处理为datetime.datetime | <input type = "text"> |
FileField | 文件上传字段 | <input type = "file"> |
SelectField | 下拉列表 | <select><option></option></select> |
SubmitField | 提交按钮 | <input type = "submit"> |
PasswordField | 密码文本字段 | <input type = "password"> |
实例化字段常用参数
参数 | 说明 |
---|---|
lable | 字段标签<lable>的值,渲染后显示在输入字段前的文字 |
render_kw | 一个字典,用来设置对应的HTML<input>标签的属性,比如传入{“placehoder”:“Your name”}渲染后的HTML代码会将<input>标签的placehoder属性设为Your name |
validators | 一个列表,会在表单提交后逐一调用验证表单数据 |
default | 为表单字段设置默认值 |
在实例化验证表单类时,message参数用来传递自定义错误信息,如果没有设置则使用自定义的错误信息
如:
name = StringField('Your name',validators=[DataRequired(message="名字不能为空")])
使用render_kw属性
username = StringField("Username",render_kw={"placehoder":"Your name"})
调用后输出的HTML代码如下所示
<input type="text" id="username" name="username" placeholder="Your name">
处理表单数据
表单数据的处理大致会经历一下步骤
-
解析请求,获取表单数据
-
对数据进行必要的转换,比如勾选框的值转换成python的布尔值
-
验证数据是否符合要求,同时验证CSRF令牌
-
如果验证未通过则需要生成错误信息,同时在模板中显示错误信息
-
如果验证通过,就把数据保存到数据库或者做进一步的处理
WTForms会自动对CSRF令牌进行验证,如果没有渲染该字段,会导致验证出错
Flask-WTF提供的validate_on_submit()方法合并了这两个操作
数据库操作
默认情况下,Flask-SQLAlchemy会自动为模型生成一个repr()方法,当在调用模型的对象时,repr()方法会返回一个类似<模型类名 主键值>的字符串** **
比如<Note 2>
例:
class Note(db.model):
...
def __rper__(self):
return '<Note %r>'%self.body
在flask shell中的操作
如果将模型类定义在单独的模块中,那么必须在调用db.creat_all()方法前导入相应模块,以便让SQLAlchemy获取模型类被创建时生成的表信息,进而正确生成数据库
1.creat
>>> from app import db,Note
>>> note1 = Note(body='remember sanny')
>>> note2 = Note(body='SHAVE')
>>> note3 = Note(body='He is the one')
>>> db.session.add(note1)
>>> db.session.add(note2)
>>> db.session.add(note3)
>>> db.session.commit()
2.read
一个完整的查询遵循下面的模式** **
<模型类>.query.<过滤方法>.<查询方法>
>>> Note.query.all()
[<Note 1>, <Note 2>, <Note 3>]
>>> Note.query.get(2)
<Note 2>
>>> Note.query.count()
3
>>> Note.query.first()
<Note 1>
filter()是最基础的查询方法,相比较而言,filter_by()方法更易于使用,在filter_by()方法中,可以使用关键字表达式来指定过滤规则,如:
>>> Note.query.filter_by(body='SHAVE').first()
<Note 2>
like:
filter(Note.body.like('%foo%'))
in:
filter(Note.body.in_(['foo','bar','zaz']))
and:
form sqlalchemy import and_
filter(and_(Note.body == 'foo',Note.title == 'FooBar'))
3.update
直接赋值给模型类的字段属性就可以改变字段值,然后调用commit()方法提交即可
>>> note = Note.query.get(2)
>>> note.body
'SHAVE'
>>> note.body = 'SHAVE LEFT THIGH'
>>> db.session.commit()
4.delete
删除记录和添加记录很相似,把add()方法换成delete()方法,最后都要调用commit()方法提交修改
>>>note = Note.query.get(2)
>>>db.session.delete(note)
>>>db.session.commit()
定义关系
class Author(db.Model):
id = db.Column(db.Integer,primary_key=True)
name = db.Column(db.String(70),unique=True)
phone = db.Column(db.String(20))
articles = db.relationship('Article')
class Article(db.Model):
id = db.Column(db.Integer,primary_key=True)
title = db.Column(db.String(50),index=True)
body = db.Column(db.Text)
author_id = db.Column(db.Integer,db.ForeignKey('author.id'))
一对多
定义外键
外键只能存储单一数据,所以外键总是在多这一侧,字段使用db.ForeignKey类定义为外键,传入关系另一侧的表名和主键字段名** **
定义关系属性
关系属性的名称没有限制,相当于一个快捷查询,不会作为字段写入数据库中,如articles=db.relationship('Article')
这个关系返回多个记录,称之为集合关系集合,relationship()函数的第一个参数为关系另一侧的模型名称,它会告诉SQLAlchemy会找到另一侧(即aiticle表)的外键字段(author_id),然后反向查询article表中的所有author_id值作为当前的表主键值(author.id)的记录,返回包含这些记录的列表,也就是返回某个作者对应的多篇文章记录
建立关系
建立关系有两种方式,第一种方式为外键字段赋值,
foo = Author(name='Foo')
spam = Article(title='Spam')
ham = Article(title='Ham')
sapm.author_id = 1
ham.author_id = 1
db.session.commit()
另一种方式是操作关系属性
foo.articles.append(spam)
foo.articles.append(ham)
db.session.commit()
建立双向关系
class Writer(db.Model):
id = db.Column(db.Interger,primary_key = True)
name = db.Column(db.String(70),unique=True)
books = db.relationship('Book',back_populates='writer')
class Book(db.Model):
id = db.Column(db.Interger,primary_key = True)
name = db.Column(db.String(50),index=True)
writer_id = db.Column(db.Interger,db.ForeignKey('writer.id'))
writer = db.relationship('Writer',back_populates='books')
使用backref简化双向关系
在一对多关系中,backref自动为关系另一侧添加关系属性,作为反向引用,赋予的值会作为关系另一侧的关系属性名称,比如在Singer一侧的关系函数中将backref参数设为singer,SQLAlchemy会自动为Song类添加一个singer属性
class Singer(db.Model):
id = db.Column(db.Interger,primary_key = True)
name = db.Column(db.String(70),unique=True)
songs = db.relationship('Song',backref='singer')
class Song(db.Model):
id = db.Column(db.Interger,primary_key = True)
name = db.Column(db.String(50),index=True)
singer_id = db.Column(db.Interger,db.ForeignKey('singer.id'))
多对一
多个居民居住在同一个城市,关系属性在关系模式的出发侧定义,当出发点在“多”这一侧时,在Citizen中添加一个关系属性city来获取对应的城市对象,这个关系属性返回单个值,称之为标量关系属性,所以在多对一关系属性和外键都定义在多这一侧
class Citizen(db.Model):
id = db.Column(db.Interger,primary_key = True)
name = db.Column(db.String(70),unique=True)
city_id = db.Column(db.Interger,db.ForeignKey('city.id'))
city = db.relationship('City')
class City(db.Model):
id = db.Column(db.Interger,primary_key = True)
name = db.Column(db.String(30),unique=True)
一对一
一对一关系实际上是通过建立双向关系的一对多关系的基础上转化而来,要确保关系两侧的关系属性都是标量属性,都只返回单个值,所以要在定义集合属性的关系函数中将uselist参数设为False,这时一对多关系被转换为一对一关系
class Coutry(db.Model):
id = db.Column(db.Interger,primary_key = True)
name = db.Column(db.String(30),unique=True)
capital = db.relationship('Capital',uselist=False)
class Capital(db.Model):
id = db.Column(db.Interger,primary_key = True)
name = db.Column(db.String(30),unique=True)
country_id = db.Column(db.Interger,db.ForeignKey('country.id'))
country = db.relation('Country')
多对多
想要表示多对多关系,除了关系两侧的模型外,还需要创建一个关联表,关联表不存储数据,值用来存储关系两侧模型的外键对应关系
secondary需要值设置为关联表的名称
association_table = db.Table('association',
db.Column('student_id', db.Integer, db.ForeignKey('student.id')),
db.Column('teacher_id', db.Integer, db.ForeignKey('teacher.id'))
)
class Student(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(70), unique=True)
grade = db.Column(db.String(20))
teachers = db.relationship('Teacher',
secondary=association_table,
back_populates='students') # collection
def __repr__(self):
return '<Student %r>' % self.name
class Teacher(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(70), unique=True)
office = db.Column(db.String(20))
students = db.relationship('Student',
secondary=association_table,
back_populates='teachers') # collection
def __repr__(self):
return '<Teacher %r>' % self.name
更新数据库
重新生成表
执行下面命令会重建数据库和表:
flask initdb --drop
使用Flask-Migrate迁移数据库
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
add = Flask(__name__)
...
db=SQLAlchemy(app)
migrate=Migrate(app, db)
创建迁移环境: flask db init
生成迁移脚本:flask db migrate
更新数据库: flask db upgrade
级联操作