Python Flask

🍦 Python编写的Web应用程序框架,基于Werkzeug WSGI工具包和Jinja2模板引擎,Armin Ronacher带领Pocco团队开发。

1 微框架

  • 微框架
    • WSGI:Web服务器的网关接口,Web服务器和Web应用程序之间通用接口的规范。
    • Werkzeug:一个WSGI工具包,实现了请求、响应对象和实用函数,能在上面构建Web框架。
    • jinja2:Python的一个流行模板引擎,Web模板系统将模板与特定数据源组合以呈现动态网页。
    • 命令窗口下使用PIP进行安装:pip install Flask,测试是否安装成功,新建hello.py文件。
      • app.route(rule, options)
        • route()函数是一个装饰器,告诉应用程序哪个URL应该调用相关的函数。
        • rule表示与该函数的URL绑定,options是转发给基础rule对象的参数列表。
      • app.run(host, port, debug, options)
        • host:主机名,默认为127.0.0.1(localhost),设为0.0.0.0以使服务器在外部可用。
        • port:端口号,默认值为5000。
        • debug:默认false,设为true提供调试信息。
        • options:要转发到底层的Werkzeug服务器。
1
2
3
4
5
6
7
8
9
10
11
12
from flask import Flask                             # hello.py文件,导入Flask模块验证测试

app = Flask(__name__) # Flask构造函数使用当前模块__name的名称作为参数


@app.route("/") # Flask类的route()是一个装饰器,告诉程序哪个URL应调用相关函数
def hello_world(): # "/"URL与函数绑定
return "<center>Hello World!</center>"


if __name__ == "__main__": # Flask中文网:https://flask.net.cn/
app.run() # Flask类的run()方法是在本地开发服务器上运行应用程序

1-1 验证测试

  • 验证测试
    • 命令窗口进入hello.py文件对应的目录中,python hello.py执行命令。
    • 在浏览器的任务栏中输入“http://127.0.0.1:5000/”,回车进行查看。
1
2
3
4
5
6
7
8
9
10
python hello.py

* Serving Flask app "hello" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

127.0.0.1 - - [01/Oct/2023 17:07:14] "GET / HTTP/1.1" 200 -

1-2 调试模式

  • 调试模式
    • 当应用程序处于开发状态时,如果代码进行了更改,则需要每次都重启更新,操作十分不便。
    • 为了避免频繁手动重启更新,可以开启调试模式,如果代码更改了,服务器将自行重新加载。
    • 调试模式还提供了一个有用的调试器,用于跟踪应用程序中的错误,有两种语法格式的书写。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from flask import Flask

app = Flask(__name__)


@app.route("/")
def hello_world(): # 浏览器访问[127.0.0.1:5000]时,显示"Hello World!"
return "<center>Hello World!</center>"


if __name__ == "__main__":
app.debug = True # 调试模式下,若代码更改,服务器将自行重新加载
app.run() # 还提供了一个有用的调试器来跟踪应用程序中的错误
# app.run(debug=True) # 或将run与debug写成一个函数带参形式

1-3 路由技术

  • 路由技术
    • route()装饰器用于将URL与函数进行绑定,可直接访问想访问的页面,不用再从主页开始导航了。
    • 若URL“/hello”规则绑定到hello_world()函数,则浏览器访问“http://localhost:5000/hello”。
    • add_url_rule()函数也可以用于将URL与函数进行绑定。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from flask import Flask

app = Flask(__name__)


@app.route("/hello") # 访问[http://127.0.0.1:5000/hello]
def hello_world():
return "<center>Hello World!</center>"


app.add_url_rule( # 访问[http://127.0.0.1:5000/],可替代route()装饰器
"/", "hello", hello_world
)

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

(1) URL构建

✧ 字符串变量

1
2
3
4
5
6
7
8
9
10
11
12
from flask import Flask

app = Flask(__name__)


@app.route("/hello/<name>")
def hello_world(name): # 访问[http://127.0.0.1:5000/hello/Aha]
return "<center>Hello %s!</center>" % name


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

✧ 整数与浮点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from flask import Flask

app = Flask(__name__)


@app.route("/blog/<int:postID>")
def show_blog(postID): # 访问[http://localhost:5000/blog/27]
return "<center>Blog Number %d.</center>" % postID


@app.route("/rev/<float:revNo>")
def revision(revNo): # 访问[http://localhost:5000/rev/2.7]
return "<center>Revision Number %f.</center>" % revNo


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

✧ 目录分隔符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from flask import Flask

app = Flask(__name__)


@app.route("/python") # 访问[http://127.0.0.1:5000/python/],Not Found
def hello_python(): # 访问[http://127.0.0.1:5000/python]
return "<center>Hello Python.</center>"


@app.route("/golang/")
def hello_golang(): # 访问[http://127.0.0.1:5000/golang]
return "<center>Hello Golang.</center>" # 或访问[http://127.0.0.1:5000/golang/]


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

✧ url_for()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from flask import Flask, redirect, url_for

app = Flask(__name__)


@app.route("/admin") # 访问[http://127.0.0.1:5000/admin]
def hello_admin():
return "<center>Hello Admin.</center>"


@app.route("/guest/<guest>") # 访问[http://127.0.0.1:5000/guest/Admin]
def hello_guest(guest):
return "<center>Hello %s as Guest.</center>" % guest


@app.route("/user/<name>") # 访问[http://127.0.0.1:5000/user/admin]
def hello_user(name): # 接收来自URL参数的值,检查接收的参数是否与admin匹配
if name == "admin": # 匹配则重定向到hello_admin()函数
return redirect(url_for("hello_admin"))
else: # 否则重定向到hello_guest()函数
return redirect(url_for("hello_guest", guest=name))


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

(2) 请求方法

  • HEAD:和GET方法相同,但是没有响应体。
  • GET:以未加密的形式将数据发送到服务器。
  • PUT:用上传的内容替换目标资源的所有当前表示。
  • DELETE:删除由URL给出目标资源的所有当前表示。
  • POST:用于将HTML表单数据发送到服务器,接收的数据不由服务器缓存。
  • Flask路由默认响应GET请求,通过route()装饰器提供的方法参数更改请求。
    • 命令窗口下执行Python脚本开启开发服务器,浏览器打开post.html,输入test后提交。
    • 此时浏览器跳转“http://localhost:5000/success/test”,页面显示“Welcom test.”。

✧ post.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from flask import Flask, redirect, url_for, request, render_template

app = Flask(__name__)


@app.route("/") # 浏览器打开post.html,输入test后提交
def post():
return render_template("post.html")


@app.route("/success/<name>")
def success(name): # 此时浏览器跳转[http://localhost:5000/success/test]
return "<center>Welcome %s.</center>" % name


@app.route("/login", methods=["POST", "GET"])
def login():
if request.method == "POST":
print(1)
user = request.form["name"]
return redirect(url_for("success", name=user))
else:
print(2) # args是包含表单参数对及其对应值对的列表字典对象
user = request.args.get("name")
return redirect(url_for("success", name=user))


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

✧ post.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>请求方法</title>
</head>
<body style="text-align:center;">
<form action="http://localhost:5000/login" method="POST">
<p>输入姓名</p>
<p><input type="text" name="name" /></p>
<p><input type="submit" value="提交" /></p>
</form>
</body>
</html>

1-4 模板引擎

  • 模板引擎
    • 视图函数的主要作用是生成请求的响应,这是最简单的请求,Flask使用Jinja2这个模板引擎来渲染模板。
    • 视图函数的两个作用是处理业务逻辑,以及返回响应内容,模板则是获取到视图函数的数据结果进行展示。
    • 项目下创建templates文件夹,用于存放所有的模板文件,并且在templates目录下创建mould.html模板文件。
