一、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中的内容咋处理  -->






