如何集成Elasticsearch到django restful
集成ES到django restful服務(wù)端項目
如果直接在Django項目直接編寫代碼作為ElasticSearch的客戶端,比較復(fù)雜,所以借助第三方包Haystack來對接ELasticSearch的客戶端。而且使用了Haystack后,以后你換其他的全文搜索服務(wù)器時,也不用修改Django項目已經(jīng)寫好的代碼。
安裝haystack
Haystack ,Django ,Elasticsearch 三者之間的關(guān)系是:
- Haystack 作為 Django 的一個插件,提供了一個 Django 應(yīng)用接口來實現(xiàn)搜索功能。
- Elasticsearch 作為 Haystack 支持的搜索引擎之一,可以被 Haystack 用來作為后端搜索引擎來存儲和檢索數(shù)據(jù)。
- 當(dāng)你在 Django 項目中使用 Haystack 并選擇 Elasticsearch 作為搜索引擎時,Haystack 會作為中間層,讓你能夠通過 Django 的視圖和模板來操作 Elasticsearch,實現(xiàn)全文搜索的功能。
簡單來說,Haystack 為 Django 提供了搜索功能的抽象層,而 Elasticsearch 是這個抽象層背后的具體實現(xiàn)之一。通過 Haystack,你可以在 Django 項目中輕松地實現(xiàn)強大的搜索功能。
haystack是django的開源搜索框架,能夠結(jié)合目前市面上大部分的搜索引擎用于實現(xiàn)自定義搜索功能,特別是全文搜索。
haystack支持多種搜索引擎,不僅僅是 jieba ,whoosh,使用solr、elasticsearch等搜索,也可通過haystack,而且直接切換引擎即可,甚至無需修改搜索代碼。中文分詞最好的就是jieba和elasticsearch+ik。
github: https://github.com/rhblind/drf-haystack
# python操作elasticsearch的模塊,注意對應(yīng)版本,類似pymysql pip install -U elasticsearch==7.13.4 # django開發(fā)的haystack的模塊,務(wù)必先安裝drf`-haystack,接著才安裝django-haystack。因為drf-haystack不支持es7 pip install -U drf-haystack pip install -U django-haystack
基本使用
安裝配置
文檔:https://drf-haystack.readthedocs.io/en/latest/01_intro.html#examples
INSTALLED_APPS = [ # 必須在自己創(chuàng)建的子應(yīng)用前面 'haystack', # 自己創(chuàng)建的子應(yīng)用 ] # haystack連接elasticsearch的配置信息 HAYSTACK_CONNECTIONS = { 'default': { # haystack操作es的核心模塊 'ENGINE': 'haystack.backends.elasticsearch7_backend.Elasticsearch7SearchEngine', # es服務(wù)端地址 'URL': 'http://127.0.0.1:9200/', # es索引倉庫 'INDEX_NAME': 'haystack', }, } # 當(dāng)mysqlORM操作數(shù)據(jù)庫改變時,自動更新es的索引,否則es的索引會找不到新增的數(shù)據(jù) HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
索引模型
在courses子應(yīng)用下創(chuàng)建search_indexes.py,用于設(shè)置es的索引模型。注意,索引模型的文件名必須是search_indexes。
- 類名必須為需要檢索的Model_name+Index
- 每個索引里面必須有且只能有一個字段為 document=True,這代表haystack 和搜索引擎將使用此字段的內(nèi)容作為索引進行檢索(primary field)。其他的字段只是附屬的屬性,方便調(diào)用,并不作為檢索數(shù)據(jù)。
- 如果使用一個字段設(shè)置了document=True,則一般約定此字段名為text,這是在SearchIndex類里面一貫的命名,以防止后臺混亂,當(dāng)然名字你也可以隨便改,不過不建議改。
- haystack提供了use_template=True在text字段,這樣就允許我們使用數(shù)據(jù)模板去建立搜索引擎索引的文件,說得通俗點就是索引里面需要存放一些什么東西
- text字段用于構(gòu)造索引,只不過具體構(gòu)造索引的值寫在另一個文件內(nèi)。
- id、title、digest、content、image_url等字段用于以索引查詢到的返回內(nèi)容。
- get_model方法用于指明建立索引的對應(yīng)模型。
- index_queryset方法用于返回建立索引的數(shù)據(jù)查詢集。
from haystack import indexes from .models import Course class CourseIndex(indexes.SearchIndex, indexes.Indexable): # 全文索引[可以根據(jù)配置,可以包括多個字段索引] # document=True 表示當(dāng)前字段為全文索引 # use_template=True 表示接下來haystack需要加載一個固定路徑的html模板文件,讓text與其他索引字段綁定映射關(guān)系 text = indexes.CharField(document=True, use_template=True) # 普通索引[單字段,只能提供單個字段值的搜索,所以此處的聲明更主要是為了提供給上面的text全文索引使用的] # es索引名 = indexes.索引數(shù)據(jù)類型(model_attr="ORM中的字段名") id = indexes.IntegerField(model_attr="id") name = indexes.CharField(model_attr="name") description = indexes.CharField(model_attr="description") teacher = indexes.CharField(model_attr="teacher__name") course_cover = indexes.CharField(model_attr="course_cover") get_level_display=indexes.CharField(model_attr="get_level_display") students=indexes.IntegerField(model_attr="students") get_status_display=indexes.CharField(model_attr="get_status_display") lessons=indexes.IntegerField(model_attr="lessons") pub_lessons=indexes.IntegerField(model_attr="pub_lessons") price=indexes.DecimalField(model_attr="price") discount=indexes.CharField(model_attr="discount_json") orders=indexes.IntegerField(model_attr="orders") # 指定與當(dāng)前es索引模型對接的mysql的ORM模型 def get_model(self): return Course # 當(dāng)用戶搜索es索引時,對應(yīng)的提供的mysql數(shù)據(jù)集有哪些? def index_queryset(self, using=None): return self.get_model().objects.filter(is_deleted=False,is_show=True)
ORM模型中新增discount_json字段方法
courses.models,代碼:
import json class Course(BaseModel): course_type = ( (0, '付費購買'), (1, '會員專享'), (2, '學(xué)位課程'), ) level_choices = ( (0, '初級'), (1, '中級'), (2, '高級'), ) status_choices = ( (0, '上線'), (1, '下線'), (2, '預(yù)上線'), ) # course_cover = models.ImageField(upload_to="course/cover", max_length=255, verbose_name="封面圖片", blank=True, null=True) course_cover = StdImageField(variations={ 'thumb_1080x608': (1080, 608), # 高清圖 'thumb_540x304': (540, 304), # 中等比例, 'thumb_108x61': (108, 61, True), # 小圖(第三個參數(shù)表示保持圖片質(zhì)量), }, max_length=255, delete_orphans=True, upload_to="course/cover", null=True, verbose_name="封面圖片",blank=True) course_video = models.FileField(upload_to="course/video", max_length=255, verbose_name="封面視頻", blank=True, null=True) course_type = models.SmallIntegerField(choices=course_type,default=0, verbose_name="付費類型") level = models.SmallIntegerField(choices=level_choices, default=1, verbose_name="難度等級") description = RichTextUploadingField(null=True, blank=True, verbose_name="詳情介紹") pub_date = models.DateField(auto_now_add=True, verbose_name="發(fā)布日期") period = models.IntegerField(default=7, verbose_name="建議學(xué)習(xí)周期(day)") attachment_path = models.FileField(max_length=1000, blank=True, null=True, verbose_name="課件路徑") attachment_link = models.CharField(max_length=1000, blank=True, null=True, verbose_name="課件鏈接") status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="課程狀態(tài)") students = models.IntegerField(default=0, verbose_name="學(xué)習(xí)人數(shù)") lessons = models.IntegerField(default=0, verbose_name="總課時數(shù)量") pub_lessons = models.IntegerField(default=0, verbose_name="已更新課時數(shù)量") price = models.DecimalField(max_digits=10,decimal_places=2, verbose_name="課程原價",default=0) recomment_home_hot = models.BooleanField(default=False, verbose_name="是否推薦到首頁新課欄目") recomment_home_top = models.BooleanField(default=False, verbose_name="是否推薦到首頁必學(xué)欄目") direction = models.ForeignKey("CourseDirection", related_name="course_list", on_delete=models.DO_NOTHING, null=True, blank=True, db_constraint=False, verbose_name="學(xué)習(xí)方向") category = models.ForeignKey("CourseCategory", related_name="course_list", on_delete=models.DO_NOTHING, null=True, blank=True, db_constraint=False, verbose_name="課程分類") teacher = models.ForeignKey("Teacher", related_name="course_list", on_delete=models.DO_NOTHING, null=True, blank=True, db_constraint=False, verbose_name="授課老師") class Meta: db_table = "fg_course_info" verbose_name = "課程信息" verbose_name_plural = verbose_name def __str__(self): return "%s" % self.name def course_cover_small(self): if self.course_cover: return mark_safe(f'<img style="border-radius: 0%;" src="{self.course_cover.thumb_108x61.url}">') return "" course_cover_small.short_description = "封面圖片(108x61)" course_cover_small.allow_tags = True course_cover_small.admin_order_field = "course_cover" def course_cover_medium(self): if self.course_cover: return mark_safe(f'<img style="border-radius: 0%;" src="{self.course_cover.thumb_540x304.url}">') return "" course_cover_medium.short_description = "封面圖片(540x304)" course_cover_medium.allow_tags = True course_cover_medium.admin_order_field = "course_cover" def course_cover_large(self): if self.course_cover: return mark_safe(f'<img style="border-radius: 0%;" src="{self.course_cover.thumb_1080x608.url}">') return "" course_cover_large.short_description = "封面圖片(1080x608)" course_cover_large.allow_tags = True course_cover_large.admin_order_field = "course_cover" @property def discount(self): # todo 將來通過計算獲取當(dāng)前課程的折扣優(yōu)惠相關(guān)的信息 import random return { "type": ["限時優(yōu)惠","限時減免"].pop(random.randint(0,1)), # 優(yōu)惠類型 "expire": random.randint(100000, 1200000), # 優(yōu)惠倒計時 "price": float(self.price - random.randint(1,10) * 10), # 優(yōu)惠價格 } def discount_json(self): # 必須轉(zhuǎn)成字符串才能保存到es中。所以該方法提供給es使用的。 return json.dumps(self.discount)
全文索引字段模板
全文索引模板必須先配置django項目中的TEMPLATES模板引擎路徑,而且全文索引模板的路徑必須是模板目錄下的search/indexes/子應(yīng)用目錄名/模型類名_text.txt
。否則報錯。settings.dev,代碼:
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [ BASE_DIR / "templates", # BASE_DIR 是apps的父級目錄,是主應(yīng)用目錄,templates需要手動創(chuàng)建 ], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ]
創(chuàng)建全文索引字段的html模板,在HTML模板中采用django的模板語法,綁定text與其他es單字段索引的映射關(guān)系。
注意:course_text.txt 中course就是ORM模型類名小寫,text就是es索引模型類中的全文索引字段名。
templates/search/indexes/courses/course_text.txt。代碼:
{{ object.name }} {{ object.description }} {{ object.teacher.name }} {{ object.category.name }} {{ object.diretion.name }}
object表示當(dāng)前orm的模型對應(yīng)。
索引序列化器
courses.serializers,代碼:
from drf_haystack.serializers import HaystackSerializer from .search_indexes import CourseIndex from django.conf import settings class CourseIndexHaystackSerializer(HaystackSerializer): """課程搜索的序列化器""" class Meta: index_classes = [CourseIndex] fields = ["text", "id", "name", "course_cover", "get_level_display", "students", "get_status_display", "pub_lessons", "price", "discount", "orders"] def to_representation(self, instance): """用于指定返回數(shù)據(jù)的字段的""" # 課程的圖片,在這里通過elasticsearch提供的,所以不會提供圖片地址左邊的域名的。因此在這里手動拼接 instance.course_cover = f'//{settings.OSS_BUCKET_NAME}.{settings.OSS_ENDPOINT}/uploads/{instance.course_cover}' return super().to_representation(instance)
全文搜索的索引視圖
from drf_haystack.viewsets import HaystackViewSet from drf_haystack.filters import HaystackFilter from .serializers import CourseIndexHaystackSerializer from .models import Course class CourseSearchViewSet(HaystackViewSet): """課程信息全文搜索視圖類""" # 指定本次搜索的最終真實數(shù)據(jù)的保存模型 index_models = [Course] serializer_class = CourseIndexHaystackSerializer filter_backends = [OrderingFilter, HaystackFilter] ordering_fields = ('id', 'students', 'orders') pagination_class = CourseListPageNumberPagination
路由
from django.urls import path,re_path from . import views from rest_framework import routers router = routers.DefaultRouter() # 注冊全文搜索到視圖集中生成url路由信息 router.register("search", views.CourseSearchViewSet, basename="course-search") urlpatterns = [ path("directions/", views.CourseDirectionListAPIView.as_view()), re_path("^categories/(?P<direction>\d+)/$", views.CourseCategoryListAPIView.as_view()), re_path("^(?P<direction>\d+)/(?P<category>\d+)/$", views.CourseListAPIView.as_view()), ] + router.urls
手動構(gòu)建es索引
因為此前mysql中已經(jīng)有了部分的數(shù)據(jù),而這部分?jǐn)?shù)據(jù)在es中是沒有創(chuàng)建索引。所以需要先把之前的數(shù)據(jù)同步生成全文索引。在終端下執(zhí)行以下命令
# 重建索引 python manage.py rebuild_index # 更新索引 # python manage.py update_index --age=<num_hours> # 刪除索引 # python manage.py clear_index
訪問
http://api.fuguang.cn:8000/courses/search/?text=入門
http://api.fuguang.cn:8000/courses/search/?text=李老師
到此這篇關(guān)于集成Elasticsearch到django restful的文章就介紹到這了,更多相關(guān)Elasticsearch集成django restful內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
網(wǎng)頁報錯"Form?elements?must?have?labels"的處理方法
這篇文章主要給大家介紹了關(guān)于網(wǎng)頁報錯"Form?elements?must?have?labels"的處理方法,文中通過實例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2023-06-06學(xué)編程選什么語言好?是PHP、Python還是Ruby?
這篇文章主要介紹了學(xué)編程選什么語言好?是PHP、Python還是Ruby?需要的朋友可以參考下2014-06-06Archlinux?Timeshift系統(tǒng)備份與還原的操作方法
這篇文章主要介紹了Archlinux?Timeshift系統(tǒng)備份與還原的操作方法,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-01-01