1
2
3
4
5
<!-- 项目目录结构 -->
- mould.py
- templates
- mould.html
<!-- 命令窗口下执行"python mould.py",访问[http://127.0.0.1:5000] -->

(1) mould.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from flask import Flask, render_template

app = Flask(__name__)


@app.route("/") # 访问[http://127.0.0.1:5000]
def index():
my_int = 27 # 往mould.html模板中传入的数据
my_str = "Hello Flask."
my_dict = {"name": "Lucy", "age": 23}
my_array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
return render_template("mould.html",
my_int=my_int,
my_str=my_str,
my_dict=my_dict,
my_array=my_array
)


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

(2) mould.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>模板引擎</title>
</head>
<body style="text-align:center;">
<h3>模板HTML内容</h3>
<br />{{ my_int }}
<br />{{ my_str }}
<br />{{ my_dict.name }}
<br />{{ my_dict }}
<br />{{ my_array }}
</body>
</html>

1-5 静态文件

  • 静态文件
    • Web应用程序通常需要静态文件,例如:JavaScript或CSS文件。
    • 一般由应用程序的static文件夹提供,用于生成静态文件的URL。
1
2
3
4
5
6
7
<!-- 项目目录结构 -->
- static.py
- static
- static.js
- templates
- static.html
<!-- 命令窗口下执行"python static.py",访问[http://127.0.0.1:5000] -->

(1) static.js

1
2
3
function sayHello() {
alert("Hello World!")
}

(2) static.py

1
2
3
4
5
6
7
8
9
10
11
12
from flask import Flask, render_template

app = Flask(__name__)


@app.route("/") # 访问[http://127.0.0.1:5000]
def get_static():
return render_template("static.html")


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

(3) static.html

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>静态文件</title>
<script type="text/javascript" src="{{ url_for('static', filename='static.js') }}">
</script>
</head>
<body style="text-align:center;">
<input type="button" onclick="sayHello()" value="你好呀!" />
</body>
</html>

2 核心内容

  • 核心内容
    • 来自客户端网页的数据作为全局请求对象发送到服务器,为处理请求数据,应从Flask模块导入。
    • 请求对象的属性
      • args(解析查询字符串的内容,是问号之后URL的一部分)。
      • files(与上传文件有关的数据)、cookies(保存Cookie名称和值的字典对象)。
      • method(当前请求方法)、form(字典对象,包含表单参数及其值的键值对)。

2-1 表单展示

1
2
3
4
5
6
<!-- 项目目录结构 -->
- formshow.py
- templates
- stufill.html
- stushow.html
<!-- 命令窗口下执行"python formshow.py",访问[http://127.0.0.1:5000] -->

(1) formshow.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from flask import Flask, render_template, request

app = Flask(__name__)


@app.route("/") # "/"URL呈现表单网页stufill.html
def stufill():
return render_template("stufill.html")


@app.route("/stushow", methods=["POST", "GET"])
def stushow(): # 填入的数据将发布到触发stushow()的"/stushow"URL中
if request.method == "POST": # 函数收集request.form中的表单数据并将其发送给stushow.html
stushow = request.form # 以字典对象形式收集并将其转发到stushow.html,呈现在网页上
return render_template("stushow.html", result=stushow)


if __name__ == "__main__":
app.run(debug=True) # 访问[http://localhost:5000],输入表单数据提交

(2) stufill.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>数据填写</title>
</head>
<body style="text-align:center;">
<form action="http://localhost:5000/stushow" method="POST">
<p>姓名 <input type = "text" name = "姓名" /></p>
<p>语文 <input type = "text" name = "语文" /></p>
<p>数学 <input type = "text" name = "数学" /></p>
<p>英语 <input type = "text" name = "英语" /></p>
<p><input type = "submit" value = "提交" /></p>
</form>
</body>
</html>

(3) stushow.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>表单展示</title>
<style>
body {text-align: center;} table {margin: auto;}
</style>
</head>
<body>
<table border="1" cellspacing="0">
{% for key, value in result.items() %}
<tr>
<th>{{ key }}</th>
<td>{{ value }}</td>
</tr>
{% endfor %}
</table>
</body>
</html>

2-2 Cookies

  • Cookies
    • 以文本形式存储于客户端,记录与跟踪用户数据,获得更好的访问者体验和网站统计信息,处理步骤如下。
    • 设置cookie:默认是临时cookie,浏览器关闭立即失效,通过max_age设置有效期,单位是秒。
    • 获取cookie:通过request.cookies的方式获取,返回的是一个字典,可以获取字典里的相应值。
    • 删除cookie:这里删除只是让cookie过期,而非直接删除,通过delete_cookie()方式进行删除。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from flask import Flask, make_response, request

app = Flask(__name__)


@app.route("/set_cookies") # [127.0.0.1:5000/set_cookies],F12查看Set-Cookie的value值
def set_cookies():
resp = make_response("<center>success</center>")
resp.set_cookie("flask", "flask", max_age=3600)
return resp


@app.route("/get_cookies") # [http://127.0.0.1:5000/get_cookies],页面输出cookies信息
def get_cookies():
cookie = request.cookies.get("flask")
return "<center>" + cookie + "</center>"


@app.route("/delete_cookies") # [http://127.0.0.1:5000/delete_cookies],cookies已删(过期)
def delete_cookies():
resp = make_response("<center>del success</center>")
resp.delete_cookie("flask")
return resp


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

2-3 会话工作

  • 会话工作
    • session,客户端登录到服务器并注销服务器的时间间隔,在会话中保存的数据会存储在服务器的临时目录中。
    • 为每个客户端的会话分配会话ID,会话数据存储在cookie的顶部,服务器以加密方式对其进行签名。
    • Flask需要定义的secret_key进行加密,session也是一个字典对象,包含会话变量和关联值的键值对。
      • Session['username'] = 'admin':设置username会话变量。
      • session.pop('username', None):释放username会话变量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from flask import Flask, session, redirect, url_for, request

app = Flask(__name__) # [http://127.0.0.1:5000],确保设置应用的secret_key
app.secret_key = "flask session"


@app.route("/") # 提示用户登录,未设置会话变量username
def index():
if "username" in session:
username = session["username"]
return "<center>登录用户名:" + username + "," + \
"<b><a href='/logout'>点击这里退出</a></b>" + "。</center>"
return "<center>您暂未登录,<a href='/login'></b>" + "点击这里登录</b></a>" + "。</center>"


@app.route("/login", methods=["GET", "POST"])
def login(): # login()视图函数,通过GET调用,打开登录表单
if request.method == "POST": # 此时会话变量已设置,应用重定向到"/",会话变量username被找到
session["username"] = request.form["username"]
return redirect(url_for("index"))
return """
<form action="" method="POST" style="text-align:center;">
<p><input type="text" name="username" /></p>
<p><input type="submit" value="登录" /></p>
</form>
"""


@app.route("/logout") # logout()视图函数
def logout(): # 弹出username会话变量,"/"URL再次显示开始页面
session.pop("username", None)
return redirect(url_for("index"))


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

2-4 Redirect

  • Redirect
    • 函数调用时返回一个响应对象,并将用户重定向到具有指定状态代码的另一目标位置。
    • redirect()重定向函数原型:Flask.redirect(location, statuscode, response)
    • location(重定向响应的URL)、statuscode(发送到浏览器标头,默认302)、response(用于实例化响应)。
    • 状态代码标准化
      • HTTP_300_MULTIPLE_CHOICES、HTTP_301_MOVED_PERMANENTLY。
      • HTTP_302_FOUND、HTTP_303_SEE_OTHER、HTTP_304_NOT_MODIFIED。
      • HTTP_305_USE_PROXY、HTTP_306_RESERVED、HTTP_307_TEMPORARY_REDIRECT。
    • 示例:执行应用程序,访问“http://127.0.0.1:5000/”。
      • 输入用户名guest,F12查看,点击登录,返回“状态代码: 302 FOUND”。
      • 输入用户名admin,点击登录,跳转至“http://127.0.0.1:5000/success”。
1
2
3
4
5
<!-- 项目目录结构 -->
- redirect.py
- templates
- redirect.html
<!-- 命令窗口下执行"python redirect.py",访问[http://127.0.0.1:5000] -->

(1) redirect.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from flask import Flask, redirect, url_for, render_template, request

app = Flask(__name__)


@app.route("/") # 访问[http://127.0.0.1:5000]
def index():
return render_template("redirect.html")


@app.route("/login", methods=["POST", "GET"])
def login(): # redirect()函数用于在登录尝试失败时再次显示登录页面
if request.method == "POST" and request.form["username"] == "admin":
return redirect(url_for("success"))
return redirect(url_for("index"))


@app.route("/success") # 输入admin,跳转到[http://127.0.0.1:5000/success]
def success(): # 输入guest,F12查看返回"302 FOUND"
return "<center>登录成功!</center>"


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

(2) redirect.html

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>重定向</title>
</head>
<body style="text-align:center;">
<form action="/login" method="POST">
<p>用户名:<input type="text" name="username" /></p>
<p><input type="submit" value="登录" /></p>
</form>
</body>
</html>

2-5 错误代码

  • 错误代码
    • Flask类具有带错误代码的abort()函数:Flask.abort(code)
    • 400(错误请求)、401(未授权)、403(Forbidden资源不可用)、404(Not Found网页不存在)。
    • 406(无法使用请求的内容特性响应请求的网页)、415(不支持的媒体类型)、429(请求过多)。
    • 更改redirect.py代码,如下所示,执行程序,访问“http://127.0.0.1:5000/”。
      • 输入用户名guest,F12查看,点击登录,返回“状态代码: 401 UNAUTHORIZED”。
      • 输入用户名admin,点击登录,跳转“http://127.0.0.1:5000/success”,登录成功。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from flask import Flask, redirect, url_for, render_template, request, abort

app = Flask(__name__)


@app.route("/") # 访问[http://127.0.0.1:5000]
def index():
return render_template("redirect.html") # redirect.html代码内容不变


@app.route("/login", methods=["POST", "GET"])
def login(): # 输入admin,跳转到[http://127.0.0.1:5000/success]
if request.method == "POST":
if request.form["username"] == "admin":
return redirect(url_for("success"))
else:
abort(401)
else: # 输入guest,F12查看返回"401 UNAUTHORIZED"
return redirect(url_for("index"))


@app.route("/success")
def success():
return "<center>登录成功!</center>"


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

2-6 消息闪现

  • 消息闪现
    • Flask提供了一个方法来使用闪现系统向用户反馈信息,可视图中创建消息,并在名为next的视图函数中呈现。
    • 在一个请求结束时记录一个信息,并且在下次(且仅在下一次)求时访问,通常与布局模板结合使用以公开信息。
    • flash(message, category)
      • 该函数会将消息传递给下一个请求,当前请求通常是一个模板。
      • message(要闪现的实际消息)、category(error、info或warning,可选)。
    • get_flashed_messages(with_categories, category_filter):会话中删除消息。
    • 如果接收到的消息具有类别,则第一个参数是元组,第二个参数仅用于显示特定消息。
    • 示例:执行程序,访问“http://127.0.0.1:5000/”,点击登录跳转到登录页面。
      • 用户名admin,密码1235,提交,显示“错误:无效用户名或密码,请重试!”。
      • 重新输入用户名admin,密码admin,提交,跳回登录页面并显示“登录成功!”。
1
2
3
4
5
6
<!-- 项目目录结构 -->
- flash.py
- templates
- flashmess.html
- flashlogin.html
<!-- 命令窗口下执行"python flash.py",访问[http://127.0.0.1:5000] -->

(1) 接收消息

1
2
3
4
5
6
7
8
<!-- 闪现在模板中接收消息 -->
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
{{ message }}
{% endfor %}
{% endif %}
{% endwith %}

(2) flash.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from flask import Flask, flash, redirect, render_template, request, url_for

app = Flask(__name__)
app.secret_key = "flask flash"


@app.route("/") # 访问[http://127.0.0.1:5000]
def get_flash(): # "/"URL显示登录页面的链接,没有消息闪现
return render_template("flashmess.html")


@app.route("/flashlogin", methods=["GET", "POST"])
def flash_login(): # 显示登录表单,由"/"URL链接引导用户到"/flashlogin"
error = None
if request.method == "POST": # 视图函数验证用户名和密码,并闪现登录成功消息或创建error变量
if request.form["username"] != "admin" or request.form["password"] != "admin":
error = "无效用户名或密码,请重试!"
else: # 输入用户名admin,密码admin,则登录成功,否则失败提示
flash("登录成功!")
return redirect(url_for("get_flash"))
return render_template("flashlogin.html", error=error)


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

(3) flashmess.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>消息闪现</title>
<style>
body {text-align: center;}
</style>
</head>
<body>
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<p>{{ message }}</p>
{% endfor %}
{% endif %}
{% endwith %}
<h3>欢迎!<a href = "{{ url_for('flash_login') }}">点击这里进行登录</a></h3>
</body>
</html>

(4) flashlogin.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
<style>
body {text-align: center;} table {margin: auto;}
</style>
</head>
<body>
<form method="POST" action="http://localhost:5000/flashlogin"><br>
<table>
<tr>
<td>用户名</td>
<td><input type="username" name="username"></td>
</tr>
<tr>
<!-- &nbsp; 半角不断行的空白格 -->
<!-- &ensp; 半角的空格 -->
<!-- &emsp; 全角的空格 -->
<td>&emsp;</td>
<td><input type="password" name="password"></td>
</tr>
</table><br>
<input type="submit" value="提&emsp;交">
</form>
{% if error %}
<p><strong>错误:</strong>{{ error }}</p>
{% endif %}
</body>
</html>

2-7 文件上传

  • 文件上传
    • 需HTML表单,enctype属性设为“multipart/form-data”,将文件发布到URL。
    • URL处理程序从request.files[]对象中提取文件,并将其保存到所需的位置。
    • 每个上传的文件首先保存在服务器的临时位置中,然后再将其保存到最终位置上。
    • 目标文件的名称可以是硬编码的,也可以从request.files[]对象的属性中获取。
      • secure_filename():获取安全版本。
      • app.config['UPLOAD_FOLDER']:定义上传文件夹的路径。
      • app.config['MAX_CONTENT_LENGTH']:指定要上传文件大小的最大值(以字节为单位)。
1
2
3
4
5
<!-- 项目目录结构 -->
- upload.py
- templates
- upload.html
<!-- 命令窗口下执行"python upload.py",访问[http://127.0.0.1:5000/upload] -->

(1) upload.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import os
from werkzeug.utils import secure_filename
from flask import Flask, render_template, request

app = Flask(__name__) # 上传图片路径自定义,必须存在,否则报错
app.config["UPLOAD_FOLDER"] = "C:/Users/<username>/Pictures/"


@app.route("/upload") # 访问[http://127.0.0.1:5000/upload]
def upload_file():
return render_template("upload.html")


@app.route("/uploaded", methods=["GET", "POST"])
def uploaded():
if request.method == "POST":
f = request.files["file"]
print(request.files)
path = os.path.join(app.config["UPLOAD_FOLDER"], secure_filename(f.filename))
print(path) # 注意:图片路径不含中文,中文打印识别不到
f.save(path)
return "<center>文件上传成功!</center>"
else:
return render_template("upload.html")


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

(2) upload.html

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>文件上传</title>
</head>
<body style="text-align:center;">
<form action="http://localhost:5000/uploaded" method="POST" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" value="提交" />
</form>
</body>
</html>

3 扩展功能

  • 扩展功能
    • 向Flask应用程序添加特定类型的支持,Flask Extension Registry(扩展注册表)是一个可用的扩展目录。
    • 常用扩展包
      • Flask-migrate(管理迁移数据库)、Flask-Admin(可扩展的管理接口框架)。
      • Flask-script(插入脚本)、Flask-Bable(提供国际化和本地化支持的翻译)。
      • Flask-Moment(本地化日期和时间)、Flask-Bootstrap(集成前端的Twitter Bootstrap框架)。
      • Flask-Session(Session存储方式指定)、Flask-OpenID(认证)、Flask-RESTful(开发REST API的工具)。
      • Flask-Mail(邮件)、Flask-WTF(表单)、Flask-SQLAlchemy(操作数据库)、Flask-Login(认证用户状态)。
    • 扩展通常命名为Flask-Foo或Foo-Flask,在PyPI搜索标记为“FRAMEWORK::FLASK”扩展包,通过PIP​下载。

3-1 Mail

  • Mail
    • 通过PIP命令安装Flask-Mail扩展:pip install Flask-Mail
    • 参数
      • MAIL_SERVER->电子邮件服务器的名称或IP地址、MAIL_PORT->服务器的端口号。
      • MAIL_USERNAME->发件人的用户名、MAIL_DEFAULT_SENDER->设置默认发件人。
      • MAIL_PASSWORD->发件人的密码、MAIL_MAX_EMAILS->设置要发送的最大邮件数。
      • MAIL_ASCII_ATTACHMENTS->如果值设置为True,那么附加的文件名将转换为ASCII。
      • MAIL_USE_TLS->启用或禁用传输安全层加密、MAIL_USE_SSL->启用或禁用安全套接字层加密。
      • MAIL_DEBUG->默认调试状态、MAIL_SUPPRESS_SEND->app.testing设为True,则发送被抑制。
    • 重要类的定义
      • Mail类:管理电子邮件消息传递需求,类构造函数为flask-mail.Mail(app = None)
        • send_message():发送消息的对象。
        • connect():打开与邮件主机的连接。
        • send():发送Message类对象的内容。
      • Message类:封装一封电子邮件,类构造函数为flask-mail.Message(subject, ...)
        • attach():支持向邮件添加相关附件。
        • add_recipient():添加另一个收件人。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from flask import Flask
from flask_mail import Mail, Message # flask-mail模块导入Mail和Message类

app = Flask(__name__) # 配置Flask-Mail

app.config["MAIL_SERVER"] = "smtp.126.com" # 126电子邮件服务器的名称或IP地址
app.config["MAIL_PORT"] = 25 # 服务器的端口号
app.config["MAIL_USERNAME"] = "Id1@126.com" # 发件人的用户名
app.config["MAIL_PASSWORD"] = "******" # 发件人的密码(授权码)
# 并非使用邮箱密码而是授权码,注意授权码需重新获取,旧的不行,否则报错smtplib.SMTPAuthenticationError
# 将邮箱的安全验证级别降至最低,例如:设了邮箱登录二次验证(密码登录后需输入手机验证码),将该功能关闭
# ssl.SSLError: [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:1129),MAIL_USE_SSL改为False
app.config["MAIL_USE_SSL"] = False # 禁用安全套接字层加密
app.config["MAIL_USE_TLS"] = False # 禁用传输安全层加密
mail = Mail(app) # 创建Mail类的实例


@app.route("/") # 访问[http://127.0.0.1:5000/]即发送邮件
def index():
msg = Message("Flask-Mail测试", sender="Id1@126.com", recipients=["Id2@126.com"])
msg.body = """
Name:Dr.626
Description:测试Flask-Mail扩展是否生效。
"""
mail.send(msg)
return "<center>发送成功!</center>"


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

3-2 WTF*

  • WTF
    • PIP命令安装Flask-WTF扩展:pip install Flask-WTF
    • WTforms包中的标准表单字段
      • DecimalField->显示带小数的数字文本字段、TextAreaField-><textarea>
      • SelectField->选择表单元素、PasswordField-><input type='password'>
      • IntegerField->显示整数的文本字段、RadioField-><input type='radio'>
      • StringField-><input type='text'>、SubmitField-><input type='submit'>
      • BooleanField-><input type='checkbox'>
    • WTForms包中常用的验证器
      • NumberRange->验证给定范围内输入字段中的数字、URL->验证在输入字段中输入的URL。
      • DataRequired->检查输入字段是否为空、Email->检查字段中的文本是否遵循电子邮件的ID约定。
      • IPAddress->输入字段中验证IP地址、Length->验证输入字段中的字符串长度是否在给定范围内。
1
2
3
4
5
6
7
<!-- 项目目录结构 -->
- form.py
- formnew.py
- templates
- formfill.html
- formsub.html
<!-- 命令窗口下执行"python form.py",访问[http://127.0.0.1:5000/formfill] -->

(1) form.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from formnew import ContactForm
from flask import Flask, render_template, request, flash

app = Flask(__name__)
app.secret_key = "flask wtf"


@app.route("/formfill", methods=["GET", "POST"]) # 访问[http://127.0.0.1:5000/formfill],填写数据
def form_fill():
form = ContactForm() # formnew.py中设置了name与email必填

if request.method == "POST":
if not form.validate(): # 判断必填项是否全部填写,没有则提示,有则跳转页面
flash("请填写所有必填字段。")
return render_template("formfill.html", form=form)
else:
return render_template("formsub.html") # 为什么提交后跳转页面的URL仍为“/formfill”???
elif request.method == "GET":
return render_template("formfill.html", form=form)


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

(2) formnew.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from wtforms import validators
from flask_wtf import FlaskForm
from wtforms import StringField, IntegerField, TextAreaField, SubmitField, RadioField, SelectField


class ContactForm(FlaskForm):
name = StringField("姓名", [validators.DataRequired(message="请输入你的姓名!")])
sex = RadioField("性别", choices=[("M", "男"), ("F", "女")])
address = TextAreaField("地址")
email = StringField("邮件", # 校验邮箱是否必填,以及邮箱正确性
[validators.DataRequired(message="请输入你的邮件地址!"),
validators.Email(message="请输入正确的邮件地址!")])
age = IntegerField("年龄")
# language = SelectField("语言", choices=["C#", "CPP", "Java", "Python", "Golang"])
# language上述写法,执行时可能报错ValueError: too many values to unpack (expected 2)
language = SelectField(
"语言",
choices=[
("c#", "C#"), ("cpp", "CPP"), ("java", "Java"),
("python", "Python"), ("golang", "Golang")
],
default="python",
render_kw={"style": "width:180px;height:30px;"},
)
submit = SubmitField("提交")

(3) formfill.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>表单填写</title>
</head>
<body>
<h2 style="text-align: center;">Flask-WTF表单</h2>
<form action="http://localhost:5000/formfill" method="POST">
<fieldset>
<legend>Flask-WTF表单</legend>
<!-- formnew.py的ContactForm类创建name字段时,会自动创建CSRF令牌的隐藏字段 -->
<!-- 防止跨站请求伪造攻击,CSRF令牌的隐藏字段,相当于如下脚本 -->
<!-- <input id="csrf_token" name="csrf_token" type="hidden" /> -->
{{ form.hidden_tag() }}
<div style="font-size:20px;font-weight:bold;text-align:center;">
{{ form.name.label }}<br>
{{ form.name(style="width:170px;height:20px;") }}<br>
<!-- 这里貌似只校验了邮箱的正确性,必填项弹窗提示词都一致 -->
<!-- 并不是formnew.py文件中自定义的提示词 -->
{% for message in form.name.errors %}
<div class="alert alert-danger">{{ message }}</div>
{% endfor %}
{{ form.sex.label }}<br>
{{ form.sex(style="display:inline-block;text-align:center;") }}<br>
{{ form.address.label }}<br>
{{ form.address(style="width:170px;height:20px;") }}<br>
{{ form.email.label }}<br>
{{ form.email(style="width:170px;height:20px;") }}<br>
{% for message in form.email.errors %}
<div class="alert alert-danger">{{ message }}</div>
{% endfor %}
{{ form.age.label }}<br>
{{ form.age(style="width:170px;height:20px;") }}<br>
{{ form.language.label }}<br>
<!-- {{ form.language(style="width:180px;height:30px;") }}<br><br> -->
{{ form.language() }}<br><br>
{{ form.submit(style="width:50px;height:30px;") }}<br><br>
</div>
</fieldset>
</form>
</body>
</html>

(4) formsub.html

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>提交显示</title>
</head>
<body>
<center>信息提交成功!</center>
</body>
</html>

3-3 Sijax*

  • Sijax
    • 即Simple Ajax,可轻松地实现AJAX功能,PIP安装Flask-Sijax扩展:pip install flask-sijax
    • Flask-Sijax是一个专门为Flask设计的Sijax扩展,提供了与Flask无缝集成的API,也是个轻量级的库。
1
2
3
4
5
<!-- 项目目录结构 -->
- sijaxapp.py
- templates
- sijaxapp.html
<!-- 命令窗口下执行"python sijaxapp.py",访问[http://127.0.0.1:5000/] -->

(1) sijaxapp.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import flask_sijax, os
from flask import Flask, g, render_template

app = Flask(__name__)

# SIJAX_STATIC_PATH,要被镜像的Sijax javascript文件的静态路径,默认“/static/js/sijax/”
# SIJAX_JSON_URI,从中加载json2.js静态文件的URI,Sijax使用Json在浏览器和服务器之间传递数据
path = os.path.join(".", os.path.dirname(__file__), "static/js/sijax/")
app.config["SIJAX_STATIC_PATH"] = path
app.config["SIJAX_JSON_URI"] = "/static/js/sijax/json2.js"
flask_sijax.Sijax(app)


@app.route("/") # 访问[http://127.0.0.1:5000/]
def index():
return "<center><a href='/app'>跳转app页面</a></center>"


@flask_sijax.route(app, "/app")
def hello():
def say_hi(obj_response):
obj_response.alert("Hi there!") # 函数回复浏览器的方式
if g.sijax.is_sijax_request: # 为什么不会弹窗显示内容???
g.sijax.register_callback("say_hi", say_hi)
return g.sijax.process_request() # 检测到Ajax请求时,Sijax会自行处理
return render_template("sijaxapp.html")


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

(2) sijaxapp.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Flask-Sijax</title>
<!-- 调用百度的jQuery加速 -->
<script type="text/javascript" src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<!-- 安装Flask-sijax时默认安装的sijax.js -->
<script type="text/javascript" src="/static/js/sijax/sijax.js"></script>
<!-- 使用过滤器safe禁止转译sijax_get_js() -->
<script type="text/javascript">{{ g.sijax.get_js()|safe }}</script>
</head>
<body style="text-align: center;">
<h1>Flask-Sijax</h1>
<!-- 创建id为my_form的表单 -->
<form id="my_form">
<p><input type="text" name="username" value="admin" /></p>
<p><input type="password" name="password" value="123456" /></p>
</form>
<!-- 使用Sijax.getFormValues方法获取id为my_form的表单数据 -->
<script type="text/javascript">
var values = Sijax.getFormValues("#my_form");
</script>
<a href="javascript://" onclick="Sijax.request('say_hi', values);">点击这里</a>
<!-- 单击链接将向服务器发出一个Sijax请求,一个特殊的jQuery.ajax()请求 -->
<!-- 具体参看Flask-Sijax官网“https://pythonhosted.org/Flask-Sijax/” -->
</body>
</html>

3-4 SQLite

  • SQLite
    • 通过PIP命令安装SQLite扩展:pip install sqlite3
    • SQLite是一种嵌入式数据库管理系统,易于使用无需配置。
1
2
3
4
5
6
7
8
9
<!-- 项目目录结构 -->
- adox.py
- sqloper.py
- templates
- sqlres.html
- sqlstu.html
- sqllist.html
- sqlhome.html
<!-- 命令窗口下执行"python sqloper.py",访问[http://127.0.0.1:5000/] -->

(1) adox.py

1
2
3
4
5
6
7
8
import sqlite3                                       # 实例操作前需要先创建数据库和数据表

conn = sqlite3.connect("database.db") # 创建SQLite数据库和一个学生表
print("成功打开数据库!")

conn.execute("CREATE TABLE students (name TEXT, address TEXT, city TEXT, pin TEXT)")
print("数据表创建成功!")
conn.close()

(2) sqloper.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import sqlite3 as sql                               # 实例将学生信息添加到数据库中,并列表展示
from flask import Flask, render_template, request

app = Flask(__name__)


@app.route("/") # 显示[127.0.0.1:5000]主页,链接到sqlhome.html
def sql_home():
return render_template("sqlhome.html")


@app.route("/sqlstu") # 页面表单添加学生记录,链接到sqlstu.html
def new_stu():
return render_template("sqlstu.html")


@app.route("/addrec", methods=["POST", "GET"]) # 将表单记录插入数据库对应的表中
def add_rec():
global msg
if request.method == "POST":
try:
name = request.form["name"] # 这里并未做表单内容的校验
address = request.form["address"]
city = request.form["city"]
pin = request.form["pin"]
with sql.connect("database.db") as con:
cur = con.cursor() # 打开database.db数据库
cur.execute("INSERT INTO students (name, address, city, pin) \
VALUES(?, ?, ?, ?)", (name, address, city, pin))
con.commit() # 将数据插入数据库中并commit提交
msg = "记录添加成功!"
except:
con.rollback()
msg = "插入操作有误!"
finally:
return render_template("sqlres.html", msg=msg)
con.close() # 关闭数据库连接,释放资源避免潜在的问题


@app.route("/sqllist") # 从数据库中检索所有学生信息,并在页面展示
def sql_list():
con = sql.connect("database.db")
con.row_factory = sql.Row
cur = con.cursor()
cur.execute("SELECT * FROM students") # 数据表记录查询语句
rows = cur.fetchall()
return render_template("sqllist.html", rows=rows)


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

(3) sqlres.html

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>添加结果</title>
</head>
<body style="text-align: center;">
添加后的结果 : {{ msg }}
<h2><a href = "\">点击回到首页</a></h2>
</body>
</html>

(4) sqlstu.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>表单填写</title>
</head>
<body>
<form action="{{ url_for('add_rec') }}" method="POST" style="text-align: center;">
<h3>学生信息</h3>
<br>姓名<br>
<input type="text" name="name" style="width: 180px; height: 30px;" />
<br>地址<br>
<textarea name="address" style="width: 180px; height: 30px;"></textarea>
<br>城市<br>
<input type="text" name="city" style="width: 180px; height: 30px;" />
<br>认证码<br>
<input type="text" name="pin" style="width: 180px; height: 30px;" /><br><br>
<input type="submit" value="提交" /><br>
</form>
</body>
</html>

(5) sqllist.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>列表展示</title>
<style>
body {text-align: center;} table {margin: auto;}
</style>
</head>
<body>
<table border=2>
<thead>
<td>姓名</td>
<td>地址</td>
<td>城市</td>
<td>认证码</td>
</thead>
{% for row in rows %}
<tr>
<td>{{ row["name"] }}</td>
<td>{{ row["address"] }}</td>
<td>{{ row["city"] }}</td>
<td>{{ row["pin"] }}</td>
</tr>
{% endfor %}
</table><br>
<a href = "/">点击回到首页</a>
</body>
</html>

(6) sqlhome.html

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>数据操作</title>
</head>
<body style="text-align: center;"><br>
<a href="/sqlstu">添加记录</a><br><br>
<a href="/sqllist">列表展示</a>
</body>
</html>

3-5 SQLAlchemy

  • SQLAlchemy
    • PIP命令安装Flask-SQLAlchemy扩展:pip install flask-sqlalchemy
    • Flask-SQLAlchemy是Flask框架的一个扩展,提供了一种简单易用的方式来操作SQL数据库。
    • 使用SQLAlchemy可轻松定义数据模型、查询数据并进行CRUD操作,不必编写原始SQL语句。
    • ORM,Object-Relational Mapping,对象关系映射,ORM API提供了执行CRUD操作的方法。
      • 大多数编程语言平台是面向对象的,而RDBMS服务器中的数据存储为表。
      • ORM将关系数据库表视为对象,把对象参数映射到底层RDBMS表结构中。
1
2
3
4
5
6
<!-- 项目目录结构 -->
- sqlaloper.py
- templates
- sqlalnew.html
- sqlalshow.html
<!-- 命令窗口下执行"python sqlaloper.py",访问[http://127.0.0.1:5000/] -->

(1) sqlaloper.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
from flask_sqlalchemy import SQLAlchemy             # 支持添加、编辑、删除和查看学生信息
from flask import Flask, request, flash, url_for, redirect, render_template

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///students.sqlite3"
app.config["SECRET_KEY"] = "flask sqlalchemy"

db = SQLAlchemy(app)


class students(db.Model): # students类继承自db.Model类
id = db.Column("id", db.Integer, primary_key=True)
name = db.Column(db.String(100)) # 定义了id、name、city和address四个属性
city = db.Column(db.String(50))
address = db.Column(db.String(200))
pincode = db.Column(db.String(10))

def __init__(self, name, city, address, pincode):
self.name = name # 定义构造函数,使用四个参数初始化类实例的属性
self.city = city
self.address = address
self.pincode = pincode # 访问[http://127.0.0.1:5000]


@app.route("/") # 主应用程序定义两个路由器,“/”和“/sqlalnew”
def sqlal_show(): # 访问根路径时渲染sqlalshow.html并显示所有记录
return render_template("sqlalshow.html", students=students.query.all())


@app.route("/sqlalnew", methods=["GET", "POST"]) # 访问“/sqlalnew”路径时渲染sqlalnew.html模板
def sqlal_new(): # 并允许用户添加新的记录到SQLite3数据库中
if request.method == "POST": # 表单校验,如果用户提交失败,则显示错误消息
if not request.form["name"] or not request.form["city"] or not request.form["address"]:
flash("请输入所有字段!", "error")
else: # 用户提交成功,则将新记录添加到数据库中
student = students(request.form["name"], request.form["city"],
request.form["address"], request.form["pincode"])
db.session.add(student) # session对象执行CRUD的插入映射表操作
db.session.commit()
flash("记录添加成功!")
return redirect(url_for("sqlal_show")) # 在任何情况下,都会将用户重定向回显示页面
return render_template("sqlalnew.html")


if __name__ == "__main__":
with app.app_context(): # 创建一个应用程序上下文
db.create_all() # 调用db.create_all()函数以创建数据库表

# 若执行时报错ImportError: cannot import name "EVENT_TYPE_OPENED" from "watchdog.events"
app.run(debug=True) # 升级:pip install --upgrade watchdog

(2) sqlalnew.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>表单填写</title>
</head>
<body style="text-align: center;">
<h3>Flask-SQLAlchemy 实例</h3>
<hr><!-- 水平线标签 -->
{% for category, message in get_flashed_messages(with_categories = true) %}
<div class="alert alert-danger">
{{ message }}
</div>
{% endfor %}
<form action="{{ request.path }}" method="post">
<label for="name">姓名</label><br>
<input type="text" name="name" placeholder="姓名" style="width: 180px; height: 15px;" /><br>
<label for="city">城市</label><br>
<input type="text" name="city" placeholder="城市" style="width: 180px; height: 15px;" /><br>
<label for="address">地址</label><br>
<textarea name="address" placeholder="地址" style="width: 180px; height: 15px;"></textarea>
<br>
<label for="pincode">认证码</label><br>
<input type="text" name="pincode" placeholder="认证码" style="width: 180px; height: 15px;" />
<br><br>
<input type="submit" value="提交" />
</form>
</body>
</html>

(3) sqlalshow.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>数据操作</title>
<style>
body {text-align: center;} table {margin: auto;}
</style>
</head>
<body>
<h3>
<a href="{{ url_for('sqlal_show') }}">Flask-SQLAlchemy 实例</a>
</h3>
<hr><!-- 水平线标签 -->
{%- for message in get_flashed_messages() %}
{{ message }}
{%- endfor %}
<h3>学生:(<a href="{{ url_for('sqlal_new') }}">新增</a>)</h3>
<table>
<thead>
<tr>
<th>姓名</th>
<th>城市</th>
<th>地址</th>
<th>认证码</th>
</tr>
</thead>
<tbody>
{% for student in students %}
<tr>
<td>{{ student.name }}</td>
<td>{{ student.city }}</td>
<td>{{ student.address }}</td>
<td>{{ student.pincode }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>

4 即插视图

  • 即插视图
    • Pluggable Views,一种处理请求的类,可以根据不同的HTTP方法来执行不同的操作。
    • 基本原则
      • 视图类必须继承自flask.views.View或其子类,例如:flask.views.MethodView
      • 可定义多个HTTP方法(例如:GET、POST、PUT、DELETE等)来处理不同类型的请求。
      • 请求到达应用程序时,Flask将使用URL规则匹配器查找与该请求URL相对应的视图函数。
      • 如果找到匹配的视图函数,创建该视图类的实例,并调用该类方法(GET等)来处理请求。
      • 提供可选装饰器和钩子函数,以便在调用视图方法前后执行例如授权验证、日志记录等操作。
      • 视图方法应该返回一个响应对象,通常是响应字符串,或者模板渲染结果,亦或是JSON数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from flask import Flask
from flask.views import View

app = Flask(__name__) # 访问[http://127.0.0.1:5000/hello]


class HelloWorld(View): # 定义了HelloWorld继承自View的即插视图类
def dispatch_request(self): # 实现dispatch_request方法,处理视图接收到的请求
return "<center>Hello, World!</center>"


# 用户访问“/hello”时创建HelloWorldView实例,并调用dispatch_request方法来响应请求,返回“Hello, World!”
app.add_url_rule("/hello", view_func=HelloWorld.as_view("Hello"))

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

4-1 方法调度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from flask.views import MethodView
from flask import Flask, request, jsonify

app = Flask(__name__)


class HelloWorld(MethodView): # 访问[http://127.0.0.1:5000/hello]
def get(self): # 网页访问“/hello”,响应GET请求
return "<center>GET请求</center>"

def post(self): # 应用程序启动后,使用JMeter模拟响应POST请求
data = request.get_json() # 正确的JSON格式:{"name": "John", "surname": "Doe"}
return f"<center>POST请求返回的结果:{data}。</center>"
# return jsonify(data) # JSON数据的所有属性名都必须使用双引号括起来


# 使用app.add_url_rule()方法将HelloWorld类附加到URL规则上,并将as_view()方法作为视图函数
# 为特定的HTTP请求方法指定处理函数,可以使用methods参数,发送POST请求时需设置Content-Type标头
app.add_url_rule("/hello", view_func=HelloWorld.as_view("GET-Request"), methods=["GET"])
app.add_url_rule("/hello", view_func=HelloWorld.as_view("POST-Request"), methods=["POST"])

if __name__ == "__main__": # JMeter需设置信息头管理器
app.run(debug=True) # Content-Type为application/json; charset=UTF-8;

4-2 装饰视图

1
2
3
4
5
6
7
<!-- 项目目录结构 -->
- decview.py
- templates
- declogin.html
- dechello.html
- decerror.html
<!-- 命令窗口下执行"python decview.py",访问[http://127.0.0.1:5000/declogin] -->

(1) decview.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
from functools import wraps
from flask.views import MethodView
from flask import Flask, g, render_template, session, request, redirect

app = Flask(__name__) # 访问[http://127.0.0.1:5000/declogin]

app.secret_key = b"flask decorating views"


class LoginView(MethodView): # LoginView是继承自MethodView基类的视图类
def get(self): # 该类包含了一个get()方法和一个post()方法
return render_template("declogin.html")

def post(self):
username = request.form["username"] # 若输入用户名admin,密码12345,则跳转dechello.html
password = request.form["password"] # 否则显示"错误:无效的用户名或密码!"

if username == "admin" and password == "12345":
g.user = {"username": username}
session["user"] = g.user # 通过验证设置g.user
return redirect("/dechello") # 重定向到受保护的页面
else:
error = "错误:无效的用户名或密码!"
return render_template("declogin.html", error=error)


def login_required(f): # login_required装饰器函数接受一个函数作为参数
@wraps(f) # 并返回一个新的函数,调用原始函数,登录或返回401
def decorated_function(*args, **kwargs): # 若未登录进行访问[http://127.0.0.1:5000/dechello]
if not session.get("user"): # 则页面将显示"请登录后再进行访问!"
return unauthorized("请登录后再进行访问!")
else:
g.user = session["user"]
return f(*args, **kwargs)

return decorated_function


def unauthorized(message):
return render_template("decerror.html", message=message), 401


class HelloView(MethodView):
decorators = [login_required] # 引用装饰器函数

def get(self):
message = "Hello, %s!" % g.user["username"]
return render_template("dechello.html", message=message)


app.add_url_rule("/declogin", view_func=LoginView.as_view("login"))
app.add_url_rule("/dechello", view_func=HelloView.as_view("hello"))

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

(2) declogin.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
<style>
body {text-align: center;}
</style>
</head>
<body>
{% if error %}
<p>{{ error }}</p>
{% endif %}
<form action="{{ url_for("login") }}" method="post"><br>
<label for="username">用户名:</label>
<input type="text" name="username" id="username"><br><br>
<label for="password">&emsp;码:</label>
<input type="password" name="password" id="password"><br><br>
<input type="submit" value="提&emsp;交"><br>
</form>
</body>
</html>

(3) dechello.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>欢迎首页</title>
<style>
body {text-align: center;}
</style>
</head>
<body>
<h1>{{ message }}</h1>
<p><a href="{{ url_for('login') }}">退出登录</a></p>
</body>
</html>

(4) decerror.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>错误页面</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
h1 {
text-align: center;
font-size: 27px;
color: #dc3545;
}
</style>
</head>
<body>
<h1>{{ message }}</h1>
</body>
</html>

4-3 API的方法视图*

1
2
3
4
5
6
7
8
<!-- 项目目录结构 -->
- apiview.py
- apidata.json
- templates
- apiadds.html
- apiedit.html
- apiusers.html
<!-- 命令窗口下执行"python apiview.py",访问[http://127.0.0.1:5000/users/] -->

(1) apiview.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import json
from flask.views import MethodView
from flask import Flask, request, render_template, redirect, url_for

app = Flask(__name__)
app.secret_key = "flask method view of api"


class UserAPI(MethodView): # GET查询、POST添加(或登录)、PUT修改、DELETE删除
def __init__(self):
self.load_users()

def load_users(self):
with open("apidata.json", "r") as f:
self.users = json.load(f)

def save_users(self):
with open("apidata.json", "w") as f:
json.dump(self.users, f, indent=2)

def get(self, user_name=None): # 根据user_name来获取用户的信息
print(request.method)
if user_name is None: # 访问[http://127.0.0.1:5000/users/]
user_all = [
{"id": user["id"], "name": user["name"], "age": user["age"], "email": user["email"]}
for user in self.users
] # 如果没有提供user_name,将展示所有用户信息
return render_template("apiusers.html", users=user_all)
else: # 如果提供user_name,只显示该用户信息
user_sigle = next((user for user in self.users if user["name"] == user_name), None)
if user_sigle is None: # 例如访问[http://127.0.0.1:5000/users/Adam]
return render_template("apiusers.html", message="没有找到用户,请先添加用户!")
return render_template("apiusers.html", users=[user_sigle])

def post(self): # 添加新的用户信息
print(request.method)
next_id = max(int(user["id"]) for user in self.users) + 1
name = request.form.get("name")
age = request.form.get("age") # 添加用户,跳转到[http://127.0.0.1:5000/users/add/]
email = request.form.get("email")
if not name or not age or not email:
return render_template("apiadds.html")
new_user = {"id": next_id, "name": name, "age": age, "email": email}
self.users.append(new_user)
next_id += 1
self.save_users() # 将添加的用户信息写入apidata.json中
return redirect(url_for("user_get")) # 重定向到用户列表页面

def put(self, user_name=None): # 更新指定用户的代码
print(request.method) # 路由无论如何定义都无法请求到该请求!!!
users = list(filter(lambda user: user["name"] == user_name, self.users))
user = users[0] # 使用hidden input传递模拟put请求
if request.form.get("_method") == "PUT":
age = request.form.get("age")
email = request.form.get("email")
user["age"] = age # 更新用户信息
user["email"] = email
self.save_users()
return redirect(url_for("user_get"))
else:
return render_template("apiedit.html", user_name=user_name)

def delete(self, user_name=None): # 删除指定用户的代码,修改与删除功能未实现!
pass


def register_api(view, endpoint, url, pk="name", pk_type="string"):
view_func = view.as_view(endpoint)
app.add_url_rule(url, view_func=view_func, methods=["GET"])
app.add_url_rule(url, view_func=view_func, methods=["POST"])
app.add_url_rule("%s<%s:%s>" % (url, pk_type, pk), view_func=view_func, methods=["GET", "PUT"])


register_api(UserAPI, "user_get", "/users/", pk="user_name")
register_api(UserAPI, "user_post", "/users/add/")
register_api(UserAPI, "user_put", "/users/edit/", pk="user_name")

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

(2) apidata.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[
{
"id": 1,
"name": "Test",
"age": 32,
"email": "Test@126.com"
},
{
"id": 2,
"name": "Mike",
"age": 26,
"email": "Mike@example.com"
},
{
"id": 3,
"name": "Adminstator",
"age": "27",
"email": "Adminstator@qq.com"
}
]

(3) apiadds.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>添加用户</title>
</head>
<body style="text-align: center;">
<h1>添加用户信息</h1>
<form method="post" action="{{ url_for('user_post') }}">
<label for="name">姓名</label>
<input type="text" name="name"><br><br>
<label for="age">年龄</label>
<input type="text" name="age"><br><br>
<label for="email">邮箱</label>
<input type="text" name="email"><br><br>
<button type="submit" onclick="return check()">提交</button>
<script>
function check() {
var name = document.getElementsByName("name")[0].value;
var age = document.getElementsByName("age")[0].value;
var email = document.getElementsByName("email")[0].value;
if (name === "" || age === "" || email === "") {
alert("请先把信息填写完整,再进行提交!");
return false; // 防止表单提交
}
return true;
}
</script>
</form>
</body>
</html>

(4) apiedit.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>修改用户</title>
</head>
<body style="text-align: center;">
{% for user in users %}
<h1>修改用户“{{ user.name }}”信息</h1>
<form action="{{ url_for('user_put', user_name=user.name) }}" method="POST">
<input type="hidden" name="_method" value="PUT">
<div>
<label for="age">年龄</label>
<input type="text" name="age" value="{{ user.age }}">
</div><br>
<div>
<label for="email">邮箱</label>
<input type="text" name="email" value="{{ user.email }}">
</div><br>
<div>
<input type="submit" value="保存">
<button class="btn btn-primary" type="submit" formaction="{{ url_for('user_get') }}">
取消
</button>
</div>
</form>
{% endfor %}
</body>
</html>

(5) apiusers.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户信息</title>
<h3>{{ message }}</h3>
<style>
table {margin: auto; border-collapse: collapse;}
tr:hover {background-color: #f5f5f5;}
td, th {padding: 8px; text-align: left;}
th {background-color: #333; color: #fff; text-align: center;}
td {border-bottom: 1px solid #ddd;}
</style>
</head>
<body style="text-align: center;">
<table>
<tr>
<th>识别码</th>
<th>姓名</th>
<th>年龄</th>
<th>邮箱</th>
<th>操作</th>
</tr>
{% for user in users %}
<tr>
<td>{{ user.id }}</td>
<td>{{ user.name }}</td>
<td>{{ user.age }}</td>
<td>{{ user.email }}</td>
<td>
<form action="{{ url_for('user_put', user_name=user.name) }}" method="POST">
<input type="hidden" name="_method" value="PUT">
<button class="btn btn-primary" type="submit">修改</button>
</form>
</td>
</tr>
{% endfor %}
</table><br>
<form method="post" action="{{ url_for('user_post') }}">
<button class="btn btn-primary" type="submit">添加用户</button>
</form>
</body>
</html>

5 上下文作用域

  • 上下文作用域
    • 应用上下文,Application Context。
      • 是整个Flask应用的上下文环境,用于在不同的线程中共享Flask的应用实例。
      • 使用app.app_context()显式创建,也可通过Flask的方法和函数隐式创建。
    • 请求上下文,Request Context。
      • 是在每个HTTP请求中创建的上下文,包含了当前请求及其相关信息。
      • 在处理请求的过程中,将创建一个请求上下文,然后将请求对象存储在全局变量request中。
      • 可以访问到请求对象、会话对象和其他工具函数,例如:url_forrender_template等。
    • 测试上下文,Test Context。
      • 是在Flask进行单元测试时使用的上下文环境。
      • 可使用app.test_request_context()显式创建,也可使用FlaskClient类隐式创建。
      • 可以访问到Flask的应用实例、请求对象和响应对象,从而进行针对Flask应用的测试。
      • 还可以访问Flask提供的测试工具函数,例如:jsonifymake_response等。

5-1 应用上下文

  • 应用上下文
    • 隐式创建,适用于大多情况。
      • 默认的创建方式,运行应用时每个请求都会隐式地创建应用上下文。
      • 方便使用,不需要额外的代码就可以在Flask中访问g对象来共享数据。
    • 显式创建,适用于特殊场景。
      • 需要手动创建应用上下文对象,使用更加灵活,可以在请求之外的地方使用应用上下文对象。
      • 例如测试Flask应用时,可能需要显式创建应用上下文来测试应用中的某部分,以确保正确性。

(1) 隐式创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from flask import Flask, g, current_app             # 使用g对象来存储和共享应用上下文中的数据

app = Flask(__name__)


@app.route("/") # 访问[127.0.0.1:5000]
def index(): # 定义一个路由函数index
app_name = current_app.name # 在视图函数中获取应用上下文的属性
print("应用名:", app_name)

g.username = "administrator" # 访问根路径时使用g对象将用户名存储在应用上下文中
print("用户名:", g.username)
return "<center>Hello, Flask!</center>"


if __name__ == "__main__": # 返回响应后,应用上下文将被弹出
app.run(debug=True) # 确保每个请求之间的数据隔离

(2) 显式创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from flask import Flask, g

app = Flask(__name__)

with app.app_context(): # 通过使用上下文管理器来显式创建Flask应用上下文
g.my_variable = None # 设置全局变量默认值,将其传递到路由处理函数中

@app.before_request
def set_my_variable():
g.my_variable = "Hello, Flask!" # 执行路由处理函数前,设置g.my_variable变量

@app.route("/", methods=["GET"]) # 访问[127.0.0.1:5000]
def get_my_variable():
return f"<center>{g.my_variable}</center>"

if __name__ == "__main__": # 命令函数结束后,应用上下文将被弹出
app.run(debug=True) # 确保每个命令之间的数据隔离

5-2 请求上下文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from flask import Flask, request

app = Flask(__name__)


@app.route("/") # 访问[127.0.0.1:5000]
def index(): # 只在请求处理函数中才能使用request对象,否则抛出异常
url = request.url # 获取当前请求URL
path = request.path # 获取当前请求路径,request对象是Flask的全局变量
method = request.method # 获取当前请求方法
headers = request.headers # 获取当前请求头部信息
return f"<div style='text-align:center;'>" \
f"<p>Request Path: {path}</p><p>Request Method: {method}</p>" \
f"<p>Request URL: {url}</p><p>Request Headers: {headers}</p></div>"


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

(1) 回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from flask import Flask, request

app = Flask(__name__)


@app.before_request # 在每个请求处理之前执行的回调函数
def before_request():
print(" * 请求处理前---执行")


@app.after_request # 在每个请求处理之后执行的回调函数
def after_request(response):
print(" * 请求处理后---执行")
return response


@app.teardown_request # 在每个请求处理完成并响应客户端后执行的回调函数
def teardown_request(exc):
print(" * 请求处理完成并响应客户端后---执行")


@app.route("/") # 访问[127.0.0.1:5000]
def index(): # 只在请求处理函数中才能使用request对象,否则抛出异常
url = request.url # 获取当前请求URL
method = request.method # 获取当前请求方法
return f"<div style='text-align:center;'>" \
f"<p>Request Method: {method}</p><p>Request URL: {url}</p></div>"


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

(2) 错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from flask import Flask, request

app = Flask(__name__)


@app.route("/") # 访问[127.0.0.1:5000]
def index():
name = request.form.get("name") # 定义一个路由,获取表单中的名字
if not name: # 名字为空时使用ValueError抛出异常
raise ValueError("Name is required.")
return f"<center>Message: Hello, {name}!</center>"


@app.errorhandler(ValueError) # 触发错误处理机制,调用page_not_found
def page_not_found(error): # 404错误,属于页面错误
return f"<center>Error: {str(error)}</center>"


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

(3) 全局错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from flask import Flask

app = Flask(__name__)


@app.route("/") # 访问[127.0.0.1:5000]
def index():
return f"<center>Hello, Flask!</center>"


@app.route("/divide/<int:num>") # 访问[http://127.0.0.1:5000/divide/0]
def divide(num): # 若传递num为0,抛出异常
result = 100 / num # 将被Flask全局错误处理捕获
return f"<center>Result: {result}.</center>"


@app.errorhandler(Exception) # 捕获到异常,调用handle_exception
def handle_exception(e): # 500页面错误,Internal server error
return f"<center>An error occurred: {str(e)}</center>"


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

(4) 异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from flask import Flask, request

app = Flask(__name__)
app.config["DEBUG"] = True


@app.route("/") # 访问[127.0.0.1:5000]
def index(): # 请求没有传递name参数,抛出ValueError
name = request.form.get("name")
try: # 使用try...except语句处理异常
if not name: # 使用raise语句手动触发ValueError异常
raise ValueError("Name is missing.")
except Exception as e:
app.logger.error(f"Error handling request: {e}")
return f"<center>Error: {str(e)}</center>"
return f"<center>Message: Hello, {name}!</center>"


@app.route("/bye") # 手动抛出一个Exception异常
def bye(): # 访问[127.0.0.1:5000/bye]
try:
raise Exception("Oops, Something went wrong.")
except Exception as e:
app.logger.error(f"Error handling request: {e}")
return f"<center>Error: {str(e)}</center>"


@app.before_request # 在请求到达应用之前,记录请求日志
def before_request():
app.logger.info(f"Request received: {request.method} {request.url}")


@app.after_request # 应用发送回答之前,记录HTTP响应代码
def after_request(response):
app.logger.info(f"Response sent: {response.status_code}")
return response


@app.teardown_request # 请求的最后阶段,记录请求结束并打印错误信息
def teardown_request(exception):
if exception:
app.logger.error(f"Error processing request: {exception}")
else:
app.logger.info("Request processing finished")


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

(5) 日志记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from flask import Flask, request

app = Flask(__name__)
app.config["DEBUG"] = True # 开启DEBUG模式


@app.route("/") # 访问[127.0.0.1:5000]
def index(): # app.logger记录请求和应用程序状态
app.logger.info("Welcome request successful")
return f"<center>Message: Welcome to the Flask API!</center>"


@app.route("/bye") # 访问[127.0.0.1:5000/bye]
def bye():
name = request.args.get("name")
if not name:
app.logger.error("Name is missing in request")
return f"<center>Error: Name is missing.</center>"
app.logger.info(f"Hello request successful for name {name}")
return f"<center>Message: Hello, {name}!<center>"


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

(6) 销毁回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from flask import Flask, request

app = Flask(__name__)
app.config["DEBUG"] = True


@app.route("/", methods=["GET", "POST"]) # 访问[127.0.0.1:5000]
def index():
if request.method == "GET":
return """
<form method="post" style="text-align: center;">
<label for="name">输入姓名</label>
<input type="text" id="name" name="name">
<button type="submit">提交</button>
</form>
"""
elif request.method == "POST":
name = request.form.get("name")
if not name:
return f"<center>Error: Name is missing.</center>"
else:
return f"<center>Message: Hello, {name}!</center>"


@app.before_request
def before_request():
app.logger.info(f"Request received: {request.method} {request.url}")


@app.after_request
def after_request(response):
app.logger.info(f"Response sent: {response.status_code}")
return response


@app.teardown_request # 销毁回调,对请求结束后进行清理操作
def teardown_request(exception=None): # 请求结束时teardown_request函数将被自动调用
if exception:
app.logger.error(f"Error processing request: {exception}")
else:
app.logger.info("Request processing finished")


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

(7) 留意代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import threading
from flask import Flask, request, current_app

app = Flask(__name__)


def worker():
with app.app_context(): # 在新线程中打开应用程序上下文
with app.test_request_context(): # 推送测试请求上下文
print("请求方法:" + request.method)
print("应用名称:" + current_app._get_current_object().name)


@app.route("/") # _get_current_object()可获取应用程序对象而非代理对象
def index(): # 访问[127.0.0.1:5000]
t = threading.Thread(target=worker)
t.start()
return "<center>Hello, Flask!</center>"


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

5-3 测试上下文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import unittest
from flask import Flask, request

app = Flask(__name__)
app.config["TESTING"] = True


@app.route("/", methods=["GET", "POST"])
def index():
if request.method == "GET":
return """
<form method="post" style="text-align: center;">
<label for="name">输入姓名</label>
<input type="text" id="name" name="name">
<button type="submit">提交</button>
</form>
"""
elif request.method == "POST":
name = request.form.get("name")
if not name:
return f"<center>Error: Name is missing.</center>"
else:
return f"<center>Message: Hello, {name}!</center>"


class TestApp(unittest.TestCase): # 定义了两个测试用例
def setUp(self):
self.app = app.test_client()

def test_index_get(self): # 一个测试请求主页时的行为
response = self.app.get("/") # 使用测试客户端来发送模拟请求
self.assertEqual(response.status_code, 200)
self.assertIn(b"<form", response.data) # 并断言响应是否符合预期

def test_index_post(self): # 一个测试提交表单时的行为
response = self.app.post("/")
self.assertEqual(response.status_code, 200)
self.assertIn(b"<center>Error: Name is missing.</center>", response.data)

response = self.app.post("/", data={"name": "Alice"})
self.assertEqual(response.status_code, 200)
self.assertIn(b"<center>Message: Hello, Alice!</center>", response.data)


if __name__ == "__main__": # 命令执行该测试用例
unittest.main() # 都通过为OK,若其中任何一个失败,则FAILED

6 蓝图实现模块化

  • 蓝图实现模块化
    • 允许把不同的URL请求处理程序组成一个蓝图,用于管理应用程序的不同功能部分,使代码易于维护和扩展。
    • 每个模块相互独立,可以单独测试和调试,每个蓝图都有自己的路由和视图,可在主应用程序中注册和卸载。
1
2
3
4
5
6
7
8
9
10
11
12
import random
from flask import Blueprint # module.py

blueprint = Blueprint("blueprint", __name__) # Blueprint类的构造函数需蓝图名称和模块导入名称


@blueprint.route("/")
def index():
num = random.randint(0, 10)
print(num)
variable = f"Hello, Flask! Num: {num}."
return f"<center>{variable}</center>"

6-1 注册蓝图

1
2
3
4
5
6
7
8
9
from flask import Flask                              # app.py
from module import blueprint # 与module.py关联

app = Flask(__name__) # 访问[127.0.0.1:5000]

app.register_blueprint(blueprint) # 在主应用程序中注册该蓝图

if __name__ == "__main__":
app.run(debug=True) # 命令python app.py执行该应用

6-2 静态文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from flask import Blueprint                         # module.py

# 使用蓝图对象的static_folder参数指定静态文件的文件夹为“static”
# static_url_path参数指定访问该文件夹的路径为“/static”,可放置任何静态文件,例如CSS、JavaScript或图像
blueprint = Blueprint("blueprint", __name__, static_folder="static", static_url_path="/static")

# 通过以下方式在模板中引用上述指定的静态文件
# <link rel="stylesheet" type="text/css" href="{{url_for('blueprint.static', filename='style.css')}}">
# <script type="text/javascript" src="{{url_for('blueprint.static', filename='script.js')}}"></script>
# <img src="{{url_for('blueprint.static', filename='image.jpg')}}" alt="Image">


@blueprint.route("/")
def index():
return "<center>Hello, Flask!</center>"

6-3 资源文件夹

1
2
3
4
5
6
7
8
<!-- 项目目录结构 -->
- app.py <!-- 内容不变 -->
- module.py <!-- 修改文件 -->
- static
- data.json
- templates
- index.html
<!-- 命令窗口下执行"python app.py",访问[http://127.0.0.1:5000] -->

(1) data.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"title": "My Website",
"name": "John",
"age": 30,
"gender": "male",
"email": "john@example.com",
"phone": "123-456-7890",
"address": {
"street": "123 Main St",
"city": "Los Angeles",
"state": "CA",
"zip": "90012"
},
"interests": ["music", "travel", "movies"]
}

(2) module.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import os
import json
from flask import Blueprint, render_template

blueprint = Blueprint("blueprint", __name__, # root_path指定应用程序的根目录
static_folder="static",
static_url_path="/static",
template_folder="templates",
root_path=os.path.abspath(os.path.dirname(__file__)))


@blueprint.route("/") # 访问[127.0.0.1:5000]
def index():
with blueprint.open_resource("static/data.json") as f:
data = json.load(f)
return render_template("index.html", data=data)

(3) index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ data.title }}</title>
<style>
body {
font-family: Arial, Helvetica, sans-serif;
text-align: center; /* 将页面内容居中显示 */
}
h1 {color: blue;}
p {line-height: 1.5em;}
ul {padding: 0px;}
table {margin: auto; border-collapse: collapse;}
tr:hover {background-color: #f5f5f5;}
td {padding: 8px; text-align: left; border-bottom: 1px solid #ddd;}
</style>
</head>
<body>
<h1>Hello, {{data.name}}!</h1>
<p>You are {{data.age}} years old, and your email is {{data.email}}.</p>
<p>You live in {{data.address.city}}, {{data.address.state}}.</p>
<h2>Interests are as follows</h2>
<ul>
{% for interest in data.interests %}
<table><tr><td>{{ interest }}</td></tr></table>
{% endfor %}
</ul>
</body>
</html>

6-4 蓝图构造URL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from flask import Blueprint, url_for, Flask

app = Flask(__name__)
app.config["SERVER_NAME"] = "localhost:5000" # 设置SERVER_NAME配置
blueprint = Blueprint("bp", __name__, url_prefix="/my")


# [http://localhost:5000/my/index],本地回环地址,只允许在同一台机器上的应用程序访问
# [http://127.0.0.1:5000/my/index],127.0.0.1是指向本机的IP地址,Flask应用程序部署在其他机器上使用
@blueprint.route("/index") # url_prefix指蓝图的URL前缀
def index(): # 访问[http://localhost:5000/my/index]
return "<center>Hello, Flask!</center>" # 访问[http://127.0.0.1:5000/my/index]无效


app.register_blueprint(blueprint) # 注册蓝图

with app.app_context():
url = url_for("bp.index") # 使用蓝图构造URL
print(url)

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

7 Flask与Shell结合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import time
from flask import Flask, g

app = Flask(__name__)


@app.before_request
def before_request():
g.request_start_time = time.time() # request_start_time变量记录请求开始时间


@app.after_request
def after_request(response): # 计算请求执行时间,并使用日志记录功能记录该时间
request_time = time.time() - g.request_start_time
app.logger.info("Request Time: %f", request_time)
return response


@app.route("/") # 访问[127.0.0.1:5000]
def index():
return "<center>Hello, Flask!</center>"


if __name__ == "__main__":
with app.app_context(): # 创建一个请求上下文来启动Flask应用程序
app.run(debug=True)
  • 进入当前文件目录中,鼠标右击Git Bash Here,打开一个终端模拟器,进行以下交互式操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 若报错Error: Could not locate a Flask application. Use ... in the current directory.
$ export FLASK_APP=yourfilename # 注意:这里文件不需要扩展名,也不用中文名(会报错)
$ flask shell
Python 3.9.13 (main, Oct 25 2021, 23:51:50) [MSC v.1916 64 bit (AMD64)] on win32
App: app
Instance: D:\...\Flask\instance
>>> app # 返回Flask应用程序定义的对象
<Flask "app">
>>> ctx = app.test_request_context() # 创建一个测试请求上下文对象ctx
>>> ctx.push() # 将ctx推到栈中,以便访问上下文中的变量和对象
>>> app.preprocess_request() # 激活预处理器函数,将在请求处理之前自动调用
>>> app.process_response(app.response_class()) # 激活相应处理器函数,负责处理产生HTTP响应的内容
<Response 0 bytes [200 OK]>
>>> ctx.pop() # 当测试请求上下文处理完毕后,需要将其从栈中弹出
>>> exit() # 退出Shell会话

Python Flask
https://stitch-top.github.io/2021/12/10/python/python13-python-flask/
作者
Dr.626
发布于
2021年12月10日 22:43:15
许可协议