自学Python第十九天-flask框架

自学Python第十九天-flask框架

  • 安装和引用
  • 使用
    • 创建和运行应用
    • 设置应用
    • 处理函数及路由
      • 另一种路由注册
      • 唯一URL和重定向行为
    • 反向解析
    • 响应 get 和 post 请求 ,以及其他类型
  • Flask 的 MTV 设计
    • 规范的目录结构
  • request 请求的处理
    • 获取请求的一些信息
    • 获取 get 参数数据(查询参数)
    • 路由的路径中使用参数
    • 获取 post 参数数据(正文参数)
      • 获取 form 数据
      • 获取 JSON 数据
      • 获取二进制文件
  • response 响应的处理
    • 返回数据
    • 返回中断
    • 处理错误
    • 自定义响应对象
    • 返回页面(模板)
    • 返回重定向
    • 给前端页面传递数据
    • 一个登录页面的示例
  • Flask 使用的模板语言
    • 语法
    • "." 的作用
    • 注释
    • {{ var }} 变量模板标签
      • 接收从 views 传递的数据
        • 传递单个数据给页面
        • 接收传递的数据
      • 过滤器
      • 列表数据的使用
      • 字典数据的使用
    • {% tag %} 布局模板标签
      • 结构标签
        • block 和 extends
          • 母版页
          • 子页面
        • include 包含、引入
      • 宏定义
        • 定义宏
        • 调用宏
      • 循环及流程控制
        • 循环语句
        • 判断语句
  • cookie 和 session 的使用
    • 工作机制
    • 设置 session
    • 获取 session 的数据
    • 删除 session 中的值
    • 设置 session 过期
    • 手动写入 cookie 信息
    • 获取 cookie 数据
    • 删除 cookie 信息
  • 全局对象 g
  • 其他常用的钩子函数
  • Flask 常用插件
    • flask-script
    • flask-blueprint
    • flask-cors
      • 使用 cors 类
      • 使用装饰器
      • 与 cookie 一起使用
      • 配置Access-Control-Allow-Origin(响应头添加header)
    • flask-session
      • 安装和引入 flask-session
      • 配置 session
      • 初始化 session 对象
      • flask-session 不能替代 cookie
    • flask-SQLAlchemy
      • 安装和配置
      • 初始化
      • 创建ORM模型
      • 使用模型
        • 增加数据
        • 查询数据
          • session 查询
          • 模型查询
          • 原生查询
          • 聚合查询
          • 分组查询
          • 存在查询
          • 联合查询
        • 修改数据
        • 删除数据
        • 刷新数据
      • 事务操作
      • 关系表
  • Flask 的其他一些插件
    • 日志
      • flask 中使用日志
    • Flask-Bootstrap
    • Flask-Cache
    • flask-RESTful

Flask是一个使用 Python 编写的轻量级 Web 应用框架,常用来做 Web API 开发

安装和引用

使用 pip install flask 进行安装,使用 from flask import Flask 引入

使用

flask 能够很简单的根据 http 请求输出数据。因为 flask 是一个 web 的应用框架,所以使用时需要创建和运行相应的应用。

创建和运行应用

from flask import Flask

# 创建 web 应用程序
app = Flask(__name__)		# 参数是模块名称,可以随便写,一般小写字母,表示 flask 应用对象名称

if __name__ == '__main__':
    app.run(debug=True)  # 启动应用程序(flask项目) (debug=True 可以在更改代码后不用重启应用,也可以不写)

这时运行程序,可以看见运行窗口提示 Running on http://127.0.0.1:5000 (Press CTRL+C to quit) 就可以使用这个地址在浏览器里访问 web 程序了。

可以在初始化 app 对象时添加参数,进行一些设置。

  • static_url_path=None : 访问静态资源的路径
  • static_folder=‘static’ : 静态资源存放目录
  • static_host=None : 静态资源存放主机
  • template_folder=‘templates’ : 模板存放目录

也可以在 run() 函数中添加一些参数,用来设置 web 服务器,例如访问 IP 和端口。

app.run(host='127.0.0.1', port=5000)

还有一些默认的参数可以修改

  • threaded=False : 是否启用多线程处理
  • processes=1 : 使用的进程数量
  • debug=False : 是否开始调试模式

需注意的是,多进程和多线程不能同时开启。

设置应用

在创建了应用对象后,运行之前,可以对 app 进行配置

# 使用 app.config 来添加设置,例如密钥
app.config['SECRET_KEY'] = os.urandom(24) 	# 设置随机的24位字符串作为密钥

或者使用配置文件 settings.py

# settings.py
class Dev():
	ENV = 'developement'
	SECRET_KEY = 'ddkakda%&akd#@'
	STATIC_URL_PATH = '/s'
class Product():
	ENV = 'production'
	SECRET_KEY = 'drtjuda%&akd#@'
# app.py
from flask import Flask
import settings
app = Flask(__name__)
# 从指定的对象中加载 Flask 服务的配置
app.config.from_object(settings.Dev)

处理函数及路由

路由就是访问路径,即访问地址中,域名、端口之后的部分。每个路由请求由一个处理函数进行处理。

from flask import Flask

# 创建 web 应用程序
app = Flask(__name__)		

# 处理函数和路由
@app.route('/')			# 路由
def index():		# 路由的处理函数,函数名称没有要求,可以随便起
	return '访问主页测试'

if __name__ == '__main__':
    app.run(debug=True)  # 启动应用程序(flask项目) (debug=True 可以在更改代码后不用重启应用,也可以不写)

另一种路由注册

可以使用 app.add_url_rule ( '/ ' , view_func = viewFunction ) 来注册路由。参数1是路径,参数2是视图函数

from flask import Flask

app = Flask(__name__)

def fn():
    return "lxc"
    
app.add_url_rule('/',view_func=fn)

if __name__ == '__main__':
    app.run(debug=True)

在基于类的视图中 —— 即插视图,必须使用这种方式来注册路由。其实使用装饰器来注册路由的底层是使用 add_url_rule() 方法来注册的 ,只不过 flask 把 add_url_rule() 做了一层封装,封装成了一个装饰器,使用起来更加方便优雅。

唯一URL和重定向行为

这里举两个规则

@app.route(‘/projects/’)
@app.route(‘/about’)

这两个 url 规则看起来很相似,但是结尾斜线的使用在 URL 定义中不同。第一种情况时,访问一个结尾不带斜线的 URL 会被 flask 重定向到带斜线的规范 URL 去。而第二种情况下,访问结尾带斜线的 URL 则会产生一个 404 错误。

反向解析

有时候,我们需要跳转到一个页面,但是页面地址可能不太确定(例如添加了前缀、使用路径参数等)。这时候我们可以使用反向解析(url_for)的方法,通过视图处理函数获取反向路径

url_for(‘函数名’, 参数名=值)
url_for(‘蓝图名.函数名’, 参数名=值)

from flask import url_for

@app.route('/ok/')
def ok(para):
	return '跳转成功,获取参数 %s' % para


@app.route('/index')
def index():
	para = '参数值'
	return "跳转链接" % url_for('ok', para=para)
from flask import url_for
from flask import Blueprint

blue = Blueprint('test', __name__)

