分类筛选与关键词搜索
本章你将学会处理 URL 查询参数,实现分类筛选导航和关键词搜索功能。
URL 查询参数
查询参数是 URL 中问号后面的部分,如 /?category=django&q=入门。
在 Django 视图函数中,通过 request.GET 获取这些参数。
实例
def index(request):
# request.GET 是一个类似字典的对象
# .get('key', '默认值') 安全获取参数,不存在时返回默认值
category_slug = request.GET.get('category', '') # 分类筛选
keyword = request.GET.get('q', '') # 搜索关键词
print(f'分类:{category_slug}, 搜索:{keyword}')
# request.GET 是一个类似字典的对象
# .get('key', '默认值') 安全获取参数,不存在时返回默认值
category_slug = request.GET.get('category', '') # 分类筛选
keyword = request.GET.get('q', '') # 搜索关键词
print(f'分类:{category_slug}, 搜索:{keyword}')
ORM 链式过滤与 Q 对象
筛选和搜索需要动态拼接查询条件。
实例
# 文件路径:blog/views.py 更新 index 函数
from django.shortcuts import render
from django.db.models import Q # Q 对象用于组合复杂查询条件
from .models import Post, Category
def index(request):
"""博客首页:展示文章列表,支持分类筛选 + 关键词搜索"""
posts = Post.objects.all().order_by('-created_at')
# 1. 获取查询参数
category_slug = request.GET.get('category', '')
keyword = request.GET.get('q', '')
# 2. 按分类筛选
if category_slug:
# category__slug:通过外键访问分类表的 slug 字段(双下划线)
posts = posts.filter(category__slug=category_slug)
# 3. 按关键词搜索(标题或摘要)
if keyword:
# Q(title__icontains=...) | Q(summary__icontains=...):OR 组合
# icontains:大小写不敏感的包含查询
posts = posts.filter(
Q(title__icontains=keyword) | Q(summary__icontains=keyword)
)
# 4. 获取所有分类(用于渲染筛选导航)
categories = Category.objects.all()
context = {
'posts': posts,
'categories': categories,
'category_slug': category_slug, # 传给模板,用于高亮当前选中
'keyword': keyword, # 回显搜索关键词
'title': 'RUNOOB 博客 - 首页',
}
return render(request, 'blog/index.html', context)
from django.shortcuts import render
from django.db.models import Q # Q 对象用于组合复杂查询条件
from .models import Post, Category
def index(request):
"""博客首页:展示文章列表,支持分类筛选 + 关键词搜索"""
posts = Post.objects.all().order_by('-created_at')
# 1. 获取查询参数
category_slug = request.GET.get('category', '')
keyword = request.GET.get('q', '')
# 2. 按分类筛选
if category_slug:
# category__slug:通过外键访问分类表的 slug 字段(双下划线)
posts = posts.filter(category__slug=category_slug)
# 3. 按关键词搜索(标题或摘要)
if keyword:
# Q(title__icontains=...) | Q(summary__icontains=...):OR 组合
# icontains:大小写不敏感的包含查询
posts = posts.filter(
Q(title__icontains=keyword) | Q(summary__icontains=keyword)
)
# 4. 获取所有分类(用于渲染筛选导航)
categories = Category.objects.all()
context = {
'posts': posts,
'categories': categories,
'category_slug': category_slug, # 传给模板,用于高亮当前选中
'keyword': keyword, # 回显搜索关键词
'title': 'RUNOOB 博客 - 首页',
}
return render(request, 'blog/index.html', context)
ORM 查询方法说明
| 方法 | 含义 | 示例 |
|---|---|---|
field__exact | 精确匹配 | title__exact='Django' |
field__iexact | 忽略大小写精确匹配 | title__iexact='django' |
field__contains | 包含(区分大小写) | title__contains='入门' |
field__icontains | 包含(忽略大小写) | title__icontains='django' |
field__gte / lte | 大于等于 / 小于等于 | created_at__gte='2024-01-01' |
field__in=[...] | 在列表中 | category__in=[1, 2, 3] |
双下划线
__是 Django ORM 的核心约定:用于访问关联模型的字段、或指定字段的查询方式。例如category__slug表示跨越外键到 Category 表的 slug 字段。
更新首页模板
在文章列表上方加入分类筛选导航和搜索框。
实例
<!-- 文件路径:blog/templates/blog/index.html -->
{% extends 'blog/base.html' %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<h2 class="section-title">最新文章</h2>
<!-- 搜索框 -->
<div class="search-bar">
<!-- 表单 method="get":提交后参数出现在 URL 中(/search/?q=xxx) -->
<form method="get" action="{% url 'index' %}">
<input
type="text"
name="q"
value="{{ keyword }}"
placeholder="搜索文章标题或摘要..."
class="search-input"
/>
{% if keyword %}
<a href="{% url 'index' %}" class="clear-btn">✕</a>
{% endif %}
</form>
</div>
<!-- 分类筛选导航 -->
<div class="category-bar">
<!-- 「全部」按钮:不带 category 参数 -->
<a href="{% url 'index' %}"
class="{% if not category_slug %}active{% endif %}">全部</a>
{% for cat in categories %}
<a href="{% url 'index' %}?category={{ cat.slug }}{% if keyword %}&q={{ keyword }}{% endif %}"
class="{% if category_slug == cat.slug %}active{% endif %}">
{{ cat.name }}
</a>
{% endfor %}
</div>
<!-- 筛选结果统计 -->
<p class="result-info">
共 {{ posts.count }} 篇
{% if keyword %},搜索「{{ keyword }}」{% endif %}
</p>
{% if posts %}
<div class="article-grid">
{% for post in posts %}
<a href="{% url 'post_detail' post.pk %}" 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|truncatechars:80 }}</p>
<span class="card-date">{{ post.created_at|date:"Y-m-d" }}</span>
</div>
</div>
</a>
{% endfor %}
</div>
{% else %}
<p class="empty-tip">没有找到匹配的文章</p>
{% endif %}
{% endblock %}
{% extends 'blog/base.html' %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<h2 class="section-title">最新文章</h2>
<!-- 搜索框 -->
<div class="search-bar">
<!-- 表单 method="get":提交后参数出现在 URL 中(/search/?q=xxx) -->
<form method="get" action="{% url 'index' %}">
<input
type="text"
name="q"
value="{{ keyword }}"
placeholder="搜索文章标题或摘要..."
class="search-input"
/>
{% if keyword %}
<a href="{% url 'index' %}" class="clear-btn">✕</a>
{% endif %}
</form>
</div>
<!-- 分类筛选导航 -->
<div class="category-bar">
<!-- 「全部」按钮:不带 category 参数 -->
<a href="{% url 'index' %}"
class="{% if not category_slug %}active{% endif %}">全部</a>
{% for cat in categories %}
<a href="{% url 'index' %}?category={{ cat.slug }}{% if keyword %}&q={{ keyword }}{% endif %}"
class="{% if category_slug == cat.slug %}active{% endif %}">
{{ cat.name }}
</a>
{% endfor %}
</div>
<!-- 筛选结果统计 -->
<p class="result-info">
共 {{ posts.count }} 篇
{% if keyword %},搜索「{{ keyword }}」{% endif %}
</p>
{% if posts %}
<div class="article-grid">
{% for post in posts %}
<a href="{% url 'post_detail' post.pk %}" 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|truncatechars:80 }}</p>
<span class="card-date">{{ post.created_at|date:"Y-m-d" }}</span>
</div>
</div>
</a>
{% endfor %}
</div>
{% else %}
<p class="empty-tip">没有找到匹配的文章</p>
{% endif %}
{% endblock %}
筛选导航与搜索框样式
实例
/* 在 base.html style 中追加 */
.search-bar {
margin-bottom: 20px;
}
.search-input {
width: 100%;
padding: 12px 16px;
border: 2px solid #eee;
border-radius: 8px;
font-size: 15px;
outline: none;
transition: border-color 0.2s;
}
.search-input:focus {
border-color: #2c3e50;
}
.clear-btn {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
color: #999;
text-decoration: none;
font-size: 18px;
}
.category-bar {
display: flex;
gap: 10px;
margin-bottom: 16px;
flex-wrap: wrap;
}
.category-bar a {
padding: 6px 16px;
border: 1px solid #ddd;
border-radius: 20px;
text-decoration: none;
color: #333;
font-size: 14px;
transition: all 0.2s;
}
.category-bar a.active {
background: #2c3e50;
color: #fff;
border-color: #2c3e50;
}
.category-bar a:hover {
border-color: #2c3e50;
}
.result-info {
color: #999;
font-size: 14px;
margin-bottom: 16px;
}
.card-link {
text-decoration: none;
color: inherit;
}
.search-bar {
margin-bottom: 20px;
}
.search-input {
width: 100%;
padding: 12px 16px;
border: 2px solid #eee;
border-radius: 8px;
font-size: 15px;
outline: none;
transition: border-color 0.2s;
}
.search-input:focus {
border-color: #2c3e50;
}
.clear-btn {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
color: #999;
text-decoration: none;
font-size: 18px;
}
.category-bar {
display: flex;
gap: 10px;
margin-bottom: 16px;
flex-wrap: wrap;
}
.category-bar a {
padding: 6px 16px;
border: 1px solid #ddd;
border-radius: 20px;
text-decoration: none;
color: #333;
font-size: 14px;
transition: all 0.2s;
}
.category-bar a.active {
background: #2c3e50;
color: #fff;
border-color: #2c3e50;
}
.category-bar a:hover {
border-color: #2c3e50;
}
.result-info {
color: #999;
font-size: 14px;
margin-bottom: 16px;
}
.card-link {
text-decoration: none;
color: inherit;
}
分类按钮的链接同时保留了
category和keyword参数:?category={{ cat.slug }}&q={{ keyword }}。这样用户先搜索再点分类后,搜索条件不会丢失,两个条件同时生效。
动手:测试筛选与搜索
启动服务器,验证以下几点:
- 点击分类按钮,只显示对应分类的文章
- 输入关键词,实时过滤标题和摘要
- 分类 + 搜索叠加:过滤结果同时满足两个条件
- "全部" 按钮清除分类筛选,恢复显示所有文章
本章小结
本章你掌握了 Django 中处理 URL 查询参数的方式:request.GET.get() 获取参数、ORM 链式过滤 + Q 对象实现复杂查询、模板中动态拼接 URL 参数实现筛选链接。
分类筛选和搜索功能让用户能够快速找到感兴趣的文章。
