现在位置: 首页 > Flask 教程 > 正文

视图函数 — 渲染文章列表与详情页

本章你将学会用视图函数从数据库查询数据,渲染首页和详情页,并用 Blueprint 拆分路由。


视图函数的职责

Flask 视图函数的职责与 Django 一致:接收请求 → 查询数据 → 渲染模板

实例

# 文件路径:app.py
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=pythonrequest.args.get('key')

Flask 路由支持多种类型转换器:<int:id>(整数)、<string:slug>(默认,不包含 /)、<path:path>(包含 /)、<float:price> 等。


文章详情页

实例

# 文件路径:app.py 追加
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 错误页面

实例

@app.errorhandler(404)
def not_found(error):
    return render_template('404.html'), 404

分类筛选

实例

# 文件路径:app.py 改造 index 视图
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

实例

# 文件路径:app/blueprints/main.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)

实例

# 文件路径:app/blueprints/posts.py
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:

实例

# 文件路径:app.py
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

实例

<!-- 文件路径:app/templates/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 按模块拆分路由。

博客现在有了首页文章列表、分类筛选和详情页三个完整页面。