Skip to content

Commit

Permalink
Merge pull request liangliangyy#599 from liangliangyy/dev
Browse files Browse the repository at this point in the history
feat: 新增 elasticsearch 搜索 suggest_search 拼写纠正功能
  • Loading branch information
liangliangyy authored Aug 18, 2022
2 parents 38a2d42 + 3ec5ab6 commit c5f0810
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 13 deletions.
22 changes: 20 additions & 2 deletions blog/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@
from django.views.decorators.csrf import csrf_exempt
from django.views.generic.detail import DetailView
from django.views.generic.list import ListView
from haystack.views import SearchView

from blog.models import Article, Category, Tag, Links, LinkShowType
from blog.models import Article, Category, LinkShowType, Links, Tag
from comments.forms import CommentForm
from djangoblog.utils import cache, get_sha256, get_blog_setting
from djangoblog.utils import cache, get_blog_setting, get_sha256

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -267,6 +268,23 @@ def get_queryset(self):
return Links.objects.filter(is_enable=True)


class EsSearchView(SearchView):
def get_context(self):
paginator, page = self.build_page()
context = {
"query": self.query,
"form": self.form,
"page": page,
"paginator": paginator,
"suggestion": None,
}
if hasattr(self.results, "query") and self.results.query.backend.include_spelling:
context["suggestion"] = self.results.query.get_spelling_suggestion()
context.update(self.extra_context())

return context


@csrf_exempt
def fileupload(request):
"""
Expand Down
49 changes: 46 additions & 3 deletions djangoblog/elasticsearch_backend.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.utils.encoding import force_str
from elasticsearch_dsl import Q
from haystack.backends import BaseEngine, BaseSearchBackend, BaseSearchQuery, log_query
from haystack.forms import ModelSearchForm
from haystack.models import SearchResult
from haystack.utils import log as logging

Expand All @@ -18,6 +19,7 @@ def __init__(self, connection_alias, **connection_options):
connection_alias,
**connection_options)
self.manager = ArticleDocumentManager()
self.include_spelling = True

def _get_models(self, iterable):
models = iterable if iterable and iterable[0] else Article.objects.all()
Expand Down Expand Up @@ -51,15 +53,40 @@ def remove(self, obj_or_string):
def clear(self, models=None, commit=True):
self.remove(None)

@staticmethod
def get_suggestion(query: str) -> str:
"""获取推荐词, 如果没有找到添加原搜索词"""

search = ArticleDocument.search() \
.query("match", body=query) \
.suggest('suggest_search', query, term={'field': 'body'}) \
.execute()

keywords = []
for suggest in search.suggest.suggest_search:
if suggest["options"]:
keywords.append(suggest["options"][0]["text"])
else:
keywords.append(suggest["text"])

return ' '.join(keywords)

@log_query
def search(self, query_string, **kwargs):
logger.info('search query_string:' + query_string)

start_offset = kwargs.get('start_offset')
end_offset = kwargs.get('end_offset')

q = Q('bool', should=[Q('match', body=query_string), Q(
'match', title=query_string)], minimum_should_match="70%")
# 推荐词搜索
if getattr(self, "is_suggest", None):
suggestion = self.get_suggestion(query_string)
else:
suggestion = query_string

q = Q('bool',
should=[Q('match', body=suggestion), Q('match', title=suggestion)],
minimum_should_match="70%")

search = ArticleDocument.search() \
.query('bool', filter=[q]) \
Expand All @@ -85,7 +112,7 @@ def search(self, query_string, **kwargs):
**additional_fields)
raw_results.append(result)
facets = {}
spelling_suggestion = None
spelling_suggestion = None if query_string == suggestion else suggestion

return {
'results': raw_results,
Expand Down Expand Up @@ -134,6 +161,22 @@ def get_count(self):
results = self.get_results()
return len(results) if results else 0

def get_spelling_suggestion(self, preferred_query=None):
return self._spelling_suggestion

def build_params(self, spelling_query=None):
kwargs = super(ElasticSearchQuery, self).build_params(spelling_query=spelling_query)
return kwargs


class ElasticSearchModelSearchForm(ModelSearchForm):

def search(self):
# 是否建议搜索
self.searchqueryset.query.backend.is_suggest = self.data.get("is_suggest") != "no"
sqs = super().search()
return sqs


class ElasticSearchEngine(BaseEngine):
backend = ElasticSearchBackend
Expand Down
12 changes: 8 additions & 4 deletions djangoblog/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
from django.conf import settings
from django.urls import include, re_path
from django.conf.urls.static import static
from django.contrib.sitemaps.views import sitemap
from django.urls import include
from django.urls import re_path
from haystack.views import search_view_factory

from blog.views import EsSearchView
from djangoblog.admin_site import admin_site
from djangoblog.elasticsearch_backend import ElasticSearchModelSearchForm
from djangoblog.feeds import DjangoBlogFeed
from djangoblog.sitemap import StaticViewSitemap, ArticleSiteMap, CategorySiteMap, TagSiteMap, UserSiteMap
from djangoblog.sitemap import ArticleSiteMap, CategorySiteMap, StaticViewSitemap, TagSiteMap, UserSiteMap

sitemaps = {

Expand All @@ -43,10 +46,11 @@
re_path(r'', include('accounts.urls', namespace='account')),
re_path(r'', include('oauth.urls', namespace='oauth')),
re_path(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps},
name='django.contrib.sitemaps.views.sitemap'),
name='django.contrib.sitemaps.views.sitemap'),
re_path(r'^feed/$', DjangoBlogFeed()),
re_path(r'^rss/$', DjangoBlogFeed()),
re_path(r'^search', include('haystack.urls'), name='search'),
re_path('^search', search_view_factory(view_class=EsSearchView, form_class=ElasticSearchModelSearchForm),
name='search'),
re_path(r'', include('servermanager.urls', namespace='servermanager')),
re_path(r'', include('owntracks.urls', namespace='owntracks'))
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
coverage==6.4
bleach==5.0.0
Django==4.0.7
django-compressor==4.0
Django==4.1
django-compressor==4.1
django-haystack==3.2.1
django-ipware==4.0.2
django-mdeditor==0.1.20
Expand Down
12 changes: 10 additions & 2 deletions templates/search/search.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,16 @@
<div id="content" role="main">
{% if query %}
<header class="archive-header">

<h2 class="archive-title"> 搜索:<span style="color: red">{{ query }}</span></h2>
{% if suggestion %}
<h2 class="archive-title">
已显示<span style="color: red"> “{{ suggestion }}” </span>的搜索结果。&nbsp;&nbsp;
仍然搜索:<a style="text-transform: none;" href="/search/?q={{ query }}&is_suggest=no">{{ query }}</a> <br>
</h2>
{% else %}
<h2 class="archive-title">
搜索:<span style="color: red">{{ query }} </span> &nbsp;&nbsp;
</h2>
{% endif %}
</header><!-- .archive-header -->
{% endif %}
{% if query and page.object_list %}
Expand Down

0 comments on commit c5f0810

Please sign in to comment.