简单全文搜索

2017-06-1425211 阅读27 评论

搜索是一个复杂的功能,但对于一些简单的搜索任务,我们可以使用 Django Model 层提供的一些内置方法来完成。现在我们来为我们的博客提供一个简单的搜索功能。

概述

博客文章通常包含标题和正文两个部分。当用户输入某个关键词进行搜索后,我们希望为用户显示标题和正文中含有被搜索关键词的全部文章。整个搜索的过程如下:

  1. 用户在搜素框中输入搜索关键词,假设为 “django”,然后用户点击了搜索按钮提交其输入的结果到服务器。
  2. 服务器接收到用户输入的搜索关键词 “django” 后去数据库查找文章标题和正文中含有该关键词的全部文章。
  3. 服务器将查询结果返回给用户。

整个过程就是这样,下面来看看 Django 如何用实现这些过程。

将关键词提交给服务器

先来回顾一下我们的 Django 博客的 Post(文章)模型:

blog/models.py

class Post(models.Model):
    # 标题
    title = models.CharField(max_length=70)
    # 正文
    body = models.TextField()

    # 其他属性...

    def __str__(self):
        return self.title

先看到第 1 步,用户在搜索框输入搜索关键词,因此我们要在博客上为用户提供一个搜索表单,HTML 表单代码大概像这样:

base.html

<form role="search" method="get" id="searchform" action="{% url 'blog:search' %}">
  <input type="search" name="q" placeholder="搜索" required>
  <button type="submit"><span class="ion-ios-search-strong"></span></button>
</form>

用户输入了搜索关键词并点击了搜索按钮后,数据就被发送给了 Django 后台服务器。表单的 action 属性的值为 {% url 'blog:search' %}(虽然我们还没有写这个视图函数),表明用户提交的结果将被发送给 blog 应用下 search 视图函数对应的 URL。

查找含有搜索关键词的文章

搜索的功能将由 search 视图函数提供,代码写在 blog/views.py 里:

blog/views.py

from django.db.models import Q

def search(request):
    q = request.GET.get('q')
    error_msg = ''

    if not q:
        error_msg = "请输入关键词"
        return render(request, 'blog/index.html', {'error_msg': error_msg})

    post_list = Post.objects.filter(Q(title__icontains=q) | Q(body__icontains=q))
    return render(request, 'blog/index.html', {'error_msg': error_msg,
                                               'post_list': post_list})

首先我们使用 request.GET.get('q') 获取到用户提交的搜索关键词。用户通过表单 get 方法提交的数据 Django 为我们保存在 request.GET 里,这是一个类似于 Python 字典的对象,所以我们使用 get 方法从字典里取出键 q 对应的值,即用户的搜索关键词。这里字典的键之所以叫 q 是因为我们的表单中搜索框 input 的 name 属性的值是 q,如果修改了 name 属性的值,那么这个键的名称也要相应修改。

接下来我们做了一个小小的校验,如果用户没有输入搜索关键词而提交了表单,我们就无需执行查询,我们就在模板中渲染一个错误提示信息。

如果用户输入了搜索关键词,我们就通过 filter 方法从数据库里过滤出符合条件的所有文章。这里的过滤条件是 title__icontains=q,即 title 中包含(contains)关键字 q,前缀 i 表示不区分大小写。这里 icontains 是查询表达式(Field lookups),我们在之前也使用过其他类似的查询表达式,其用法是在模型需要筛选的属性后面跟上两个下划线。Django 内置了很多查询表达式,建议过一遍 Django 官方留个印象,了解每个表达式的作用,以后碰到相关的需求就可以快速定位到文档查询其用途 Field lookups

此外我们这里从 from django.db.models 中引入了一个新的东西:Q 对象。Q 对象用于包装查询表达式,其作用是为了提供复杂的查询逻辑。例如这里 Q(title__icontains=q) | Q(body__icontains=q) 表示标题(title)含有关键词 q 或者正文(body)含有关键词 q ,或逻辑使用 | 符号。如果不用 Q 对象,就只能写成 title__icontains=q, body__icontains=q,这就变成标题(title)含有关键词 q 正文(body)含有关键词 q,就达不到我们想要的目的。