@blue.route('/ok/')
def ok(para):
	return '跳转成功,获取参数 %s' % para

@blue.route('/index')
def index():
	para = '参数值'
	return "跳转链接" % url_for('test.ok', para=para)

需注意 url_for() 函数的参数1必须是字符串类型。

响应 get 和 post 请求 ,以及其他类型

可以在路由信息参数中添加 methods 来确定处理的请求类型,如果没有此参数,则默认处理 get 请求。methods 是一个列表,可以添加多个类型。

from flask import Flask

# 创建 web 应用程序
app = Flask(__name__)		# 参数是模块名称,可以随便写

# 处理函数和路由
# 支持 RESTful 风格中关于资源的动作。
@app.route('/', methods=['GET', 'POST'])			# 路由,参数2是请求类型。也支持 PUT、DELETE、PATCH 等其他类型
def index():		# 路由的处理函数,函数名称没有要求,可以随便起
	return '访问主页测试'

if __name__ == '__main__':
    app.run(debug=True)  # 启动应用程序(flask项目) (debug=True 可以在更改代码后不用重启应用,也可以不写)

Flask 的 MTV 设计

Flask 使用的是 MTV 设计思想,是基于 MVC 的:

  • M - Model 模型
  • T - Template 模板(View)
  • V - View 处理函数(Controller)

所以除了名称不同,设计思想基本是一致的。

规范的目录结构

对于 flask 来说,没有必要的必须遵守的目录结构。即使需要返回的模板文件(HTML文件)也可以在定义 app 时,使用参数更改其目录。但是做项目时还是推荐使用规范的目录结构,方便资源的调用和修改。

app 所在目录中,使用 view.py(或创建 views 文件夹,使用此文件夹里创建的 py 文件) 来定义视图处理函数,使用 models.py 来定义模型,使用 templates 来存放模板文件,使用 static 来存放静态资源。

request 请求的处理

对于附带参数或数据的 http 请求,需要获取其中的数据内容,使用 flask 库的 request 对象(HttpRequest)。request 包含了请求资源的路径、请求方法、请求头、上传的表单数据、文件等信息。

获取请求的一些信息

通过 request.method 可以获取请求方法

if request.method == 'GET':
	pass
elif request.method == 'POST':
	pass

使用 request.url 获取完整的路径,包含根路径

#请求 http://0.0.0.0:9527/req?id=10
print(request.url) # 'http://0.0.0.0:9527/req?id=10'

使用 request.base_url 获取完整路径,包含根路径,但不包含get参数

#请求 http://0.0.0.0:9527/req?id=10
print(request.base_url) # 'http://0.0.0.0:9527/req'

使用 request.path 可以获取获取斜线后边的url路径 (不包含根路径)

# 请求的是  http://0.0.0.0:9527/req?id=10
print(request.path) # '/req'

使用 request.host 获取根路径

# 请求路径为 http://0.0.0.0:9527/req?id=10
print(request.host) # http://0.0.0.0:9527

使用 request.host_url 获取根路径,包含后边斜线

# 请求路径为 http://0.0.0.0:9527/req?id=10
print(request.host) # http://0.0.0.0:9527/

使用 request.headers 获取请求头信息

使用 request.cookies 获取 cookie 数据

使用 request.remote_addr 获取访问的远端ip地址

使用 request.files 获取上传的文件

获取 get 参数数据(查询参数)

get 请求的数据是添加在 url 内的,可以使用 reuqest.args 来获取参数。可以使用 get() 方法获取特定的数据(使用此方法好处是如果没有返回 None,而其他的方法会出错),也可以使用 to_dict() 将 form 数据转换为字典。

from flask import Flask, request

# 请求地址:http://127.0.0.1:5000/data?u=1112&p=3324
@app.route('/data')
def api_data():
    # 获取 url 传参
    u = request.args.get('u', 'args没有参数 u')		# 获取参数 u
    p = request.args.get('p', 'args没有参数 p') 		# 获取参数 p
    q = request.args.get('q', 'args没有参数 q') 		# 获取参数 q
    return u + p + q

if __name__ == '__main__':
    app.run(debug=True)  # 启动应用程序(flask项目) (debug=True 可以在更改代码后不用重启应用)

路由的路径中使用参数

将参数添加到 URL 里,例如 http://web.io/find/112 ,其中 112 就可以是参数。

# 使用转换器 converter 来转换参数,语法格式为 ,例如
@app.route('/find/', methods=['GET'])
def find(word):
	# 将 int 型的参数以 word 变量传入
	pass

converter 是参数转换器,一般是指定类型,如 int, float, path。 path 转换器主要用于引用别的网址,例如

@app.route('/forward/')
def forward(url):
	# 重定向到 url
	return redirect(url)

以上配置的路由,对于路径 "/forward/http://www.baidu.com" 是合法的,如果将 path 转换器换成 string 则可能会报错。

另外如果需要使用到 string 类型的参数,则省略 converter 部分

@app.route('/user/')
def show_user_profile(username):
    return '用户 %s' % username

如果使用多个参数,则需要使用斜杠隔开

@app.route('/user//')
def show_user_profile(username, id):
	pass

注:路径中的变量名要和视图处理函数的参数名相同。

获取 post 参数数据(正文参数)

使用 post 提交的数据一般是表单数据。也有其他类型数据,例如 JSON 或二进制文件等。

获取 form 数据

使用 request.form 来获取数据。可以使用 get() 方法获取特定的数据(使用此方法好处是如果没有返回 None,而其他的方法会出错),也可以使用 to_dict() 将 form 数据转换为字典。

from flask import Flask, request

@app.route('/login', methods=['post'])
def login():
    # 从请求中获取数据
    username = request.form.get('username', '')				# 获取 form 中的数据
    password = request.form.get('pwd', '')					# 获取 form 中的数据
    if username == '123123' and password == '123':
        return '成功'

if __name__ == '__main__':
    app.run(debug=True)  # 启动应用程序(flask项目) (debug=True 可以在更改代码后不用重启应用)

注,用来获取请求头为 Content-Type : application/x-www-form-urlencoded 的数据,如果是 JSON 数据是获取不到的

获取 JSON 数据

可以使用 request.get_json()request.json() 获取 json 数据,两个方法是等价的。

@app.route('/home',methods=["GET","POST"])
def fn():
    if request.method == "POST":
      data = request.get_json()
      print(data) 
      return "获取了JSON数据"

注,用来获取请求头为 Content-Type : application/json 的数据,如果是 form 数据是获取不到的,会是 None,除非设置参数 force get_json(force=True) ,此时会忽略mimetype并始终尝试解析json。

mimetype说穿了其实指的就是文件后缀名。你向web服务器请求一个文件,服务器会根据你的后缀名去匹配对应的值设置为response中content-type的值。而content-type是正文媒体类型,游览器根据content-type的不同来分别处理你返回的东西。

获取二进制文件

response 响应的处理

根据 http 请求做出响应,返回需要的内容。所有返回前台的内容其实都应该是 Response 的对象或者其子类,我们看到如果返回的是字符串直接可以写成 return '字符串' 的形式,但是其实这个字符串也是经过了Response包装的:return Response('字符串')

返回数据

flask 可以在函数 return 中添加任意数据,如果返回一个 json ,就成了一个简单的 WebAPI 。

