第136集 Flask路由与视图

1. 路由与视图概述

在Flask中,路由(Routing)是指将URL与对应的处理函数(视图函数)关联起来的机制。视图函数(View Function)是处理客户端请求并返回响应的函数。

1.1 路由的作用

  • 将用户请求的URL映射到相应的处理函数
  • 支持动态URL参数和多种HTTP方法
  • 实现应用的页面导航结构

1.2 视图函数的作用

  • 处理客户端的请求
  • 执行业务逻辑
  • 生成响应内容
  • 返回HTML、JSON、文件等各种类型的响应

2. 路由的高级用法

2.1 路由参数转换器

Flask内置了多种路由参数转换器,可以将URL中的参数转换为特定类型:

转换器 描述 示例
string 默认类型,接受不包含斜杠的文本 /user/<string:name>
int 接受正整数 /post/<int:id>
float 接受正浮点数 /price/<float:amount>
path 类似string,但可以包含斜杠 /file/<path:filename>
uuid 接受UUID字符串 /resource/<uuid:id>
from flask import Flask
app = Flask(__name__)

@app.route('/user/<string:name>')
def show_user(name):
    return f'User: {name}'

@app.route('/post/<int:id>')
def show_post(id):
    return f'Post ID: {id}'

2.2 自定义路由参数转换器

可以通过继承werkzeug.routing.BaseConverter类来创建自定义转换器:

from werkzeug.routing import BaseConverter
from flask import Flask

app = Flask(__name__)

# 自定义列表转换器,将逗号分隔的字符串转换为列表
class ListConverter(BaseConverter):
    def to_python(self, value):
        return value.split(',')
    
    def to_url(self, values):
        return ','.join(BaseConverter.to_url(self, item) for item in values)

# 注册自定义转换器
app.url_map.converters['list'] = ListConverter

@app.route('/items/<list:items>')
def get_items(items):
    return f'Items: {items}'

# 生成URL
url = app.url_for('get_items', items=['apple', 'banana', 'cherry'])
# 输出: /items/apple,banana,cherry

2.3 路由重定向

可以使用redirect()函数实现路由重定向:

from flask import Flask, redirect, url_for

app = Flask(__name__)

@app.route('/old')
def old_route():
    # 重定向到新路由
    return redirect(url_for('new_route'))

@app.route('/new')
def new_route():
    return 'This is the new route'

# 重定向到外部URL
@app.route('/external')
def external_route():
    return redirect('https://www.example.com')

2.4 路由别名

可以通过endpoint参数为路由指定别名,用于URL构建:

from flask import Flask, url_for

app = Flask(__name__)

@app.route('/user/profile', endpoint='profile')
def show_profile():
    return 'User Profile'

# 使用路由别名构建URL
url = url_for('profile')  # 输出: /user/profile

2.5 路由前缀

可以通过蓝图或应用配置为一组路由添加前缀:

from flask import Blueprint, Flask

app = Flask(__name__)

# 创建蓝图并指定前缀
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')

# 蓝图中的路由都会自动添加/auth前缀
@auth_bp.route('/login')
def login():
    return 'Login Page'

@auth_bp.route('/register')
def register():
    return 'Register Page'

# 注册蓝图
app.register_blueprint(auth_bp)

# 访问/login会得到404,需要访问/auth/login

2.6 路由方法

可以通过methods参数指定路由支持的HTTP方法:

from flask import Flask, request

app = Flask(__name__)

@app.route('/data', methods=['GET', 'POST', 'PUT', 'DELETE'])
def handle_data():
    if request.method == 'GET':
        return 'GET Request'
    elif request.method == 'POST':
        return 'POST Request'
    elif request.method == 'PUT':
        return 'PUT Request'
    elif request.method == 'DELETE':
        return 'DELETE Request'

3. 视图函数的高级用法

3.1 视图函数的返回值类型

视图函数可以返回多种类型的响应:

3.1.1 返回字符串

@app.route('/')
def index():
    return 'Hello World'

3.1.2 返回元组

@app.route('/custom')
def custom_response():
    # 返回(响应内容, 状态码, 响应头)
    return 'Custom Response', 200, {'X-Custom-Header': 'Value'}

3.1.3 返回Response对象

from flask import Flask, Response

app = Flask(__name__)

@app.route('/response')
def response_object():
    response = Response('Response Object', status=200, mimetype='text/plain')
    response.headers['X-Custom'] = 'Value'
    return response

3.1.4 返回JSON

from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/json')
def json_response():
    data = {'name': '张三', 'age': 25}
    return jsonify(data)

3.2 视图装饰器

可以使用装饰器为视图函数添加额外功能:

from flask import Flask
import functools

app = Flask(__name__)

# 定义一个装饰器,记录请求时间
def log_request_time(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        import time
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f'Request took {end_time - start_time:.2f} seconds')
        return result
    return wrapper

# 应用装饰器到视图函数
@app.route('/')
@log_request_time
def index():
    return 'Hello World'

3.3 视图函数中的变量作用域

注意视图函数中变量的作用域,避免使用全局变量存储请求相关的数据:

# 错误示例:使用全局变量存储用户信息
current_user = None

@app.route('/login', methods=['POST'])
def login():
    global current_user
    current_user = request.form['username']
    return 'Login successful'

# 正确做法:使用session存储用户信息
from flask import session

app.secret_key = 'secret_key'

@app.route('/login', methods=['POST'])
def login():
    session['username'] = request.form['username']
    return 'Login successful'

4. URL构建

Flask提供了url_for()函数来构建URL,避免硬编码URL:

4.1 基本用法

from flask import Flask, url_for

app = Flask(__name__)

@app.route('/user/<name>')
def user_profile(name):
    return f'User: {name}'

@app.route('/post/<int:id>')
def show_post(id):
    return f'Post ID: {id}'

# 构建URL
with app.test_request_context():
    # 构建/user/john的URL
    url1 = url_for('user_profile', name='john')
    # 构建/post/123的URL
    url2 = url_for('show_post', id=123)
    # 添加查询参数
    url3 = url_for('user_profile', name='john', page=1, per_page=10)
    
    print(url1)  # /user/john
    print(url2)  # /post/123
    print(url3)  # /user/john?page=1&per_page=10

4.2 URL构建的好处

  • 避免硬编码URL,提高代码可维护性
  • 自动处理URL编码
  • 支持动态参数和查询参数
  • 与路由配置保持一致

4.3 构建静态文件URL

# 构建静态文件URL
url = url_for('static', filename='css/style.css')
# 输出: /static/css/style.css

5. 请求钩子

请求钩子(Request Hooks)是在请求处理过程中执行的函数,用于在请求前后执行特定操作:

5.1 常用的请求钩子

5.1.1 before_first_request

在处理第一个请求之前执行:

@app.before_first_request
def before_first_request():
    print('This runs before the first request')

5.1.2 before_request

在每次请求之前执行:

@app.before_request
def before_request():
    print('This runs before every request')

5.1.3 after_request

在每次请求之后执行(如果没有异常):

@app.after_request
def after_request(response):
    print('This runs after every successful request')
    return response  # 必须返回response对象

5.1.4 teardown_request

在每次请求之后执行(无论是否有异常):

@app.teardown_request
def teardown_request(exception):
    print('This runs after every request, even if an exception occurs')

5.1.5 teardown_appcontext

在应用上下文被销毁时执行:

@app.teardown_appcontext
def teardown_appcontext(exception):
    print('This runs when the application context is destroyed')

5.2 请求钩子的应用场景

  • 初始化数据库连接
  • 记录请求日志
  • 验证用户身份
  • 设置响应头
  • 清理资源

6. 基于类的视图

