diff --git a/blog/models.py b/blog/models.py index 083788bb..fa33b02c 100644 --- a/blog/models.py +++ b/blog/models.py @@ -9,7 +9,7 @@ from django.utils.timezone import now from django.utils.translation import gettext_lazy as _ from mdeditor.fields import MDTextField -from uuslug import slugify +from slugify import slugify from djangoblog.utils import cache_decorator, cache from djangoblog.utils import get_current_site diff --git a/blog/views.py b/blog/views.py index 773bb756..bec44ed2 100644 --- a/blog/views.py +++ b/blog/views.py @@ -285,14 +285,68 @@ def get_queryset(self): class EsSearchView(SearchView): + def get_queryset(self): + queryset = super(EsSearchView, self).get_queryset() + + # 获取排序参数 + sort_by = self.request.GET.get('sort', 'relevance') + + # 根据排序参数对结果进行排序 + if sort_by == 'time': + # 按时间排序(最新在前) + queryset = queryset.order_by('-pub_date') + elif sort_by == 'views': + # 按浏览量排序(最多在前) + queryset = queryset.order_by('-views') + # 默认按相关性排序 + + return queryset + def get_context(self): paginator, page = self.build_page() + + # 获取当前排序参数 + sort_by = self.request.GET.get('sort', 'relevance') + + # 关键词高亮处理 + query = self.query + if query: + # 替换HTML特殊字符以避免XSS攻击 + query = query.replace('&', '&').replace('<', '<').replace('>', '>') + + # 创建正则表达式,不区分大小写 + import re + regex = re.compile(r'(' + re.escape(query) + r')', re.IGNORECASE) + + # 对搜索结果中的标题和摘要进行关键词高亮 + for result in page.object_list: + article = result.object + + # 高亮标题中的关键词 + if article.title: + article.title = regex.sub(r'\1', article.title) + + # 高亮摘要中的关键词 + if hasattr(article, 'excerpt') and article.excerpt: + article.excerpt = regex.sub(r'\1', article.excerpt) + + # 如果没有摘要,从正文中提取部分内容并高亮 + elif article.body: + # 提取前200个字符作为摘要 + excerpt = article.body[:200] + if len(article.body) > 200: + excerpt += '...' + + # 高亮摘要中的关键词 + article.excerpt = regex.sub(r'\1', excerpt) + context = { "query": self.query, "form": self.form, "page": page, "paginator": paginator, "suggestion": None, + "sort_by": sort_by, # 将当前排序参数传递到模板 } if hasattr(self.results, "query") and self.results.query.backend.include_spelling: context["suggestion"] = self.results.query.get_spelling_suggestion() diff --git a/db.sqlite3 b/db.sqlite3 new file mode 100644 index 00000000..5ca15a3e Binary files /dev/null and b/db.sqlite3 differ diff --git a/djangoblog/settings.py b/djangoblog/settings.py index b755d24f..86f76323 100644 --- a/djangoblog/settings.py +++ b/djangoblog/settings.py @@ -52,7 +52,7 @@ def env_to_bool(env, default): 'django.contrib.staticfiles', 'django.contrib.sites', 'django.contrib.sitemaps', - 'mdeditor', + # 'mdeditor', 'haystack', 'blog', 'accounts', @@ -108,16 +108,10 @@ def env_to_bool(env, default): DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': os.environ.get('DJANGO_MYSQL_DATABASE') or 'djangoblog', - 'USER': os.environ.get('DJANGO_MYSQL_USER') or 'root', - 'PASSWORD': os.environ.get('DJANGO_MYSQL_PASSWORD') or 'root', - 'HOST': os.environ.get('DJANGO_MYSQL_HOST') or '127.0.0.1', - 'PORT': int( - os.environ.get('DJANGO_MYSQL_PORT') or 3306), - 'OPTIONS': { - 'charset': 'utf8mb4'}, - }} + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} # Password validation # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators @@ -360,7 +354,9 @@ def env_to_bool(env, default): DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +# 搜索引擎配置 if os.environ.get('DJANGO_ELASTICSEARCH_HOST'): + # 使用Elasticsearch作为搜索引擎 ELASTICSEARCH_DSL = { 'default': { 'hosts': os.environ.get('DJANGO_ELASTICSEARCH_HOST') @@ -371,6 +367,18 @@ def env_to_bool(env, default): 'ENGINE': 'djangoblog.elasticsearch_backend.ElasticSearchEngine', }, } +else: + # 默认使用Whoosh作为搜索引擎 + import os + HAYSTACK_CONNECTIONS = { + 'default': { + 'ENGINE': 'djangoblog.whoosh_cn_backend.WhooshEngine', + 'PATH': os.path.join(BASE_DIR, 'whoosh_index'), + }, + } + +# 自动更新搜索索引 +HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor' # Plugin System PLUGINS_DIR = BASE_DIR / 'plugins' diff --git a/djangoblog/whoosh_cn_backend.py b/djangoblog/whoosh_cn_backend.py index 04e3f7fd..aefb48bd 100644 --- a/djangoblog/whoosh_cn_backend.py +++ b/djangoblog/whoosh_cn_backend.py @@ -22,7 +22,21 @@ from haystack.utils import get_identifier, get_model_ct from haystack.utils import log as logging from haystack.utils.app_loading import haystack_get_model -from jieba.analyse import ChineseAnalyzer +import jieba +from whoosh.analysis import Tokenizer, Token + +class ChineseTokenizer(Tokenizer): + def __call__(self, text, **kwargs): + # 使用jieba分词 + words = jieba.cut(text) + for word in words: + token = Token() + token.text = word + yield token + +# 创建中文分词器实例 +ChineseAnalyzer = ChineseTokenizer() + from whoosh import index from whoosh.analysis import StemmingAnalyzer from whoosh.fields import BOOLEAN, DATETIME, IDLIST, KEYWORD, NGRAM, NGRAMWORDS, NUMERIC, Schema, TEXT @@ -186,7 +200,7 @@ def build_schema(self, fields): else: # schema_fields[field_class.index_fieldname] = TEXT(stored=True, analyzer=StemmingAnalyzer(), field_boost=field_class.boost, sortable=True) schema_fields[field_class.index_fieldname] = TEXT( - stored=True, analyzer=ChineseAnalyzer(), field_boost=field_class.boost, sortable=True) + stored=True, analyzer=ChineseAnalyzer, field_boost=field_class.boost, sortable=True) if field_class.document is True: content_field_name = field_class.index_fieldname schema_fields[field_class.index_fieldname].spelling = True diff --git a/templates/blog/tags/sidebar.html b/templates/blog/tags/sidebar.html index ecb6d201..2bd7fb2a 100755 --- a/templates/blog/tags/sidebar.html +++ b/templates/blog/tags/sidebar.html @@ -5,11 +5,174 @@
+ + + + + + {% if extra_sidebars %} {% for sidebar in extra_sidebars %} @@ -40,7 +203,14 @@ {% endif %} + + +