Flask 笔记五:给列表加上搜索和筛选

1. 这篇学完你能做什么

  • 在列表页加一个 搜索框
  • 用 GET 参数(如 ?q=关键词)做筛选
  • 搜索后 分页仍然有效(不会翻页就丢条件)
  • 把查询逻辑从视图里 抽一小段出来,避免 views.py 越写越长

2. 为什么搜索用 GET,不用 POST

方式 适合什么 搜索为什么选它
GET 查询、筛选、分页 地址栏带参数,可刷新、可收藏、可分享链接
POST 新增、修改、删除 会改数据,还要 CSRF

搜索 不改变数据库,只是「换一种看法」看同一张表,所以用 GET 最合适。

表单写法:

<form method="get" action="{{ url_for('home.note_list') }}">

<input type="text" name="q" value="{{ q or '' }}" placeholder="搜标题">

<button type="submit">搜索</button>

</form>

注意:method="get",字段名是 q(query 的缩写,习惯写法)。


3. 视图里读取参数并过滤

app/home/views.py 的列表路由里加几行:

from sqlalchemy import or_

@home.route("/notes/")

def note_list():

q = (request.args.get("q") or "").strip()

page = request.args.get("page", 1, type=int)

query = Note.query.order_by(Note.addtime.desc())

if q:

like = f"%{q}%"

query = query.filter(

or_(

Note.title.like(like),

Note.content.like(like),

)

)

page_data = query.paginate(page=page, per_page=10)

return render_template(

"home/note_list.html",

page_data=page_data,

delete_form=DeleteForm(),

q=q,

)

几个要点:

  • request.args 读的是 URL 上的参数,不是表单 POST body
  • type=intpage 自动转成整数,乱填也不会炸
  • like("%关键词%") 是 模糊匹配,标题和内容都能搜

4. 模板里显示「当前搜了什么」

列表页顶部除了搜索框,建议加一句提示:

<form method="get" action="{{ url_for('home.note_list') }}">

<input type="text" name="q" value="{{ q or '' }}" placeholder="搜标题或内容">

<button type="submit">搜索</button>

{% if q %}

<a href="{{ url_for('home.note_list') }}">清空</a>

{% endif %}

</form>

{% if q %}

<p>搜索「{{ q }}」,共 {{ page_data.total }} 条</p>

{% endif %}

「清空」就是 不带任何参数 回到 /notes/,比手动删输入框更省事。


5. 分页时别把搜索条件弄丢

这是新手最常踩的坑之一。

错误写法(翻页后 q 没了):

<a href="?page=2">下一页</a>

正确写法:把现有参数一起带上。Flask 可以这么写:

{% if page_data.has_next %}

<a href="{{ url_for('home.note_list', page=page_data.next_num, q=q) }}">下一页</a>

{% endif %}

{% if page_data.has_prev %}

<a href="{{ url_for('home.note_list', page=page_data.prev_num, q=q) }}">上一页</a>

{% endif %}

规则很简单:列表页有哪些筛选条件,分页链接就要传哪些。

以后加了「按日期筛」,分页里也要加 date_from=...date_to=...


6. 再加一个「日期范围」筛选(可选)

假设 Noteaddtime 字段,可以再加两个 GET 参数:

date_from = (request.args.get("date_from") or "").strip()

date_to = (request.args.get("date_to") or "").strip()

if date_from:

query = query.filter(Note.addtime >= date_from)

if date_to:

query = query.filter(Note.addtime <= date_to + " 23:59:59")

表单里用 <input type="date" name="date_from"> 即可。

多个条件可以 同时生效:既搜关键词,又限定日期------因为它们都是往同一个 query 上继续 .filter()


7. 把查询逻辑抽出去(视图别越写越长)

当筛选变多(关键词 + 日期 + 状态 + 排序),全堆在 views.py 里会难读。

入门阶段不用上复杂架构,抽一个函数 就够:

app/note_service.py

from sqlalchemy import or_

from app.models import Note

def search_notes(*, q="", date_from="", date_to="", page=1, per_page=10):

query = Note.query.order_by(Note.addtime.desc())

q = (q or "").strip()

if q:

like = f"%{q}%"

query = query.filter(

or_(Note.title.like(like), Note.content.like(like))

)

date_from = (date_from or "").strip()

date_to = (date_to or "").strip()

if date_from:

query = query.filter(Note.addtime >= date_from)

if date_to:

query = query.filter(Note.addtime <= date_to + " 23:59:59")

page_data = query.paginate(page=page, per_page=per_page)

return page_data

视图变成:

from app.note_service import search_notes

@home.route("/notes/")

def note_list():

q = (request.args.get("q") or "").strip()

date_from = (request.args.get("date_from") or "").strip()

date_to = (request.args.get("date_to") or "").strip()

page = request.args.get("page", 1, type=int)

page_data = search_notes(

q=q,

date_from=date_from,

date_to=date_to,

page=page,

)

return render_template(

"home/note_list.html",

page_data=page_data,

delete_form=DeleteForm(),

q=q,

date_from=date_from,

date_to=date_to,

)

习惯可以记成:

  • 视图:读参数、调函数、选模板
  • service 函数:拼 SQLAlchemy 查询
  • 模板:展示表单和结果

真实项目里常见这种分层,只是文件名可能叫 xxx_service.pyqueries.py,套路一样。


8. 一张图串起来

用户输入「会议」点搜索

GET /notes/?q=会议

views.note_list 读 request.args

search_notes(q="会议") 拼 query.filter(...)

paginate → render_template

列表只显示匹配项;翻页仍带 q=会议


9. 新手常踩的 4 个坑

坑 1:搜索用了 POST

每次搜都要提交表单,URL 不变,刷新页面还可能重复提交。

查数据用 GET。

坑 2:分页丢了 q

第二页突然变成「全量列表」。

url_for(..., q=q) 别忘传。

坑 3:空字符串也当条件去 filter

q="" 时不应加 like 条件。

.strip(),再 if q:

坑 4:所有逻辑堆在一个 200 行的视图里

短期能跑,长期难改。

查询抽成函数,视图保持十几行。


10. 和「多数据源搜索」的关系(只讲思路)

以后你可能不只查一张表,而是:

  • 表 A 里有没有这条记录
  • 表 B 里有没有
  • 表 C 里有没有

页面仍是一个搜索框,但后端会 分别查几块数据,再汇总展示。

本篇的 GET 参数 + search_xxx() 函数,就是这类功能的基础:

一个入口读参数,多个小函数各查各的,模板分区展示结果。


11. 小结

这一篇核心四件事:

  1. 搜索用 GET --- request.args 读参数
  2. 动态拼查询 --- 有条件才 .filter()
  3. 分页带参数 --- url_for 里把 q 等一并传入
  4. 查询抽函数 --- 视图变薄,后面好扩展