Flask支持基于类的视图(Class-Based Views, CBV),与基于函数的视图(Function-Based Views, FBV)相比,具有更好的代码组织和复用性。

6.1 基本用法

from flask import Flask, render_template
from flask.views import View

app = Flask(__name__)

# 继承View类创建基于类的视图
class HelloView(View):
    def dispatch_request(self):
        return 'Hello from Class-Based View'

# 注册视图
app.add_url_rule('/', view_func=HelloView.as_view('hello'))

6.2 内置的基于类的视图

Flask提供了一些常用的基于类的视图:

6.2.1 MethodView

根据HTTP方法路由到不同的处理函数:

from flask.views import MethodView

class DataView(MethodView):
    def get(self):
        return 'GET Request'
    
    def post(self):
        return 'POST Request'
    
    def put(self):
        return 'PUT Request'
    
    def delete(self):
        return 'DELETE Request'

# 注册视图,自动处理不同的HTTP方法
app.add_url_rule('/data', view_func=DataView.as_view('data'))

6.2.2 TemplateView

渲染模板的视图:

from flask.views import TemplateView

class IndexView(TemplateView):
    template_name = 'index.html'
    
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['message'] = 'Hello from TemplateView'
        return context

app.add_url_rule('/', view_func=IndexView.as_view('index'))

6.2.3 RedirectView

重定向视图:

from flask.views import RedirectView

class RedirectToHomeView(RedirectView):
    url = '/'

class RedirectToProfileView(RedirectView):
    # 使用endpoint而不是url
    endpoint = 'profile'
    permanent = True  # 301永久重定向

app.add_url_rule('/go-home', view_func=RedirectToHomeView.as_view('go_home'))
app.add_url_rule('/go-profile', view_func=RedirectToProfileView.as_view('go_profile'))

6.3 基于类的视图的优势

  • 代码复用性更好
  • 支持多重继承
  • HTTP方法分离更清晰
  • 便于扩展和维护

7. 蓝图(Blueprint)

蓝图是Flask中用于组织路由和视图的方式,可以将应用分解为多个模块:

7.1 蓝图的创建

from flask import Blueprint

# 创建蓝图
main_bp = Blueprint('main', __name__)
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')

7.2 在蓝图中定义路由

# 在main_bp蓝图中定义路由
@main_bp.route('/')
def index():
    return 'Home Page'

@main_bp.route('/about')
def about():
    return 'About Page'

# 在auth_bp蓝图中定义路由(自动添加/auth前缀)
@auth_bp.route('/login')
def login():
    return 'Login Page'

@auth_bp.route('/register')
def register():
    return 'Register Page'

7.3 注册蓝图

from flask import Flask

app = Flask(__name__)

# 注册蓝图
app.register_blueprint(main_bp)
app.register_blueprint(auth_bp)

7.4 蓝图的模板和静态文件

7.4.1 蓝图的模板目录

# 创建蓝图时指定模板目录
bp = Blueprint('admin', __name__, template_folder='templates')

7.4.2 在蓝图中使用模板

@bp.route('/admin')
def admin():
    return render_template('admin/index.html')

7.4.3 蓝图的静态文件

# 创建蓝图时指定静态目录
bp = Blueprint('admin', __name__, static_folder='static')

7.4.4 在模板中引用蓝图的静态文件

<link rel="stylesheet" href="{{ url_for('admin.static', filename='css/admin.css') }}">

7.5 蓝图的请求钩子

# 为蓝图注册请求钩子
@bp.before_request
def bp_before_request():
    print('This runs before every request in the blueprint')

7.6 蓝图的错误处理

# 为蓝图注册错误处理
@bp.errorhandler(404)
def bp_page_not_found(error):
    return render_template('admin/404.html'), 404

7.7 蓝图的优势

  • 模块化组织代码
  • 支持模板和静态文件的隔离
  • 便于团队协作开发
  • 提高代码的可维护性

8. 路由与视图的最佳实践

