Python测开28期-偕行-flask后端开发框架

一、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 相关文档:

  • 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()

image

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"],表示该接口支持多种请求方式;

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类型

  1. 第一种是使用 jsonify() 方法,此方法支持,直接传入一个字典,也支持通过关键字参数传递。
  2. 第二种方法就是直接返回字典或者列表,在 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端口,可以根据需要修改端口;

2、dubug模式

(1)开启关闭调试模式

  • 方法:app.run(debug=True),默认是 production关闭,True为开启;

(2) 调试模式的作用

  1. 显示详细的错误信息:在调试模式下,当应用程序出现错误时,Flask 会显示详细的错误信息,包括错误堆栈跟踪。这对于定位和修复错误非常有帮助。

  2. 自动重新加载代码:调试模式下,如果你修改了应用程序的代码文件,Flask 会自动重新加载修改后的代码,而无需手动重启应用程序。这样可以加快开发的迭代速度,节省重启服务器的时间。

  3. 支持实时调试器:调试模式下,Flask 提供了一个实时调试器(Debugger),可以在浏览器中显示源代码和调试信息,并允许你在运行时进行断点调试和变量查看。

六、蓝图与视图

1、什么是蓝图

  • Flask 中的蓝图(Blueprint)是一种组织和管理应用程序路由和视图的机制。它允许开发者将相关功能的路由和视图进行分组,从而更好地组织项目结构和实现模块化开发。蓝图可以极大地简化大型应用并为扩展提供集中的注册入口。

  • Flask 可以通过蓝图来组织 URL 以及处理请求。如果使用蓝图,应用会在 Flask 层中进行管理,共享配置,通过注册按需改变应用对象。蓝图的缺点是一旦应用被创建后,只有销毁整个应用对象才能注销蓝图。

  • 一个项目可以具有多个蓝图但是一个蓝图并不是一个完整的应用,它不能独立于应用运行,而必须要注册到某一个应用中

2、应用场景及作用

应用场景:

  1. 项目复杂度增加,路由和视图函数增多。
  2. 路由需要结构化,模块化进行管理
    image

作用:

  • 蓝图是 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、 应用场景与价值

  1. 动态内容:Flask 模板支持将动态数据插入 HTML 页面,从而可以创建个性化和交互式的 Web 应用程序。

  2. 代码重用:模板允许开发人员在多个页面之间重用常见的 HTML 组件,减少冗余代码,提高可维护性。

  3. 一致的设计:通过使用模板,开发人员可以确保应用程序在设计和布局上保持一致,从而实现专业和统一的用户体验。

  4. 与其他技术的集成:Flask 模板可以轻松与其他前端技术(如 CSS 框架和 JavaScript 库)集成,实现现代化和视觉上吸引人的 Web 界面。

3、 模版渲染

  • 1.在项目中创建模板的目录,注意模板的目录名称为 templates目录名固定不变,并且该目录需要和“app.py”文件同级

    1. 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)

结果:

image

(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)

结果:
image

(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 %} &copy; 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 %}

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 %} &copy; 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 %}

image

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 %}

image

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() 函数有以下几个优点:

  1. 比直接使用 URL 的描述性更好。

  2. 可以只在一个地方改变 URL ,而不用到处乱找,可维护性好。

  3. url_for() 函数生成的 URL 创建会处理特殊字符的转义和 Unicode 数据,比较直观。

  4. url_for() 函数生成的路径总是绝对路径,可以避免使用相对路径导致的一些问题。

  5. 如果应用是放在 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)

image

(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)

image

4、 路由跳转(重定向)

重定向(Redirect)就是通过各种方法将各种网络请求重新定个方向转到其它位置。可以在生成视图的路由地址后,使用 redirect() 方法实现路由的跳转。

  • redirect() 使得一个路由地址 A 与另一个路由地址 B 联系起来,执行 A 的时候会跳转执行 B。

  • 语法:flask.redirect(location, code=302, Response=None)

    • location 是一个链接地址,可以使用 url_for() 函数得到,也可以是静态文件地址。
    • code HTTP 协议中的一个状态码。
    • Response 是一个响应类。
  • 用法:

    • 用法一: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)

image

学生管理系统案例

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)

image

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函数;
  • 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中的内容咋处理  -->