渲染搜索结果

接下来就是渲染搜索结果页面,这里我们复用了 index.html 模板,唯一需要修改的地方就是当有错误信息时,index.html 应该显示错误信息。只需要在文章列表前加个 error_msg 模板变量即可:

templates/blog/index.html

{% extends 'base.html' %}
{% block main %}
  {% if error_msg %}
    <p>{{ error_msg }}</p>
  {% endif %}

  {% for post in post_list %}
    ...
  {% empty %}
    <div class="no-post">暂时还没有发布的文章!</div>
  {% endfor %}
{% endblock main %}

绑定 URL

有了视图函数后记得把视图函数映射到相应了 URL,如下。

blog/urls.py

urlpatterns = [
    # 其他 url 配置
    url(r'^search/$', views.search, name='search'),
]

大功告成,在导航栏尝试输入一些关键词,看看效果吧!

当然这样的搜索功能是非常简略的,难以满足一些复杂的搜索需求。编写一个搜索引擎是一个大工程,好在 django-haystack 这款第三方 app 为我们完成了全部工作。使用它我们可以实现更加复杂的搜索功能,比如全文检索、按搜索相关度排序、关键字高亮等等类似于百度搜索的功能,功能十分强大。当然其使用也会复杂一些,下一篇教程将向大家介绍 django-haystack 的使用方法。

总结

本章节的代码位于:Step25: simple search

如果遇到问题,请通过下面的方式寻求帮助。

-- EOF --

27 评论
登录后回复
南南南南南极姑娘
2019-07-26 14:07:29

博主,请问如果添加一个新增博客功能,该如何实现新增页面选择分类和标签的展示,以及值的传递呢

回复
DewangYang
2019-01-06 21:44:05

请问一下 <script src="{% static 'js/script.js' %}"></script> 为什么要写在base.html的最后,写在前面却不行?

回复
追梦人物 DewangYang
2019-01-11 23:44:45

一般放在文档最后,这样等 dom 全部加载完毕再执行 js 脚本。

回复
DewangYang 追梦人物
2019-01-19 23:19:12

好的,谢谢!

回复
czr
2018-12-16 19:50:53

提出一个错误

q = request.GET.get('q')

这里应该是['q'],

回复
追梦人物 czr
2019-01-03 21:36:23

这不是错误,如果用 ['q'],有可能抛出 IndexError 异常

回复
jinyong
2018-10-24 16:19:39

教程很详细  感谢感谢

回复
言淅皁
2018-10-19 11:37:17

请问博主主页的搜索框为什么渲染不出来?只有个放大镜图标

<form role="search" method="get" id="searchform" action="{% url 'blog:search' %}"> 

 <input type="search" name="q" placeholder="搜索" required>

 <button type="submit"><span class="ion-ios-search-strong"></span></button>

回复
言淅皁 言淅皁
2018-10-19 18:52:19

完整代码是这样的,我没有改动过

<div id="header-search-box"> 

 <a id="search-menu" href="#"><span id="search-icon" class="ion-ios-search-strong"></span></a>

 <div id="search-form" class="search-form"> 

 <form role="search" method="get" id="searchform" action="{% url 'haystack_search' %}"> 

 <input type="search" name="q" placeholder="搜索" required> 

 <button type="submit"><span class="ion-ios-search-strong"></span></button>

 </form> 

 </div>

</div>

回复
追梦人物 言淅皁
2018-11-24 17:07:57

要点一下那个搜索按钮才会出来吧?

回复
用户7275707198 言淅皁
2019-08-07 22:04:47

为什么我连放大镜都没有

回复
三根K线改三观
2018-08-11 08:30:12

博主,按你的教程做不出效果。

