第 11 篇:基於 drf-haystack 的文章搜索接口

做者: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 咱們不須要完整的內容,只須要摘出其中一部分做爲搜索結果的摘要便可。這兩個功能,輔助類均已經爲咱們提供了,咱們只須要調用所需的方法就行。

注意到這裏咱們須要對 titlebody 兩個字段進行高亮處理,其基本邏輯其實就是接收 titlebody 的值做爲輸入,高亮處理後再輸出。回顧一下序列化器的序列化字段,其實也是接收某個字段的值做爲輸入,對其進行處理,將其轉化爲可序列化的結果後輸出,和咱們須要的邏輯很像。可是,django-rest-framework 並無提供這些比較個性化需求的序列化字段,所以接下來咱們接觸 drf 的一點高級用法——自定義序列化字段。

自定義序列化字段其實很是的簡單,基本流程分兩步走:

  1. 從 drf 官方提供的序列化字段中找一個數據類型最爲接近的做爲父類。
  2. 重寫 to_representation 方法,加入本身的序列化邏輯。

以咱們的需求爲例。由於 titlebody 均爲字符型,所以選擇父類序列化字段爲 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 方法對輸入的值進行序列化,這個方法接收的第一個參數就是須要序列化的值。在咱們自定義的邏輯中,首先調用父類 CharFieldto_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 來開發博客,到時候調用搜索接口拿到搜索結果後就會實際用到了。


關注公衆號加入交流羣
相關文章
相關標籤/搜索