做者:HelloGitHub-追梦人物css
在 django 博客教程中,咱们使用了 django-haystack 和 Elasticsearch 进行文章内容的搜索。django-haystack 默认返回的搜索结果是一个相似于 django QuerySet
的对象,须要配合模板系统使用,由于未被序列化,因此没法直接用于 django-rest-framework 的接口。固然解决方案也很简单,编写相应的序列化器将返回结果序列化就能够了。html
可是,经过以前的功能咱们看到,使用 django-rest-framework 是一个近乎标准化但又枯燥无聊的过程:首先是编写序列化器用于序列化资源,而后是编写视图集,提供对资源各种操做的接口。既然是标准化的东西,确定已经有人写好了相关的功能以供复用。此时就要发挥开源社区的力量,去 GitHub 使用关键词 rest haystack 搜索,果真搜到一个 drf-haystack 开源项目,专门用于解决 django-rest-framework 和 haystack 结合使用的问题。所以咱们就再也不重复造轮子,直接使用开源第三方库来实现咱们的需求。python
既然要使用第三方库,第一步固然是安装它,进入项目根目录,运行:git
$ pipenv install drf-haystack
复制代码
因为须要使用到搜索功能,所以须要启动 Elasticsearch 服务,最简单的方式就是使用项目中编排的 Elasticsearch 镜像启动容器。github
项目根目录下运行以下命令启动所有项目所需的容器服务:web
$ docker-compose -f local.yml up --build
复制代码
启动完成后运行 docker ps 命令能够检查到以下 2 个运行的容器,说明启动成功:docker
hellodjango_rest_framework_tutorial_local hellodjango_rest_framework_tutorial_elasticsearch_local 复制代码
接着建立一些文章,以便用于搜索测试,能够本身在 admin 后台添加,固然最简单的方法是运行项目中的 fake.py 脚本,批量生成测试数据:数据库
$ docker-compose -f local.yml run --rm hellodjango.rest.framework.tutorial.local python -m scripts.fake
复制代码
测试文章生成后,还要运行下面的命令给文章的内容建立索引,这样搜索引擎才能根据索引搜索到相应的内容:django
$ docker-compose -f local.yml run --rm hellodjango.rest.framework.tutorial.local python manage.py rebuild_index
# 输出以下 Your choices after this are to restore from backups or rebuild via the `rebuild_index` command. Are you sure you wish to continue? [y/N] y Removing all documents from your index because you said so. All documents removed. Indexing 201 文章 GET /hellodjango_blog_tutorial/_mapping [status:404 request:0.005s] 复制代码
注意flask
若是生成索引时看到以下错误:
elasticsearch.exceptions.ConnectionError: ConnectionError(<urllib3.connection.HTTPConnection object at 0x7f25daa83c50>: Failed to establish a new connection: [Errno -2] Name does not resolve) caused by: NewConnectionError(<urllib3.connection.HTTPConnection object at 0x7f25daa83c50>: Failed to establish a new connection: [Errno -2] Name does not resolve)
这是因为项目配置中 Elasticsearch 服务的 URL 配置出错致使,解决方法是进入 settings/local.py 配置文件中,将搜索设置改成下面的内容:
HAYSTACK_CONNECTIONS['default']['URL'] = 'http://elasticsearch.local:9200/'
由于这个 URL 地址需和容器编排文件 local.yml 中指定的容器服务名一致 Docker 才能正确解析。
如今万事具有了,数据库中已经有了文章,搜索服务已经有了文章的索引,只须要等待客户端来进行查询,而后返回结果。因此接下来就进入到 django-rest-framework 标准开发流程:定义序列化器 -> 编写视图 -> 配置路由,这样一个标准的搜索接口就开发出来了。
先来定义序列化器,粗略过一遍 drf-haystack 官方文档,依葫芦画瓢建立文章(Post) 的 Serializer
blog/serializers.py
from drf_haystack.serializers import HaystackSerializerMixin class PostHaystackSerializer(HaystackSerializerMixin, PostListSerializer): class Meta(PostListSerializer.Meta): search_fields = ["text"] 复制代码
根据官方文档的介绍,为了复用已经定义好用于序列化文章列表的序列化器,咱们直接继承了 PostListSerializer
,同时咱们还混入了 HaystackSerializerMixin
,这是 drf-haystack 的混入类,提供搜索结果序列化相关的功能。
另外内部类 Meta
一样继承 PostListSerializer.Meta
,这样就无需重复定义序列化字段列表 fields
。关键的地方在这个 search_fields
,这个列表声明用于搜索的字段(一般都定义为索引字段),咱们在上一部教程设置 django-haystack 时,文章的索引字段设置的名字叫 text,若是对这一块有疑惑,能够简单回顾一下 Django Haystack 全文检索与关键词高亮 中的内容。
而后编写视图集,需继承 HaystackViewSet
:
blog/views.py
from drf_haystack.viewsets import HaystackViewSet from .serializers import PostHaystackSerializer class PostSearchView(HaystackViewSet): index_models = [Post] serializer_class = PostHaystackSerializer 复制代码
这个视图集很是简单,只须要经过类属性 index_models
声明须要搜索的模型,以及搜索结果的序列化器就好了,剩余的功能均由 HaystackViewSet
内部替咱们实现了。
最后是在路由器中注册视图集,自动生成 URL 模式:
blogproject/urls.py
router = routers.DefaultRouter() router.register(r"search", blog.views.PostSearchView, basename="search") 复制代码
搞定了!一套标准化的 django-restful-framework 开发流程,不过大量工做已由 drf-haystack 在背后替咱们完成,咱们只写了很是少许的代码即实现了一套搜索接口。
来看看搜索效果。咱们启动 Docker 容器,在浏览器输入以下格式的 URL:
http://127.0.0.1:8000/api/search/?text=key-word
复制代码
将 key-word 替换为须要搜索的关键字,例如将其替换为 markdown,测试集数据中获得的搜索结果以下:
搜索结果符合预期,但略微有一点不太好的地方,就是没有高亮的标题和摘要,咱们但愿未来显示的结果应该是下面这样的,所以返回的数据必须支持这样的显示:
关键词高亮的实现原理其实很是简单,经过解析整段文本,将搜索关键词替换为由 HTML 标签包裹的富文本,并给这个包裹标签设置 CSS 样式,让其显示不一样的字体颜色就能够了。
了解其原理后固然就是实现其功能,不过 django-haystack 已经为咱们造好了轮子,并且在上一部教程的 Django Haystack 全文检索与关键词高亮,咱们还对默认的高亮辅助类进行了改造,优化了文章标题被从关键字位置截断的问题,所以咱们使用改造后的辅助类来对须要高亮的结果进行处理。
须要高亮的实际上是 2 个字段,一个是 title
、一个是 body
。而 body
咱们不须要完整的内容,只须要摘出其中一部分做为搜索结果的摘要便可。这两个功能,辅助类均已经为咱们提供了,咱们只须要调用所需的方法就行。
注意到这里咱们须要对 title
、body
两个字段进行高亮处理,其基本逻辑其实就是接收 title
、body
的值做为输入,高亮处理后再输出。回顾一下序列化器的序列化字段,其实也是接收某个字段的值做为输入,对其进行处理,将其转化为可序列化的结果后输出,和咱们须要的逻辑很像。可是,django-rest-framework 并无提供这些比较个性化需求的序列化字段,所以接下来咱们接触 drf 的一点高级用法——自定义序列化字段。
自定义序列化字段其实很是的简单,基本流程分两步走:
to_representation
方法,加入本身的序列化逻辑。
以咱们的需求为例。由于 title
、body
均为字符型,所以选择父类序列化字段为 CharField
,定义一个 HighlightedCharField
字段以下:
from .utils import Highlighter
class HighlightedCharField(CharField): def to_representation(self, value): value = super().to_representation(value) request = self.context["request"] query = request.query_params["text"] highlighter = Highlighter(query) return highlighter.highlight(value) 复制代码
django-rest-framework 经过调用序列化字段的 to_representation
方法对输入的值进行序列化,这个方法接收的第一个参数就是须要序列化的值。在咱们自定义的逻辑中,首先调用父类 CharField
的 to_representation
方法,父类序列化的逻辑是将任何输入的值都转为字符串;接着咱们从 context
属性中取得 request
对象,这个对象就是视图中的 HTTP 请求对象,可是由于 django 中 request
对象没法像 flask 那样从全局获取,所以 drf 在视图中将其保存在了序列化器和序列化字段的 context
属性中以便在视图外访问;获取 request 对象的目的是但愿获取查询的关键字,query_params
属性是一个类字典对象,用于记录来自 URL 的查询参数,例如咱们以前测试查询功能时调用的 URL 为 /api/search/?text=markdown,因此 query_params
保存了 URL 中的查询参数,将其封装为一个类字段对象 {"text": "markdown"}
,这里 text 的值就是查询的关键字,咱们将它传给 Highlighter
辅助类,而后调用 highlight
方法将须要序列化的值进行进一步的高亮处理。
序列化字段定义好后,咱们就能够在序列化器中用它了:
class PostHaystackSerializer(HaystackSerializerMixin, PostListSerializer):
title = HighlightedCharField() summary = HighlightedCharField(source="body") class Meta(PostListSerializer.Meta): search_fields = ["text"] fields = [ "id", "title", "summary", "created_time", "excerpt", "category", "author", "views", ] 复制代码
title
字段本来使用默认的 CharField
进行序列化,这里咱们从新指定为自定义的 HighlightedCharField
,这样序列化后的值就是高亮的格式。
summary
是咱们新增的字段,注意咱们序列化的对象是文章 Post,但这个对象是没有 summary
这个属性的,可是 summary
实际上是对属性 body
序列化后的结果,所以咱们经过指定序列化化字段的 source
参数,指定值的来源。
最后别忘了在 fields
中申明所有序列化的字段,主要是把新增的 summary
加进去。
来看看改进后的搜索效果:
注意观察返回的 title 和 summary,咱们搜索的关键词是 markdown,能够看到全部 markdown 关键字都被包裹了一个 span 标签,而且设置了 class 属性为 highlighted,只要设置好 css 样式,页面全部的 markdown 关键词就会显示不一样的颜色,从而实现搜索关键词高亮的效果了。
固然,咱们如今并无实际用到这个特性,下一部教程咱们将使用 Vue 来开发博客,到时候调用搜索接口拿到搜索结果后就会实际用到了。