前言:如果还没学Django的同学,可以看Django 教程 | 菜鸟教程,也可以忽略下文所提及的Django内容;另外,由于我们接手的项目大多都是前后端分离的项目,所以本文会跳过对模板的介绍,感兴趣的朋友可以自行查阅其他相关资料;
Flask快速上手
创建项目:
相较于Django而已,Flask的学习比较轻松,在构建视图、路由等方面也是简洁很多,上面代码就是一个简单的接口,可以直接右键当前文件运行:
在浏览器访问http://127.0.0.1:5000 就可以看到输出的内容:
上面代码对应的Django代码如下:
# myapp/views.py
from django.http import HttpResponsedef hello_world(request):return HttpResponse("Hello World")#myapp/urls.pyfrom django.urls import path
from . import viewsurlpatterns = [path('', views.hello_world, name='hello_world'),
]# urls.py
from django.contrib import admin
from django.urls import include, pathurlpatterns = [path('admin/', admin.site.urls),path('', include('myapp.urls')), # 包含myapp的urls
]
Flask路由
Flask中的route()装饰器用于将URL绑定到函数。例如:
@app.route('/hello')
def hello_world():return 'hello world'
在这里,URL '/ hello' 规则绑定到hello_world()函数。
因此,如果用户访问http://localhost:5000/hello ,hello_world()函数的输出将在浏览器中呈现。
另外使用add_url_rule()函数也可用于将URL与函数绑定,如下所示:
def hello_world():return 'hello world'
app.add_url_rule('/nihao', 'hello', hello_world)
以下是 add_url_rule
方法的主要参数说明:
- rule: 这是一个字符串,表示URL规则。例如,'/'代表根路径。
- endpoint (可选): 标识这个URL规则的名称(相当于唯一标识这个视图的名字,因为后续视图多了,有可能出现重名,就可以通过endpoint来标明,类似于Djnago中的路由命名),默认是视图函数的名字。这个值用于反向生成URL,比如通过 url_for() 函数。
- view_func (可选): 一个函数或方法,一旦匹配到相应的URL规则就会被调用执行。这个参数通常是你的视图函数。
跟Django有所不同,Django的路由是通过单独的urls文件注册,如下:
from django.urls import path
from . import viewsurlpatterns = [path('', views.hello_world, name='hello_world'),
]
当你要构建动态URL时,此变量部分标记为<variable-name> 。它作为关键字参数传递给与规则相关联的函数。在以下示例中,route()装饰器的规则参数包含附加到URL '/hello' 的<name>。 因此,如果在浏览器中输入http://localhost:5000/hello/mm作为URL,则'mm'将作为参数提供给 hello()函数。
from flask import Flask
app = Flask(__name__)@app.route('/hello/<name>') #<name>默认的传值类型是字符串类型
def hello_name(name):return 'Hello %s!' % nameif __name__ == '__main__':app.run()
在 Flask 路由中,路径参数 <name> 默认是作为字符串类型处理的。然而,Flask 允许你指定路径参数的转换器(converter),从而可以接受不同类型的值。以下是 Flask 支持的一些内置转换器以及它们对应的类型:
- string: (默认类型)接受任何不包含斜杠的文本,例如 "/hello/world" 中的 "world"。
- int: 接受正整数,并将其作为整数类型传递给视图函数,例如 "/hello/123" 会将 123 作为整数传入。
- float: 接受正浮点数,例如 "/hello/123.45" 会将 123.45 作为浮点数传入。
- path: 类似于 string,但它也接受包含斜杠的文本,例如 "/hello/a/b/c" 可以匹配并传入 "a/b/c"。
- uuid: 接受符合 UUID 格式的字符串,例如 "/hello/6eade98b-7dce-4c62-bf6f-ccbe9cf95bc1"。UUID 是一种标准格式用于定义通用唯一标识符。
- any: 匹配多个预定义的值中的任意一个。你需要提供这些值作为参数,用逗号分隔。例如,<any(abc,def):name> 只接受 "abc" 或 "def" 作为有效的参数值。
使用这些转换器时,只需在路径参数名称前加上转换器名称和冒号,如 <int:id> 或 <float:number>。
Django中构建动态路由的方法:
urlpatterns = [re_path('^index/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})$',views.index,name='index')
]from django.urls import path,include
urlpatterns = [ path('app01/',include(('app01.urls','app01'),namespace='app01'))]# views.pyt.
def index(request,year,month,day):args = ['2023','12','31']return HttpResponse(resolve(reverse('app01:index',args=args)))
另外,在Flask中使用app.route()注册路由时,默认只支持get方法,要想支持post或其他方法,就得使用method参数说明,如下:
@app.route('/login',methods = ['POST', 'GET'])
def login():if request.method == 'POST':print(1)user = request.form['mm']return redirect(url_for('success',name = user))else:print(2)name = request.args.get("name")return name
这里需要注意的是:request.form()方法是用于获取前端表单POST传过来的数据,而request.args.get()是用于获取请求的各种信息,比如请求方法、GET方法表单数据、URL参数等。例如,在浏览器访问127.0.0.1:5000/login?name=John&age=25&city=New+York,request.args.get("name")获取的值就是John:
补充:Flask中的Request对象重要属性如下所列:
-
Form - 它是一个字典对象,包含表单参数及其值的键和值对。
-
args - 解析查询字符串的内容,它是问号(?)之后的URL的一部分。
-
Cookies - 保存Cookie名称和值的字典对象。
-
files - 与上传文件有关的数据。
-
method - 当前请求方法。
拓展:Flask不像Django一样,Flask的Request对象是通过上下文管理获取的,需要导入使用,如:from flask import request
Url构建
在Flask中,url_for()函数用来构建特定函数的url,有点类似于Django中的reverse()函数;url_for()函数接受函数的名称作为第一个参数,以及一个或多个关键字参数,每个参数对应于URL的变量部分,如下例子:
from flask import Flask, redirect, url_for
app = Flask(__name__)@app.route('/teacher')
def hello_teacher():return 'Hello teacher'@app.route('/student/<stu_name>')def hello_student(stu_name):return 'Hello %s' % stu_name@app.route('/user/<name>')
def hello_user(name):if name =='teacher':return redirect(url_for('hello_teacher'))else:return redirect(url_for('hello_student', stu_name = name))if __name__ == '__main__':app.run(debug = True)
当运行后,在浏览器中访问127.0.0.1:5000/student/小明
其中“小明”会作为参数传入到hello_user(name)中,经过if判断后,重定向到hello_student(stu_name)函数
这里的url_for('hello_student', stu_name = name)生成的路由是/hello_student/<stu_name>
Django中与之对应的函数是reverse(),用法如下:
reverse通过路由命名或可调用的视图对象来生成相应的路由地址
resolve通过路由地址获取路由对象的信息
from django.http import HttpResponse
from django.shortcuts import render,reverse
from django.urls import resolve#urls.py
from django.urls import path,re_path
from . import views
urlpatterns = [re_path('^index/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})$',views.index,name='index')
]from django.urls import path,include
urlpatterns = [ path('app01/',include(('app01.urls','app01'),namespace='app01'))]# views.pyt.
def index(request,year,month,day):args = ['2023','12','31']return HttpResponse(resolve(reverse('app01:index',args=args)))#reverse('app01:index',args=args) app01/index/2023/12/31#resolve(reverse('app01:index',args=args))
#ResolverMatch(func=app01.views.index, args=(), kwargs={'year': '2023', 'month': '12', 'day': '31'}, url_name=index, app_names=['app01'], namespaces=['app01'], route=app01/index/(?P[0-9]{4})/(?P[0-9]{2})/(?P[0-9]{2})$)
Cookie/Session
from flask import Flask, make_response, request # 注意需导入 make_responseapp = Flask(__name__)
app.secret_key = 'asdfgsdfsfasdfsdvzfbstrjthgraeg' #记得配上,不然用session的时候会报错@app.route("/set_cookies")
def set_cookie():resp = make_response("success")resp.set_cookie("w3cshool", "w3cshool",max_age=3600)return resp@app.route("/get_cookies")
def get_cookie():cookie_1 = request.cookies.get("w3cshool") # 获取名字为Itcast_1对应cookie的值return cookie_1@app.route("/delete_cookies")
def delete_cookie():resp = make_response("del success")resp.delete_cookie("w3cshool")return resp# 设置session
@app.route("/set_session")
def set_session():session['key'] = 'value' # 在会话中存储数据return 'Session set'# 获取session
@app.route("/get_session")
def get_session():value = session.get('key', 'Not set') # 获取会话中的数据return value# 删除session
@app.route("/delete_session")
def delete_session():session.pop('key', None) # 从会话中删除数据return 'Session deleted'if __name__ == '__main__':app.run(debug=True)
abort()函数
在Flask中,abort
函数用于提前终止请求并返回一个HTTP错误状态码给客户端。它通常用于处理异常情况或错误条件,比如当资源未找到、权限不足等情况下返回相应的HTTP错误码。
abort
函数可以接受一个HTTP状态码作为参数,并且可以选择性地提供一个描述信息或者自定义响应体。Flask会将这个状态码转换为对应的HTTP错误页面。如果想要自定义错误页面的显示内容,可以通过Flask的 errorhandler
装饰器来实现。
下面是一个简单的例子,演示了如何使用 abort
来返回404(Not Found)和403(Forbidden)错误:
from flask import Flask, abortapp = Flask(__name__)@app.route('/resource/<int:resource_id>')
def get_resource(resource_id):if resource_id != 1:# 如果资源ID不是1,则返回404错误abort(404)else:return f'Resource with ID {resource_id}'@app.route('/access/<string:permission>')
def check_access(permission):if permission != 'admin':# 如果权限不是admin,则返回403错误abort(403)else:return 'Access Granted'if __name__ == '__main__':app.run(debug=True)
你还可以通过 errorhandler
装饰器来自定义不同HTTP错误码的错误页面:
from flask import Flask, abort, render_template_stringapp = Flask(__name__)@app.errorhandler(404)
def page_not_found(error):# 自定义404错误页面return render_template_string('<h1>404 - Not Found</h1>'), 404@app.errorhandler(403)
def forbidden(error):# 自定义403错误页面return render_template_string('<h1>403 - Forbidden</h1>'), 403# 前面提到的路由函数
@app.route('/resource/<int:resource_id>')
def get_resource(resource_id):if resource_id != 1:abort(404)else:return f'Resource with ID {resource_id}'@app.route('/access/<string:permission>')
def check_access(permission):if permission != 'admin':abort(403)else:return 'Access Granted'if __name__ == '__main__':app.run(debug=True)
文件上传
在 Flask 中处理文件上传非常简单。它需要一个 HTML 表单,其 enctype
属性设置为“multipart/form-data”
,将文件发布到 URL。
URL 处理程序从 request.files[]
对象中提取文件,并将其保存到所需的位置。
每个上传的文件首先会保存在服务器上的临时位置,然后将其实际保存到它的最终位置。
目标文件的名称可以是硬编码的,也可以从 request.files[file]
对象的 filename
属性中获取。但是,建议使用 secure_filename()
函数获取它的安全版本。
可以在 Flask 对象的配置设置中定义默认上传文件夹的路径和上传文件的最大大小。
app.config['UPLOAD_FOLDER'] 定义上传文件夹的路径
app.config['MAX_CONTENT_LENGTH'] 指定要上传的文件的最大大小(以字节为单位)
先来看个简单的例子:
首先,你需要一个HTML表单让用户可以选择并上传文件。这里是一个使用POST方法和enctype="multipart/form-data"
属性的基本表单示例:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Upload File</title>
</head>
<body><form action="/upload" method="post" enctype="multipart/form-data"><input type="file" name="file"><input type="submit"></form>
</body>
</html>
接下来,在你的Flask应用中,编写处理文件上传的视图函数:
from flask import Flask, request, redirect, url_for, render_template, flash
import os
from werkzeug.utils import secure_filenameapp = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads' # 设置上传文件存放的路径
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 限制文件大小为16MB# 确保保存上传文件目录存在
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)@app.route('/upload', methods=['GET', 'POST'])
def upload_file():if request.method == 'POST':# 检查是否有一个名为file的文件部分if 'file' not in request.files:flash('No file part')return redirect(request.url)file = request.files['file']# 如果用户没有选择文件,则浏览器也会提交一个空的file部分if file.filename == '':flash('No selected file')return redirect(request.url)if file:filename = secure_filename(file.filename) # 安全地获取文件名file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))return redirect(url_for('upload_file', filename=filename))return render_template('upload.html') # 假设你将上面的HTML放在templates/upload.html中if __name__ == '__main__':app.secret_key = 'your_secret_key' # 设置secret key用于flash消息app.run(debug=True)
启动应用,选择文件并上传:
提交后,项目目录下的upload文件夹多了刚刚上传的文件:
数据库操作
由于Flask不直接支持ORM操作,因此需要额外下载扩展。在Flask应用中进行ORM(对象关系映射)操作,最常用的库是SQLAlchemy。SQLAlchemy是一个功能强大的Python SQL工具包和ORM,它允许你以面向对象的方式与数据库交互。
pip install Flask-SQLAlchemy
pip install pymysql
以下是SQLAlchemy的配置以及简单用法
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemyapp = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:123456@localhost/flask_test'
# 禁用追踪修改以节省内存
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = Falsedb = SQLAlchemy(app)class User(db.Model):__tablename__ = 'user'id = db.Column(db.Integer, primary_key=True)username = db.Column(db.String(80), unique=True, nullable=False)email = db.Column(db.String(120), unique=True, nullable=False)def to_dict(self):return {'id': self.id, 'username': self.username, 'email': self.email}@app.route('/add', methods=['POST'])
def add_user():"""添加用户:return: """username = request.json.get('username')email = request.json.get('email')new_user = User(username=username, email=email)db.session.add(new_user)db.session.commit()return jsonify(new_user.to_dict()), 201@app.route('/users', methods=['GET'])
def get_users():"""获取用户:return: """users = User.query.all()return jsonify([user.to_dict() for user in users])@app.route('/user/<int:user_id>', methods=['PUT'])
def update_user(user_id):"""更新用户:return: """user = User.query.get_or_404(user_id)user.email = request.json.get('email')db.session.commit()return jsonify(user.to_dict())@app.route('/user/<int:user_id>', methods=['DELETE'])
def delete_user(user_id):"""删除用户:return: """user = User.query.get_or_404(user_id)db.session.delete(user)db.session.commit()return '', 204if __name__ == '__main__':with app.app_context():db.create_all() # 创建表结构app.run(debug=True)
注意:代码中的db.create_all() 是用来创建表结构的,但是如果当你定义的模型类有所改变,你再次执行db.create_all() 是检测不到的,举个例子:
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)这是你原本的模型代码,当你新增或减少了一个字段,然后你再去执行db.create_all()的时候,此时你的表结构并没有发生什么改变,那么这时候你就得借助第三方的一个扩展:
flask-migrate
pip install flask-migrate
下载之后有三个命令需要执行:
flask db init #只需要运行一次
flask db upgrade #生成迁移脚本,后续有表结构发生改变(或新增表),也要运行一次
flask db migrate #运行迁移脚本,后续表结构发生改变(或新增表),也需要运行一次
Flask-SQLAlchemy高级用法
Flask-SQLAlchemy 提供了强大的 ORM(对象关系映射)功能,可以帮助你更方便地进行数据库操作。除了基本的增删改查操作外,它还支持许多高级用法,如复杂查询、关联查询、事务管理等。以下是一些 Flask-SQLAlchemy 的高级用法示例:
1. 复杂查询
使用 `filter` 和 `filter_by`
- `filter_by` 更适合用于简单的等值匹配查询。
# 查找名字为 'Liming' 的用户
users = User.query.filter_by(name='Liming').all()
- `filter` 提供了更多的灵活性,支持复杂的条件表达式。
# 查找名字为 'Liming' 且邮箱包含 'example.com' 的用户
users = User.query.filter(User.name == 'Liming', User.email.like('%example.com%')).all()
使用 `join` 进行关联查询
假设你有两个模型 `User` 和 `Post`,其中 `Post` 模型有一个外键指向 `User` 模型。
class Post(db.Model):id = db.Column(db.Integer, primary_key=True)title = db.Column(db.String(80), nullable=False)body = db.Column(db.Text, nullable=False)user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)user = db.relationship('User', backref=db.backref('posts', lazy=True))# 查询所有用户的帖子
results = db.session.query(User, Post).join(Post).all()
for user, post in results:print(user.name, post.title)
2. 分页查询
分页查询在处理大量数据时非常有用。
page = request.args.get('page', 1, type=int)
per_page = 10 # 每页显示的数量
pagination = User.query.paginate(page, per_page, error_out=False)
users = pagination.items
3. 事务管理
使用 `db.session.commit()` 和 `db.session.rollback()` 来手动管理事务。
try:user1 = User(name='John', email='john@example.com')user2 = User(name='Jane', email='jane@example.com')db.session.add_all([user1, user2])db.session.commit()
except:db.session.rollback()raise
4. 使用 `hybrid_property` 创建动态属性
`hybrid_property` 可以让你创建一个既可以作为类方法也可以作为实例属性使用的属性。
from sqlalchemy.ext.hybrid import hybrid_propertyclass User(db.Model):id = db.Column(db.Integer, primary_key=True)first_name = db.Column(db.String(50))last_name = db.Column(db.String(50))@hybrid_propertydef full_name(self):return f"{self.first_name} {self.last_name}"
5. 自定义查询构造器
通过继承 `Query` 类并自定义查询构造器来扩展查询能力。
from flask_sqlalchemy import BaseQueryclass UserQuery(BaseQuery):def search_by_name(self, name):return self.filter(User.name.like(f'%{name}%'))db.Query = UserQuery# 使用自定义的查询构造器
users = User.query.search_by_name('Li').all()
6. 聚合函数
SQLAlchemy 支持多种聚合函数,如 `count`, `sum`, `avg` 等。
from sqlalchemy import func# 计算用户数量
user_count = db.session.query(func.count(User.id)).scalar()# 计算年龄总和
age_sum = db.session.query(func.sum(User.age)).scalar()
这些只是 Flask-SQLAlchemy 高级用法的一部分。根据你的具体需求,还可以探索更多功能,比如事件监听、索引、唯一约束等。掌握这些高级用法可以让你更加高效地构建和维护复杂的数据库应用。
类视图CBV
上面介绍的用法都是基于函数视图的,比如:
@app.route('/hello')
def hello_world():return 'hello world'
这种写法比较简单,但是不利于扩展,下面介绍基于面向对象的方法来实现视图;在 Flask 中,基于类的视图(Class-Based Views)通过 flask.views.View 和 flask.views.MethodView 提供了组织视图逻辑的另一种方式。相比于函数视图,类视图可以更好地支持代码重用、继承和扩展。以下是关于如何使用这些类视图的详细说明:
flask.views.View
from flask import Flask, views, requestapp = Flask(__name__)class MyView(views.View):def dispatch_request(self):return f"Hello, method used: {request.method}"app.add_url_rule('/myview', view_func=MyView.as_view('myview'))if __name__ == '__main__':app.run(debug=True)
添加额外参数
如果你需要向类视图传递一些初始化参数,可以通过 as_view 方法完成。这在需要根据不同上下文初始化视图时非常有用。
class GreetingView(views.View):def __init__(self, greeting_message):self.greeting_message = greeting_messagedef dispatch_request(self):return self.greeting_messagegreeting_view = GreetingView.as_view('greeting_view', greeting_message='Hello, world!')
app.add_url_rule('/greet', view_func=greeting_view)
构建动态路由以及添加装饰器
def log_access(f):@wraps(f)def decorated_function(*args, **kwargs):print("Accessing the view...")return f(*args, **kwargs)return decorated_functionclass GreetingView(MethodView):decorators = [log_access] #添加装饰器def get(self, name=None):print(111)if name:return f"Hello, {name}!"return f"Hello, {name}!"app.add_url_rule('/greet/', defaults={'name': "PMJ"}, view_func=GreetingView.as_view('greet'))
#构建动态路由
app.add_url_rule('/greet/<name>', view_func=GreetingView.as_view('greet_name'))
flask.views.MethodView
from flask import Flask, jsonify, request, viewsapp = Flask(__name__)class UserAPI(views.MethodView):def get(self, user_id):if user_id is None:# 返回用户列表return jsonify([{'id': 1, 'name': 'John Doe'}, {'id': 2, 'name': 'Jane Doe'}])else:# 返回单个用户的详细信息return jsonify({'id': user_id, 'name': 'John Doe'})def post(self):# 创建新用户data = request.get_json()return jsonify({'id': 3, 'name': data['name']}), 201def put(self, user_id):# 更新用户信息data = request.get_json()return jsonify({'id': user_id, 'name': data['name']})def delete(self, user_id):# 删除用户return '', 204# 将 URL 规则和视图类关联起来
user_view = UserAPI.as_view('user_api')# 定义路由规则
app.add_url_rule('/users/', defaults={'user_id': None}, view_func=user_view, methods=['GET'])
app.add_url_rule('/users/', view_func=user_view, methods=['POST'])
app.add_url_rule('/users/<int:user_id>', view_func=user_view, methods=['GET', 'PUT', 'DELETE'])if __name__ == '__main__':app.run(debug=True)
蓝图
Flask中的蓝图类似于Django中的app,都是为了更好的应用解耦,比如一个系统有订单模块、用户模块、商品模块等,就可以将它们分别创建一个蓝图来管理。如下:
文件对应代码:
#Order/views.py#函数视图版本
from flask import Blueprintorder = Blueprint('order', __name__)@order.route('/f1')
def f1():return 'f1'#类视图版本
from flask import Blueprint, viewsorder = Blueprint('order', __name__)class F1View(views.MethodView):def get(self):return 'f1'# 添加 URL 规则
order.add_url_rule('/f1', view_func=F1View.as_view('f1'))
#User/views/py#函数视图版本
from flask import Blueprintuser = Blueprint('user', __name__)@user.route('/f2')
def f2():return 'f2'#类视图版本
from flask import Blueprint, viewsuser = Blueprint('user', __name__)class F2View(views.MethodView):def get(self):return 'f2'# 添加 URL 规则
user.add_url_rule('/f2', view_func=F2View.as_view('f2'))
#lantu/app.pyfrom flask import Flaskfrom lantu.Order.views import order
from lantu.User.views import userdef create_app():app = Flask(__name__)#注册蓝图app.register_blueprint(order, url_prefix='/order')app.register_blueprint(user, url_prefix='/user')return app
#manage.pyfrom lantu.app import create_appif __name__ == '__main__':app = create_app()app.run(debug=True)
启动之后访问: