一、Flask环境安装与配置
1、Flask介绍
-
Flask 是一个轻量级的 Web 开发框架。它是依赖 Jinja2 、SQLAlchemy 和Werkzeug WSGI 服务的一个微型框架。之所以说是轻量级,是因为 Flask 框架有这两个核心的内容。其中 Jinja2 是模板引擎,Werkzeug 是路由模块。Flask 的轻量级特性使其非常适合用于小型和中型项目的开发。对于快速原型设计、个人项目或小团队开发来说,Flask 提供了足够的功能和灵活性,而无需引入过多的复杂性。
-
组件说明:
- flask = werkzeug + sqlalchemy + jianjia2
- werkzeug:处理url和视图函数之间的映射和http请求
- sqlalchemy:处理数据库相关的逻辑操作
- jianjia2:处理模板
-
优点:
- 简单易用。
- 微型轻量级。
- 高度可扩展。
- 社区活跃。
-
多样化功能需要使用插件进行扩展:
- 发邮件,需要使用到 Flask-mail 扩展。
- 登录 ,需要使用到 Flask-login。
- 操作数据库,需要使用到 SQLAlchemy。
- 开发 REST API 风格的接口,需要使用到 Flask-RESTful 或者 flask-restx。
-
Flask 相关文档:
2、Flask环境安装及快速入门
(1)Flask环境安装
- 使用pip安装:
pip install flask
;
(2)快速入门
A、入门案例
hello_falsk.py
from flask import Flask
# 1、创建flask应用程序实例,__name__参数固定写法,意思是把当前文件作为服务内容
my_app = Flask(__name__)
# 2、创建路由和视图函数--"/"是根目录,这里在根目录下再加了一层目录one_hello,所以访问的时候需要加速路径才能正常访问下面的视图函数
@my_app.route("/one_hello")
def hello():
return "HELLO FLASK!"
# 3、运行应用程序
if __name__ == '__main__':
# 暂时全部使用默认值
my_app.run()
运行:入门案例的运行方式有两个(Windows PowerShell)中:
- 1、直接输入命令:
$env:FLASK_APP=.\hello_falsk.py
- 2、先设置环境变量,再启动服务:
# 设置环境变量
$env:FLASK_APP=".\hello_falsk.py"
# 启动
flask run
浏览器输入地址访问:
# 注意代码中的路由
http://127.0.0.1:5000/one_hello
B、如何运行程序启动服务
a、直接运行这个py文件即可,右键运行或者使用python命令运行;
# 通过python命令运行这个py文件
python .\L1\hello_falsk.py
b、使用Flask的命令行命令启动服务
- 1、先设置环境变量:
# 类linux 设置环境变量命令
export FLASK_APP=hello_flask
# cmd 设置环境变量
set FLASK_APP=hello_flask
# Windows powershell 设置环变量--注意:环境变量的值需要双引号
$env:FLASK_APP=".\hello_flask.py"
-
2、启动服务:
flask run
; -
注意:windows中cmd使用flask需要安装flask并配置环境变量才能使用,在Windows PowerShell
(或者pycharm)中配置环境变量需要使用$env:环境变量名="变量值"
,变量值是py文件的完整路径;
二、接口路由技术
1、说明是路由
路由是将 URL 地址与应用程序中的函数相映射的过程。当用户在浏览器中输入特定的 URL 地址时,Flask 会调用与该地址相匹配的函数并返回相应的结果。
2、Flask中路由分类
在 Flask 中,可以使用 @app.route()
装饰器来定义路由。路由决定了用户请求的 URL 路径与对应的视图函数之间的关系。
路由类型 | 实现方式 |
---|---|
基本路由 | @app.route('/') |
动态路由 | @app.route('/user/<username>') |
限定类型 | @app.route('/post/<int:post_id>') |
(1)基本路由
from flask import Flask
# 创建 Flask 应用程序实例
app = Flask(__name__)
# 定义基本路由
@app.route("/")# 访问地址为 http://127.0.0.1:5000/
def index():
return "Home Page"
@app.route("/about")# http://127.0.0.1:5000/about
def about():
return "About Page"
# 运行应用程序
if __name__ == '__main__':
app.run()
(2) 动态路由
-
URL 中某些地方存在可变部分,为动态的 URL,Flask 支持这种动态 URL。
-
动态路由是通过尖括号
<>
实现的,放在尖括号里面的部分为动态部分,即在装饰器 route 传入<>
部分,定义视图函数时,将该<>
里面的内容作为参数传入到函数里面即可。
(3) 限定类型
- 如果希望限定输入的动态字段的类型,可以使用
<类型:变量名>
来实现,例如如果希望某个字段只能是整数,那么可以写为/user/<int:user_id>
。 - Flask 支持在路由上使用的类型有 int、float、string、path。path 类型是字符串的一种,不同的是它可以包含正斜线。–默认是string类型;
from flask import Flask
# 创建服务实例
app = Flask(__name__)
# 动态路由-也就是url是可以变化的,根据变化的url得到不同的结果
@app.route("/dynamic/<content>")
def dynamic_route(content):
return f"自定义路由为/dynamic/{content}"
# 限定路由-限定路由的类型:int、float、string、path,path是一个钟特殊的字符串,里面运行输入\
@app.route("/limit/<int:int_content>")
def limit_route_int(int_content):
return f"limit_route_int--限定路由为整型/limit/{int_content}"
@app.route("/limit/<float:float_content>")
def limit_route_float(float_content):
return f"limit_route_float--限定路由为浮点数/limit/{float_content}"
@app.route("/limit/<string:string_content>")
def limit_route_string(string_content):
return f"limit_route_string--限定路由为字符串/limit/{string_content}"
@app.route("/limit/<path:path_content>")
def limit_route_path(path_content):
return f"limit_route_path--限定路由为path,允许包含\:/limit/{path_content}"
if __name__ == '__main__':
app.run()
3、路由规则
Flask 的 URL 规则基于 Werkzeug 的路由模块。可以确保形成的 URL 是唯一的,并且基于 Apache 规定的先例。
规范的URL: 路由的尾部使用斜杠 (/);
from flask import Flask
# 创建服务实例
app = Flask(__name__)
@app.route('/about')# 结尾没有使用/,那么在访问的时候加了/就会Not Found
def about():
return 'About Page'
@app.route('/hogwarts/')# 结尾加了/,在访问的时候给不给最后一个/都能找到资源
def hello_hogwarts():
return 'Hello Hogwarts'
if __name__ == '__main__':
app.run()
三、请求方法与处理请求数据
1、请求方法
Flask 框架支持常见的 HTTP 请求方法,最常用的请求方法为:
- GET:一般是从 URI 中从 服务器中获取资源(比如获取用例列表,比如,获取用户信息等等),但一般 GET 是存在不安全性的,如果有敏感信息,会使用 POST。
- POST: 主要用于将【数据发送到服务器】创建或更新资源。注意,POST 对数据长度是没有限制的,GET 会有限制,这是因为某些浏览器对请求的 URL 长度有限制。
- PUT:也是用于将 【数据发送到服务器】创建或更新资源(客户端提供改变后的完整资源)。
- DELETE:用来删除指定的资源。
2、请求方法的具体使用
- Flask 支持的请求方法通过在路由定义时使用
methods
参数进行指定。可以指定多个请求方法,以列表的形式传递给methods
参数。 - 语法:
@app.route("/自定义路由", methods=["大小写都可以的方法名"])
; - **注意:
- 默认情况下,Flask 路由定义的视图函数只支持 GET 请求。如果需要支持其他请求方法,需要显式地指定
methods
参数。 -
methods
参数列表中可以有多个请求方法,比如methods=["GET","POST"]
,表示该接口支持多种请求方式;
- 默认情况下,Flask 路由定义的视图函数只支持 GET 请求。如果需要支持其他请求方法,需要显式地指定
method_usage.py
from flask import Flask
# 初始化服务
app = Flask(__name__)
# get请求
@app.route("/my_get_01/<int:id>/")# 如不指定methods,默认是get请求
def get_data_01(id):
return f"通过id={id}获取数据,get_data_01"
@app.route("/my_get_02/<string:name>/",methods=["GET"])# 也可以使用methods指定请求方式
def get_data_02(name):
return f"通过name={name}获取数据,get_data_02"
# post请求
@app.route("/my_post/<string:name>/<int:id>/",methods=["POST"])
def post_data(name,id):
return f"通过name={name},id={id}创建或更新资源,post_data"
# put请求
@app.route("/my_put/<string:name>/<int:id>/",methods=["PUT"])
def put_data(name,id):
return f"通过name={name},id={id}创建或更新资源,put_data"
# delete请求
@app.route("/my_delete/<int:id>/",methods={"DELETE"})
def delete_data(id):
return f"通过id={id}删除数据,delete_data"
if __name__ == '__main__':
app.run()
单元测试:
import requests
def test_get_data_01():
url = "http://127.0.0.1:5000"
path = "/my_get_01/9527"
response = requests.request(method="get", url=url + path)
print(response.text) # 通过id=9527获取数据,get_data_01
def test_get_data_02():
url = "http://127.0.0.1:5000"
path = "/my_get_02/张三/"
response = requests.request(method="get", url=url + path)
print(response.text) # 通过name=张三获取数据,get_data_02
def test_post_data():
url = "http://127.0.0.1:5000"
path = "/my_post/李四/9887"
response = requests.request(method="post", url=url + path)
print(response.text) # 通过name=李四,id=9887创建或更新资源,post_data
def test_put_data():
url = "http://127.0.0.1:5000"
path = "/my_put/王五/9001"
response = requests.request(method="put", url=url + path)
print(response.text) # 通过name=王五,id=9001创建或更新资源,put_data
def test_delete_data():
url = "http://127.0.0.1:5000"
path = "/my_delete/9595"
response = requests.request(method="delete", url=url + path)
print(response.text)# 通过id=9595删除数据,delete_data
3、 处理请求数据
(1)flask中request对象
-
当浏览器去访问一个地址时,HTTP 协议会向后台传递一个 request 对象。这个 request 对象包含请求头、请求参数、以及请求方式。后台可以取到 request,然后进行逻辑处理。
-
在 Flask 中,可以使用 request 对象来处理请求数据。request 对象提供了访问请求数据的方法和属性。
-
request 的常用属性:
属性/方法 | 说明 | 备注 |
---|---|---|
args | 记录请求中的查询参数 | 字典格式,get请求url拼接的query参数 |
json | 记录请求中的 json 数据 | 字典格式,post或者put请求中发送的JSON格式数据 |
files | 记录请求上传的文件 | 字典格式,每个上传的文件都会存储在这个字典里 |
form | 记录请求中的表单数据 | 字典格式,post请求发送的form格式数据 |
method | 记录请求使用的 HTTP 方法 | |
url | 记录请求的 URL 地址 | |
host | 记录请求的域名 | |
headers | 记录请求的头信息 |
(2)get请求参数处理
如果一个 GET 请求在 URL 中拼接了请求参数,可以使用 request.args
来获取 GET 请求中携带的请求参数。request.args
是一个字典,可以通过键名来获取参数的值。
request_get_args.py
from flask import Flask, request
# 初始化服务
app = Flask(__name__)
# get请求query处理
@app.route("/getinfo/")
def get_request_args():
# 获取请求url中携带的参数
url_params = request.args
# 从请求参数中获取id
id = url_params.get("id")
# request中的属性值
header_items = request.headers.items()
# items是一个生成器
print(f"request.headers--type={type(header_items)}")
# 将request.headers.items()中的数据转字典
header_json = {}
for header in header_items:
# header是一个元组
key = header[0]
header_json[key] = header[1]
host = request.host
host_url = request.host_url
method = request.method
base_url = request.base_url
path = request.path
values = request.values
# request.values=CombinedMultiDict([ImmutableMultiDict([])])
print(f"request.values={values}")
# 转换成字符串进行返回
result = {"request.args":url_params,
"从请求参数中获取id":id,
"request.host":host,
"request.host_url":host_url,
"request.method":method,
"request.base_url":base_url,
"request.path":path,
"request.headers.items()转字典":header_json
}
return result
if __name__ == '__main__':
app.run()
单元测试:
import requests
def test_get_request_args():
url = "http://127.0.0.1:5000"
path = "/getinfo"
param = {"id":9527}
header = {
"Content-Type":"application/json"
}
response = requests.request(method="get",url=url+path,params=param,headers=header)
print(response.json())
返回值:
{
'request.args': {
'id': '9527'
},
'request.base_url': 'http://127.0.0.1:5000/getinfo/',
'request.host': '127.0.0.1:5000',
'request.host_url': 'http://127.0.0.1:5000/',
'request.method': 'GET',
'request.path': '/getinfo/',
'从请求参数中获取id': '9527',
'request.headers.items()转字典': {
'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate',
'Connection': 'keep-alive',
'Content-Type': 'application/json',
'Host': '127.0.0.1:5000',
'User-Agent': 'python-requests/2.31.0'
}
}
(3)post请求form处理
-
request.form
:得到是字典; -
request.values
:得到一个字典CombinedMultiDict([ImmutableMultiDict([])])
,前端通过form表单提交数据;
from flask import Flask, request
# 初始化服务
app = Flask(__name__)
@app.route("/postform/",methods=["POST"])
def post_request_form():
form = request.form
return {"服务端获取到的表单数据":form}
if __name__ == '__main__':
app.run()
单元测试:
import requests
def test_post_request_form():
url = "http://127.0.0.1:5000"
path = "/postform"
data = {"username":"唐伯虎","id":9527}
response = requests.request(method="post",url=url+path,data=data)
print(response.json())# {'服务端获取到的表单数据': {'id': '9527', 'username': '唐伯虎'}}
(4) post请求json处理
- 前端在请求接口的时候需要添加请求头信息;
headers: { 'Content-Type': 'application/json;charset=utf-8' }
from flask import Flask, request
app = Flask(__name__)
@app.route("/putjson",methods=["put"])
def put_request_json():
return {"服务端收到的json数据":request.json}
if __name__ == '__main__':
app.run()
单元测试:
import requests
def test_put_request_json():
url = "http://127.0.0.1:5000"
path = "/putjson"
json_data = {"username": "唐伯虎", "id": 9527}
response = requests.request(method="put", url=url + path, json=json_data)
print(response.json()) # {'服务端收到的json数据': {'id': 9527, 'username': '唐伯虎'}}
(5) post请求file处理
-
可以使用
request.files
来获取请求中包含的文件。request.files
是一个字典,每个上传的文件都会存储在这个字典里。可以通过file
这个 key 来获取其中的文件对象。 -
已上传的文件存储在内存或是文件系统中一个临时的位置。它有一个
save()
方法,这个方法允许把文件保存到服务器的文件系统上。 -
如果想知道上传前文件在客户端的文件名是什么,可以访问
filename
属性。但这个值是可以伪造的。如果要把文件按客户端提供的文件名存储在服务器上,需要把它传递给 Werkzeug 提供的secure_filename()
函数。这个函数可以用来确保文件名是安全的。
from flask import Flask, request
from werkzeug.utils import secure_filename
app = Flask(__name__)
@app.route("/postfiles",methods=["post"])
def post_request_files():
files = request.files
print(type(files))# <class 'werkzeug.datastructures.structures.ImmutableMultiDict'>
print(files)# ImmutableMultiDict([('new_pic_name', <FileStorage: 'shootup.png' (None)>)])
# 通过文件名获取上传的文件
file = files.get("new_pic_name")
# 使用save()方法把文件保存到本地--需要提前创建存放文件的目录,使用secure_filename() 函数确保文件名安全
file.save("../uploads" + secure_filename(file.filename))# save有两个参数,一个是路径+文件名,另一个是buffer_size
return f"你上传的真实文件名是不是{secure_filename(file.filename)}?"
if __name__ == '__main__':
app.run()
单元测试:
import requests
def test_post_request_files():
url = "http://127.0.0.1:5000"
path = "/postfiles"
file = {"new_pic_name":open("./shootup.png",'rb')}
response = requests.post(url=url+path,files=file)
print(response.text)# 你上传的真实文件名是不是shootup.png?
(6) put请求json处理
from flask import Flask, request
app = Flask(__name__)
@app.route("/postjson",methods=["post"])
def post_request_json():
return {"服务端收到的json数据":request.json}
if __name__ == '__main__':
app.run()
单元测试:
import requests
def test_post_request_json():
url = "http://127.0.0.1:5000"
path = "/postjson"
json_data = {"username":"唐伯虎","id":9527}
response = requests.request(method="post",url=url+path,json=json_data)
print(response.json())# {'服务端收到的json数据': {'id': 9527, 'username': '唐伯虎'}}
四、处理响应数据
使用Flask的视图函数的return给客户端返回响应信息;
1、 接口响应常见类型
- 文本型:直接return字符串;
- 元组:返回多个值,但是排列有讲究,第一个是Response对象(也就是响应体信息),第二个是status_code(如果不写默认就是200),第三个是响应头信息;Response为必选项,其他两个任意;
- JSON:JSON格式的响应体信息;
- HTML:html格式的响应体信息;
- 额外数据:其他响应信息,比如cookie等;
(1) 返回文本类型
- 返回文本类型比较简单,直接在视图函数返回字符串即可;
- 特点就是返回的Headers中默认会返回响应类容格式:
Content-Type:text/html; charset=utf-8
;
(2) 返回元组类型
元组格式包含 3 个参数类型。第一个是 response 对象,第二个是响应状态码,第三个是响应头信息。也可以只填写 2 个返回信息,但是Response为必反项。比如 (response, status)
结合,还有 (response, headers)
结合。
from flask import Flask
app = Flask(__name__)
@app.route("/tuple/")
def response_tuple():
return {"content":"这是响应体信息"},200,{"my_header":"private_header"}
if __name__ == '__main__':
app.run()
(3)返回json类型
-
第一种是使用
jsonify()
方法,此方法支持,直接传入一个字典,也支持通过关键字参数传递。 -
第二种方法就是直接返回字典或者列表,在 Flask 1.1 版本之后,直接返回 python 的字典类型时,Flask 会调用
jsonify()
方法。
注意: json的数据可以用花括号{}
或中括号[]
包裹,对应js中的object和array,对应Python中的dict和list;
json数据的格式可以是:
{"name":"admin","age":18}
也可以是:
["hello",3.1415,"json"]
还可以是:
[{"name":"admin","age":18},{"name":"root","age":16},{"name":"张三","age":20}]
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/json/")
def response_json_01():
return {"content":"直接返回json信息","type":1}
@app.route("/jsonify_dict/")
def response_json_02():
return jsonify({"content":"通过传递字典给jsonify返回json","type":2})
@app.route("/jsonify_key_value/")
def response_json_03():
return jsonify(content="通过给jsonify传递键值对方式返回json",type=3)
if __name__ == '__main__':
app.run()
单元测试:
import requests
def test_response_json_01():
url = "http://127.0.0.1:5000"
path = "/json"
response = requests.get(url=url+path)
print(response.json())# {'content': '直接返回json信息', 'type': 1}
def test_response_json_02():
url = "http://127.0.0.1:5000"
path = "/jsonify_dict"
response = requests.get(url=url + path)
print(response.json())# {'content': '通过传递字典给jsonify返回json', 'type': 2}
def test_response_json_03():
url = "http://127.0.0.1:5000"
path = "/jsonify_key_value"
response = requests.get(url=url + path)
print(response.json())# {'content': '通过给jsonify传递键值对方式返回json', 'type': 3}
(4)返回HTML
- 返回 HTML 主要使用的是模板渲染技术。
- 方法:
render_template(html文件名)
; - 注意:注意html文件必须在 templates 目录下,templates目录名称固定不变,并且在“app”py文件的同级目录下;
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/index_html/")
def response_html():
# 调用render_template方法,传入html 文件的名称。
# 注意html文件必须在 templates 目录下
return render_template("qq_email.html")
if __name__ == '__main__':
app.run()
(5) 返回额外数据
- 可以返回更多响应字段类容,比如:文件格式、文件语言、文件大小、cookie、headers等;
- 方法:使用
make_response(render_template('文件名'))
得到一个Response对象,通过对象去设置响应的返回字段内容;
from flask import Flask, render_template, make_response
app = Flask(__name__)
@app.route("/")
def response_other():
# 调用render_template方法,传入html 文件的名称。注意html文件必须在 templates 目录下
# 创建一response对象
response = make_response(render_template("qq_email.html"))
# 设置cookie
response.set_cookie('cookies_key','cookies_content')
# 设置响应头
response.headers["myheader"] = 'private_header'
# 设置响应数据类型
response.content_type = 'multipart/form-data'
# 设置响应类容语言
response.content_language = 'html'
return response
if __name__ == '__main__':
app.run()
五、服务主机端口配置及debug模式
1、监听主机和端口
-
方法:
app.run(host="x.x.x.x",port=5000)
;- host传递主机名:
-
127.0.0.1
只能本机访问—默认值; -
0.0.0.0
服务发布到局域网。–当 host 值为 ‘0.0.0.0’,这表示应用程序将监听所有可用的网络接口。可以通过运行应用程序的主机的 IP 地址进行访问。
-
- port:默认5000端口,可以根据需要修改端口;
- host传递主机名:
2、dubug模式
(1)开启关闭调试模式
- 方法:
app.run(debug=True)
,默认是 production关闭,True为开启;
(2) 调试模式的作用
-
显示详细的错误信息:在调试模式下,当应用程序出现错误时,Flask 会显示详细的错误信息,包括错误堆栈跟踪。这对于定位和修复错误非常有帮助。
-
自动重新加载代码:调试模式下,如果你修改了应用程序的代码文件,Flask 会自动重新加载修改后的代码,而无需手动重启应用程序。这样可以加快开发的迭代速度,节省重启服务器的时间。
-
支持实时调试器:调试模式下,Flask 提供了一个实时调试器(Debugger),可以在浏览器中显示源代码和调试信息,并允许你在运行时进行断点调试和变量查看。
六、蓝图与视图
1、什么是蓝图
-
Flask 中的蓝图(Blueprint)是一种组织和管理应用程序路由和视图的机制。它允许开发者将相关功能的路由和视图进行分组,从而更好地组织项目结构和实现模块化开发。蓝图可以极大地简化大型应用并为扩展提供集中的注册入口。
-
Flask 可以通过蓝图来组织 URL 以及处理请求。如果使用蓝图,应用会在 Flask 层中进行管理,共享配置,通过注册按需改变应用对象。蓝图的缺点是一旦应用被创建后,只有销毁整个应用对象才能注销蓝图。
-
一个项目可以具有多个蓝图。但是一个蓝图并不是一个完整的应用,它不能独立于应用运行,而必须要注册到某一个应用中。
2、应用场景及作用
应用场景:
- 项目复杂度增加,路由和视图函数增多。
-
路由需要结构化,模块化进行管理。
作用:
- 蓝图是 Flask 提供的一个类。
- 具备 Flask 核心对象的很多功能,最重要的就是注册路由。
- 可以把整个项目分成不同的模块并在不同的模块中增加不同的功能。
3、蓝图的使用步骤
(1)创建蓝图对象
要使用蓝图,首先要声明一个蓝图对象。
from flask import Blueprint
# 要使用蓝图,首先要声明一个蓝图对象。
goods_router = Blueprint(name="goods", import_name=__name__)
-
参数说明:
-
name
:蓝图的名称;–必填 -
import_name
:蓝图所在的模块 ,一般定义为__name__
;–必填
-
(2)定义路由
创建蓝图对象之后,使用蓝图对象来定义路由;
# 2. 路由定义
@goods_router.route("/")
def index():
return {"code":0, "msg": "get success", "data": []}
@goods_router.route("/add/", methods=["POST"])
def add_goods():
return {"code":0, "msg": "add success"}
(3)注册蓝图对象
创建蓝图对象并定义好路由之后,需要将蓝图注册到flask启动对象之后才可以启动flask服务;
if __name__ == '__main__':
# 注册蓝图
app.register_blueprint(goods_router)
app.run(port=5055, debug=True)
(4) 定义 path前缀
-
当在应用对象上注册一个蓝图时,还可以指定一个
url_prefix
关键字参数,该参数是字符串类型,并且必须以/
开头,默认是/
。-
当设置了
url_prefix
参数后,定义路由时,如果路由直接使用url_prefix
的值,则route()
方法中传入空字符串即可。 -
如果需要在
url_prefix
值的基础上再加下一级路由,则在route()
方法中传入对应路由即可,需要使用/
开头
-
-
它的作用是在蓝图上注册的路由 path自动会加上这个前缀。这样可以保证在多个蓝图中使用相同的 path规则而不会最终引起冲突。这样就可以在不同的前缀下定义行为,比如增删改查。–简单说就是通过
url_prefix
的参数将路由进行分家,只要家分好了,相同的名字也无所谓,因为在不同的家里;
from flask import Blueprint
# 定义path前缀为:/user,只要是这个蓝图下的path,全部都会在前面加上/user
user_router = Blueprint("user", __name__, url_prefix="/user")
# 设置了url_prefix,直接使用前缀路由,那就给路由传个空字符串
@user_router.route("")
def user_list():
return {"code": 0, "msg": "get success", "data": []}
# 访问的时候会自动加上/user,也就是要通过/user/login才能访问到这个资源
@user_router.route("/login", methods=["POST"])
def login():
return {"code": 0, "msg": "login success"}
if __name__ == '__main__':
# 注册蓝图
app.register_blueprint(user_router)
app.run(port=5055, debug=True)
说明:
- 1、
http://127.0.0.1:5055/user
获取user_list的数据; - 2、
http://127.0.0.1:5055/user/login
访问login的数据; - 3、
http://127.0.0.1:5055/login
访问不到数据,404,因为path不对;
七、模板技术
1、模板技术介绍
-
Web 程序里,访问一个地址通常会返回一个包含各类信息的 HTML 页面。其中包含变量和运算逻辑的 HTML 或其他格式的文本叫做模板,执行这些变量替换和逻辑计算工作的过程被称为渲染。
-
Flask 模板渲染是通过 Jinja2 引擎来完成的。
-
默认情况下,Flask 会从模块同级的
templates
目录下寻找模版。
2、 应用场景与价值
-
动态内容:Flask 模板支持将动态数据插入 HTML 页面,从而可以创建个性化和交互式的 Web 应用程序。
-
代码重用:模板允许开发人员在多个页面之间重用常见的 HTML 组件,减少冗余代码,提高可维护性。
-
一致的设计:通过使用模板,开发人员可以确保应用程序在设计和布局上保持一致,从而实现专业和统一的用户体验。
-
与其他技术的集成:Flask 模板可以轻松与其他前端技术(如 CSS 框架和 JavaScript 库)集成,实现现代化和视觉上吸引人的 Web 界面。
3、 模版渲染
-
1.在项目中创建模板的目录,注意模板的目录名称为
templates
–目录名固定不变,并且该目录需要和“app.py”文件同级。 -
- 在
templates
目录中创建 HTML 文件,创建完成之后,目录结构为:
- 在
./
├── xx.py
└── templates
└── hogwarts.html
- 3.视图函数的返回使用
render_template(html文件名)
渲染对应的 HTML 文件;
4、模板语法
利用 Jinja2 模板引擎,可以将一部分的程序逻辑放到模板中处理。也就是可以在模板中使用 python 语句和表达式来操作数据的输出。但需要注意的是,Jinja2 并不支持所有 python 语法。并且出于效率和代码组织等方面的考虑,应该适度使用模板,仅把和输出控制有关的逻辑操作放到模板中。
Jinja2 允许在模板中使用大部分 python 对象,比如字符串、列表、字典、元组、整型、浮点型、布尔值。它支持基本的运算符号(+、-、*、/
等)、比较符号(比如==、!=
等)、逻辑符号(and、or、not 和括号)以及 in、is、None 和布尔值(True、False)。
-
变量代码块
{{ }}
: 主要用于变量的内容显示。 -
控制代码块
{% %}
: 主要用于涉及到与逻辑相关的代码块展示。
(1)传递数据
在调用 render_template(“html文件名”,关键字传参)
方法的时候,可以使用关键字传参的方式,给模板数据传递数据。
variable.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{title}}</title>
</head>
<body>
<h1>模板语法-传递数据</h1>
<h2>{{content}}</h2>
</body>
</html>
templates_variable.py
from flask import Flask, Blueprint, render_template
# 初始化服务对象
app = Flask(__name__)
# 初始化蓝图对象
template_router = Blueprint(name="template",import_name=__name__,url_prefix="/template")
# 定义路由
@template_router.route("/variable/")
def variable():
# 返回模板文件,并且将值传递给模板文件
return render_template("variable.html",title="模板语法",content="通过render_template()方法将值传递到模板文件~~")
if __name__ == '__main__':
# 注册蓝图
app.register_blueprint(template_router)
# debug模式启动服务
app.run(port=5000,debug=True)
结果:
(2)判断语句
- Flask 中的 Jinja2 模板提供了多种控制结构,通过这些控制结构可以改变模板的渲染过程;
- 判断语句语法:
<!-- if 条件判断-->
{% if 条件表达式 %}
.......
{% elif 条件表达式 %}
.......
{% else %}
.......
{% endif %}
- 模板中获取变量属性语法:Jinja2 支持使用
.
获取变量的属性,比如 person 字典中的 gender 键值通过.
获取,即person.gender
,在效果上等同于person['gender']
。
if_endif.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{title}}</title>
</head>
<body>
<p>
游戏开始!
{% if person.money < 10000000 and person.gender == "男" %}
<!-- 如果金额大于100并且是男性 -->
对不起!{{person.name}}先生,你啥也不是!
{% elif person.money > 10000000 and person.gender == "男" %}
{{person.name}}老板,你那里还招人不!
{% elif person.money <10000 and person.gender == "女"%}
没事儿{{person.name}}老妹儿,哥每个月给你10万零花钱,那你第一个月资产就有{{person.money + 100000}}元!
{% else %}
不出意外你应该是个富婆!{{other}}
<!-- 不要忘了if的结束语句,endif -->
{% endif %}
</p>
</body>
</html>
template_if_endif.py
from flask import Flask, render_template
from L2.templates_usage.templates_variable import template_router
app = Flask(__name__)
@template_router.route("/if_endif/")
def if_endif():
person_data = {
"name":"张三",
"money":10000,
"gender":"男"
}
return render_template("if_endif.html",person=person_data,title="模板语法",other="其实你长得很漂亮!")
if __name__ == '__main__':
app.register_blueprint(template_router)
app.run(debug=True)
结果:
(3)循环语句
- 和 python 里一样,for 语句用来迭代一个序列。
- 语法:
<!-- for 循环 -->
{% for row in list_or_dict %}
{{ row }}
{% endfor %}
for_endfor.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p> {{content}}</p>
<p>
遍历列表:大于100的数字如下<br />
{% for money in money_list %}
{% if money > 100 %}
{{money}}<br />
{% endif %}
{% endfor %}
</p>
<p>
遍历字典:<br />
{% for p in person %}
{% if p.money > 100000 %}
{{p.naem}},你的小日子还过得去!<br />
{% else %}
{{p.naem}},你啥也不是!<br />
{% endif %}
{% endfor %}
</p>
</body>
</html>
template_for_endfor.py
from flask import Flask, render_template
from L2.templates_usage.templates_variable import template_router
app = Flask(__name__)
@template_router.route("/for_endfor/")
def for_endfor():
# 列表
money = [50,90,100,102,180,75,900]
# 字典
person_dict = [
{
"naem": "张三",
"money": 100000000
},
{
"naem": "李四",
"money": 1000
},
{
"naem": "王五",
"money": 1000
},
]
return render_template("for_endfor.html",money_list=money,person=person_dict,content="循环语句")
if __name__ == '__main__':
app.register_blueprint(template_router)
app.run(debug=True)
(4)继承语句
-
模板继承允许创建一个基础的骨架模板, 这个模板包含网站的通用元素,并且定义子模板可以重载的区域。
-
一般在前端页面中有很多页面中有很多相同的地方,比如页面顶部的导航栏,底部的页脚等部分,这时候如果每一个页面都重写一遍,会很麻烦,而且也没必要。
-
这时候就可以做一个模板,叫做父模板,里面放上页面中相同的部分,不同的部分先使用其他东西占位。然后在不同的页面中,继承这个父模板,不同的部分填充不同的内容。
-
模板继承和类的继承含义是一样的,主要是为了提高代码重用,减轻开发工作量。
A、定义父模板语法:
标签 block 用于在父模板中预留区域,留给子模板填充差异性的内容,名字不能相同。父模板中也可以使用上下文中传递过来的数据—也就是使用占位符{{}}
占位的变量数据传递。
<!-- 父模版中定义不同的部分,子类继承之后可以进行私人定制 -->
{% block 变量 %}
{% endblock %}
B、定义子模块语法:
<!-- 继承父模板 -->
{% extends '父模版' %}
<!-- 将父模板中预留的不同部分进行私人定制 -->
{% block 变量 %}
{% endblock %}
parent.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- 预留不同部分-title -->
<title>{% block title %}{% endblock %}</title>
</head>
<body>
<!-- 预留不同部分-content -->
<div id="content">{% block content %}{% endblock %}</div>
<div id="footer">
<!-- 预留不同部分-footer -->
{% block footer %} © Copyright 2023 by
<a href="https://ceshiren.com">测试人社区</a>. {% endblock %}
</div>
</body>
</html>
<script type="text/javascript"></script>
son.html
<!--继承父模板-->
{% extends "parent.html" %}
<!--将父模板中预留的不同部分进行私人定制-->
{% block title%} 这是son私人定制title{% endblock %}
{% block content %}
<!--私人定制的时候使用变量传值已经逻辑语句-->
<h1>son私人定制content={{content}}</h1>
<button>私人地址按钮</button>
<p>
{% if money > 100 %}
money={{money}}大于100
{% endif %}
</p>
{% endblock %}
template_extends.py
from flask import Flask, render_template
from L2.templates_usage.templates_variable import template_router
app = Flask(__name__)
@template_router.route("/parent/")
def template_parent():
# 因为父模板中是定义子类可继承类容,并没有使用{{}}声明变量,所以传值过去会报错
return render_template("parent.html",title="父模板title",content="父模板content")
@template_router.route("/son/")
def template_son():
return render_template("son.html",content="我是私人订制中使用变量传递值过去",money=1000)
if __name__ == '__main__':
app.register_blueprint(template_router)
app.run(debug=True)
(5)模块导入
- 模板导入就是将另一个模板加载到当前模板中,直接渲染;
- 语法:
-
1、导入单个html文件,需要单个字符串文件名:
{% include '文件名' %}
; -
2、也可以导入一个模板列表,程序会按照顺序依次寻找模板文件,第一个被找到的模板将被加载和渲染,后续的忽略。
-
导入多个html文件,需要使用列表的形式:
{% include ['footer.html','bottom.html','top.html'] %}
; - 此时,如果都没找到,程序会报错,如果想要忽略报错,可以在后面添加 ignore missing,用来忽略 include 语句:
{% include ['footer.html','bottom.html','end.html'] ignore missing %}
;
-
导入多个html文件,需要使用列表的形式:
-
parent.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- 预留不同部分-title -->
<title>{% block title %}{% endblock %}</title>
</head>
<body>
<!-- 预留不同部分-content -->
<div id="content">{% block content %}{% endblock %}</div>
<div id="footer">
<!-- 预留不同部分-footer -->
{% block footer %} © Copyright 2023 by
<a href="https://ceshiren.com">测试人社区</a>. {% endblock %}
</div>
</body>
</html>
<script type="text/javascript"></script>
three.html
<a>three首页</a>
<a>three关于</a>
son_include_list.html
<!--继承父模板-->
{% extends "parent.html" %}
<!--将父模板中预留的不同部分进行私人定制-->
{% block title%} 这是son私人定制title{% endblock %}
<!--导入模板,多个模板-->
{% include ["fore.html","three.html"] ignore missing %}
{% block content %}
<!--私人定制的时候使用变量传值已经逻辑语句-->
<h1>son私人定制content={{content}}</h1>
<button>私人地址按钮</button>
<p>
{% if money > 100 %}
money={{money}}大于100
{% endif %}
</p>
{% endblock %}
son_include_single.html
<!--继承父模板-->
{% extends "parent.html" %}
<!--将父模板中预留的不同部分进行私人定制-->
{% block title%} 这是son私人定制title{% endblock %}
<!--导入模板,单个模板-->
{% include "three.html" %}
{% block content %}
<!--私人定制的时候使用变量传值已经逻辑语句-->
<h1>son私人定制content={{content}}</h1>
<button>私人地址按钮</button>
<p>
{% if money > 100 %}
money={{money}}大于100
{% endif %}
</p>
{% endblock %}
templates_include.py
from flask import Flask, render_template
from L2.templates_usage.templates_variable import template_router
app = Flask(__name__)
@template_router.route("/single/")
def template_include_single():
return render_template("son_include_single.html",content="导入单个文件",money=200)
@template_router.route("/list/")
def template_include_list():
return render_template("son_include_list.html",content="导入模板列表",money=300)
if __name__ == '__main__':
app.register_blueprint(template_router)
app.run(debug=True)
八、路由跳转
1、应用场景
-
场景:
- 假设在新增信息页面,完成新增操作之后需要跳转到展示页面。
- 展示页面是个动态页面,它的逻辑是,如果有参数,则展示相关参数的结果,如果没有参数,则展示所有的信息。
-
解决:
- 使用
url_for()
生成 url ,再使用redirect()
方法完成路径的重定向。实现当完成添加的逻辑后,跳转展示的逻辑。
- 使用
2、 使用 url_for()
函数的优点
相比使用相对路径,url_for()
函数有以下几个优点:
-
比直接使用 URL 的描述性更好。
-
可以只在一个地方改变 URL ,而不用到处乱找,可维护性好。
-
url_for()
函数生成的 URL 创建会处理特殊字符的转义和 Unicode 数据,比较直观。 -
url_for()
函数生成的路径总是绝对路径,可以避免使用相对路径导致的一些问题。 -
如果应用是放在 URL 根路径之外的地方(如在
/myapplication
中,不在/
中),url_for()
也可以妥善处理。
3、 url_for()
的用法
-
url_for()
可以根据视图函数名生成视图的路由地址。 -
语法:
url_for(视图函数名, *)
- 视图地址:str 类型,可以是视图函数名,也可以是由蓝图和视图函数组成的地址。
-
*
:允许传递参数。
-
用法:
- 用法一:
url_for(视图函数名)
- 用法二:
url_for("蓝图名.视图函数名")
- 用法一:
(1)用法一:url_for(视图函数名)
使用 url_for(视图函数名)
即可返回对应视图函数的 url。
"""
url_for(视图名),获取到该视图的path
"""
from flask import Flask, url_for
app = Flask(__name__)
@app.route("/login/")
def login():
# url_for(视图名),获取到该视图的path
return url_for("account")
@app.route("/account/")
def account():
return "我的账户页面"
if __name__ == '__main__':
app.run(debug=True)
(2)用法二:url_for("蓝图名.视图函数名")
使用 url_for("蓝图名.视图函数名")
即可返回对应视图函数的 url。
"""
url_for("蓝图名.视图名"),获取对应视图的path
"""
from flask import Flask, Blueprint, url_for
app = Flask(__name__)
# 定义两个视图
login_router = Blueprint(name="login_module",import_name=__name__,url_prefix='/login')
cart_router = Blueprint(name="cart_module",import_name=__name__,url_prefix="/cart")
# 登录视图
@login_router.route("")
def login():
return url_for("cart_module.cart")
@cart_router.route("")
def cart():
return "进入购物车"
if __name__ == '__main__':
app.register_blueprint(login_router)
app.register_blueprint(cart_router)
app.run(debug=True)
4、 路由跳转(重定向)
重定向(Redirect)就是通过各种方法将各种网络请求重新定个方向转到其它位置。可以在生成视图的路由地址后,使用 redirect()
方法实现路由的跳转。
-
redirect()
使得一个路由地址 A 与另一个路由地址 B 联系起来,执行 A 的时候会跳转执行 B。 -
语法:
flask.redirect(location, code=302, Response=None)
- location 是一个链接地址,可以使用
url_for()
函数得到,也可以是静态文件地址。 - code HTTP 协议中的一个状态码。
- Response 是一个响应类。
- location 是一个链接地址,可以使用
-
用法:
- 用法一:
redirect(url地址)
- 用法二:
redirect(路由地址)
(可以结合 url_for 使用)
- 用法一:
(1)用法一:redirect(url地址)
"""
redirct(url)跳转到给定的url
"""
"""
redirct(url)跳转到给定的url
"""
from flask import Flask, request, redirect
app = Flask(__name__)
@app.route("/login/")
def login():
args = request.args
username = args.get("username")
password = args.get("password")
print(type(username))
print(type(password))
# 判断是否登录成功-如果登录成功就进行跳转
if username == "tangbohu" and password == "9527":
return redirect("https:www.baidu.com")
else:
return f"username={username}-类型{type(username)},password={password}-类型{type(password)},用户名或密码错误!"
if __name__ == '__main__':
app.run(debug=True)
(2) 用法二:redirect(路由地址)
(可以结合 url_for 使用)
"""
redirect(路由),跳转到对应的路由视图
"""
from flask import Flask, Blueprint, request, redirect, url_for
app = Flask(__name__)
# 初始化两个蓝图
login_router = Blueprint(name="login_module",import_name=__name__,url_prefix="/login")
cart_route = Blueprint(name="cart_module",import_name=__name__,url_prefix="/cart")
@login_router.route("")
def login():
username = request.args.get("username")
password = request.args.get("password")
if username == "tangbohu" and password == "9527":
# 如果登录成功,则跳转到购物车列表页面
return redirect(url_for("cart_module.cart_list"))
else:
return "用户名或密码错误!"
@cart_route.route("/list/")
def cart_list():
cart_list = {
"code":0,
"errmsg":"success",
"cart_list":["鞋子","袜子","鞋垫"]
}
return cart_list
if __name__ == '__main__':
app.register_blueprint(login_router)
app.register_blueprint(cart_route)
app.run(debug=True)
学生管理系统案例
stubp.py
# 蓝图管理
from flask import Blueprint
# 获取蓝图对象并添加路由前缀
stu_blueprint = Blueprint(name='student',import_name=__name__,url_prefix='/student')
server.py
"""
需求:学生管理系统,支撑增删改查操作及相关页面展示,修改和删除使用学号
分析:
页面:
1、首页
2、添加页面
3、修改页面
接口:
1、获取首页接口
2、查询所有学生列表接口
3、获取添加学生页面接口
4、添加学生接口,添加成功回到首页并展示学生列表
5、获取修改页面接口
6、通过学号修改学生信息接口,修改成功回到首页并展示学生列表
7、修改成功之后在修改界面需要回显修改之后的数据
8、通过学号删除学生信息接口
restful风格接口:
GET(SELECT):从服务器取出资源(一项或多项)。
POST(CREATE):在服务器新建一个资源。
PUT(UPDATE):在服务器更新资源(客户端提供完整资源数据)。--TODO
DELETE(DELETE):从服务器删除资源。
"""
# 服务
import json
from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import *
from studentsystem.stubp import stu_blueprint
# 获取服务对象
app = Flask(__name__)
# 数据库连接封装--Student和SQLAlchemy有关联关系,暂时还没想清楚怎么封装到不同的模块,先将就写在一个py文件中--TODO
class MyDB:
# 连接默认为none
sqlalchemy = None
def __init__(self, app: Flask, username, password, host, port, database):
# 如果连接不为None则创建连接
if self.sqlalchemy is None:
# 将服务和数据库建立映射关系
app.config["SQLALCHEMY_DATABASE_URI"] = f"mysql+pymysql://{username}:{password}@{host}:{port}/{database}"
self.sqlalchemy = SQLAlchemy(app)
# 将当前上下文推到栈顶
self.__context = app.app_context()
self.__context.push()
def insert(self, stu):
if self.sqlalchemy is not None:
try:
# 向数据库插入数据
self.sqlalchemy.session.add(stu)
# 提交事务
self.sqlalchemy.session.commit()
except Exception as e:
return e
finally:
# 关闭连接
self.sqlalchemy.session.close()
# 获取服务与数据库连接对象
username = "root"
password = "123456"
host = "127.0.0.1"
port = "3306"
database = "studentsystem"
mydb = MyDB(app=app,username=username,password=password,host=host,port=port,database=database)
# 因为获取的查询结果是Student对象,暂时还没搞定自定义对象的序列化问题,所以定义一个方法来讲对象转为json格式
def to_dict(obj):
if type(obj) is Student:
temp_dict = {
'sid':obj.sid,
'name':obj.name,
'age':obj.age,
'gender':obj.age
}
return temp_dict
if type(obj) is list:
temp_list = []
for o in obj:
temp_dict = {
'sid':o.sid,
'name':o.name,
'age':o.age,
'gender':o.age
}
temp_list.append(temp_dict)
return temp_list
# 创建学生表模型
class Student(mydb.sqlalchemy.Model):
# 设置表名
__tablename = 'student'
sid = Column(Integer,primary_key=True,autoincrement=True)
name = Column(String(20),unique=True)
age = Column(Integer,unique=True)
gender = Column(String(1))
# 解决:TypeError: Object of type Student is not JSON serializable -- TODO
# def __getstate__(self):
# return to_dict(self)
#
# def __setstate__(self, state):
# return state
# def __iter__(self):
# yield from {
# 'sid':self.sid,
# 'name':self.name,
# 'age':self.age,
# 'gender':self.gender
# }.items()
#
# def __str__(self):
# return json.dumps(dict(self), ensure_ascii=False)
def __repr__(self):
return "{"+f"sid:{self.sid},name:{self.name},age:{self.age},gender:{self.gender}"+"}"
# 创建表映射--如果表已经存在不会重新创建
mydb.sqlalchemy.create_all()
# 1、获取首页接口
@stu_blueprint.route('/') # 默认get请求
def index():
return render_template('index.html')
# 2、查询所有学生列表接口
@stu_blueprint.route('/list/')# 默认get请求
def list():
# 通过数据库查询获取所有学生信息并返回
stu_list = Student.query.all()
print(stu_list)
print(type(stu_list))
# 返回列表、字典类型的数据,default=to_dict是序列化规则,ensure_ascii=False是显示中文
return json.dumps(stu_list,default=to_dict,ensure_ascii=False)
# 3、获取添加学生页面接口
# 4、添加学生接口,添加成功回到首页并展示学生列表
@stu_blueprint.route('/add/',methods=['get','post'])
# 同时支持get和post请求,get请求获取添加页面,post请求添加数据
def add():
if request.method == 'GET':
# 获取添加页面
return render_template('add.html')
elif request.method == 'POST':
# 接收json格式数据,解析之后将数据保存到数据库,保存成功后返回到首页并展示所有学生数据
sid = request.json['sid']
name = request.json['name']
age = request.json['age']
gender = request.json['gender']
stu = Student(sid=sid,name=name,age=age,gender=gender)
# 向数据库插入数据
mydb.insert(stu)
return redirect(url_for('student.index'))
# return request.json
# 5、获取修改页面接口
# 6、通过学号修改学生信息接口,修改成功回到首页并展示学生列表
@stu_blueprint.route('/update/<sid>',methods=['get','post'])
def update(sid):
if request.method == 'GET':
# 获取修改页面
return render_template('update.html')
elif request.method == 'POST':
# 查询数据库,通过sid修改学生信息,成功之后返回到首页
try:
# 查询并更新
Student.query.filter_by(sid=sid).update(
{'name': request.json['name'], 'age': request.json['age'], 'gender': request.json['gender']})
# 提交事务
mydb.sqlalchemy.session.commit()
except Exception as e:
return e
finally:
# 关闭事务
mydb.sqlalchemy.session.close()
return redirect('student.index')
# 7、修改成功之后在修改界面需要回显修改之后的数据
@stu_blueprint.route('/updateresult/<sid>') # 默认get请求
def updateresult(sid):
# 通过学号查询数据之后返回
try:
result = Student.query.filter_by(sid=sid).first()
except Exception as e:
return e
return json.dumps(result,default=to_dict,ensure_ascii=False)
# 8、通过学号删除学生信息接口
@stu_blueprint.route('/delete/<sid>',methods=['delete'])
def delete(sid):
# 在数据库通过sid删除该学生信息,成功删除后返回首页并展示所有学生信息
try:
# 查询之后直接删除
Student.query.filter_by(sid=sid).delete()
# 提交事务
mydb.sqlalchemy.session.commit()
except Exception as e:
return e
finally:
# 关闭事务
mydb.sqlalchemy.session.close()
return redirect('student.index')
if __name__ == '__main__':
# 注册蓝图对象
app.register_blueprint(stu_blueprint)
# 启动服务
app.run(debug=True)
单元测试:
import requests
class TestServer:
def setup_class(self):
self.host = 'http://127.0.0.1:5000/student'
def test_list(self):
path = '/list'
response = requests.get(url=self.host + path)
print(response.json())
def test_add(self):
path = '/add/'
json_data = {'sid': 10, 'name': '芈月', 'age': 19, 'gender': "女"}
response = requests.post(url=self.host + path, json=json_data)
print(response.text)
def test_update(self):
path = '/update/10'
json_data = {'name': '杨玉环', 'age': 19, 'gender': "女"}
header = {'Content-Type': 'application/json;charset=utf-8'}
response = requests.post(url=self.host + path, json=json_data, headers=header)
print(response.text)
def test_updateresult(self):
path = '/updateresult/10'
response = requests.get(url=self.host + path)
print(response.json())
def test_delete(self):
path = '/delete/9'
response = requests.delete(url=self.host+path)
print(response.text)
九、静态文件
静态文件(static files)和模板概念相反,指的是内容不需要动态生成的文件,比如图片、CSS 文件和 JavaScript 脚本等。
在 Flask 中,需要创建一个 static 目录来保存静态文件,它应该和程序模块、templates 在同一目录层级。
1、 生成静态文件 URL
-
在 HTML 文件里,引入这些静态文件需要给出资源所在的 URL。为了更加灵活,这些文件的 URL 可以通过
url_for()
函数来生成。 -
对于静态文件,
url_for()
中需要传入的路由是 static,同时使用 **filename 参数来传入相对于 static 文件夹的文件路径。**假如在 static 文件夹的根目录下面放了一个 logo.jpg 文件,可以这样来获取图片- 花括号部分的调用会返回
/static/pic.jpg
。
- 花括号部分的调用会返回
<img src="{{ url_for('static', filename='pic.jpg') }}">
-
注意: 在 Python 脚本里,
url_for()
函数需要从 flask 包中导入,而在模板中则可以直接使用就可以,因为 Flask 把一些常用的函数和对象添加到了模板上下文里。
2、添加图标
图标(favourite icon) 是显示在标签页和书签栏的网站头像。需要准备一个 ICO、PNG 或 GIF 格式的图片,大小一般为 16×16、32×32、48×48 或 64×64 像素。把这个图片放到 static 目录下,然后在 HTML 模板里引入它。
static_icon.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>静态文件之icon</title>
<!-- 使用url_for()函数获取静态文件的url,两个参数,第一个固定static不变,第二个通过filename=静态文件名-->
<link rel="icon" href="{{url_for('static',filename='logo.png')}}" >
</head>
<body>
<h1>静态文件</h1>
</body>
</html>
static_icon.py
from flask import Flask, Blueprint, render_template
app = Flask(__name__)
static_router = Blueprint(name="static_usage",import_name=__name__,url_prefix="/static_usage")
@static_router.route("/icon/")
def static_pic():
return render_template("static_icon.html")
if __name__ == '__main__':
app.register_blueprint(static_router)
app.run(debug=True)
3、添加图片
-
可以在 static 目录下面创建一个子文件夹 images,把图片都放到这个文件夹里。
-
创建子文件夹并不是必须的,只是为了更好的组织同类文件。同样的,如果有多个 CSS 文件,也可以创建一个 css 文件夹来组织他们。
static_pic.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>静态文件之picture</title>
<link rel="icon" href="{{url_for('static',filename='logo.png')}}" >
</head>
<body>
<h1>
<img alt="animals" src="{{url_for('static',filename='images/animals.png')}}">
</h1>
<div>
<img alt="cat" src="{{url_for('static',filename='images/cat.png')}}">
<img alt="tiger" src="{{url_for('static',filename='images/tiger.png')}}">
</div>
</body>
</html>
static_pic.py
from flask import Flask, render_template
from L2.static_usage.static_icon import static_router
app = Flask(__name__)
@app.route("/picture/")
def static_picture():
return render_template("static_pic.html")
if __name__ == '__main__':
app.run(debug=True)
4、添加图片
第一步:准备css文件
style.css
body {
margin: auto;
max-width: 800px;
font-size: 14px;
font-family: Helvetica, Arial, sans-serif;
}
/* 霍格沃兹学院 */
.animals {
display: auto;
margin: 0 auto;
height: 300px;
}
/* 小图片 */
.pic {
display: auto;
margin: 0 auto;
width: 100px;
height: 100px;
}
第二步:在html文件中引入css文件,并且通过class关键字给定对应的style
static_css.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>静态文件之picture</title>
<link rel="icon" href="{{url_for('static',filename='logo.png')}}" >
<!-- 引入css文件-->
<link rel="stylesheet" href="{{url_for('static',filename='css/style.css')}}">
</head>
<body>
<h1>
<!-- 通过class关键字添加css属性-->
<img alt="animals" class="animals" src="{{url_for('static',filename='images/animals.png')}}">
</h1>
<div>
<!-- 通过class关键字添加css属性-->
<img alt="cat" class="pic" src="{{url_for('static',filename='images/cat.png')}}">
<img alt="tiger" class="pic" src="{{url_for('static',filename='images/tiger.png')}}">
</div>
</body>
</html>
第三步:创建服务返回html
static_css.py
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/css_style/")
def static_css():
return render_template("static_css.html")
if __name__ == '__main__':
app.run(debug=True)
十、路由以及跨越
1、什么是跨域
- 当一个请求 URL 的协议、域名、端口三者之间的任意一个与当前页面 URL 不同即为跨域,为了安全性,浏览器会阻止不同源的交互。
- 此时,浏览器的控制台会显示
Access-Control-Allow-Origin
相关内容;
2、为什么会有跨域限制-同源策略
浏览器中有一个同源策略,它是一个安全策略,用来限制源的交互。
- 同源策略:
- 是一种约定,它是浏览器核心也最基本的安全功能。
- 它会阻止一个域的 JS 脚本和另外一个域的内容进行交互。
- 如果缺少了同源策略,浏览器很容易受到 XSS、CSFR 等攻击。
- 所谓同源(即在同一个域)就是两个页面具有相同的协议(protocol)、主机(host)和端口号(port)。
kuayu_demo.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>跨域问题</title>
</head>
<body>
<div>
<button class="button" id="hello">点击出现跨域问题</button>
</div>
</body>
</html>
<script type="text/javascript">
var btnHello = document.querySelector("button#hello");
btnHello.onclick = () => {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
alert(xhr.responseText);
}
};
xhr.withCredentials = true;
<!-- 请求本地定义的hello接口--如果这个请求的协议、主机、端口有任意一个和访问这个页面的url的这3项不一致,那就会发生跨域问题 -->
xhr.open("get", "http://localhost:5000/hello", true);
xhr.setRequestHeader("Content-Type", "text");
xhr.send();
};
</script>
kuayu_demo.py
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/hello")
def hello():
return "hello world"
# 加载页面,在页面中请求上方的hello接口
@app.route("/cors_demo")
def kuayu_demo():
return render_template("cors_demo.html")
if __name__ == '__main__':
app.run(debug=True)
协议、端口虽然一致,但是主机不一致,出现跨越问题:
协议、主机、端口都一致,不会出现跨越问题:
3、 解决跨越问题
cors 是一个 w3c 的标准,全称为跨域资源共享,允许浏览器向跨域服务器发出请求。
-
安装 flask-cors插件:
pip install flask-cors
-
解决跨域问题分为两种:
- 针对全局所有的 API ,使用CORS类:
CORS(app, **kwargs)
- 针对特定的 API ,使用装饰器:
@cross_origin(*args, **kwargs)
,用于指定接口是否支持跨域请求以及允许的来源、方法等。 - 还可以结合蓝图使用,直接把蓝图对象传递给CORS函数;
- 针对全局所有的 API ,使用CORS类:
cors_demo.py
from flask import Flask, render_template
from flask_cors import CORS, cross_origin
app = Flask(__name__)
# 第一种方式:全局解决跨域问题
CORS(app,supports_credentials=True)
# 第二种方式:只有被装饰的接口才解决跨域问题---暂时无效,还搞清楚每个参数的意义和用法--TODO
# @cross_origin(origins='http://localhost:5000', methods=['GET', 'POST'])
# @cross_origin(supports_credentials=True)
@app.route("/hello")
def hello():
return "hello world"
# 加载页面,在页面中请求上方的hello接口
@app.route("/cors_demo")
def cors_demo():
return render_template("kuayu_demo.html")
if __name__ == '__main__':
app.run(debug=True)
十一、页面优化插件-Bootstrap
1、什么是Bootstrap
Bootstrap,来自 Twitter,是目前最受欢迎的前端开源框架。Bootstrap 是基于 HTML、CSS、JAVASCRIPT 的,它简洁灵活,使得 Web 开发更加快捷。
2、Flask中对应的插件
(1)插件Flask-Bootstrap
- 不支持Bootstrap 4;
- 安装:
pip install flask-bootstrap
;
(2)插件Bootstrap-Flask
- 支持Bootstrap 4;
- 安装:
# 需要卸载flask-bootstrap
$ pip uninstall flask-bootstrap bootstrap-flask -y
$ pip install bootstrap-flask
3、插件Flask-Bootstrap的简单使用
1、在安装成功之后,在 templates 目录创建一个 base.html,在 base.html 中继承bootstrap/base.html
模板:
{% extends "bootstrap/base.html" %}
2、在服务中需要将服务app
传给Bootstrap:
from flask_bootstrap import Bootstrap
# 将服务给到Bootstrap
Bootstrap(app)
3、在templates目录下新建需要的html文件,比如index.html,在该文件中导入base.html,重点是index.html中的内容咋处理,flask-bootstrap的具体用法是个啥?–TODO
{% include 'base.html' %}
<!-- 重点是index.html中的内容咋处理 -->