首页

打开 blueprints/front.py 文件,然后找到 index 视图函数,将代码修改为返回 index.html 模板,代码如下。

@bp.route("/")
def index():
    return render_template("front/index.html")

在浏览器中访问 http://127.0.0.1:5000 ,可以看到如图 9-32 所示的页面。

image 2025 01 22 15 58 38 150
Figure 1. 图9-32 渲染index.html模板后的效果

在首页中,左侧帖子列表和右侧板块列表都需要先从视图函数中提取数据,再传给 index.html 模板文件,我们将 index 视图函数代码修改为如下形式。

@bp.route("/")
def index():
    posts = PostModel.query.all()
    boards = BoardModel.query.all()
    context = {
        "posts": posts,
        "boards": boards
    }
    return render_template("front/index.html", **context)

接下来在 templates/front/index.html 文件中,循环帖子列表和板块列表。修改后的帖子列表和板块列表代码如下。

image 2025 01 22 16 00 11 147

上述代码中,首先循环帖子列表,然后把帖子的标题、作者、发表时间以及该帖子评论的数量都显示出来。在板块列表,通过 current_board 参数来表示当前选中的是哪个板块,该参数后续在实现根据板块过滤帖子功能时再加进去。

生成帖子测试数据

在帖子超过一定数量时应该进行分页,但是现在数据库中帖子的数量还太少,我们来生成一些测试数据。这里需要用到 Faker 库来生成随机文字,Faker 库是一个用来生成随机数据的库,可以生成姓名、邮箱、地址以及段落等内容。在 PyCharm 的 Terminal 中通过以下命令安装 Faker 库。

$ pip install faker

更多 Faker 的使用文档请参考 https://faker.readthedocs.io/en/master/index.html。

我们把生成帖子测试数据写成命令,在 commands.py 文件中添加以下代码。

from faker import Faker
import random

def create_test_post():
    fake = Faker(locale="zh_CN")
    author = UserModel.query.first()
    boards = BoardModel.query.all()

    click.echo("开始生成测试帖子...")

    for x in range(98):
        title = fake.sentence()
        content = fake.paragraph(nb_sentences=10)
        random_index = random.randint(0, 4)
        board = boards[random_index]
        post = PostModel(title=title, content=content, board=board, author=author)
        db.session.add(post)

    db.session.commit()
    click.echo("测试帖子生成成功!")

在 app.py 文件中注册命令,代码如下。

$ app.cli.command("create-test-post")(commands.create_test_post)

打开 PyCharm 的 Terminal,执行 flask create-test-post 命令,即可随机生成 98 篇测试帖子。

使用Flask-Paginate实现分页

在 Flask 项目中使用 Flask-Paginate 插件可以轻松地实现分页,Flask-Paginate 用的是 Bootstrap 样式,正好与现在的项目架构一样,如果要使用其他样式,可以修改 CSS 属性。在 PyCharm 的 Terminal 中输入以下命令安装 Flask-Paginate。

$ pip install flask-paginate

在 config.py 文件的 BaseConfig 中添加 PER_PAGE_COUNT 配置,用来指定一页中展示多少数据,这里设置 10 条,代码如下。

class BaseConfig:
...

PER_PAGE_COUNT = 10

在 blueprints/front.py 的 index 视图函数中,按照当前的页码提取对应的数据,代码如下。

@bp.route("/")
def index():
    boards = BoardModel.query.all()

    # 获取页码参数
    page = request.args.get("page", type=int, default=1)

    # 当前 page 下的起始位置
    start = (page - 1) * current_app.config.get("PER_PAGE_COUNT")

    # 当前 page 下的结束位置
    end = start + current_app.config.get("PER_PAGE_COUNT")

    # 查询对象
    query_obj = PostModel.query.order_by(PostModel.create_time.desc())

    # 总共有多少帖子
    total = query_obj.count()

    # 当前 page 下的帖子列表
    posts = query_obj.slice(start, end)

    # 分页对象
    pagination = Pagination(
        bs_version=4, page=page, total=total, outer_window=0,
        inner_window=2, alignment="center"
    )

    context = {
        "posts": posts,
        "boards": boards,
        "pagination": pagination
    }

    return render_template("front/index.html", **context)

