不管是最新文章列表也好、最热文章列表也罢,都是把所有的文章数据全部展示给了用户。
但是如果用户只关心某些特定类型的文章,抽取全部数据就显得既不方便、又不效率了。
因此,给用户提供一个搜索功能,提供给用户感兴趣的几篇文章,就大有用处了。
准备工作
逻辑
尽管细节不同,但是搜索和列表有很多类似的地方:它们都是先检索出一些文章对象,并将其展示给用户。上一章已经说过,代码重复是万恶之源,好的实践必须把功能类似的模块尽量复用起来。基于这个原则,我们打算继续在原有的 article_list() 上添砖加瓦,让其功能更加的强大。
随着项目越来越庞大,又需要将功能复杂的模块拆分成更简单的多个模块。目前我们还不用担心这个问题。
更酷的是,我们希望搜索出来的文章也能够按照时间、热度等各种方式进行排序。因此需要构造一个新的参数 search,能够和之前的 order 参数进行联合查询。
GET 还是 POST?
用户搜索内容时提交的文本,可以用 GET 请求提交,也可以用 POST 请求提交。根据实际的需要进行选择。
因为 order 是用 GET 提交的,并且翻页是 GET 请求,因此选择 GET 方式提交搜索文本,可以方便地和之前的模块结合起来。
之前我们已经用过表单组件 <form method=”POST”>,通过 POST 请求提交数据。表单组件同样也可以提交 GET 请求,只要去掉 method=”POST” 属性就可以了。
Q 对象
Model.objects.all() 能够返回表中的所有对象。
对应的,Model.objects.filter(**kwargs) 可以返回与给定参数匹配的部分对象。
还有 Model.objects.exclude(**kwargs) 返回与给定参数不匹配的对象
如果想对多个参数进行查询怎么办?比如同时查询文章标题和正文内容。这时候就需要 Q 对象。
视图
那么按照前面说好的,修改 article_list():
article/views.py
…
# 引入 Q 对象
from django.db.models import Q
def article_list(request):
search = request.GET.get(‘search’)
order = request.GET.get(‘order’)
# 用户搜索逻辑
if search:
if order == ‘total_views’:
# 用 Q 对象 进行联合搜索
article_list = ArticlePost.objects.filter(
Q(title__icontains=search) |
Q(body__icontains=search)
).order_by(‘-total_views’)
else:
article_list = ArticlePost.objects.filter(
Q(title__icontains=search) |
Q(body__icontains=search)
)
else:
# 将 search 参数重置为空
search = ”
if order == ‘total_views’:
article_list = ArticlePost.objects.all().order_by(‘-total_views’)
else:
article_list = ArticlePost.objects.all()
paginator = Paginator(article_list, 3)
page = request.GET.get(‘page’)
articles = paginator.get_page(page)
# 增加 search 到 context
context = {‘articles’: articles, ‘order’: order, ‘search’: search}
return render(request, ‘article/list.html’, context)
…
重点知识如下:
新增参数 search,存放需要搜索的文本。若 search 不为空,则检索特定文章对象。
留意 filter 中 Q 对象的用法。Q(title__icontains=search) 意思是在模型的 title 字段查询,icontains 是不区分大小写的包含,中间用两个下划线隔开。search 是需要查询的文本。多个 Q 对象用管道符 | 隔开,就达到了联合查询的目的。
icontains 不区分大小写,对应的 contains 区分大小写
为什么需要 search = ” 语句?如果用户没有搜索操作,则 search = request.GET.get(‘search’) 会使得 search = None,而这个值传递到模板中会错误地转换成 ”None” 字符串!等同于用户在搜索“None”关键字,这明显是错误的。
完成本章内容后,可以删除此语句看看效果
除此之外还有一点小的代码优化工作:将需要重复用到 order = request.GET.get(‘order’) 提取到顶部,让模块稍稍清爽一点。
模板
还是修改文章列表的模板文件。
需要修改的内容稍多,仔细一些不要看错:
templates/article/list.html
…
<div class=”container”>
<!– 修改,面包屑的 href 增加 search 参数 –>
<nav aria-label=”breadcrumb”>
<ol class=”breadcrumb”>
<li class=”breadcrumb-item”>
<a href=”{% url ‘article:article_list’ %}?search={{search}}”>
最新
</a>
</li>
<li class=”breadcrumb-item”>
<a href=”{% url ‘article:article_list’ %}?order=total_views&search={{search}}”>
最热
</a>
</li>
</ol>
</nav>
<!– 新增,搜索栏 –>
<div class=”row”>
<div class=”col-auto mr-auto”>
<form class=”form-inline” >
<label class=”sr-only”>content</label>
<input type=”text”
class=”form-control mb-2 mr-sm-2″
name=”search”
placeholder=” 搜索文章 …”
required
>
</form>
</div>
</div>
<!– 新增,搜索提示语 –>
{% if search %}
{% if articles %}
<h4><span style=”color: red”>”{{search}}”</span> 的搜索结果如下:</h4>
<hr>
{% else %}
<h4> 暂无 <span style=”color: red”>”{{search}}”</span> 有关的文章。</h4>
<hr>
{% endif %}
{% endif %}
…
<!– 修改,页码 href 增加 search 参数 –>
<a href=”?page=1&order={{order}}&search={{search}}” class=”btn btn-success”>
…
<a href=”?page={{articles.previous_page_number}}&order={{order}}&search={{search}}” class=”btn btn-secondary”>
…
<a href=”?page={{articles.next_page_number}}&order={{order}}&search={{search}}” class=”btn btn-secondary”>
…
<a href=”?page={{articles.paginator.num_pages}}&order={{order}}&search={{search}}”class=”btn btn-success”>
…
面包屑组件、页码组件都改动了 href:增加了 search 参数
新增搜索栏,以 GET 请求提交 search 参数;required 属性阻止用户提交空白文本
新增搜索提示语。好的 UI 必须让用户了解当前的状态
Emmm… 想想也不用改动其他东西了。
开始测试吧!
测试
还是打开文章列表页面:
出现了搜索栏!并且翻页、最热等功能一切正常。
在搜索栏中输入“PYTHON”,结果如下:
成功将标题或正文中含有 ”python” 关键字的文章检索出来了,并且是忽略大小写的。点击最热可以让检索结果按浏览量排序,翻页功能也正常工作。很好,达成了目标!
学到这里的读者应该感到自豪:你用了同一个 url,集成了很多种功能,展示了不同的内容!这对新手来说其实并不容易做到。
这种方法有一个小缺点:有的时候 url 中会包含像 search=”(空值)这样无意义的字符串,强迫症简直不能忍。所幸这无伤大雅,通常用户并不会关心你的 url 是什么样子的,只要网页美观好用就行。
总结
本章完成了一个简单的搜索功能,这对于个人博客来说应该够用了。
更加复杂、深度定制的搜索可以借助第三方模块,如 Haystack。
另外笔者这样实现搜索不一定是最优的。相信你已经掌握多种途径来实现搜索功能了(POST 请求?搜索专用视图?另写 url?),尽情尝试一番吧。
有疑问请在杜赛的个人网站留言,我会尽快回复。
或 Email 私信我:dusaiphoto@foxmail.com
项目完整代码:Django_blog_tutorial
转载请注明出处。