你是不是少贴了前端的JS代码?

不然这样的话,url是http:/www.xxx.com/search/

后端怎么取得到搜索关键词?

回复
追梦人物 三根K线改三观
2018-08-23 09:17:36

这样获取不到的,需要通过 url 参数,‘?q=keyword’ 来吧关键字传给后端。

回复
1007307796
2018-05-29 18:58:56

博主您好,这串代码    

```

if not q:

error_msg = "请输入关键词" 

return render(request, 'blog/index.html', {'error_msg': error_msg})

```

```

<input type="search" name="q" placeholder="搜索" required>

```

在一起会产生冲突,导致渲染的错误页面效果无法得到展示,建议把required去掉

不止理解得对不对?

回复
桀骜不驯镍铬合金 1007307796
2018-06-03 22:25:46

是这样的, 不过我觉得不用error_msg,直接用required反而更加简洁直观

回复
追梦人物 1007307796
2018-06-04 18:10:13

是的,required 是在客户端验证,考虑到有些用户会关闭掉浏览器端验证,因此在后台在做一次验证是有必要的。

回复
Foreve1Xf
2018-04-09 21:51:39
if not q:
        error_msg = "请输入关键词"
        return render(request, 'blog/index.html', {'error_msg': error_msg})

请问这段的代码的意思是不是如果没有输入关键词就会显示“请输入关键词”?

如果是的话为什么不输入东西直接点搜索没有反应呢?

回复
追梦人物 Foreve1Xf
2018-04-10 13:52:58

是的。渲染错误信息模板页面。

回复
zhouanqi
2018-03-09 15:47:34

如果有人按照教程改了发现还是不能检索的话,记得去看一下base.html文件中 搜索 地方的修改,改动了两个地方之后就可以成功了~

回复
曹宇聪
2017-10-18 20:56:55

Reverse for 'search' with arguments '()' and keyword arguments '{}' not found. 0 pattern(s) tried: []

折腾了一天,真烦啊,还是不知道为什么。本地运行没问题,上传到服务器部署就出现这种问题。各种代码检查还是这样。。。

回复
尘缘_5717
2017-09-15 14:49:57

点击搜索后报如下错误:local variable 'q' referenced before assignment 

模板中name = 'q'已经写了,

 <div id="search-form" class="search-form"> <form role="search" method="get" id="searchform" action="{% url 'blog:search' %}"> <input type="search" name="q" placeholder="搜索" required> <button type="submit"><span class="ion-ios-search-strong"></span></button> </form></div> 

在视图中q = request.GET.get(q)一句,括号内的q有个红色提示:unresolved reference 'q' 

以上问题的可能原因是什么? 

非常感谢!

回复
追梦人物 尘缘_5717
2017-09-15 14:58:19

q是一个字符串,使用 request.GET.get('q')

回复
尘缘_5717 追梦人物
2017-09-15 15:17:37

果然如此,非常感谢;

回复
木犀草号
2017-08-02 09:53:10

为什么我点的那个搜索那个放大镜的图标,没有反应呢?

回复
追梦人物 木犀草号
2017-08-02 10:07:41

肯定是 js 没有正确加载,检查一下模板中的 js 文件,确保每一个都正确加载。

回复
木犀草号 追梦人物
2017-08-02 10:34:21

博主您好,刚刚我按你说了,检查了一下js,包括路径。。。
并没有发现问题呀。。

<script src="{% static 'blog/js/jquery-2.1.3.min.js' %}"></script>
<script src="{% static 'blog/js/bootstrap.min.js' %}"></script>
<script src="{% static 'blog/js/pace.min.js' %}"></script>
<script src="{% static 'blog/js/modernizr.custom.js' %}"></script>
回复
木犀草号 追梦人物
2017-08-02 11:05:24

谢谢博主,我找到原因了,是base.html页面最下面最下面,引的那个js文件的路径不对,开头head里的js是对的

回复