上述代码中,首先从 URL 参数中提取 page,然后计算当前 page 下提取帖子的起始位置和结束位置。接着按照帖子的发布时间进行排序,统计总共有多少帖子,然后使用 start 和 end 提取帖子列表,最后构建分页对象 Pagination, Pagination 中的参数说明如下。

  • bs_version: Bootstrap版本,我们的项目中用的是4,所以这里写4。

  • page:当前的page页码,从1开始。

  • total:所有帖子的总数量。

  • outer_window:页码数量太多,出现省略号后,在省略号外层要显示多少个页码,如这里设置为0,那么就会显示1个页码,总是比设置的多1。

  • inner_window:出现省略号后,当前页码左右两边要显示几个页码。

  • alignment:分页盒子在父盒子内的对齐方式,默认是左对齐,这里修改为中间对齐。

下面在 templates/front/index.html 中的帖子列表的最底部添加 {{ pagination. links }} ,示例代码如下。

<div class="post-group">
    <ul class="post-list-group">
        <!-- 帖子列表内容 -->
    </ul>
    {{ pagination.links }}
</div>

在浏览器中重新访问首页,可以看到如图 9-33 所示的帖子分页效果。

image 2025 01 22 16 08 26 870
Figure 2. 图9-33 帖子分页

过滤帖子

在图 9-33 中,实现了所有板块下的帖子分页功能,本节再来实现帖子按照板块过滤的功能。我们规定板块参数同样用查询字符串的方式传递,在 blueprints/front.py 的 index 视图函数中,将代码修改如下。

@bp.route("/")
def index():
    boards = BoardModel.query.all()

    # 获取页码参数
    page = request.args.get("page", type=int, default=1)

    # 新增:获取板块参数
    board_id = request.args.get("board_id", type=int, default=0)

    # 当前 page 下的起始位置
    start = (page - 1) * current_app.config.get("PER_PAGE_COUNT")

    # 当前 page 下的结束位置
    end = start + current_app.config.get("PER_PAGE_COUNT")

    # 查询对象
    query_obj = PostModel.query.order_by(PostModel.create_time.desc())

    # 新增:过滤帖子
    if board_id:
        query_obj = query_obj.filter_by(board_id=board_id)

    # 总共有多少帖子
    total = query_obj.count()

    # 当前 page 下的帖子列表
    posts = query_obj.slice(start, end)

    # 分页对象
    pagination = Pagination(
        bs_version=4, page=page, total=total, outer_window=0,
        inner_window=2, alignment="center"
    )

    context = {
        "posts": posts,
        "boards": boards,
        "pagination": pagination,
        # 新增:当前板块
        "current_board": board_id
    }

    return render_template("front/index.html", **context)

上述代码中,注释中以 “新增:” 开头的是新增的代码。在上述代码中,我们先从 request.args 中获取 board_id 参数,如果获取到了,则让 query_obj 对 board_id 进行过滤后重新赋值,这样得到的 query_obj 就是该板块下所有的帖子。最后为了能在首页中显示当前的板块,将 board_id 赋值给 current_board 传给模板,为了在首页中实现板块过滤,给板块列表加上超链接,修改后的代码如下。

...
{% for board in boards %}
    {% if board.id == current_board %}
        <a href="{{ url_for('front.index', board_id=board.id) }}" class="list-group-item active">{{ board.name }}</a>
    {% else %}
        <a href="{{ url_for('front.index', board_id=board.id) }}" class="list-group-item">{{ board.name }}</a>
    {% endif %}
{% endfor %}
...

加载以上代码后,在首页中单击右侧的某个板块,就可以实现根据板块过滤帖子的功能了。