做者: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 來開發博客,到時候調用搜索接口拿到搜索結果後就會實際用到了。