Flask
安装Flask
pip3 install flask
创建Flask项目
在文件夹下创建app.py,在其中写入以下内容
from flask import Flask
app = Flask(__name__) # 实例化app对象
@app.route('/',methods=['GET','POST']) # 路由装饰器,将路由映射到函数,methods是请求方法
def hello_world():
return 'hello world'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=54188, debug=True) # 调用run()方法启动服务器,启用debug可以实时显示代码更改后的情况
当同一个路由装饰器装饰多个视图函数时,只会访问第一个路由下的视图函数的返回值,即先到先得
当同一个视图函数被多个路由装饰器装饰时,所有的路由都会正常执行当前视图函数
更改模板\静态资源文件夹等
app = Flask(__name__,static_folder="static",template_folder='templates',...)
修改配置文件
方法零 在启动项目时添加参数
python -m flask run -h 0.0.0.0 -p 54188 --debug
方法一 在app.py中直接修改
# app.py
app.config['DEBUG'] = True
# print(app.config) # 打印可配置的项目
# print(app.config.get('DEBUG')) # 打印指定配置的内容
# print(dir(app.config)) # 打印可以调用的方法
Flask框架的配置文件是一个字典形式,直接通过Key定位到待修改的项目,然后进行修改
方法二 添加一个setting.py文件统一管理
# app.py
app.config.from_pyfile('setting.py')
# setting.py
DEBUG = True
方法三 通过对象修改
# 创建一个类,然后在类中添加配置项
class Spt2503(object):
DEBUG = True
class Spt2504(Spt2503):
HOST = '0.0.0.0'
# 调用命令获取对象的属性,通过其修改配置
app.config.from_object(Spt2504)
查看已有的路由信息
print(app.url_map) # 打印当前路由列表
print(dir(app.url_map)) # 打印路由中可配置的项目
print(dir(app.url_map.iter_rules))
待整理...
根据已有的函数动态填充指定url
app.url_for(endpoint, \**values)
endpoint
:路由关联的端点名(通常是视图函数名)**values
:URL 变量部分的参数(如/user/<username>
中的username
)
根据端点名称(endpoint)和参数动态生成 URL
url = app.url_for('user_profile', username='alice')
返回JSON格式信息
@app.route('/')
def ok():
return jsonify({"code":200, "message":"hello"})
通过动态路由实现在链接传值
直接在访问链接的后面添加要传的值
@app.route('/get/<string:name>') # 在路由中命名
# 一般常用的类型有 int float string 三种类型
def get(name):
print()
return f'hello{name}'
异常处理
@app.errorhandler(404)
def pageNotFound(e):
return f'页面未找到<hr>{e}'
Flask 请求和响应数据
from flask import Flask
from flask import request
from flask import render_template
from flask import redirect
from flask import make_response
app = Flask(__name__)
@app.route('/login.html', methods=['GET', "POST"])
def login():
# 请求相关信息
# request.method
# request.args
# request.form
# request.values
# request.cookies
# request.headers
# request.path
# request.full_path
# request.script_root
# request.url
# request.base_url
# request.url_root
# request.host_url
# request.host
# request.files
# obj = request.files['the_file_name']
# obj.save('/var/www/uploads/' + secure_filename(f.filename))
# 响应相关信息
# return "字符串"
# return render_template('html模板路径',**{})
# return redirect('/index.html')
# response = make_response(render_template('index.html'))
# response是flask.wrappers.Response类型
# response.delete_cookie('key')
# response.set_cookie('key', 'value')
# response.headers['X-Something'] = 'A value'
# return response
return "内容"
if __name__ == '__main__':
app.run()
获取前端表单内容
在 Flask 中获取前端表单内容主要有以下两种方式(根据表单的 Content-Type
选择):
application/x-www-form-urlencoded
(默认表单类型)
前端示例(HTML):
<form action="/submit" method="POST">
<input type="text" name="username">
<input type="password" name="password">
<button type="submit">提交</button>
</form>
后端处理(Flask):
from flask import request
@app.route('/submit', methods=['POST'])
def handle_form():
username = request.form.get('username') # 推荐使用 .get() 避免 KeyError
password = request.form.get('password')
return f"Received: {username}, {password}"
application/json
(AJAX/前端框架常用)
前端示例(JavaScript):
// 使用 Fetch API 发送 JSON
fetch('/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'test', password: '123' })
})
后端处理(Flask):
from flask import request, jsonify
@app.route('/submit', methods=['POST'])
def handle_json():
data = request.get_json() # 自动解析 JSON
username = data.get('username')
password = data.get('password')
return jsonify({"status": "success", "data": data})
获取前端上传的文件
在 Flask 中处理文件上传需要结合 enctype="multipart/form-data"
表单和 request.files
对象。以下是具体实现方法:
- 前端表单(HTML)
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="file">
<button type="submit">上传</button>
</form>
- 后端处理(Flask)
from flask import request, jsonify
import os
UPLOAD_FOLDER = 'uploads'
os.makedirs(UPLOAD_FOLDER, exist_ok=True) # 确保上传目录存在
@app.route('/upload', methods=['POST'])
def handle_upload():
if 'file' not in request.files:
return jsonify({"error": "No file part"}), 400
file = request.files['file']
if file.filename == '':
return jsonify({"error": "No selected file"}), 400
if file:
file.save(os.path.join(UPLOAD_FOLDER, file.filename))
return jsonify({"status": "success", "filename": file.filename})
返回信息修改和Cookie\Session设置
可以返回一个元组,这样的元组必须是 (response, status, headers) 的 形式,且至少包含一个元素。 status 值会覆盖状态代码, headers 可以是 一个列表或字典,作为额外的消息标头值
@app.route('/set_res1')
def res1():
print('元组返回,外面的括号可以省略')
return 'hello world', 200, {'Content-Type':'text/html;chatset=utf-8'}
也可以通过make_response()
来设置返回的信息,包括设置cookie和session
@app.route('/set_res')
def setRes():
res = make_response('hello')
res.status_code = 200
res.header['Content-Type'] = 'text/html;chatset=gbk'
return res
@app.route('/set_cookie')
def setCookie():
res = make_response('hello world')
res.set_cookie('key','value',max_age=3600*24*15) # Cookie一般设置半个月,要删除Cookie,就将事件设置为0
return res
@app.route('/get_cookie')
def getCookie():
print(request.cookies) # 获取全部Cookie
print(request.cookies.get('key')) # 获取指定key的Cookie
return 'hello world'
@app.route('/set_session')
def setSession():
session['key'] = 'value'
return '设置成功'
@app.route('/get_session')
def getSession():
ses = session.get('key')
return ses
@app.route('/del_session')
def delSession():
ses = session.pop('key')
return 'Session删除成功'
session还有许多方法,类似clear(),update()等用法
要设置session,必须设置一个密钥来保护安全,例如在代码中输入
app.config['SECRET_KEY'] = 'SECRET_KEY'
来设置密钥
设置类视图
...
from flask.views import MethodView
...
class cbv(MethodView):
def get(self): # 固定写法,实现GET方法
return 'hello'
user_view = cbv.as_view('cbv') # cbv是传值的名字(类似前端表单的name),一般等同于类名
app.add_url_rule('/rule',view_func=user_view,methods=['GET','POST']) # 添加路由
请求\应用上下文(线程局部变量)
请求上下文(request context) request和session
应用上下文(application context) current_app和g
current_app 表示当前运行程序文件的程序实例;g 表示临时存储的对象,每次请求都会重设这个变量,请求完后会清空
请求钩子函数(中间件)
请求钩子是通过装饰器的形式实现,Flask支持如下四种请求钩子:
before_first_request
:在处理第一个请求前运行。before_request
:在每次请求前运行。after_request
:如果没有未处理的异常抛出,在每次请求后运行。teardown_request
:在每次请求后运行,即使有未处理的异常抛出。
before_first_request
作用:在应用处理第一个客户端请求之前运行,通常用于初始化操作(如数据库连接、全局配置等)。
from flask import Flask
app = Flask(__name__)
@app.before_first_request
def initialize():
print("This runs ONCE before the first request.")
@app.route('/')
def home():
return "Home Page"
if __name__ == '__main__':
app.run()
行为:启动应用后,首次访问 /
时会打印 "This runs ONCE before the first request."
,后续请求不再触发。
before_request
作用:在 每个请求 处理前运行,常用于身份验证、请求预处理等。
from flask import Flask, request
app = Flask(__name__)
@app.before_request
def log_request_info():
print(f"Request Path: {request.path}")
@app.route('/')
def home():
return "Home Page"
@app.route('/api')
def api():
return "API Data"
行为:每次访问 /
或 /api
时,均会打印当前请求路径(如 "Request Path: /api"
)。
after_request
作用:在每次请求 正常完成后(无未处理异常)运行,常用于修改响应(如添加头信息)。
from flask import Flask, make_response
app = Flask(__name__)
@app.after_request
def add_header(response):
response.headers['X-Custom-Header'] = 'Hello'
print("Response modified")
return response
@app.route('/')
def home():
return "Home Page"
行为:访问 /
后,响应会添加 X-Custom-Header
,并打印 "Response modified"
。若视图函数抛出异常,此钩子不执行。
teardown_request
作用:在每次请求 结束后 运行(即使有未处理异常),适用于清理资源(如关闭文件、数据库连接)。
from flask import Flask
app = Flask(__name__)
@app.teardown_request
def cleanup(exception):
if exception:
print(f"An error occurred: {exception}")
print("Resource cleanup completed")
@app.route('/error')
def force_error():
raise ValueError("Demo Error")
@app.route('/')
def home():
return "Home Page"
行为:
- 访问
/
:打印"Resource cleanup completed"
。 - 访问
/error
:先打印错误信息,再打印"Resource cleanup completed"
。
钩子类型 | 触发时机 | 异常影响 |
---|---|---|
before_first_request |
首次请求前 | 无 |
before_request |
每次请求前 | 后续流程终止 |
after_request |
请求正常完成后 | 未处理异常时跳过 |
teardown_request |
请求结束后(无论是否有异常) | 始终执行 |
Jinja2 模板语法
- Flask 应用代码 (
app.py
)
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def home():
# 传递变量到模板
user = {"name": "Alice", "age": 25}
numbers = [1, 2, 3, 4]
return render_template(
'index.html', # 模板文件名
user=user, # 变量1:字典
numbers=numbers, # 变量2:列表
greeting="Hello World!" # 变量3:字符串
)
if __name__ == '__main__':
app.run()
- 模板文件 (
templates/index.html
)
<!DOCTYPE html>
<html>
<head>
<title>Jinja2 Demo</title>
</head>
<body>
<h1>{{ greeting }}</h1> <!-- 输出字符串变量 -->
<!-- 输出字典内容 -->
<p>User: {{ user.name }}, Age: {{ user.age }}</p>
<!-- 循环输出列表 -->
<ul>
{% for num in numbers %}
<li>Number: {{ num }}</li>
{% endfor %}
</ul>
<!-- 条件判断 -->
{% if user.age > 18 %}
<p>This user is an adult.</p>
{% endif %}
</body>
</html>
Web表单
WTForms支持的HTML标准字段
|
|
| ------------------------------------------------------------ | ------------------------------------------------------------ |
WTForms常用验证函数
应用: 判断密码输入是否一致
# 定义自己的表单类
class MyForm(FlaskForm):
username = StringField('用户名')
password = PasswordField('密码')
qrpassword = PasswordField('确认密码', validators=[EqualTo('password', message='密码不一致')])
@app.route('/form', methods=['GET', 'POST'])
def form():
form = MyForm(request.form if request.method == 'POST' else None)
if request.method == 'POST':
if form.validate():
print(form.username.data)
pd = form.password.data
rpd = form.qrpassword.data
print(pd, rpd)
# 成功处理后的逻辑
else:
print("表单验证失败:", form.errors)
# 无论 GET 还是 POST,都传递表单对象到模板
return render_template('main.html', form=form)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>
表单 测试
</title>
</head>
<body>
<form action="/form" method="post">
{{ form.csrf_token }}
<input type="text" name="username" id="" value="{{}}">
<input type="password" name="password" id="">
<input type="password" name="qrpassword" id="">
<input type="submit" value="提交">
</form>
</body>
</html>
控制语句
常用的几种控制语句: 模板中的if控制语句
{% if %} {% endif %}
模板中的for语句
{% for item in samples %} {% endfor %}
宏
类似于python中的函数,宏的作用就是在模板中重复利用代码,避免代码 冗余。 Jinja2支持宏,还可以导入宏,需要在多处重复使用的模板代码片段可以写 入单独的文件,再包含在所有模板中,以避免重复。
定义宏
{% macro input() %}
<input type="text"
name="username"
value=""
size="30"/>
{% endmacro %}
调用宏
{{ input() }}
定义带参数的宏
{% macro input(name,value='',type='text',size=20) %}
<input type="{{ type }}"
name="{{ name }}"
value="{{ value }}"
size="{{ size }}"/>
{% endmacro %}
调用宏,并传递参数
{{ input(value='name',type='password',size=40)}}
把宏单独抽取出来,封装成html文件,其它模板中导入使用
文件名可以自定义macro.html
{% macro function() %}
<input type="text" name="username" placeholde="Username">
<input type="password" name="password" placeholde="Password">
<input type="submit">
{% endmacro %}
在其它模板文件中先导入,再调用
{% import 'macro.html' as func %}
{{ func.function() }}
模板继承
模板继承是为了重用模板中的公共内容。一般Web开发中,继承主要使用在网站的顶部菜单、底部。这些内容可以定义在父模板中,子模板直接继承,而不需要重复书写。
{% block top %}``{% endblock %}标签定义的内容,相当于在父模板中挖个坑,当子模板继承父模板时,可以进行填充。
子模板使用extends指令声明这个模板继承自哪?父模板中定义的块在子模板中被重新定义,在子模板中调用父模板的内容可以使用super()。
父模板
{% block top %}
顶部
{% endblock top %}
{% block content %}
中间
{% endblock content %}
{% block bottom %}
底部
{% endblock bottom %}
子模板
{% extends 'base.html' %}
{% block content %}
需要填充的内容
{% endblock content %}
模板继承使用时注意点:
- 不支持多继承。
- 为了便于阅读,在子模板中使用extends时,尽量写在模板的第一行。
- 不能在一个模板文件中定义多个相同名字的block标签。
- 当在页面中使用多个block标签时,建议给结束标签起个名字,当多个block嵌套时,阅读性更好。
包含
Jinja2模板中,除了宏和继承,还支持一种代码重用的功能,叫包含 (Include)。它的功能是将另一个模板整个加载到当前模板中,并直接渲染。
{% include 'hello.html' ignore missing %}
包含在使用时,如果包含的模板文件不存在时,程序会抛出 TemplateNotFound异常,可以加上ignore missing关键字。如果包含的 模板文件不存在,会忽略这条include语句。
三者之间的比较
宏(Macro)、继承(Block)、包含(include)均能实现代码的复用。
继承(Block)的本质是代码替换,一般用来实现多个页面中重复不变的区域。
宏(Macro)的功能类似函数,可以传入参数,需要定义、调用。
包含(include)是直接将目标模板文件整个渲染出来。
数据库
导包
pip3 install flask-sqlalchemy
pip3 install flask-mysqldb
设置数据库
#连接操作
app.config['SQLALCHEMY_DATABASE_URI'] =
'mysql://root:mysql@127.0.0.1:3306/zb'
#设置每次请求结束后会自动提交数据库中的改动
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
#保证数据库数据和模型类数据一致
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
#查询时会显示原始SQL语句
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)
常用的SQLAlchemy字段类型
常用的SQLAlchemy选项
常用的SQLAlchemy列选项 | 常用的SQLAlchemy关系选项 |
---|---|
![]() |
|
![]() |
|
基本操作
常用的SQLAlchemy查询操作
常用的SQLAlchemy查询过滤器 | 常用的SQLAlchemy查询执行器 |
---|---|
![]() |
|
![]() |
|
创建表
db.create_all()
删除表
db.drop_all()
插入一条数据
ro1 = Role(name='admin')
db.session.add(ro1)
db.session.commit()
#再次插入一条数据
ro2 = Role(name='user')
db.session.add(ro2)
db.session.commit()
插入多条数据
us1 = User(name='wang',email='wang@163.com',pswd='123456',role_id=ro1.id)
us2 = User(name='zhang',email='zhang@189.com',pswd='201512',role_id=ro2.id)
us3 = User(name='chen',email='chen@126.com',pswd='987654',role_id=ro2.id)
us4 = User(name='zhou',email='zhou@163.com',pswd='456789',role_id=ro1.id)
db.session.add_all([us1,us2,us3,us4])
db.session.commit()
查询
filter_by()精确查询
返回名字等于wang的所有人
User.query.filter_by(name='wang').all()
first()返回查询到的第一个对象
User.query.first()
all()返回查询到的所有对象
User.query.all()
filter()模糊查询,返回名字结尾字符为g的所有数据。
User.query.filter(User.name.endswith('g')).all()
get(),参数为主键,如果主键不存在没有返回内容
User.query.get()
逻辑非,返回名字不等于wang的所有数据
User.query.filter(User.name!='wang').all()
逻辑与,需要导入and ,返回 and()条件满足的所有数据
from sqlalchemy import and_
User.query.filter(and_(User.name!='wang',User.email.endswith('163.com'))).all()
逻辑或,需要导入or_
from sqlalchemy import or_
User.query.filter(or_(User.name!='wang',User.email.endswith('163.com'))).all()
not_ 相当于取反
from sqlalchemy import not_
User.query.filter(not_(User.name=='chen')).all()
查询数据后删除
user = User.query.first()
db.session.delete(user)
db.session.commit()
User.query.all()
更新数据
user = User.query.first()
user.name = 'dong'
db.session.commit()
User.query.first()
使用update更新
User.query.filter_by(name='zhang').update({'name':'li'})
db.session.commit()
应用:查询角色
角色和用户的关系是一对多的关系,一个角色可以有多个用户,一个用户只能属于一个角色
查询角色的所有用户
#查询roles表id为1的角色
ro1 = Role.query.get(1)
#查询该角色的所有用户
ro1.us
查询用户所属角色
#查询users表id为3的用户
us1 = User.query.get(3)
#查询用户属于什么角色
us1.role
创建迁移仓库
# 这个命令会创建migrations文件夹,所有迁移文件都放在里面。
python database.py db init
创建迁移脚本
自动创建迁移脚本有两个函数,upgrade()
函数把迁移中的改动应用到数据库中,downgrade()
函数则将改动删除。自动创建的迁移脚本会根据模型定义和数据库当前状态的差异,生成upgrade()
和downgrade()
函数的内容。对比不一定完全正确,有可能会遗漏一些细节,需要进行检查。
# 创建自动迁移脚本
python database.py db migrate -m 'initial migration'
更新数据库
python database.py db upgrade
回退数据库
回退数据库时,需要指定回退版本号,由于版本号是随机字符串,为避免出错,建议先使用python database.py db history
命令查看历史版本的具体版本号,然后复制具体版本号执行回退。
python database.py db downgrade 版本号