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

关键词搜索与分页

本章你将学会用 SQLAlchemy 实现联合搜索,并用 Flask-SQLAlchemy 内置分页功能优化列表页。


ilike — 大小写不敏感搜索

SQLAlchemy 的 ilike 方法实现了大小写不敏感的模糊匹配。

它对应 SQL 的 LIKE 操作符,但忽略大小写。

实例

# 单字段搜索
posts = Post.query.filter(Post.title.ilike('%flask%')).all()

# 多字段联合搜索:or_() 表示 OR 关系
from sqlalchemy import or_
posts = Post.query.filter(
    or_(
        Post.title.ilike('%flask%'),
        Post.summary.ilike('%flask%')
    )
).all()
方法SQL 操作符区分大小写
like()LIKE区分
ilike()LIKE(忽略大小写)不区分
contains()LIKE '%x%'区分
startswith()LIKE 'x%'区分
in_()IN精确匹配

paginate — 分页

Flask-SQLAlchemy 提供了 paginate() 方法,一次调用返回当前页数据 + 分页元信息。

实例

# 从 main.py 改造 index 视图
page = request.args.get('page', 1, type=int)  # 当前页码(默认第 1 页)
per_page = 6                                    # 每页显示 6 篇文章

# paginate(page, per_page, error_out)
# error_out=False:页码超出范围时不报 404,而是返回空列表
pagination = posts_query.paginate(page=page, per_page=per_page, error_out=False)
posts = pagination.items     # 当前页的文章列表

pagination 对象属性

属性类型说明
items列表当前页的数据
pageint当前页码
pagesint总页数
totalint总记录数
has_prevbool是否有上一页
has_nextbool是否有下一页
prev_numint上一页页码
next_numint下一页页码

完整的搜索 + 分页视图

实例

# 文件路径:app/blueprints/main.py
from flask import Blueprint, render_template, request
from sqlalchemy import or_
from app.models import Post, Category

main_bp = Blueprint('main', __name__)

@main_bp.route("/")
def index():
    category_slug = request.args.get('category', '')
    keyword = request.args.get('q', '').strip()
    page = request.args.get('page', 1, type=int)
    per_page = 6

    # 基础查询
    posts_query = Post.query.order_by(Post.created_at.desc())

    # 分类筛选
    if category_slug:
        posts_query = posts_query.join(Category).filter(Category.slug == category_slug)

    # 关键词搜索(标题或摘要)
    if keyword:
        posts_query = posts_query.filter(
            or_(
                Post.title.ilike(f'%{keyword}%'),
                Post.summary.ilike(f'%{keyword}%')
            )
        )

    # 分页
    pagination = posts_query.paginate(
        page=page, per_page=per_page, error_out=False)
    posts = pagination.items

    return render_template('index.html',
        posts=posts, pagination=pagination,
        categories=Category.query.all(),
        category_slug=category_slug, keyword=keyword)

查询参数通过 URL 传递(如 /?q=flask&page=2),分页导航的链接必须带上当前的搜索和分类参数,否则翻页后条件会丢失。


模板中的搜索框和分页导航

实例

<!-- 文件路径:app/templates/index.html -->
{% extends 'base.html' %}

{% block title %}RUNOOB 博客 - 首页{% endblock %}

{% block content %}
<h2 class="section-title">最新文章</h2>

<!-- 搜索框 -->
<div class="search-bar">
    <form method="get" action="{{ url_for('main.index') }}">
        <input type="text" name="q" value="{{ keyword }}"
               placeholder="搜索文章标题或摘要..." class="search-input">
        {% if keyword %}
        <a href="{{ url_for('main.index', category=category_slug) }}" class="clear-btn">✕</a>
        {% endif %}
    </form>
</div>

<!-- 分类筛选 -->
<div class="category-bar">
    <a href="{{ url_for('main.index', q=keyword) }}"
       class="{% if not category_slug %}active{% endif %}">全部</a>
    {% for cat in categories %}
    <a href="{{ url_for('main.index', category=cat.slug, q=keyword) }}"
       class="{% if category_slug == cat.slug %}active{% endif %}">
        {{ cat.name }}
    </a>
    {% endfor %}
</div>

<p class="result-info">
    共 {{ pagination.total }} 篇文章
    {% if keyword %},搜索「{{ keyword }}」{% endif %}
</p>

{% 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>

<!-- 分页导航 -->
{% if pagination.pages > 1 %}
<div class="pagination">
    {% if pagination.has_prev %}
    <a href="{{ url_for('main.index', page=pagination.prev_num, category=category_slug, q=keyword) }}">← 上一页</a>
    {% endif %}

    {% for page_num in range(1, pagination.pages + 1) %}
        {% if page_num == pagination.page %}
        <span class="current">{{ page_num }}</span>
        {% else %}
        <a href="{{ url_for('main.index', page=page_num, category=category_slug, q=keyword) }}">{{ page_num }}</a>
        {% endif %}
    {% endfor %}

    {% if pagination.has_next %}
    <a href="{{ url_for('main.index', page=pagination.next_num, category=category_slug, q=keyword) }}">下一页 →</a>
    {% endif %}
</div>
{% endif %}

{% else %}
<p class="empty-tip">没有找到匹配的文章。</p>
{% endif %}
{% endblock %}

本章小结

本章你掌握了列表页的实用功能:ilike 大小写不敏感搜索、or_() 多字段联合搜索、paginate() 分页查询、模板中渲染分页导航并保留筛选参数。

搜索 + 分类筛选 + 分页,三者可以任意组合,条件不会丢失。