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 bodytype=int让page自动转成整数,乱填也不会炸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. 再加一个「日期范围」筛选(可选)
假设 Note 有 addtime 字段,可以再加两个 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.py 或 queries.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. 小结
这一篇核心四件事:
- 搜索用 GET ---
request.args读参数 - 动态拼查询 --- 有条件才
.filter() - 分页带参数 ---
url_for里把q等一并传入 - 查询抽函数 --- 视图变薄,后面好扩展