8.1 路由设计原则

  • 使用有意义的URL路径
  • 保持URL的简洁性
  • 遵循RESTful设计原则
  • 避免过深的URL层级
  • 使用HTTP方法表达操作意图

8.2 视图函数设计原则

  • 保持视图函数的简洁性
  • 避免在视图函数中编写复杂的业务逻辑
  • 使用装饰器复用代码
  • 适当使用基于类的视图

8.3 蓝图使用原则

  • 按照功能模块划分蓝图
  • 为蓝图设置合理的URL前缀
  • 使用蓝图隔离模板和静态文件
  • 避免蓝图之间的循环依赖

8.4 URL构建原则

  • 始终使用url_for()构建URL
  • 避免硬编码URL
  • 使用路由别名提高代码可读性

8.5 请求钩子使用原则

  • 不要在请求钩子中执行耗时操作
  • 避免在请求钩子中修改请求数据
  • 合理使用不同类型的请求钩子

9. 综合示例

下面是一个综合示例,展示了路由与视图的各种高级用法:

from flask import Flask, Blueprint, render_template, jsonify, redirect, url_for, request, session
from flask.views import MethodView
from werkzeug.routing import BaseConverter

# 创建应用
app = Flask(__name__)
app.secret_key = 'your-secret-key'

# 自定义URL转换器
class ListConverter(BaseConverter):
    def to_python(self, value):
        return value.split(',')
    
    def to_url(self, values):
        return ','.join(BaseConverter.to_url(self, item) for item in values)

app.url_map.converters['list'] = ListConverter

# 创建蓝图
api_bp = Blueprint('api', __name__, url_prefix='/api')

# 应用级别的请求钩子
@app.before_request
def before_request():
    if 'username' not in session and request.endpoint not in ['auth.login', 'static']:
        return redirect(url_for('auth.login'))

# 认证蓝图
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')

@auth_bp.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        session['username'] = request.form['username']
        return redirect(url_for('main.index'))
    return render_template('login.html')

@auth_bp.route('/logout')
def logout():
    session.pop('username', None)
    return redirect(url_for('auth.login'))

# 主蓝图
main_bp = Blueprint('main', __name__)

@main_bp.route('/')
def index():
    return render_template('index.html', username=session.get('username'))

@main_bp.route('/user/<string:name>')
def user_profile(name):
    return render_template('profile.html', name=name)

# API蓝图中的基于类的视图
class DataView(MethodView):
    def get(self):
        return jsonify({'message': 'GET request'})
    
    def post(self):
        return jsonify({'message': 'POST request'})

api_bp.add_url_rule('/data', view_func=DataView.as_view('data'))

# 自定义转换器示例
@api_bp.route('/items/<list:items>')
def get_items(items):
    return jsonify({'items': items})

# 注册蓝图
app.register_blueprint(auth_bp)
app.register_blueprint(main_bp)
app.register_blueprint(api_bp)

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

10. 总结

本集主要介绍了Flask路由与视图的高级用法,包括:

  1. 路由的高级用法:自定义URL转换器、路由重定向、路由别名、路由前缀等
  2. 视图函数的高级用法:多种返回值类型、视图装饰器、变量作用域
  3. URL构建:使用url_for()函数构建URL的方法和优势
  4. 请求钩子before_first_requestbefore_requestafter_request等钩子的使用
  5. 基于类的视图ViewMethodViewTemplateViewRedirectView等类的使用
  6. 蓝图:蓝图的创建、注册、模板和静态文件处理
  7. 最佳实践:路由设计、视图函数设计、蓝图使用等方面的最佳实践

通过学习这些内容,你已经可以灵活运用Flask的路由与视图功能,构建结构清晰、易于维护的Web应用了。在后续的学习中,我们将深入学习Flask的模板、表单处理和数据库集成等高级功能。

« 上一篇 Flask框架基础 下一篇 » Flask模板