from flask import Flask

# 创建 web 应用程序
app = Flask(__name__)		# 参数是模块名称,可以随便写

# 处理函数和路由
@app.route('/', methods=['post', 'get'])			# 路由
def index():		# 路由的处理函数,函数名称没有要求,可以随便起
	return '访问主页测试'

# 简单的 api
@app.route('/json')
def api_json():
	from flask import jsonify
    j = {'name': 'Timmy', 'age': '18'}
    j = jsonify(j)	# 将字典序列化为 json 字符串
    return j

if __name__ == '__main__':
    app.run(debug=True)  # 启动应用程序(flask项目) (debug=True 可以在更改代码后不用重启应用,也可以不写)

这里需要注意的是,json 模块的 json.dumps() 方法也能将数据序列化为 json 字符串,但是在查看头后会发现响应内容为 text/html,而 flask 模块的 jsonify 方法响应内容类型为 application/json。相当于 jsonify 方法返回一个响应对象,内容是使用 json.dumps 方法序列化的 json 字符串,并且将响应头的 Content-Type 设置为了 application/json;charset=utf-8

返回中断

需要导入abort模块,终止响应的意思,通常会在参数中写403或404等错误代码

from flask import abort
@app.route('/list')
def fn():
    d = jsonify({'name':'guest','age':20})
    l = jsonify([1,2,3])
    t = jsonify(('1,2,3','abc'))
    abort(403)
    return t
'''
当访问该路径时,会报错
You don't have the permission to access the requested resource. It is either read-protected or not readable by the server.
意思是:您没有访问所请求资源的权限。它要么是受读保护的,要么是服务器不可读的。

或在 abort() 方法内部传入一个 Response 对象。
abort(Response("

当前访问被拒绝,原因:xxxxxx

", 403)) '''

处理错误

可以使用钩子函数定义一些错误处理函数,可以通过状态码处理相应的错误。

@app.errorhandler(404)
def notfounded(error):
	return render_template('404.html')
@app.errorhandler(403)
def Forbidden403(error):
	return redirect('/403')

也可以使用 Exception 对象来获取错误,然后再判断。使用 Exception 除了正常的访问错误外,还可以处理 raise Exception() 抛出的异常。raise 抛出的异常状态码为 500 (但不可以通过状态码来处理这个异常)。

@app.errorhandler(Exception)
def error(err):
	# err 就是传入的错误,是 werkzeug.exceptions 下的错误类型
	# 可以直接输出为错误信息,也可以通过判断 err 的数据类型来做响应处理
	if type(err) == werkzeug.exceptions.Forbidden:
		return render_template('403.html')
	elif type(err) == werkzeug.exceptions.NotFound:
		return redirect('/404')
	else:
		return err

当状态码和 Exception 混用时,会优先使用状态码处理错误,没有相应状态码处理错误函数时,使用 Exception 处理其余的错误。

需要注意的是,错误处理函数必须有一个参数,用来获取错误信息。如果没有这个参数则会报错。

自定义响应对象

可以使用 flask 的 make_response() 方法来自定义响应对象(也可以使用 flask 的 Response 类)

from flask import make_response

@app.route('/index')
def index():
	data = {'id':101,'age':20}	# 数据
	code = 200		# 状态码
	response = make_response(data,code)		# 将数据和状态码封装进响应对象
	# 或使用 response = make_response()	创建空的 response 对象
	# 然后使用 response.data = data 设置响应体
	# 使用 response.status_code = code	设置响应状态码
	# 设置响应头
	response.header['Content-Type'] = 'application/json;charset=utf-8'
	# response = Response(data, code, content_type='application/json')
	return response

返回页面(模板)

数据可以直接返回,如果需要返回一个 html 页面,则需要使用 flask 库内的 render_template 。

使用时,需要在 app 所在文件同一个目录下新建目录 templates (名称不能出错),然后将需要呈现的 html 文件放在这个目录下。

from flask import Flask, render_template

# 创建 web 应用程序
app = Flask(__name__)		# 参数是模块名称,可以随便写

@app.route('/login')
def login():
    return render_template('login.html')		# 访问 html 页面

if __name__ == '__main__':
    app.run(debug=True)  # 启动应用程序(flask项目) (debug=True 可以在更改代码后不用重启应用,也可以不写)

返回重定向

from flask import redirect

@app.route('/login')
def login():
	return redirect('/index')	# 跳转的可以是相对路径,也可以是绝对路径(不带根路径)
	# 通常会结合 url_for() 使用

给前端页面传递数据

将 python 中的变量发给 html 页面中的变量, html 中使用双大括号将变量括起来。

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>flask 学习测试title>
head>
<body>
    这是一个 flask 学习的测试页面,传进来的值是 "{{value}}"
    <hr />
    {% for item in lst %}
        这个是列表数据:{{item}} <br />
    {% endfor %}
body>
html>
from flask import Flask, render_template

# 创建 web 应用程序
app = Flask(__name__)		# 参数是模块名称,可以随便写

@app.route('/')  # 路由函数,当访问到 '/' 即根目录时执行函数
def index():
    # 把数据发送到页面
    s = '你好,这个是测试 flask 给页面传值'  # 字符串
    lst = ['0001', '0002', '0003', '0004', '0005']
    # 返回模板 html
    return render_template('hello.html', value=s, lst=lst)  # 传值进模板页面,将变量 s 传给页面中的变量 value
    """
	也可以将数据以字典形式发送回页面
	data = {
		'value' : '你好,这个是测试 flask 给页面传值',
		'lst' : ['0001', '0002', '0003', '0004', '0005']
	}
	return reder_template('hello.html', **data)		# 发送字典其实是发送字典的内容项,所以使用 **
	需注意发送字典时,字典的 key 需要与前端使用的变量名相对应。
	"""

if __name__ == '__main__':
    app.run(debug=True)  # 启动应用程序(flask项目) (debug=True 可以在更改代码后不用重启应用,也可以不写)

也可以使用 locals() 方法,收集当前函数内部的变量,生成字典对象,再返回给前端页面。

@app.route('/')
def index():
    # 把数据发送到页面
    value = '你好,这个是测试 flask 给页面传值'  # 字符串
    lst = ['0001', '0002', '0003', '0004', '0005']
    # 返回模板 html
    return render_template('hello.html', locals()) 

需注意的是,locals() 方法会收集当前函数之前所有定义的变量,有些是不能被序列化返回页面的。

一个登录页面的示例

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>flask 的传值测试页面title>
head>
<body>
    <form action="/login" method="post">
        用户名 <input type="text" name="username" >
        密码 <input type="password" name="pwd">
        <input type="submit" value="登录"><br/>
        {{res}}
    form>
body>
html>
@app.route('/login', methods=['post', 'get'])
def log():
    # 从请求中获取数据
    username = request.form.get('username')
    password = request.form.get('pwd')
    if not username and not password:
        return render_template('login.html')
    elif username == '123123' and password == '123':
        return render_template('login.html', res='登录成功')
    else:
        return render_template('login.html', res='登录失败')

Flask 使用的模板语言

