视图函数 — 渲染文章列表与详情页
本章你将学会用视图函数从数据库查询数据,渲染首页和详情页,并用 Blueprint 拆分路由。
视图函数的职责
Flask 视图函数的职责与 Django 一致:接收请求 → 查询数据 → 渲染模板。
实例
from flask import Flask, render_template
from models import Post, Category
@app.route("/")
def index():
"""博客首页:展示所有文章"""
posts = Post.query.order_by(Post.created_at.desc()).all()
categories = Category.query.all()
return render_template('index.html', posts=posts, categories=categories)
路径参数与查询参数
| 参数类型 | URL 示例 | 视图获取方式 |
|---|---|---|
| 路径参数 | /post/3 | 路由中 <类型:变量名> → 函数参数 |
| 查询参数 | /?category=python | request.args.get('key') |
Flask 路由支持多种类型转换器:<int:id>(整数)、<string:slug>(默认,不包含 /)、<path:path>(包含 /)、<float:price> 等。
文章详情页
实例
from flask import abort
@app.route("/post/<int:post_id>")
def post_detail(post_id):
"""文章详情页"""
# get_or_404 等价于:Post.query.get(post_id) or abort(404)
post = Post.query.get_or_404(post_id,
description=f'文章 #{post_id} 不存在')
return render_template('post_detail.html', post=post)
自定义 404 错误页面
实例
def not_found(error):
return render_template('404.html'), 404
分类筛选
实例
from flask import request
@app.route("/")
def index():
category_slug = request.args.get('category', '')
keyword = request.args.get('q', '') # 搜索关键词(第六章实现)
posts_query = Post.query.order_by(Post.created_at.desc())
# 按分类筛选
if category_slug:
posts_query = posts_query.join(Category).filter(Category.slug == category_slug)
posts = posts_query.all()
categories = Category.query.all()
return render_template('index.html',
posts=posts, categories=categories,
category_slug=category_slug, keyword=keyword)
request.args是一个不可变的 MultiDict,用于读取 URL 查询参数。用.get('key', '默认值')安全获取,参数不存在时返回默认值而非报错。
Blueprint — 拆分路由
当视图函数增多,全部堆在 app.py 中会失控。
Blueprint 是 Flask 的路由分组方案:按功能模块拆分视图,通过 app.register_blueprint() 注册。
app/ ├── blueprints/ │ ├── __init__.py │ ├── main.py # 首页、关于、搜索 │ └── posts.py # 文章列表、详情 ├── templates/ ├── models.py └── __init__.py
实例
from flask import Blueprint, render_template, request
from app.models import Post, Category
# Blueprint('蓝图名', __name__, url_prefix='/')
main_bp = Blueprint('main', __name__)
@main_bp.route("/")
def index():
"""博客首页"""
category_slug = request.args.get('category', '')
posts_query = Post.query.order_by(Post.created_at.desc())
if category_slug:
posts_query = posts_query.join(Category).filter(Category.slug == category_slug)
return render_template('index.html',
posts=posts_query.all(),
categories=Category.query.all(),
category_slug=category_slug)
实例
from flask import Blueprint, render_template, abort
from app.models import Post
posts_bp = Blueprint('posts', __name__)
@posts_bp.route("/post/<int:post_id>")
def post_detail(post_id):
post = Post.query.get_or_404(post_id)
return render_template('post_detail.html', post=post)
在应用入口注册 Blueprint:
实例
from flask import Flask
from app.blueprints.main import main_bp
from app.blueprints.posts import posts_bp
app = Flask(__name__)
# 注册蓝图:所有蓝图的视图函数都会合并到同一个应用中
app.register_blueprint(main_bp)
app.register_blueprint(posts_bp)
Blueprint 可以指定
url_prefix(如url_prefix='/blog'),则蓝图内所有路由都会自动加上这个前缀。Blueprint 也可以有自己的模板和静态文件目录。
创建首页和详情页模板
index.html
实例
{% extends 'base.html' %}
{% block title %}RUNOOB 博客 - 首页{% endblock %}
{% block content %}
<h2 class="section-title">最新文章</h2>
<!-- 分类筛选导航 -->
<div class="category-bar">
<a href="{{ url_for('main.index') }}"
class="{% if not category_slug %}active{% endif %}">全部</a>
{% for cat in categories %}
<a href="{{ url_for('main.index', category=cat.slug) }}"
class="{% if category_slug == cat.slug %}active{% endif %}">
{{ cat.name }}
</a>
{% endfor %}
</div>
{% if posts %}
<div class="article-grid">
{% for post in posts %}
<a href="{{ url_for('posts.post_detail', post_id=post.id) }}" class="card-link">
<div class="article-card">
<div class="card-content">
<span class="card-category">{{ post.category.name }}</span>
<h3>{{ post.title }}</h3>
<p>{{ post.summary|truncate(80) }}</p>
<span class="card-date">{{ post.created_at.strftime('%Y-%m-%d') }}</span>
</div>
</div>
</a>
{% endfor %}
</div>
{% else %}
<p class="empty-tip">该分类下暂无文章。</p>
{% endif %}
{% endblock %}
url_for() 中使用 Blueprint 视图时,格式为
url_for('蓝图名.函数名'),如url_for('posts.post_detail', post_id=3)。注意与 Django 的{% url %}标签对比:Flask 用{{ url_for() }},可以嵌套在 Python 表达式中更灵活。
本章小结
本章你掌握了 Flask 视图和路由组织:Post.query 查询数据、路径参数 <int:post_id> 和查询参数 request.args、get_or_404 安全查询、abort(404) 和自定义错误页、Blueprint 按模块拆分路由。
博客现在有了首页文章列表、分类筛选和详情页三个完整页面。
