diff --git a/blog/views.py b/blog/views.py index c9ff116..6cc05cd 100644 --- a/blog/views.py +++ b/blog/views.py @@ -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): """ diff --git a/djangoblog/elasticsearch_backend.py b/djangoblog/elasticsearch_backend.py index 5f875f4..4afe498 100644 --- a/djangoblog/elasticsearch_backend.py +++ b/djangoblog/elasticsearch_backend.py @@ -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 diff --git a/djangoblog/urls.py b/djangoblog/urls.py index 6d88345..a834684 100644 --- a/djangoblog/urls.py +++ b/djangoblog/urls.py @@ -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) diff --git a/templates/search/search.html b/templates/search/search.html index 128aef4..1404c60 100644 --- a/templates/search/search.html +++ b/templates/search/search.html @@ -15,8 +15,16 @@