Flask 能够在静态的网页文件中,将一些预先标记的数据进行渲染处理,然后输出。这些静态网页文件就成为了动态的模板文件,这些标记使用的就是模板语言。flask 识别模板语言后,根据内容,进行控制、填充等操作,填入合适内容到网页文件中然后渲染输出。

模板语言除了可以用来获取数据外,也可以用来执行判断、循环等操作。flask 使用的是 jinja 模板技术,使用了 Mustache 语法,此语法很多地方都在使用,例如 Vue、Django 等。

语法

flask 的模板语法都是包含在 {} 中的。

“.” 的作用

在模板语言中,不支持方括号式获取索引下标、元素等,而是使用 “.” 。这个点使用在很多地方获取数据

  • 对象.属性
  • 对象.方法
  • 列表.索引下标
  • 字典名.key

注释

{# 注释内容 #} ,注释内容不会被渲染。

{{ var }} 变量模板标签

使用双花括号可以接收数据或使用过滤器

接收从 views 传递的数据

传递单个数据给页面

使用 render_template() 方法时,将数据发送给页面相应的变量:

def index(request):
	text = '初学 Django'
	return render_template('index.html', n1=text)
接收传递的数据

在页面中使用 {{ 变量名 }} 来接送 render_template() 方法传递的数据。

<h1> {{ n1 }} h1>

过滤器

过滤器,就是将传入的数据以某些方法进行过滤或修改。使用管道符分隔数据和方法,冒号添加方法的参数(部分参数也可以写到括号内,类似于调用 python 的相应方法传参),可以类似链式调用的方式多次过滤。其格式是

{{ 数据变量 | 过滤方法1:参数 | 过滤方法2:参数 | 过滤方法3:参数 }}

例如:

  • 日期格式,使用管道符设置格式

{{ 日期数据 | date:“Y-m-d H:i:s” }}

使用方法上大部分都类似于 python 的相应方法。

常用的过滤器有

capitalize : 整个字符串首字母大写
lower : 全小写
upper : 全大写
title : 字符串中每个单词首字母大写
trim : 去除前后空格
reverse : 逆序排列(反转)
format : 格式化字符串
tojson : 转换为 json 对象
striptags : 渲染之前将值中的标签去掉(如果不去掉则直接以文本显示)
safe : 确定值中的标签是安全的,可以渲染出来
default : 如果值未定义,则返回默认值
last : 返回序列的最后一项
first : 返回序列的第一项
sum : 返回序列数字之和
sort : 排序,类似于 python 的 sort 方法
unique : 序列中去重,以迭代器方式返回,通常和 join 一起使用
join : 类似 python 的 join 方法,使用指定连字符连接可迭代对象中每一个元素,返回字符串
list : 将生成器、可迭代对象转为列表

也可以自定义过滤器,例如写一个格式化日期的过滤器

@app.template_filter('datefmt')		# 定义一个名称为 datefmt 的过滤器
def datefmt(value, *args):		# value 是获取的值,args 是获取的参数
	if type(value) == datetime:
		return value.strftime(args[0])
	else:
		from datetime imoprt datetime
		return datetime.strptime(value, args[0])

列表数据的使用

列表数据的传递和单个数据是一样的。注意下标使用 . 而不是方括号

def index(request):
	text=["Python", "C", "Java", "Basic"]
	return render_template('index.html', n1=text)
<h1>计算机编程语言有 {{ n1.0 }},{{ n1.1 }},{{ n1.2 }},{{ n1.3 }} 等h1>

字典数据的使用

字典数据的传递是一样的。接收时,使用 字典名.key 来获取字典数据。

def index(request):
	text ={"y1": "Python", "y2": "C", "y3": "Java", "y4": "Basic"}
	return render_template('index.html', n1=text)
<h1>第一种语言是 {{ n1.y1 }},第二种语言是 {{ n1.y2 }},第三种语言是 {{ n1.y3 }},第四种语言是 {{ n1.y4 }}h1>

{% tag %} 布局模板标签

{% %} 这个方法主要用在结构标签宏定义循环及流程控制中。

结构标签

结构标签主要包含 blockextendsinclude

block 和 extends

block 和 extends 主要用于母版。有些页面内容可以作为母版使用,在母版页面中填充子页面,可以提高代码复用,减少工作量。

母版页

在母版页中,需要更改内容(放置子页面)的地方,使用 block 进行标记,即制作好了母版页。

DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>部门列表title>
    <link rel="stylesheet" href="/plugins/bootstrap-3.3.7-dist/css/bootstrap.min.css">
head>
<body>
<script src="/js/jquery-3.6.0.min.js">script>
<script src="/plugins/bootstrap-3.3.7-dist/js/bootstrap.min.js">script>
{# 导航条 #}
<nav class="navbar navbar-default">
	...
nav>

{# 内容 #}
<div>
    {% block content %}{% endblock %}
div>
body>
html>
子页面

在子页面的第一行,写入要继承的母版页,然后将需要填充到母版页的代码同样使用 block 标记即可。

{% extends 'layout.html' %}

{% block content %}
{#内容#}
<div class="container">
    <div class="panel panel-default">
		...
    div>
div>
{% endblock %}

需注意的是,使用相同的 block 标记(即 block 后的名称相同,通常用在子页面被其它子页面继承的情况),后者会覆盖前者。如果不想覆盖, 需要后者在 block 块内部添加 {{ super() }} 继承前者。

include 包含、引入

在当前页面中,导入目标页面内容,则使用 {% include '页面文件’ %}。这种方法将导入页面的 head 标签和 body 标签去掉(但是保留其内容),然后再进行整体导入。此方法可能会因 id 冲突、脚本冲突等原因引起页面混乱,所以不推荐使用。

宏定义

宏定义使用 macro,可以在模板中定义、调用函数,来生成需要的 HTML 内容。

定义宏

定义宏时需添加名称,然后在内部添加内容。

{% macro macro_name() %}
	<ul>
		<li>列表1li>
		<li>列表2li>
		<li>列表3li>
	ul>
{% endmacro %}
调用宏

调用宏时,直接在需要添加宏内容的地方使用 {{ macro_name() }} 即可。调用外部文件中定义的宏需要导入,使用 {% from 宏定义文件 import 宏名称 %},注意宏名称不带括号。

循环及流程控制

使用循环语句进行循环控制,使用判断语句进行流程控制

循环语句

循环的语法如下:


{% for item in n1 %}
	<span>{{ item }}span>
{% endfor %}

字典也可以使用循环

{% for k, v in n1.items() %}
	<li>{{ k }} = {{ v }} li>
{% endfor %}

还可以使用 loop 对象来获取一些循环的数据信息,例如

{% for item in n1 %}
	<span>{{ loop.index }} - {{ item }}span>
{% endfor %}

loop 的一些常用的方法有

loop.index : 从1开始循环
loop.index0 : 从0开始循环
loop.first : 是否为循环的第一个元素
loop.last : 是否为循环最后一个元素
loop.revindex : 从1开始逆序循环
loop.revindex0 : 从0开始逆序循环
loop.length : 循环序列元素的个数

判断语句

判断语句语法如下:

{% if text == "Flask老手" %}
	<h1> 高手高手高高手 h1>
{% elif text == "初学 Flask" %}
	<h1> 菜鸟一个 h1>
{% else %}
	<h1> 无法识别 h1>
{% endif %}

cookie 和 session 的使用

工作机制

把敏感数据经过加密后放入session 中,然后再把 session 存放到 cookie 中,下次请求的时候,再从浏览器发送过来的 cookie 中读取 sesson ,然后再从 session 中读取敏感数据,并进行解密,获取最终的数据。flask的这种 session 机制,可以节省服务器开销,因为把所有的信息存到了客户端(浏览器)。

设置 session

在flask中,session是通过 from flask import session 引入的。使用时添加key和value进去即可。并且,flask中的session机制,是将session信息加密,然后存储在cookie中。

import os
from flask import session
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24) 	# 设置随机的24位字符串作为密钥
# 或使用 app.secret_key = '1234567890' 来设置
 
@app.route('/')
def fn():
    session['username'] = 'guest'		# 设置 session
    return 'success'

因为 session 需要经过加密存储在 cookie 中,所以需要设置密钥(SECRET_KEY),且这个值必须是24位的字符串。使用随机密钥会产生个问题,及每次服务重启后密钥会重置,所以重启前的 session 会丢失(因为密钥不一致了)。

获取 session 的数据

@app.route('/get')
def fn1():
    session_info = session.get('username')
    print(session_info) # 'lxc'
    return 'success'

删除 session 中的值

删除session中的值,相当于调用字典方法

@app.route('/delete')
def dele():
	del session['username']	# 方法一:删除session中指定的值
    session.pop('username') # 方法二;弹出session中指定的值
    session.clear() # 方法三:删除session中全部的值
    return '删除成功'

设置 session 过期

需要引入 datetime 模块

from flask import Flask,session
from datetime import timedelta
app = Flask(__name__)
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=2)	# 配置超时时间
 
@app.route('/add')
def fn():
    session['username'] = '访客'
    session.permanent = True	# 启用超时
    return 'guest'

手动写入 cookie 信息

除了 session 自动加密并写入 cookie,还可以手动写入 cookie 数据。

@app.route('/login')
def login():
	response = make_response('进入登录页面')
	# 添加 cookie
	response.set_cookie('username', 'guest', max_age=600)
	return response

set_cookie() 方法有以下常用参数

  • key : cookie 的 key
  • value : cookie 的值
  • max_age=None : 最大有效时间,单位是秒,默认 None 永不失效
  • path=‘/’ : 指定与 cookie 关联的网页
  • domain=None : 创建 cookie 所在服务器的主机名
  • secure=False : cookie 是否安全
  • samesite=None : 同一站点(当不同域使用时使用,例如 www.qq.com 和 login.qq.com 是不同域,但是需要使用同一个 cookie)

获取 cookie 数据

当有 cookie 时,访问相应页面(cookie 的 path 和 domain 指定的页面)时,request 会附带上 cookie 信息,此时就能够获取 cookie 数据了

@app.route('login')
def login():
	user = request.cookies.get('username','')
	pwd = request.cookies.get('password','')
	pass

删除 cookie 信息

cookie 信息除了超时外,还可以手动删除

@app.route('/login')
def login():
	response = make_response('进入登录页面')
	# 删除现有信息
	response.delete_cookie('username')
	return response

全局对象 g

g 其实就是 global,它专门用来保存用户数据,g对象在一次请求中,当前项目所有文件中都可以使用到;但是第二次请求时,g对象会被重新创建。

使用g对象需要先引入该模块

from flask import g

然后就可以使用了

g.key = value

例如

from flask import Flask,g,render_template,request
from readlog import read # 引入打印用户信息模块(文件)
from datetime import datetime
# 渲染页面
@app.route('/')
def fn():
    return render_template('html.html')
 
#登录,把用户信息存到 g 对象
@app.route('/login',methods=['GET','POST'])
def login():
    if request.method == 'POST':
        g.username = request.form.get('username')
        g.password = request.form.get('password')
        read()
    return '登陆成功'

def read():
    now = datetime.now()
    print('登陆时间是:%s' % str(now)) # 登陆时间是:2019-09-05 15:08:32.457721
    print('用户账号是:%s,密码是:%s ' % (g.username,g.password)) 

if __name__ == '__main__':
    app.run(debug=True)

其他常用的钩子函数

  • before_first_request
    第一次请求服务器之前被调用,且只执行一次
  • before_request
    请求已经到达flask,但是还没有进入具体的视图函数之前调用。一般在调用视图函数之前,我们可以把一些后边需要用到的数据先处理好,方便视图函数使用。
  • context_processor
    上下文处理器,返回的必须是字典形式,字典里边的key作为变量可以在任意模板中使用key对应的值,如果多个模板中需要渲染相同的数据,那么可以使用此钩子函数,否则每个视图函数在返回一个模板同时,还需要返回对应的变量

Flask 常用插件

flask 可以使用一些插件来扩展功能

flask-script

flask-script 能够批量管理多个app,并且使用脚本的方式运行它们

使用 flask-script 需要先安装插件包

pip install flask-script

然后在 app 文件夹同级创建一个服务文件,server.py (名称可以更改)。在 app 文件夹里可以创建应用,例如将 app 文件夹设置为包文件,然后在 __init__.py 里创建 app

# app/__init__.py
from flask import Flask

app = Flask(__name__)
# 也可以在这里对 app 进行设置

然后在 server.py 引用 app,然后创建 manager 对象。

# server.py
from app import app
from flask_script import Manager

if __name__ == '__main__':
	# 以脚本方式启动 flask 应用服务
	manager = Manager(app)
	manager.run()

之后就可以使用脚本命令来运行了(类似 django)

python server.py runserver
可以添加参数,例如 -h 添加主机IP,-p 添加端口,-d 启动调试模式,-r 自动加载 等。

flask-blueprint

蓝图插件主要实现拆分多个视图函数文件,让同类或同模块分到一个 view 脚本中。例如将不同的模块写成不同的视图函数文件,放在 views 文件夹下。

使用 flask-blueprint 需要先安装插件包

pip install flask-blueprint

使用时在视图函数文件中导入 flask 库的接口 (注意不是 flask-blueprint)

# view.py
from flask import Blueprint
from flask import request

blue = Blueprint('模块蓝图名称', __name__)	# 参数2是创建 app 时 Flask 对象的参数名称,即 app 名称。

@blue.route('/index', methods=['GET', 'POST'])		# 路由	
def index():		# 视图处理函数
	pass

蓝图(视图函数)写完之后,需注册到 app 中。

# server.py 
# 启动 app 的文件
# 引入视图处理函数的文件
from app.views import view

if __name__ == '__main__':
	# 将蓝图对象注册到 flask 服务中
	app.register_blueprint(view.blue)
	# 启动 app 应用
	app.run()

这样就可以将不同模块的视图函数写在不同文件中,然后进行注册就可以了。另外在注册蓝图时,可以添加处理路由的前缀

app.register_blueprint(view.blue, url_prefix='/user')

访问时,则是使用前缀加路由的方式进行访问。

flask-cors

flask-cors 插件主要用来解决跨域访问的问题。CORS的全称是 Cross-Origin Resource Sharing,由w3c组织制定的,现在这个规范,已经被大多数浏览器支持,处理跨域的需求。CORS 在后端设置,所以是一种后端跨域的解决方案。

使用 flask-cors 首先要安装库,然后引入类

pip install flask-cors
from flask_cors import CORS

使用 cors 类

使用 CORS 时,需要在创建 app 后,app 运行之前使用 CORS 类初始化 app (如果有用蓝图,则需要初始化蓝图)。此方法适用于全局的api接口配置,也就是所有的路由都支持跨域了。

from flask import Flask
from flask_cors import CORS, cross_origin
app = Flask(__name__)
cors = CORS(app)
# 也可以使用 CORS().init_app(app)
from flask import Blueprint
from flask import render_template
from flask_cors import CORS, cross_origin
blue = Blueprint('user',__name__)
cors = CORS(blue)
 
@blue.route('/')
def fn():
    return render_template('html.html')

使用 CORS 类的第一个参数是 app 对象,第二个参数可以是

参数 类型 Head字段 说明
resources 字典、迭代器或字符串 全局配置允许跨域的API接口
origins 列表、字符串或正则表达式 Access-Control-Allow-Origin 配置允许跨域访问的源,*表示全部允许
methods 列表、字符串 Access-Control-Allow-Methods 配置跨域支持的请求方式,如:GET、POST
expose_headers 列表、字符串 Access-Control-Expose-Headers 自定义请求响应的Head信息
allow_headers 列表、字符串或正则表达式 Access-Control-Request-Headers 配置允许跨域的请求头
supports_credentials 布尔值 Access-Control-Allow-Credentials 是否允许请求发送cookie,false是不允许
max_age 整数、字符串 Access-Control-Max-Age 预检请求的有效时长

使用装饰器

此种方法适用于配置特定的api接口

from flask import Flask, jsonify
from flask_cors import CORS, cross_origin
app = Flask(__name__)
 
# 只允许路径为'/login'跨域!
@app.route('/login')
@cross_origin()
def data():
    return jsonify({'name':'lxc'})

装饰器的参数有

装饰器参数 类型 Head字段 说明
origins 列表、字符串或正则表达式 Access-Control-Allow-Origin 配置允许跨域访问的源,*表示全部允许
methods 列表、字符串 Access-Control-Allow-Methods 配置跨域支持的请求方式,如:GET、POST
expose_headers 列表、字符串 Access-Control-Expose-Headers 自定义请求响应的Head信息
allow_headers 列表、字符串或正则表达式 Access-Control-Request-Headers 配置允许跨域的请求头
supports_credentials 布尔值 Access-Control-Allow-Credentials 是否允许请求发送cookie,false是不允许
max_age 整数、字符串 Access-Control-Max-Age 预检请求的有效时长

与 cookie 一起使用

默认情况下,不允许跨站点提交Cookie,如果你希望服务器允许用户跨源发出Cookie或经过身份验证的请求,那只需把supports_credentials 设置为True即可

from flask import Flask, session
from flask_cors import CORS
 
app = Flask(__name__)
CORS(app, supports_credentials=True)
 
@app.route("/")
def helloWorld():
  return "Hello, %s" % session['username']

配置Access-Control-Allow-Origin(响应头添加header)

 
@app.after_request
def after(resp):
    '''
    被after_request钩子函数装饰过的视图函数 
    ,会在请求得到响应后返回给用户前调用,也就是说,这个时候,
    请求已经被app.route装饰的函数响应过了,已经形成了response,这个时
    候我们可以对response进行一些列操作,我们在这个钩子函数中添加headers,所有的url跨域请求都会允许!!!
    '''
    resp = make_response(resp)
    resp.headers['Access-Control-Allow-Origin'] = '*'
    resp.headers['Access-Control-Allow-Methods'] = 'GET,POST'
    resp.headers['Access-Control-Allow-Headers'] = 'x-requested-with,content-type'
    return resp

flask-session

Flask 默认情况下,session 的数据是存在于 cookie 中的,而 cookie 是存在于客户端。所以说 session 是依赖于 cookie 的。客户端修改了 cookie,session 就会失效。所以 flask 自带的 session 是有缺陷的。

通过 flask-session 可以解决此问题,可以将 session 的数据存在数据库或缓存(Redis)中。

安装和引入 flask-session

pip install flask-session
from flask_session import Session

配置 session

在 app 配置中加入 session 配置信息,这里使用 settings.py 加载配置

# settings.py
class Dev():
	SECRET_KEY = '3JDLLERRMdfsdfp2'
	SESSION_TYPE = 'redis'
	SESSION_REDIS = Redis(host='127.0.0.1', port=6379,db=1)
	# 使用文件存储 session 设置文件目录
	# SESSION_FILE_DIR = '/caches/flask_cache'
	# 设置缓存文件大小,当超出设置个数,则使用 LRU 算法,删除最近最少使用的缓存文件
	# SESSION_FILE_THRESHOLD = 3000
	# SESSION_COOKIE_SECURE = True

初始化 session 对象

在应用加载完配置后,创建 session 对象,并加入 app 对象

session = Session()
session.init_app(app)
# 或使用 
# Session(app)

初始化完成后,正常使用 session ,数据就会保存到缓存中了。测试访问一下,就可以在 redis 中看到 session 的数据了。

flask-session 不能替代 cookie

session 会为每个客户端(不同的浏览器、ajax、类似postman的测试工具)都创建一个独立的 session,但是无法判断 session 是属于具体哪个客户端的。Flask 在 HTTP 协议中已考虑此问题,解决方案是给 cookie 设置一个 session_id。因为 cookie 是和客户端关联的,所以不同的客户端有不同的 session_id,服务端以此 session_id 来确定当前请求是属于哪个会话。

尽管 session 存储到服务端,但是获取 session 数据所需要的 session_id 还是依赖于 cookie 。当禁用了 cookie 后,就无法正常获取 session 了。所以 flask-session 不能替代 cookie。

flask-SQLAlchemy

SQLAlchemy 是 python 中一个比较强大的 ORM 框架,其功能全面,使用简单。flask-sqlalchemy 是为 flask 应用添加 sqlalchemy 支持的扩展插件。部分可以参考 SQLAlchemy 的文档

SQLAlchemy 官方站(英文)
支持的数据库及连接方式

安装和配置

先安装 flask-sqlalchemy,注意 flask-sqlalchemy 需要依赖 mysqlclient 或 pymysql 来操作 mysql 数据库。

pip install flask-sqlalchemy
from flask-sqlalchemy import SQLAlchemy

然后可以对 flask-sqlalchemy 进行配置,可以在配置文件(settings.py)中进行配置

配置项 说明 示例 备注
SQLALCHEMY_DATABASE_URI 数据库连接地址 app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+mysqldb://user:[email protected]:3306/dbname?charset=utf8' 如果数据库使用pymysql,则协议名需要将 mysql+mysqldb 改为 mysql+pymysql
SQLALCHEMY_BINDS 访问多个数据库时,用于设置数据库的连接地址
SQLALCHEMY_ECHO 是否显示底层执行的SQL语句 app.config['SQLALCHEMY_ECHO'] = True
SQLALCHEMY_RECORD_QUERIES 是否记录执行的查询语句,用于慢查询分析,调试模式下自动启动
SQLALCHEMY_TRACK_MODIFICATIONS 是否追踪数据库变化(触发钩子函数),会消耗额外内存 app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
SQLALCHEMY_ENGINE_OPTIONS 设置针对 sqlalchemy 本体的配置项
SQLALCHEMY_COMMIT_ON_TEARDOWN 发生异常或事务结束时是否自动提交 app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True

初始化

在完成配置后,app 运行前可以创建并初始化 sqlalchemy 对象。初始化的方法有两种

# 使用对象关联app
db = SQLAlchemy()
db.init_app(app)
# 使用 app 创建对象
db = SQLAlchemy(app)

推荐使用第一种,然后在模型包中创建 SQLAlchemy 对象,在应用中导入该对象并关联至 app

在项目目录中创建一个 models 目录,然后所有的模型文件可以放在此目录下。推荐将此目录创建成包,方便导入和使用。

# models/__init__.py
from from flask-sqlalchemy import SQLAlchemy

# 创建 SQLAlchemy 对象
db = SQLAlchemy()
# app/__init__.py
from flask import Flask
from models import db
import settings

# 创建 app 对象
app = Flask(__name__)	
# 加载设置
app.config.from_object(settings.Dev)
# 初始化 SQLAlchemy 对象
db.init_app(app)

创建ORM模型

根据需要操作的数据库表创建相应的模型类,之后对模型类的操作就是对数据库表的操作。默认情况下,类名即数据库的表名,类属性名即为字段名。

# models/__init__.py
from from flask-sqlalchemy import SQLAlchemy

# 创建 SQLAlchemy 对象
db = SQLAlchemy()

# 创建模型类,继承自 db.Model
# 默认类名称即表名,但是也可以使用 __tablename__ = '表名称' 来定义表名
class User(db.Model):
	# 声明属性,即数据库中表的字段
	# 默认属性名即字段名,若不同可以将字段名作为参数传入 uid = db.Column('id')
	id = db.Column(db.Integer, primary_key=True, autoincrement=True)	# 整型、主键、自增长
	number = db.Column(db.String(18), unique=True)	# 唯一
	name = db.Column(db.String(20), nullable=False)		# 不能为空
	phone = db.Column(db.String(11))
	password = db.Column(db.String(100), nullable=False)

	def __str__(self):		# 自定义交互模式,即 print() 函数的打印内容
		return '(%s, %s, %s, %s, %s)' % (self.id, self.number, self.name, self.phone, self.password)

常用的字段类型

类型名 python接收类型 mysql生成类型 说明
Integer int int 整型
Float float float 浮点型
Boolean bool tinyint 整形,1字节
Text str text 文本,最大64KB
LongText str longtext 文本,最大4GB
String str varchar 变长字符串,需传参限定长度
Date datetime.date date 日期
DateTime datetime.datetime datetime 日期和时间
Time datetime.time time 时间
ForeignKey 外键约束,需传参指定主键

常用的字段参数选项

选项名 说明
primary_key 主键,自增
unique 唯一约束
nullabel 非空约束,默认为True
default 默认值
index 创建索引
autoincrement 自增

如果模型中需要使用不属于数据库的字段数据,则可以添加成员函数。需注意的是,此成员不和数据库关联,所以无法进行修改、删除等操作。

@property
def birthday(self):
	return self.number[6:14]

使用模型

在视图函数内部可以引入并使用模型,来进行数据库的增删改查操作。

如果无法正确导包,可以使用 sys.path.extend() 方法,将包路径放入一个列表,然后传入。使用模型时,如果提示错误(no application found),可以加入 with app.app_context().push():

增加数据
# 导入模型
from models import User

# 1.创建模型对象,此对象就是1条记录。
# 在参数中可以添加数据
user = User(number='410300200001010101', name='张三', password='1234567890')
# 也可以使用设置对象属性的方式设置数据
# user.number = '410300200001010101'
# user.name = '张三'
# import hashlib
# user.password = hashlib.md5('1234567890'.encode('utf-8)).hexdigest()

# 2.将模型对象添加到会话中 
db.session.add(user)
# 也可以添加多个对象,即多条记录
# db.session.add_all([user1, user2, user3])

# 3.提交会话(会提交事务)
# sqlalchemy 会自动创建隐式事务
# 事务失败会回滚
db.session.commit()

需注意的是,这里使用的会话不是状态保存机制中的 session,而是 sqlalchemy 的会话。它被设计为数据操作的执行者,从 SQL 角度则可以理解为是一个加强版的数据库事务。

查询数据
session 查询

使用 db.session.query() 方法,返回重新组织的临时表(视图)。可以将需要返回的表或字段作为参数传入,过滤和筛选则使用链式调用相应的方法。

# 所有数据只范围两个字段信息
db.session.query(User.name, User.number).all()
db.session.query(User)
模型查询

是对单表的查询,可以简单使用表模型对象进行查询。返回的数据是符合查询条件的整条数据。

# 导入模型
from models import User

# 查询所有数据
User.query.all()		# 返回列表,元素为模型对象
# 返回查询第一条数据
User.query.first()		# 返回模型对象或None
# 按照主键查询,值为4的记录
User.query.get(4)		# 返回模型对象或None
# 使用过滤器,查询字段,目标为 id=4 的数据
User.query.filter_by(id=4).all()		# 返回 BaseQuery 对象,可以续接其他过滤器/执行器,如 all/count/first 等
# 复杂过滤器,参数为比较运算/函数引用等
User.query.filter(User.id == 4).first()		# 返回 BaseQuery 对象
# 复杂查询的一些方法
User.query.filter(User.name.endswith('g')).all()	# 以 g 结尾
User.query.filter(User.name.startswith('w')).all()	# 以 w 开始
User.query.filter(User.name.contains('n')).all()	# 包含 n
User.query.filter(User.name.like('w%n%g')).all()	# 模糊查询,使用通配符
# 复杂查询,逻辑且
User.query.filter(User.name.startswith('li'), User.phone.endswith('04')).all()
from sqlalchemy import and_		# 也可以使用数据库对象  db.and_
User.query.filter(and_(User.name.startswith('li'), User.phone.endswith('04'))).all()
# 复杂查询,逻辑或
from sqlalchemy import or_		# 也可以使用数据库对象  db.or_
User.query.filter(or_(User.name.startswith('zh'), User.number.endswith('2212'))).all()
# 复杂查询,逻辑非
User.query.filter(User.name != 'wang').all()
from sqlalchemy import not_		# 也可以使用数据库对象  db.not_
User.query.filter(not_(User.name == 'wang')).all()
# 复杂查询,条件在集合内
User.query.filter(User.id.in_([1, 3, 6, 9])).all()
# 使用字符串表达式附带参数
User.query.filter(User.name != :w).params(w='wang').all()
# 排序,默认升序,可以使用 desc() 方法指定降序
User.query.order_by(User.name, User.number.desc()).all()	# name 升序,number 降序
# 分页,原始方式
User.query.order_by(User.id).offset(3).limit(4).all()		# 按 id 排序,跳过前3条数据,从第4条开始返回4条数据
# 分页,简单方式,每页3条,查询第2页数据 paginate(页码, 每页大小)
pn = User.query.paginate(2, 3)
pn.pages	# 总页数
pn.page		# 当前页码
pn.items	# 当前页的数据
pn.total	# 总条数

模型查询也可以只返回某些字段

# 导入模型
from models import User
from sqlalchemy.orm import load_only
User.query.options(load_only(User.name, User.number)).all()
原生查询

SQLAlchemy 还支持使用原生 sql 语句进行查询

# 原生 sql 语句查询,每行记录为对象的 corsor 属性
rst = db.session.execute('select * from user')
for row in rst.cursor:
	print(row)
聚合查询

聚合查询可以使用 session.query() 方法,参数可以不传入表或字段,而是聚合查询函数。也可以使用模型对象加聚合方法来查询。

# 统计 Photo 表中的记录数量,返回元组
db.session.query(db.func.count(Photo.id)).all()		# count 的参数必须传入一个字段,将按照这个字段进行统计
Photo.query.count()
# 参数传入表对象,即对此表进行聚合查询
# label() 可以重命名查询字段(别名)
db.session.query(User, db.func.count('*').label('cnt')).all()	# 返回 User 表数据,并添加字段 cnt,值为数据数量
# 统计数量并进行分组(对分组统计数量)
db.session.query(User.name,db.func.count(User.id).label('cnt')).group_by(User.name).order_by(db.Column('cnt').desc()).all()
# 统计和
db.session.query(User,db.func.sum(Card.money).label('cnt')).all

常见的聚合方法有

  • db.func.count()
  • db.func.max()
  • db.func.min()
  • db.func.sum()
  • db.func.avg()

func 也可以直接导入使用 from sqlalchemy import func

分组查询

可以使用 group_by() 方法按照某一字段进行分组,也可以在分组后使用 having() 方法设置分组条件。

存在查询

使用 exists()any() 方法可以返回查询数据是否存在

from models import User, Photo
db.session.query(User).filter(db.exists().where(Photo.user_id == User.id)).all()
db.session.query(User).filter(User.photos.any()).all()
联合查询

可以联合多表进行查询

from models import User, Photo
db.session.query(User.name, Photo,name).filter(User.id==Photo.user_id).all()
# 内连接
db.session.query(Photo.name, User.name).join(User).all()
# 外连接
db.session.query(User.name).outerjoin(Photo).all()
修改数据

修改数据需要先拿到数据,即先查询

# 导入模型
from models import User

# 获取要修改的数据(对象)
user = User.query.get(4)
# 修改数据
user.name = '王五'
user.phone = '13939939999'
# 将模型对象添加到会话中,提交事务时默认会检测数据是否变化,所以一般可以省略
db.session.add(user)		# 或使用 db.session.is_modified(user) 来判断对象是否修改了属性,需要更新
# 提交事务
db.session.commit()

此种方法将查询和更新分离,效率低一些,且如果并发进行可能会出现更新丢失问题。也可以基于过滤条件同时更新,这样效率更高,且具有原子性。

# 导入模型
from models import User

User.query.filter(User.id == 4).update({'name' : '赵六'})
db.session.commit()
删除数据

同修改数据,也分为先查询再删除和同时删除两种方式

# 导入模型
from models import User

# 获取要修改的数据(对象)
user = User.query.get(4)
# 添加删除事务
db.session.delete(user)
# 提交事务
db.session.commit()
# 导入模型
from models import User

User.query.filter(User.id == 4).delete()
db.session.commit()
刷新数据

session 被设计为数据执行的操作者,会将操作产生的数据保存到内存中,然后等刷新操作时再同步到数据库。但是有两种情况下会隐式执行刷新操作:提交会话和执行查询。除此之外则需要手动刷新事务: db.session.flush()

事务操作

# 判断当前数据库会话是否可用
db.session.is_active
# 回滚事务
db.session.rollback()
# 提交事务
db.session.commit()
# 刷新数据
db.session.refresh(obj)

关系表

一对一关系和一对多关系只需要设置一个外键字段就可以关联了,这里主要研究一对多关系。

MYSQL的一对多关系需要使用第三张关系表,指明关联的外键。在 flask-SQLAlchemy 中,就是创建一个关系表(非模型类)来实现

# 创建用户和角色的关系表
user_role = db.Table('user_role',	# 表名称
                     Column('user_id', Integer, ForeignKey('user.id', name='user_role_fk')),	# 关联到user表的外键
                     Column('role_id', Integer, ForeignKey('role.id', name='user_role_pk')))	# 关联到role表的外键

然后在一个多端表中,指明使用关系即可

# user 表的数据模型
class User(Base):
    auth_key = Column(String(100), nullable=False)
    nick_name = Column(String(20))
    # 多对多关系,指定 secondary 设置关联表Table
    roles = db.relationship(Role, secondary=user_role)	# 和 Role 建立关系,关系使用关系表 user_role

Flask 的其他一些插件

日志

当定义了 app 后,可以在视图函数中输出日志。例如在控制台输出信息

# 使用 flask 中的日志记录器
app.logger.info('这里输入日志信息,字符串格式')

或使用 logging 产生日志,保存到文件或发送到日志服务器

import logging
from logging import StreamHandler, FileHandler
from logging.handlers import HTTPHandler, TimedRotatingFileHandler
from logging import Formatter

# 设置日志记录器,名称为 edu_api
logger = logging.getLogger('edu_api')	

def config_log():
	# 设置日志记录器格式,asctime(时间),name(日志记录器的名称),levelname(日志等级),message(日志信息)
	fmt = Formatter(fmt='%(asctime)s %(name)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
	# 日志处理器
	iohandler = StreamHandler()
	iohandler.setLevel(logging.DEBUG)
	iohandler.setFormatter(fmt)
	# 文件处理器
	file_handler = FileHandler('edu.log')
	file_handler.setLevel(logging.WARN)
	file_handler.setFormatter(fmt)
	# http处理器
	http_handler = HTTPHandler(host='localhost:5000', url='/log', method='POST')
	http_handler.setLevel(logging.ERROR)
	http_handler.setFormatter(fmt)		# 上传的数据不需要 fmt 格式,所以可以省略

	logger.setLevel(logging.DEBUG)
	logger.addHandler(iohandler)
	logger.addHandler(file_handler)
	logger.addHandler(http_handler)

# 执行创建日志的函数
config_log()
# 输出一些日志信息
logger.info('输出日志信息')
logger.warning('输出提示信息')
logger.error('输出错误信息')
logger.critical('输出严重信息')
# 删除日志处理器
# logger.removeHandler(file_handler)

flask 中使用日志

在创建了 app 并加载好设置后,app 运行前,可以设置日志记录器

from flask import Flask
import settings
from flask.logging import default

# 创建此app对象
app = Flask(__name__)
# 加载配置
app.config.from_object(settings.Dev)
# 设置日志记录器
app.logger.setLevel(logging.DEBUG)
app.logger.addHandler(http_handler)

Flask-Bootstrap

flask-bootstrap 是集成了 bootstrap 的插件,方便的地方是内置了一些基模板,

pip install flask-bootstrap
flask-bootstrap英文文档

使用时继承基模板 base.html 就可以了。

Flask-Cache

flask-cache 可以进行缓存优化加载,减少数据库的 IO 操作

flask-cache 英文文档

flask-RESTful

支持 RESTful 的插件

你可能感兴趣的:(python,笔记,flask,python,后端)