Merge pull request #596 from ch3nnn/master

feat: 新增 elasticsearch 搜索 suggest_search 拼写纠正功能
sh_branch
且听风吟 4 years ago committed by GitHub
commit 93ac23ddc1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -11,10 +11,11 @@ from django.shortcuts import render
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__)
@ -267,6 +268,23 @@ class LinkListView(ListView):
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):
"""

@ -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
@ -18,6 +19,7 @@ class ElasticSearchBackend(BaseSearchBackend):
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()
@ -51,6 +53,24 @@ class ElasticSearchBackend(BaseSearchBackend):
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)
@ -58,8 +78,15 @@ class ElasticSearchBackend(BaseSearchBackend):
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]) \
@ -85,7 +112,7 @@ class ElasticSearchBackend(BaseSearchBackend):
**additional_fields)
raw_results.append(result)
facets = {}
spelling_suggestion = None
spelling_suggestion = None if query_string == suggestion else suggestion
return {
'results': raw_results,
@ -134,6 +161,22 @@ class ElasticSearchQuery(BaseSearchQuery):
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

@ -14,14 +14,17 @@ Including another URLconf
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 = {
@ -43,10 +46,11 @@ urlpatterns = [
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)

@ -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 %}

Loading…
Cancel
Save