diff --git a/src/DjangoBlog-master/djangoblog/__init__.py b/src/DjangoBlog-master/djangoblog/__init__.py index 1e205f4..3279070 100644 --- a/src/DjangoBlog-master/djangoblog/__init__.py +++ b/src/DjangoBlog-master/djangoblog/__init__.py @@ -1 +1,2 @@ +# Zxy指定默认的应用配置类 default_app_config = 'djangoblog.apps.DjangoblogAppConfig' diff --git a/src/DjangoBlog-master/djangoblog/admin_site.py b/src/DjangoBlog-master/djangoblog/admin_site.py index f120405..a0cb357 100644 --- a/src/DjangoBlog-master/djangoblog/admin_site.py +++ b/src/DjangoBlog-master/djangoblog/admin_site.py @@ -1,8 +1,10 @@ +# Zxy导入 Django 的 AdminSite 类和其他相关模块 from django.contrib.admin import AdminSite from django.contrib.admin.models import LogEntry from django.contrib.sites.admin import SiteAdmin from django.contrib.sites.models import Site +# Zxy导入自定义模块和模型 from accounts.admin import * from blog.admin import * from blog.models import * @@ -16,31 +18,25 @@ from owntracks.models import * from servermanager.admin import * from servermanager.models import * - +# Zxy定义自定义 AdminSite 类 class DjangoBlogAdminSite(AdminSite): + # 自定义站点标题和头部 site_header = 'djangoblog administration' site_title = 'djangoblog site admin' + # 初始化方法 def __init__(self, name='admin'): super().__init__(name) + # 定义权限检查方法,仅允许超级用户访问 def has_permission(self, request): return request.user.is_superuser - # def get_urls(self): - # urls = super().get_urls() - # from django.urls import path - # from blog.views import refresh_memcache - # - # my_urls = [ - # path('refresh/', self.admin_view(refresh_memcache), name="refresh"), - # ] - # return urls + my_urls - - +# Zxy创建自定义 AdminSite 实例 admin_site = DjangoBlogAdminSite(name='admin') -admin_site.register(Article, ArticlelAdmin) +# Zxy注册模型到自定义 AdminSite +admin_site.register(Article, ArticleAdmin) admin_site.register(Category, CategoryAdmin) admin_site.register(Tag, TagAdmin) admin_site.register(Links, LinksAdmin) @@ -61,4 +57,4 @@ admin_site.register(OwnTrackLog, OwnTrackLogsAdmin) admin_site.register(Site, SiteAdmin) -admin_site.register(LogEntry, LogEntryAdmin) +admin_site.register(LogEntry, LogEntryAdmin) \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/apps.py b/src/DjangoBlog-master/djangoblog/apps.py index d29e318..f902735 100644 --- a/src/DjangoBlog-master/djangoblog/apps.py +++ b/src/DjangoBlog-master/djangoblog/apps.py @@ -1,11 +1,16 @@ +# Zxy导入Django的AppConfig模块 from django.apps import AppConfig +# Zxy定义Djangoblog应用的配置类 class DjangoblogAppConfig(AppConfig): + # Zxy设置默认的自动字段类型 default_auto_field = 'django.db.models.BigAutoField' + # Zxy应用名称 name = 'djangoblog' + # Zxy重写ready方法,用于初始化应用 def ready(self): - super().ready() - # Import and load plugins here + super().ready() # Zxy调用父类的ready方法 + # Zxy导入并加载插件 from .plugin_manage.loader import load_plugins - load_plugins() \ No newline at end of file + load_plugins() # Zxy加载插件 \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/blog_signals.py b/src/DjangoBlog-master/djangoblog/blog_signals.py index 393f441..9ba3376 100644 --- a/src/DjangoBlog-master/djangoblog/blog_signals.py +++ b/src/DjangoBlog-master/djangoblog/blog_signals.py @@ -1,122 +1,133 @@ +# Zxy导入线程模块 import _thread +# Zxy导入日志模块 import logging +# Zxy导入Django的信号模块 import django.dispatch +# Zxy导入Django的配置模块 from django.conf import settings +# Zxy导入Django的LogEntry模型 from django.contrib.admin.models import LogEntry +# Zxy导入用户登录和登出信号 from django.contrib.auth.signals import user_logged_in, user_logged_out +# Zxy导入邮件发送模块 from django.core.mail import EmailMultiAlternatives +# Zxy导入模型保存信号 from django.db.models.signals import post_save +# Zxy导入信号接收器 from django.dispatch import receiver +# Zxy导入评论模型 from comments.models import Comment +# Zxy导入发送评论邮件的工具函数 from comments.utils import send_comment_email +# Zxy导入爬虫通知工具 from djangoblog.spider_notify import SpiderNotify +# Zxy导入缓存和缓存清理工具 from djangoblog.utils import cache, expire_view_cache, delete_sidebar_cache, delete_view_cache +# Zxy导入获取当前站点的工具函数 from djangoblog.utils import get_current_site +# Zxy导入OAuth用户模型 from oauth.models import OAuthUser +# Zxy获取日志记录器 logger = logging.getLogger(__name__) +# Zxy定义OAuth用户登录信号 oauth_user_login_signal = django.dispatch.Signal(['id']) -send_email_signal = django.dispatch.Signal( - ['emailto', 'title', 'content']) - +# Zxy定义发送邮件信号 +send_email_signal = django.dispatch.Signal(['emailto', 'title', 'content']) +# Zxy定义发送邮件信号的处理函数 @receiver(send_email_signal) def send_email_signal_handler(sender, **kwargs): - emailto = kwargs['emailto'] - title = kwargs['title'] - content = kwargs['content'] + emailto = kwargs['emailto'] # Zxy收件人 + title = kwargs['title'] # Zxy邮件标题 + content = kwargs['content'] # Zxy邮件内容 - msg = EmailMultiAlternatives( + msg = EmailMultiAlternatives( # Zxy创建邮件对象 title, content, - from_email=settings.DEFAULT_FROM_EMAIL, - to=emailto) - msg.content_subtype = "html" + from_email=settings.DEFAULT_FROM_EMAIL, # Zxy发件人 + to=emailto # Zxy收件人 + ) + msg.content_subtype = "html" # Zxy设置邮件内容类型为HTML - from servermanager.models import EmailSendLog - log = EmailSendLog() + from servermanager.models import EmailSendLog # Zxy导入邮件发送日志模型 + log = EmailSendLog() # Zxy创建日志记录 log.title = title log.content = content - log.emailto = ','.join(emailto) + log.emailto = ','.join(emailto) # Zxy记录收件人 try: - result = msg.send() - log.send_result = result > 0 + result = msg.send() # Zxy发送邮件 + log.send_result = result > 0 # Zxy记录发送结果 except Exception as e: - logger.error(f"失败邮箱号: {emailto}, {e}") + logger.error(f"失败邮箱号: {emailto}, {e}") # Zxy记录错误信息 log.send_result = False - log.save() - + log.save() # Zxy保存日志 +# Zxy定义OAuth用户登录信号的处理函数 @receiver(oauth_user_login_signal) def oauth_user_login_signal_handler(sender, **kwargs): - id = kwargs['id'] - oauthuser = OAuthUser.objects.get(id=id) - site = get_current_site().domain - if oauthuser.picture and not oauthuser.picture.find(site) >= 0: - from djangoblog.utils import save_user_avatar - oauthuser.picture = save_user_avatar(oauthuser.picture) - oauthuser.save() - - delete_sidebar_cache() + id = kwargs['id'] # Zxy获取用户ID + oauthuser = OAuthUser.objects.get(id=id) # Zxy获取OAuth用户 + site = get_current_site().domain # Zxy获取当前站点域名 + if oauthuser.picture and not oauthuser.picture.find(site) >= 0: # Zxy检查头像URL是否包含当前站点域名 + from djangoblog.utils import save_user_avatar # Zxy导入保存头像的工具函数 + oauthuser.picture = save_user_avatar(oauthuser.picture) # Zxy保存用户头像 + oauthuser.save() # Zxy保存用户信息 + delete_sidebar_cache() # Zxy删除侧边栏缓存 +# Zxy定义模型保存后的回调函数 @receiver(post_save) -def model_post_save_callback( - sender, - instance, - created, - raw, - using, - update_fields, - **kwargs): - clearcache = False - if isinstance(instance, LogEntry): +def model_post_save_callback(sender, instance, created, raw, using, update_fields, **kwargs): + clearcache = False # Zxy标记是否需要清理缓存 + if isinstance(instance, LogEntry): # Zxy如果是LogEntry模型,直接返回 return - if 'get_full_url' in dir(instance): - is_update_views = update_fields == {'views'} - if not settings.TESTING and not is_update_views: + if 'get_full_url' in dir(instance): # Zxy检查是否有获取完整URL的方法 + is_update_views = update_fields == {'views'} # Zxy检查是否是更新浏览次数 + if not settings.TESTING and not is_update_views: # Zxy如果不是测试环境且不是更新浏览次数 try: - notify_url = instance.get_full_url() - SpiderNotify.baidu_notify([notify_url]) + notify_url = instance.get_full_url() # Zxy获取完整URL + SpiderNotify.baidu_notify([notify_url]) # Zxy通知百度爬虫 except Exception as ex: - logger.error("notify sipder", ex) + logger.error("notify spider", ex) # Zxy记录错误信息 if not is_update_views: - clearcache = True + clearcache = True # Zxy标记清理缓存 - if isinstance(instance, Comment): - if instance.is_enable: - path = instance.article.get_absolute_url() - site = get_current_site().domain - if site.find(':') > 0: + if isinstance(instance, Comment): # Zxy如果是评论模型 + if instance.is_enable: # Zxy检查评论是否启用 + path = instance.article.get_absolute_url() # Zxy获取文章的绝对URL + site = get_current_site().domain # Zxy获取当前站点域名 + if site.find(':') > 0: # Zxy去除端口号 site = site[0:site.find(':')] - expire_view_cache( + expire_view_cache( # Zxy清理视图缓存 path, servername=site, serverport=80, - key_prefix='blogdetail') - if cache.get('seo_processor'): + key_prefix='blogdetail' + ) + if cache.get('seo_processor'): # Zxy清理SEO处理器缓存 cache.delete('seo_processor') - comment_cache_key = 'article_comments_{id}'.format( - id=instance.article.id) - cache.delete(comment_cache_key) - delete_sidebar_cache() - delete_view_cache('article_comments', [str(instance.article.pk)]) + comment_cache_key = 'article_comments_{id}'.format(id=instance.article.id) # Zxy生成评论缓存键 + cache.delete(comment_cache_key) # Zxy清理评论缓存 + delete_sidebar_cache() # Zxy清理侧边栏缓存 + delete_view_cache('article_comments', [str(instance.article.pk)]) # Zxy清理文章评论缓存 - _thread.start_new_thread(send_comment_email, (instance,)) + _thread.start_new_thread(send_comment_email, (instance,)) # Zxy异步发送评论邮件 - if clearcache: + if clearcache: # Zxy清理缓存 cache.clear() - +# Zxy定义用户登录和登出的回调函数 @receiver(user_logged_in) @receiver(user_logged_out) def user_auth_callback(sender, request, user, **kwargs): - if user and user.username: - logger.info(user) - delete_sidebar_cache() - # cache.clear() + if user and user.username: # Zxy检查用户是否登录 + logger.info(user) # Zxy记录用户信息 + delete_sidebar_cache() # Zxy清理侧边栏缓存 + # cache.clear() # Zxy清理所有缓存(暂时注释) \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/elasticsearch_backend.py b/src/DjangoBlog-master/djangoblog/elasticsearch_backend.py index 4afe498..55a9b89 100644 --- a/src/DjangoBlog-master/djangoblog/elasticsearch_backend.py +++ b/src/DjangoBlog-master/djangoblog/elasticsearch_backend.py @@ -1,62 +1,74 @@ +# Zxy导入Django的编码工具 from django.utils.encoding import force_str +# Zxy导入Elasticsearch DSL的查询构造器 from elasticsearch_dsl import Q +# Zxy导入Haystack的搜索后端模块 from haystack.backends import BaseEngine, BaseSearchBackend, BaseSearchQuery, log_query +# Zxy导入Haystack的表单模块 from haystack.forms import ModelSearchForm +# Zxy导入Haystack的搜索结果模型 from haystack.models import SearchResult +# Zxy导入Haystack的日志工具 from haystack.utils import log as logging +# Zxy导入项目中的Elasticsearch文档和管理器 from blog.documents import ArticleDocument, ArticleDocumentManager +# Zxy导入项目中的文章模型 from blog.models import Article +# Zxy获取日志记录器 logger = logging.getLogger(__name__) - +# Zxy定义Elasticsearch搜索后端类 class ElasticSearchBackend(BaseSearchBackend): + # Zxy初始化方法 def __init__(self, connection_alias, **connection_options): - super( - ElasticSearchBackend, - self).__init__( - connection_alias, - **connection_options) - self.manager = ArticleDocumentManager() - self.include_spelling = True + super(ElasticSearchBackend, self).__init__(connection_alias, **connection_options) + self.manager = ArticleDocumentManager() # Zxy初始化文档管理器 + self.include_spelling = True # Zxy启用拼写建议 + # Zxy获取模型数据并转换为文档 def _get_models(self, iterable): - models = iterable if iterable and iterable[0] else Article.objects.all() - docs = self.manager.convert_to_doc(models) + models = iterable if iterable and iterable[0] else Article.objects.all() # Zxy获取模型数据 + docs = self.manager.convert_to_doc(models) # Zxy转换为Elasticsearch文档 return docs + # Zxy创建索引并重建数据 def _create(self, models): - self.manager.create_index() - docs = self._get_models(models) - self.manager.rebuild(docs) + self.manager.create_index() # Zxy创建索引 + docs = self._get_models(models) # Zxy获取文档 + self.manager.rebuild(docs) # Zxy重建索引数据 + # Zxy删除模型数据 def _delete(self, models): for m in models: - m.delete() + m.delete() # Zxy删除模型实例 return True + # Zxy重建索引数据 def _rebuild(self, models): - models = models if models else Article.objects.all() - docs = self.manager.convert_to_doc(models) - self.manager.update_docs(docs) + models = models if models else Article.objects.all() # Zxy获取模型数据 + docs = self.manager.convert_to_doc(models) # Zxy转换为文档 + self.manager.update_docs(docs) # Zxy更新索引数据 + # Zxy更新索引 def update(self, index, iterable, commit=True): + models = self._get_models(iterable) # Zxy获取文档 + self.manager.update_docs(models) # Zxy更新索引 - models = self._get_models(iterable) - self.manager.update_docs(models) - + # Zxy删除文档 def remove(self, obj_or_string): - models = self._get_models([obj_or_string]) - self._delete(models) + models = self._get_models([obj_or_string]) # Zxy获取文档 + self._delete(models) # Zxy删除文档 + # Zxy清空索引 def clear(self, models=None, commit=True): - self.remove(None) + self.remove(None) # Zxy删除所有文档 + # Zxy获取拼写建议 @staticmethod def get_suggestion(query: str) -> str: - """获取推荐词, 如果没有找到添加原搜索词""" - + """获取推荐词,如果没有找到则返回原搜索词""" search = ArticleDocument.search() \ .query("match", body=query) \ .suggest('suggest_search', query, term={'field': 'body'}) \ @@ -64,80 +76,78 @@ class ElasticSearchBackend(BaseSearchBackend): keywords = [] for suggest in search.suggest.suggest_search: - if suggest["options"]: - keywords.append(suggest["options"][0]["text"]) + if suggest["options"]: # Zxy检查是否有建议选项 + keywords.append(suggest["options"][0]["text"]) # Zxy添加建议词 else: - keywords.append(suggest["text"]) + keywords.append(suggest["text"]) # Zxy添加原搜索词 - return ' '.join(keywords) + return ' '.join(keywords) # Zxy返回拼写建议 + # Zxy执行搜索查询 @log_query def search(self, query_string, **kwargs): - logger.info('search query_string:' + query_string) + logger.info('search query_string:' + query_string) # Zxy记录搜索查询 - start_offset = kwargs.get('start_offset') - end_offset = kwargs.get('end_offset') + start_offset = kwargs.get('start_offset') # Zxy获取起始偏移量 + end_offset = kwargs.get('end_offset') # Zxy获取结束偏移量 - # 推荐词搜索 + # Zxy检查是否启用拼写建议 if getattr(self, "is_suggest", None): - suggestion = self.get_suggestion(query_string) + suggestion = self.get_suggestion(query_string) # Zxy获取拼写建议 else: - suggestion = query_string + suggestion = query_string # Zxy使用原搜索词 - q = Q('bool', - should=[Q('match', body=suggestion), Q('match', title=suggestion)], - minimum_should_match="70%") + q = Q('bool', # Zxy构造布尔查询 + should=[Q('match', body=suggestion), Q('match', title=suggestion)], # Zxy匹配标题或正文 + minimum_should_match="70%") # Zxy至少匹配70% search = ArticleDocument.search() \ .query('bool', filter=[q]) \ .filter('term', status='p') \ .filter('term', type='a') \ - .source(False)[start_offset: end_offset] + .source(False)[start_offset: end_offset] # Zxy执行搜索 - results = search.execute() - hits = results['hits'].total - raw_results = [] - for raw_result in results['hits']['hits']: - app_label = 'blog' - model_name = 'Article' - additional_fields = {} + results = search.execute() # Zxy执行搜索查询 + hits = results['hits'].total # Zxy获取总匹配数 + raw_results = [] # Zxy初始化结果列表 - result_class = SearchResult + for raw_result in results['hits']['hits']: # Zxy遍历搜索结果 + app_label = 'blog' # Zxy应用标签 + model_name = 'Article' # Zxy模型名称 + additional_fields = {} # Zxy额外字段 - result = result_class( + result_class = SearchResult # Zxy搜索结果类 + + result = result_class( # Zxy创建搜索结果对象 app_label, model_name, raw_result['_id'], raw_result['_score'], - **additional_fields) - raw_results.append(result) - facets = {} - spelling_suggestion = None if query_string == suggestion else suggestion + **additional_fields + ) + raw_results.append(result) # Zxy添加到结果列表 + + facets = {} # Zxy初始化分面信息 + spelling_suggestion = None if query_string == suggestion else suggestion # Zxy拼写建议 return { - 'results': raw_results, - 'hits': hits, - 'facets': facets, - 'spelling_suggestion': spelling_suggestion, + 'results': raw_results, # Zxy返回搜索结果 + 'hits': hits, # Zxy返回匹配数 + 'facets': facets, # Zxy返回分面信息 + 'spelling_suggestion': spelling_suggestion, # Zxy返回拼写建议 } - +# Zxy定义Elasticsearch搜索查询类 class ElasticSearchQuery(BaseSearchQuery): + # Zxy转换日期时间格式 def _convert_datetime(self, date): if hasattr(date, 'hour'): return force_str(date.strftime('%Y%m%d%H%M%S')) else: return force_str(date.strftime('%Y%m%d000000')) + # Zxy清理用户输入 def clean(self, query_fragment): - """ - Provides a mechanism for sanitizing user input before presenting the - value to the backend. - - Whoosh 1.X differs here in that you can no longer use a backslash - to escape reserved characters. Instead, the whole word should be - quoted. - """ words = query_fragment.split() cleaned_words = [] @@ -154,30 +164,33 @@ class ElasticSearchQuery(BaseSearchQuery): return ' '.join(cleaned_words) + # Zxy构建查询片段 def build_query_fragment(self, field, filter_type, value): return value.query_string + # Zxy获取结果数量 def get_count(self): results = self.get_results() return len(results) if results else 0 + # Zxy获取拼写建议 def get_spelling_suggestion(self, preferred_query=None): return self._spelling_suggestion + # Zxy构建查询参数 def build_params(self, spelling_query=None): kwargs = super(ElasticSearchQuery, self).build_params(spelling_query=spelling_query) return kwargs - +# Zxy定义Elasticsearch模型搜索表单 class ElasticSearchModelSearchForm(ModelSearchForm): - + # Zxy执行搜索 def search(self): - # 是否建议搜索 - self.searchqueryset.query.backend.is_suggest = self.data.get("is_suggest") != "no" - sqs = super().search() + self.searchqueryset.query.backend.is_suggest = self.data.get("is_suggest") != "no" # Zxy检查是否启用拼写建议 + sqs = super().search() # Zxy调用父类搜索方法 return sqs - +# Zxy定义Elasticsearch搜索引擎 class ElasticSearchEngine(BaseEngine): - backend = ElasticSearchBackend - query = ElasticSearchQuery + backend = ElasticSearchBackend # Zxy后端类 + query = ElasticSearchQuery # Zxy查询类 \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/feeds.py b/src/DjangoBlog-master/djangoblog/feeds.py index 8c4e851..c7afbe3 100644 --- a/src/DjangoBlog-master/djangoblog/feeds.py +++ b/src/DjangoBlog-master/djangoblog/feeds.py @@ -1,40 +1,54 @@ +# Zxy导入Django的用户模型 from django.contrib.auth import get_user_model +# Zxy导入Django的Feed视图 from django.contrib.syndication.views import Feed +# Zxy导入Django的时区工具 from django.utils import timezone +# Zxy导入RSS 2.0 Feed生成器 from django.utils.feedgenerator import Rss201rev2Feed +# Zxy导入项目中的文章模型 from blog.models import Article +# Zxy导入Markdown工具 from djangoblog.utils import CommonMarkdown - +# Zxy定义Django博客Feed class DjangoBlogFeed(Feed): - feed_type = Rss201rev2Feed + feed_type = Rss201rev2Feed # Zxy使用RSS 2.0格式 - description = '大巧无工,重剑无锋.' - title = "且听风吟 大巧无工,重剑无锋. " - link = "/feed/" + description = '大巧无工,重剑无锋.' # ZxyFeed描述 + title = "且听风吟 大巧无工,重剑无锋." # ZxyFeed标题 + link = "/feed/" # ZxyFeed链接 + # Zxy获取作者名称 def author_name(self): return get_user_model().objects.first().nickname + # Zxy获取作者链接 def author_link(self): return get_user_model().objects.first().get_absolute_url() + # Zxy获取Feed项 def items(self): - return Article.objects.filter(type='a', status='p').order_by('-pub_time')[:5] + return Article.objects.filter(type='a', status='p').order_by('-pub_time')[:5] # Zxy获取最近5篇已发布的文章 + # Zxy获取Feed项标题 def item_title(self, item): return item.title + # Zxy获取Feed项描述 def item_description(self, item): - return CommonMarkdown.get_markdown(item.body) + return CommonMarkdown.get_markdown(item.body) # Zxy将文章内容转换为Markdown格式 + # Zxy获取Feed版权信息 def feed_copyright(self): now = timezone.now() - return "Copyright© {year} 且听风吟".format(year=now.year) + return "Copyright© {year} 且听风吟".format(year=now.year) # Zxy动态生成版权年份 + # Zxy获取Feed项链接 def item_link(self, item): return item.get_absolute_url() + # Zxy获取Feed项GUID def item_guid(self, item): - return + return \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/logentryadmin.py b/src/DjangoBlog-master/djangoblog/logentryadmin.py index 2f6a535..e1b4bd3 100644 --- a/src/DjangoBlog-master/djangoblog/logentryadmin.py +++ b/src/DjangoBlog-master/djangoblog/logentryadmin.py @@ -1,91 +1,88 @@ +# Zxy导入Django的admin模块 from django.contrib import admin +# Zxy导入Django的LogEntry模型 from django.contrib.admin.models import DELETION +# Zxy导入Django的内容类型模型 from django.contrib.contenttypes.models import ContentType +# Zxy导入Django的URL工具 from django.urls import reverse, NoReverseMatch +# Zxy导入Django的编码工具 from django.utils.encoding import force_str +# Zxy导入Django的HTML工具 from django.utils.html import escape +# Zxy导入Django的安全字符串工具 from django.utils.safestring import mark_safe +# Zxy导入Django的翻译工具 from django.utils.translation import gettext_lazy as _ - +# Zxy定义LogEntryAdmin类 class LogEntryAdmin(admin.ModelAdmin): - list_filter = [ - 'content_type' - ] - - search_fields = [ - 'object_repr', - 'change_message' - ] + list_filter = ['content_type'] # Zxy按内容类型过滤 + search_fields = ['object_repr', 'change_message'] # Zxy搜索字段 - list_display_links = [ - 'action_time', - 'get_change_message', - ] - list_display = [ - 'action_time', - 'user_link', - 'content_type', - 'object_link', - 'get_change_message', - ] + list_display_links = ['action_time', 'get_change_message'] # Zxy显示链接的字段 + list_display = ['action_time', 'user_link', 'content_type', 'object_link', 'get_change_message'] # Zxy显示字段 + # Zxy检查是否有添加权限 def has_add_permission(self, request): return False + # Zxy检查是否有修改权限 def has_change_permission(self, request, obj=None): return ( request.user.is_superuser or request.user.has_perm('admin.change_logentry') ) and request.method != 'POST' + # Zxy检查是否有删除权限 def has_delete_permission(self, request, obj=None): return False + # Zxy获取对象链接 def object_link(self, obj): - object_link = escape(obj.object_repr) + object_link = escape(obj.object_repr) # Zxy转义对象表示 content_type = obj.content_type - if obj.action_flag != DELETION and content_type is not None: - # try returning an actual link instead of object repr string + if obj.action_flag != DELETION and content_type is not None: # Zxy检查是否为删除操作 try: - url = reverse( - 'admin:{}_{}_change'.format(content_type.app_label, - content_type.model), + url = reverse( # Zxy生成反向URL + 'admin:{}_{}_change'.format(content_type.app_label, content_type.model), args=[obj.object_id] ) - object_link = '{}'.format(url, object_link) + object_link = '{}'.format(url, object_link) # Zxy生成链接 except NoReverseMatch: pass - return mark_safe(object_link) + return mark_safe(object_link) # Zxy标记为安全字符串 - object_link.admin_order_field = 'object_repr' - object_link.short_description = _('object') + object_link.admin_order_field = 'object_repr' # Zxy排序字段 + object_link.short_description = _('object') # Zxy字段描述 + # Zxy获取用户链接 def user_link(self, obj): - content_type = ContentType.objects.get_for_model(type(obj.user)) - user_link = escape(force_str(obj.user)) + content_type = ContentType.objects.get_for_model(type(obj.user)) # Zxy获取用户的内容类型 + user_link = escape(force_str(obj.user)) # Zxy转义用户表示 + try: - # try returning an actual link instead of object repr string - url = reverse( - 'admin:{}_{}_change'.format(content_type.app_label, - content_type.model), + url = reverse( # Zxy生成反向URL + 'admin:{}_{}_change'.format(content_type.app_label, content_type.model), args=[obj.user.pk] ) - user_link = '{}'.format(url, user_link) + user_link = '{}'.format(url, user_link) # Zxy生成链接 except NoReverseMatch: pass - return mark_safe(user_link) + return mark_safe(user_link) # Zxy标记为安全字符串 - user_link.admin_order_field = 'user' - user_link.short_description = _('user') + user_link.admin_order_field = 'user' # Zxy排序字段 + user_link.short_description = _('user') # Zxy字段描述 + # Zxy获取查询集 def get_queryset(self, request): queryset = super(LogEntryAdmin, self).get_queryset(request) - return queryset.prefetch_related('content_type') + return queryset.prefetch_related('content_type') # Zxy预加载内容类型 + # Zxy获取操作 def get_actions(self, request): actions = super(LogEntryAdmin, self).get_actions(request) - if 'delete_selected' in actions: + if 'delete_selected' in actions: # Zxy移除删除操作 del actions['delete_selected'] - return actions + return actions \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/plugin_manage/base_plugin.py b/src/DjangoBlog-master/djangoblog/plugin_manage/base_plugin.py index 2b4be5c..e87d7df 100644 --- a/src/DjangoBlog-master/djangoblog/plugin_manage/base_plugin.py +++ b/src/DjangoBlog-master/djangoblog/plugin_manage/base_plugin.py @@ -1,27 +1,33 @@ +# Zxy导入日志模块 import logging +# Zxy获取日志记录器 logger = logging.getLogger(__name__) - +# Zxy定义基础插件类 class BasePlugin: - # 插件元数据 - PLUGIN_NAME = None - PLUGIN_DESCRIPTION = None - PLUGIN_VERSION = None + # Zxy插件元数据 + PLUGIN_NAME = None # Zxy插件名称 + PLUGIN_DESCRIPTION = None # Zxy插件描述 + PLUGIN_VERSION = None # Zxy插件版本 + # Zxy初始化插件 def __init__(self): + # Zxy检查插件元数据是否已定义 if not all([self.PLUGIN_NAME, self.PLUGIN_DESCRIPTION, self.PLUGIN_VERSION]): raise ValueError("Plugin metadata (PLUGIN_NAME, PLUGIN_DESCRIPTION, PLUGIN_VERSION) must be defined.") - self.init_plugin() - self.register_hooks() + self.init_plugin() # Zxy调用初始化方法 + self.register_hooks() # Zxy注册钩子 + # Zxy插件初始化逻辑 def init_plugin(self): """ 插件初始化逻辑 子类可以重写此方法来实现特定的初始化操作 """ - logger.info(f'{self.PLUGIN_NAME} initialized.') + logger.info(f'{self.PLUGIN_NAME} initialized.') # Zxy记录插件初始化日志 + # Zxy注册插件钩子 def register_hooks(self): """ 注册插件钩子 @@ -29,13 +35,14 @@ class BasePlugin: """ pass + # Zxy获取插件信息 def get_plugin_info(self): """ 获取插件信息 :return: 包含插件元数据的字典 """ return { - 'name': self.PLUGIN_NAME, - 'description': self.PLUGIN_DESCRIPTION, - 'version': self.PLUGIN_VERSION - } + 'name': self.PLUGIN_NAME, # Zxy插件名称 + 'description': self.PLUGIN_DESCRIPTION, # Zxy插件描述 + 'version': self.PLUGIN_VERSION # Zxy插件版本 + } \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/plugin_manage/hook_constants.py b/src/DjangoBlog-master/djangoblog/plugin_manage/hook_constants.py index 6685b7c..cabaed1 100644 --- a/src/DjangoBlog-master/djangoblog/plugin_manage/hook_constants.py +++ b/src/DjangoBlog-master/djangoblog/plugin_manage/hook_constants.py @@ -1,7 +1,8 @@ -ARTICLE_DETAIL_LOAD = 'article_detail_load' -ARTICLE_CREATE = 'article_create' -ARTICLE_UPDATE = 'article_update' -ARTICLE_DELETE = 'article_delete' - -ARTICLE_CONTENT_HOOK_NAME = "the_content" +# Zxy定义文章相关的钩子常量 +ARTICLE_DETAIL_LOAD = 'article_detail_load' # Zxy文章详情加载时触发的钩子 +ARTICLE_CREATE = 'article_create' # Zxy文章创建时触发的钩子 +ARTICLE_UPDATE = 'article_update' # Zxy文章更新时触发的钩子 +ARTICLE_DELETE = 'article_delete' # Zxy文章删除时触发的钩子 +# Zxy定义文章内容处理的钩子名称 +ARTICLE_CONTENT_HOOK_NAME = "the_content" # Zxy文章内容处理的钩子名称 \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/plugin_manage/hooks.py b/src/DjangoBlog-master/djangoblog/plugin_manage/hooks.py index d712540..75317ab 100644 --- a/src/DjangoBlog-master/djangoblog/plugin_manage/hooks.py +++ b/src/DjangoBlog-master/djangoblog/plugin_manage/hooks.py @@ -1,44 +1,47 @@ +# Zxy导入日志模块 import logging +# Zxy获取日志记录器 logger = logging.getLogger(__name__) +# Zxy定义全局钩子字典 _hooks = {} - +# Zxy注册钩子回调 def register(hook_name: str, callback: callable): """ 注册一个钩子回调。 """ - if hook_name not in _hooks: - _hooks[hook_name] = [] - _hooks[hook_name].append(callback) - logger.debug(f"Registered hook '{hook_name}' with callback '{callback.__name__}'") - + if hook_name not in _hooks: # Zxy检查钩子是否已存在 + _hooks[hook_name] = [] # Zxy初始化钩子列表 + _hooks[hook_name].append(callback) # Zxy添加回调到钩子列表 + logger.debug(f"Registered hook '{hook_name}' with callback '{callback.__name__}'") # Zxy记录注册日志 +# Zxy执行Action Hook def run_action(hook_name: str, *args, **kwargs): """ 执行一个 Action Hook。 它会按顺序执行所有注册到该钩子上的回调函数。 """ - if hook_name in _hooks: - logger.debug(f"Running action hook '{hook_name}'") - for callback in _hooks[hook_name]: + if hook_name in _hooks: # Zxy检查钩子是否存在 + logger.debug(f"Running action hook '{hook_name}'") # Zxy记录执行日志 + for callback in _hooks[hook_name]: # Zxy遍历钩子回调 try: - callback(*args, **kwargs) + callback(*args, **kwargs) # Zxy执行回调 except Exception as e: - logger.error(f"Error running action hook '{hook_name}' callback '{callback.__name__}': {e}", exc_info=True) - + logger.error(f"Error running action hook '{hook_name}' callback '{callback.__name__}': {e}", exc_info=True) # Zxy记录错误日志 +# Zxy执行Filter Hook def apply_filters(hook_name: str, value, *args, **kwargs): """ 执行一个 Filter Hook。 它会把 value 依次传递给所有注册的回调函数进行处理。 """ - if hook_name in _hooks: - logger.debug(f"Applying filter hook '{hook_name}'") - for callback in _hooks[hook_name]: + if hook_name in _hooks: # Zxy检查钩子是否存在 + logger.debug(f"Applying filter hook '{hook_name}'") # Zxy记录执行日志 + for callback in _hooks[hook_name]: # Zxy遍历钩子回调 try: - value = callback(value, *args, **kwargs) + value = callback(value, *args, **kwargs) # Zxy调用回调处理值 except Exception as e: - logger.error(f"Error applying filter hook '{hook_name}' callback '{callback.__name__}': {e}", exc_info=True) - return value + logger.error(f"Error applying filter hook '{hook_name}' callback '{callback.__name__}': {e}", exc_info=True) # Zxy记录错误日志 + return value # Zxy返回处理后的值 \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/plugin_manage/loader.py b/src/DjangoBlog-master/djangoblog/plugin_manage/loader.py index 12e824b..9ee7372 100644 --- a/src/DjangoBlog-master/djangoblog/plugin_manage/loader.py +++ b/src/DjangoBlog-master/djangoblog/plugin_manage/loader.py @@ -1,19 +1,24 @@ +# Zxy导入os模块,用于操作路径 import os +# Zxy导入日志模块 import logging +# Zxy导入Django的配置模块 from django.conf import settings +# Zxy获取日志记录器 logger = logging.getLogger(__name__) +# Zxy动态加载插件 def load_plugins(): """ Dynamically loads and initializes plugins from the 'plugins' directory. This function is intended to be called when the Django app registry is ready. """ - for plugin_name in settings.ACTIVE_PLUGINS: - plugin_path = os.path.join(settings.PLUGINS_DIR, plugin_name) - if os.path.isdir(plugin_path) and os.path.exists(os.path.join(plugin_path, 'plugin.py')): + for plugin_name in settings.ACTIVE_PLUGINS: # Zxy遍历激活的插件 + plugin_path = os.path.join(settings.PLUGINS_DIR, plugin_name) # Zxy获取插件路径 + if os.path.isdir(plugin_path) and os.path.exists(os.path.join(plugin_path, 'plugin.py')): # Zxy检查插件目录和文件 try: - __import__(f'plugins.{plugin_name}.plugin') - logger.info(f"Successfully loaded plugin: {plugin_name}") + __import__(f'plugins.{plugin_name}.plugin') # Zxy动态导入插件模块 + logger.info(f"Successfully loaded plugin: {plugin_name}") # Zxy记录加载成功日志 except ImportError as e: - logger.error(f"Failed to import plugin: {plugin_name}", exc_info=e) \ No newline at end of file + logger.error(f"Failed to import plugin: {plugin_name}", exc_info=e) # Zxy记录加载失败日志 \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/settings.py b/src/DjangoBlog-master/djangoblog/settings.py index d076bb6..943c243 100644 --- a/src/DjangoBlog-master/djangoblog/settings.py +++ b/src/DjangoBlog-master/djangoblog/settings.py @@ -16,12 +16,13 @@ from pathlib import Path from django.utils.translation import gettext_lazy as _ +# Zxy环境变量布尔值转换函数:将环境变量转换为布尔值 def env_to_bool(env, default): str_val = os.environ.get(env) return default if str_val is None else str_val == 'True' -# Build paths inside the project like this: BASE_DIR / 'subdir'. +# Zxy项目根目录:获取项目的绝对路径 BASE_DIR = Path(__file__).resolve().parent.parent # Quick-start development settings - unsuitable for production @@ -30,20 +31,20 @@ BASE_DIR = Path(__file__).resolve().parent.parent # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = os.environ.get( 'DJANGO_SECRET_KEY') or 'n9ceqv38)#&mwuat@(mjb_p%em$e8$qyr#fw9ot!=ba6lijx-6' -# SECURITY WARNING: don't run with debug turned on in production! +# Zxy调试模式:从环境变量获取或默认为 True DEBUG = env_to_bool('DJANGO_DEBUG', True) -# DEBUG = False +# Zxy测试模式检测:检查是否运行测试 TESTING = len(sys.argv) > 1 and sys.argv[1] == 'test' -# ALLOWED_HOSTS = [] +# Zxy允许的主机名:允许访问的域名列表 ALLOWED_HOSTS = ['*', '127.0.0.1', 'example.com'] -# django 4.0新增配置 +# ZxyCSRF 可信来源:允许的 CSRF 来源 CSRF_TRUSTED_ORIGINS = ['http://example.com'] # Application definition - +# Zxy应用程序定义:定义项目中使用的 Django 应用程序 INSTALLED_APPS = [ - # 'django.contrib.admin', + # 默认的 Django 应用程序 'django.contrib.admin.apps.SimpleAdminConfig', 'django.contrib.auth', 'django.contrib.contenttypes', @@ -52,6 +53,7 @@ INSTALLED_APPS = [ 'django.contrib.staticfiles', 'django.contrib.sites', 'django.contrib.sitemaps', + # 第三方和自定义应用程序 'mdeditor', 'haystack', 'blog', @@ -63,7 +65,7 @@ INSTALLED_APPS = [ 'compressor', 'djangoblog' ] - +# Zxy中间件配置:定义请求处理的中间件 MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', @@ -80,9 +82,9 @@ MIDDLEWARE = [ 'django.middleware.http.ConditionalGetMiddleware', 'blog.middleware.OnlineMiddleware' ] - +# ZxyURL 配置:定义项目的 URL 配置文件 ROOT_URLCONF = 'djangoblog.urls' - +# Zxy模板配置:配置 Django 模板引擎 TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', @@ -99,29 +101,27 @@ TEMPLATES = [ }, }, ] - +# ZxyWSGI 应用程序:定义 WSGI 应用程序入口 WSGI_APPLICATION = 'djangoblog.wsgi.application' # Database # https://docs.djangoproject.com/en/1.10/ref/settings/#databases - +# Zxy数据库配置:配置数据库连接 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'}, - }} + 'NAME': 'djangoblog', + 'USER': 'root', + 'PASSWORD': '123456', + 'HOST': '127.0.0.1', + 'PORT': 3306, + } +} # Password validation # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators - +# Zxy密码验证器:配置密码验证规则 AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', @@ -136,7 +136,7 @@ AUTH_PASSWORD_VALIDATORS = [ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] - +# Zxy语言和时区配置:配置语言和时区 LANGUAGES = ( ('en', _('English')), ('zh-hans', _('Simplified Chinese')), @@ -159,7 +159,7 @@ USE_TZ = False # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.10/howto/static-files/ - +# ZxyHaystack 搜索配置:配置 Haystack 搜索引擎 HAYSTACK_CONNECTIONS = { 'default': { 'ENGINE': 'djangoblog.whoosh_cn_backend.WhooshEngine', @@ -171,12 +171,12 @@ HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor' # Allow user login with username and password AUTHENTICATION_BACKENDS = [ 'accounts.user_login_backend.EmailOrUsernameModelBackend'] - +# Zxy静态文件配置:配置静态文件路径 STATIC_ROOT = os.path.join(BASE_DIR, 'collectedstatic') STATIC_URL = '/static/' STATICFILES = os.path.join(BASE_DIR, 'static') - +# Zxy用户模型配置:定义自定义用户模型和登录 URL AUTH_USER_MODEL = 'accounts.BlogUser' LOGIN_URL = '/login/' @@ -193,6 +193,7 @@ PAGINATE_BY = 10 # http cache timeout CACHE_CONTROL_MAX_AGE = 2592000 # cache setting +# Zxy缓存配置:配置缓存后端 CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', @@ -208,7 +209,7 @@ if os.environ.get("DJANGO_REDIS_URL"): 'LOCATION': f'redis://{os.environ.get("DJANGO_REDIS_URL")}', } } - +# Zxy站点 ID:定义当前站点的 ID SITE_ID = 1 BAIDU_NOTIFY_URL = os.environ.get('DJANGO_BAIDU_NOTIFY_URL') \ or 'http://data.zz.baidu.com/urls?site=https://www.lylinux.net&token=1uAOGrMsUm5syDGn' @@ -232,7 +233,7 @@ WXADMIN = os.environ.get( LOG_PATH = os.path.join(BASE_DIR, 'logs') if not os.path.exists(LOG_PATH): os.makedirs(LOG_PATH, exist_ok=True) - +# Zxy日志配置:配置日志记录 LOGGING = { 'version': 1, 'disable_existing_loggers': False, @@ -293,7 +294,7 @@ LOGGING = { } } } - +# Zxy压缩配置:配置静态文件压缩 STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', @@ -313,13 +314,13 @@ COMPRESS_CSS_FILTERS = [ COMPRESS_JS_FILTERS = [ 'compressor.filters.jsmin.JSMinFilter' ] - +# Zxy媒体文件配置:配置媒体文件路径 MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads') MEDIA_URL = '/media/' X_FRAME_OPTIONS = 'SAMEORIGIN' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' - +# ZxyElasticsearch 配置:配置 Elasticsearch 搜索引擎 if os.environ.get('DJANGO_ELASTICSEARCH_HOST'): ELASTICSEARCH_DSL = { 'default': { @@ -332,7 +333,7 @@ if os.environ.get('DJANGO_ELASTICSEARCH_HOST'): }, } -# Plugin System +# Zxy插件系统配置:定义插件目录和激活的插件 PLUGINS_DIR = BASE_DIR / 'plugins' ACTIVE_PLUGINS = [ 'article_copyright', diff --git a/src/DjangoBlog-master/djangoblog/sitemap.py b/src/DjangoBlog-master/djangoblog/sitemap.py index 8b7d446..986f1ef 100644 --- a/src/DjangoBlog-master/djangoblog/sitemap.py +++ b/src/DjangoBlog-master/djangoblog/sitemap.py @@ -1,59 +1,70 @@ +# Zxy导入 Django 的 Sitemap 模块和其他相关模块 from django.contrib.sitemaps import Sitemap from django.urls import reverse from blog.models import Article, Category, Tag - +# Zxy定义静态页面的 Sitemap class StaticViewSitemap(Sitemap): - priority = 0.5 - changefreq = 'daily' + priority = 0.5 # Zxy设置优先级 + changefreq = 'daily' # Zxy设置更新频率 def items(self): + # Zxy返回静态页面的 URL 名称 return ['blog:index', ] def location(self, item): + # Zxy通过 URL 名称生成完整的 URL return reverse(item) - +# Zxy定义文章的 Sitemap class ArticleSiteMap(Sitemap): - changefreq = "monthly" - priority = "0.6" + changefreq = "monthly" # Zxy文章更新频率为每月 + priority = "0.6" # Zxy文章优先级 def items(self): + # Zxy返回所有已发布的文章 return Article.objects.filter(status='p') def lastmod(self, obj): + # Zxy返回文章的最后修改时间 return obj.last_modify_time - +# Zxy定义分类的 Sitemap class CategorySiteMap(Sitemap): - changefreq = "Weekly" - priority = "0.6" + changefreq = "Weekly" # Zxy分类更新频率为每周 + priority = "0.6" # Zxy分类优先级 def items(self): + # Zxy返回所有分类 return Category.objects.all() def lastmod(self, obj): + # Zxy返回分类的最后修改时间 return obj.last_modify_time - +# Zxy定义标签的 Sitemap class TagSiteMap(Sitemap): - changefreq = "Weekly" - priority = "0.3" + changefreq = "Weekly" # Zxy标签更新频率为每周 + priority = "0.3" # Zxy标签优先级 def items(self): + # Zxy返回所有标签 return Tag.objects.all() def lastmod(self, obj): + # Zxy返回标签的最后修改时间 return obj.last_modify_time - +# Zxy定义用户的 Sitemap class UserSiteMap(Sitemap): - changefreq = "Weekly" - priority = "0.3" + changefreq = "Weekly" # Zxy用户更新频率为每周 + priority = "0.3" # Zxy用户优先级 def items(self): + # Zxy返回所有用户的作者列表(去重) return list(set(map(lambda x: x.author, Article.objects.all()))) def lastmod(self, obj): - return obj.date_joined + # Zxy返回用户的注册时间 + return obj.date_joined \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/spider_notify.py b/src/DjangoBlog-master/djangoblog/spider_notify.py index 7b909e9..20fed14 100644 --- a/src/DjangoBlog-master/djangoblog/spider_notify.py +++ b/src/DjangoBlog-master/djangoblog/spider_notify.py @@ -1,21 +1,26 @@ +# Zxy导入日志模块 import logging +# Zxy导入 requests 模块用于发送 HTTP 请求 import requests from django.conf import settings +# Zxy获取日志记录器 logger = logging.getLogger(__name__) - +# Zxy定义爬虫通知类 class SpiderNotify(): @staticmethod def baidu_notify(urls): + # Zxy向百度站长平台发送 URL 提交请求 try: - data = '\n'.join(urls) - result = requests.post(settings.BAIDU_NOTIFY_URL, data=data) - logger.info(result.text) + data = '\n'.join(urls) # Zxy将 URL 列表拼接为字符串 + result = requests.post(settings.BAIDU_NOTIFY_URL, data=data) # Zxy发送 POST 请求 + logger.info(result.text) # Zxy记录响应内容 except Exception as e: - logger.error(e) + logger.error(e) # Zxy记录异常信息 @staticmethod def notify(url): - SpiderNotify.baidu_notify(url) + # Zxy调用百度通知方法 + SpiderNotify.baidu_notify(url) \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/tests.py b/src/DjangoBlog-master/djangoblog/tests.py index 01237d9..83af4c1 100644 --- a/src/DjangoBlog-master/djangoblog/tests.py +++ b/src/DjangoBlog-master/djangoblog/tests.py @@ -1,16 +1,21 @@ +# Zxy导入 Django 的测试模块 from django.test import TestCase +# Zxy导入项目中的工具函数 from djangoblog.utils import * - +# Zxy定义测试类 class DjangoBlogTest(TestCase): def setUp(self): + # Zxy测试初始化方法(暂无内容) pass def test_utils(self): - md5 = get_sha256('test') - self.assertIsNotNone(md5) - c = CommonMarkdown.get_markdown(''' + # Zxy测试工具函数 + md5 = get_sha256('test') # Zxy测试 SHA256 函数 + self.assertIsNotNone(md5) # Zxy断言返回值不为空 + + c = CommonMarkdown.get_markdown(''' # Zxy测试 Markdown 转换 # Title1 ```python @@ -23,10 +28,11 @@ class DjangoBlogTest(TestCase): ''') - self.assertIsNotNone(c) + self.assertIsNotNone(c) # Zxy断言返回值不为空 + d = { 'd': 'key1', 'd2': 'key2' } - data = parse_dict_to_url(d) - self.assertIsNotNone(data) + data = parse_dict_to_url(d) # Zxy测试字典转 URL 函数 + self.assertIsNotNone(data) # Zxy断言返回值不为空 \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/urls.py b/src/DjangoBlog-master/djangoblog/urls.py index 4aae58a..f158317 100644 --- a/src/DjangoBlog-master/djangoblog/urls.py +++ b/src/DjangoBlog-master/djangoblog/urls.py @@ -1,3 +1,4 @@ +# Zxy定义项目的 URL 配置 """djangoblog URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: @@ -27,8 +28,8 @@ from djangoblog.elasticsearch_backend import ElasticSearchModelSearchForm from djangoblog.feeds import DjangoBlogFeed from djangoblog.sitemap import ArticleSiteMap, CategorySiteMap, StaticViewSitemap, TagSiteMap, UserSiteMap +# Zxy定义站点地图 sitemaps = { - 'blog': ArticleSiteMap, 'Category': CategorySiteMap, 'Tag': TagSiteMap, @@ -36,29 +37,32 @@ sitemaps = { 'static': StaticViewSitemap } +# Zxy定义 404、500 和 403 错误页面的视图 handler404 = 'blog.views.page_not_found_view' handler500 = 'blog.views.server_error_view' handle403 = 'blog.views.permission_denied_view' +# Zxy定义 URL 模式 urlpatterns = [ - path('i18n/', include('django.conf.urls.i18n')), + path('i18n/', include('django.conf.urls.i18n')), # Zxy国际化语言切换 ] urlpatterns += i18n_patterns( - re_path(r'^admin/', admin_site.urls), - re_path(r'', include('blog.urls', namespace='blog')), - re_path(r'mdeditor/', include('mdeditor.urls')), - re_path(r'', include('comments.urls', namespace='comment')), - re_path(r'', include('accounts.urls', namespace='account')), - re_path(r'', include('oauth.urls', namespace='oauth')), - re_path(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps}, + re_path(r'^admin/', admin_site.urls), # Zxy自定义 Admin 站点 + re_path(r'', include('blog.urls', namespace='blog')), # Zxy博客应用的 URL + re_path(r'mdeditor/', include('mdeditor.urls')), # Zxy Markdown 编辑器的 URL + re_path(r'', include('comments.urls', namespace='comment')), # Zxy评论应用的 URL + re_path(r'', include('accounts.urls', namespace='account')), # Zxy用户账户的 URL + re_path(r'', include('oauth.urls', namespace='oauth')), # Zxy OAuth 应用的 URL + re_path(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps}, # Zxy站点地图 name='django.contrib.sitemaps.views.sitemap'), - re_path(r'^feed/$', DjangoBlogFeed()), - re_path(r'^rss/$', DjangoBlogFeed()), - re_path('^search', search_view_factory(view_class=EsSearchView, form_class=ElasticSearchModelSearchForm), + re_path(r'^feed/$', DjangoBlogFeed()), # Zxy RSS 订阅 + re_path(r'^rss/$', DjangoBlogFeed()), # Zxy RSS 订阅 + re_path('^search', search_view_factory(view_class=EsSearchView, form_class=ElasticSearchModelSearchForm), # Zxy搜索功能 name='search'), - re_path(r'', include('servermanager.urls', namespace='servermanager')), - re_path(r'', include('owntracks.urls', namespace='owntracks')) - , prefix_default_language=False) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + re_path(r'', include('servermanager.urls', namespace='servermanager')), # Zxy服务器管理应用的 URL + re_path(r'', include('owntracks.urls', namespace='owntracks')), # Zxy位置跟踪应用的 URL + prefix_default_language=False +) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) # Zxy静态文件的 URL if settings.DEBUG: - urlpatterns += static(settings.MEDIA_URL, - document_root=settings.MEDIA_ROOT) + urlpatterns += static(settings.MEDIA_URL, # Zxy媒体文件的 URL + document_root=settings.MEDIA_ROOT) \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/utils.py b/src/DjangoBlog-master/djangoblog/utils.py index 57f63dc..90cfa42 100644 --- a/src/DjangoBlog-master/djangoblog/utils.py +++ b/src/DjangoBlog-master/djangoblog/utils.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # encoding: utf-8 - +# Zxy导入日志模块 import logging import os import random @@ -17,20 +17,21 @@ from django.contrib.sites.models import Site from django.core.cache import cache from django.templatetags.static import static +# Zxy获取日志记录器 logger = logging.getLogger(__name__) - +# Zxy获取文章和评论的最大 ID def get_max_articleid_commentid(): from blog.models import Article from comments.models import Comment return (Article.objects.latest().pk, Comment.objects.latest().pk) - +# Zxy计算字符串的 SHA256 值 def get_sha256(str): m = sha256(str.encode('utf-8')) return m.hexdigest() - +# Zxy缓存装饰器,用于缓存函数的返回值 def cache_decorator(expiration=3 * 60): def wrapper(func): def news(*args, **kwargs): @@ -41,41 +42,27 @@ def cache_decorator(expiration=3 * 60): key = None if not key: unique_str = repr((func, args, kwargs)) - m = sha256(unique_str.encode('utf-8')) key = m.hexdigest() value = cache.get(key) if value is not None: - # logger.info('cache_decorator get cache:%s key:%s' % (func.__name__, key)) if str(value) == '__default_cache_value__': return None else: return value else: - logger.debug( - 'cache_decorator set cache:%s key:%s' % - (func.__name__, key)) + logger.debug('cache_decorator set cache:%s key:%s' % (func.__name__, key)) value = func(*args, **kwargs) if value is None: cache.set(key, '__default_cache_value__', expiration) else: cache.set(key, value, expiration) return value - return news - return wrapper - +# Zxy刷新视图缓存 def expire_view_cache(path, servername, serverport, key_prefix=None): - ''' - 刷新视图缓存 - :param path:url路径 - :param servername:host - :param serverport:端口 - :param key_prefix:前缀 - :return:是否成功 - ''' from django.http import HttpRequest from django.utils.cache import get_cache_key @@ -91,24 +78,17 @@ def expire_view_cache(path, servername, serverport, key_prefix=None): return True return False - +# Zxy获取当前站点 @cache_decorator() def get_current_site(): site = Site.objects.get_current() return site - +# Zxy定义 CommonMarkdown 类,用于处理 Markdown 转换 class CommonMarkdown: @staticmethod def _convert_markdown(value): - md = markdown.Markdown( - extensions=[ - 'extra', - 'codehilite', - 'toc', - 'tables', - ] - ) + md = markdown.Markdown(extensions=['extra', 'codehilite', 'toc', 'tables']) body = md.convert(value) toc = md.toc return body, toc @@ -123,28 +103,22 @@ class CommonMarkdown: body, toc = CommonMarkdown._convert_markdown(value) return body - +# Zxy发送邮件 def send_email(emailto, title, content): from djangoblog.blog_signals import send_email_signal - send_email_signal.send( - send_email.__class__, - emailto=emailto, - title=title, - content=content) - + send_email_signal.send(send_email.__class__, emailto=emailto, title=title, content=content) +# Zxy生成随机验证码 def generate_code() -> str: - """生成随机数验证码""" return ''.join(random.sample(string.digits, 6)) - +# Zxy将字典转换为 URL 参数 def parse_dict_to_url(dict): from urllib.parse import quote - url = '&'.join(['{}={}'.format(quote(k, safe='/'), quote(v, safe='/')) - for k, v in dict.items()]) + url = '&'.join(['{}={}'.format(quote(k, safe='/'), quote(v, safe='/')) for k, v in dict.items()]) return url - +# Zxy获取博客设置 def get_blog_setting(): value = cache.get('get_blog_setting') if value: @@ -172,22 +146,15 @@ def get_blog_setting(): cache.set('get_blog_setting', value) return value - +# Zxy保存用户头像 def save_user_avatar(url): - ''' - 保存用户头像 - :param url:头像url - :return: 本地路径 - ''' logger.info(url) - try: basedir = os.path.join(settings.STATICFILES, 'avatar') rsp = requests.get(url, timeout=2) if rsp.status_code == 200: if not os.path.exists(basedir): os.makedirs(basedir) - image_extensions = ['.jpg', '.png', 'jpeg', '.gif'] isimage = len([i for i in image_extensions if url.endswith(i)]) > 0 ext = os.path.splitext(url)[1] if isimage else '.jpg' @@ -200,7 +167,7 @@ def save_user_avatar(url): logger.error(e) return static('blog/img/avatar.png') - +# Zxy删除侧边栏缓存 def delete_sidebar_cache(): from blog.models import LinkShowType keys = ["sidebar" + x for x in LinkShowType.values] @@ -208,13 +175,13 @@ def delete_sidebar_cache(): logger.info('delete sidebar key:' + k) cache.delete(k) - +# Zxy删除视图缓存 def delete_view_cache(prefix, keys): from django.core.cache.utils import make_template_fragment_key key = make_template_fragment_key(prefix, keys) cache.delete(key) - +# Zxy获取资源 URL def get_resource_url(): if settings.STATIC_URL: return settings.STATIC_URL @@ -222,11 +189,10 @@ def get_resource_url(): site = get_current_site() return 'http://' + site.domain + '/static/' - -ALLOWED_TAGS = ['a', 'abbr', 'acronym', 'b', 'blockquote', 'code', 'em', 'i', 'li', 'ol', 'pre', 'strong', 'ul', 'h1', - 'h2', 'p'] +# Zxy定义允许的 HTML 标签和属性 +ALLOWED_TAGS = ['a', 'abbr', 'acronym', 'b', 'blockquote', 'code', 'em', 'i', 'li', 'ol', 'pre', 'strong', 'ul', 'h1', 'h2', 'p'] ALLOWED_ATTRIBUTES = {'a': ['href', 'title'], 'abbr': ['title'], 'acronym': ['title']} - +# Zxy清理 HTML 内容 def sanitize_html(html): - return bleach.clean(html, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES) + return bleach.clean(html, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES) \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/whoosh_cn_backend.py b/src/DjangoBlog-master/djangoblog/whoosh_cn_backend.py index 04e3f7f..994247c 100644 --- a/src/DjangoBlog-master/djangoblog/whoosh_cn_backend.py +++ b/src/DjangoBlog-master/djangoblog/whoosh_cn_backend.py @@ -1,7 +1,8 @@ +# Zxy 导入未来支持模块,确保代码在 Python 2 和 3 中的兼容性 # encoding: utf-8 - from __future__ import absolute_import, division, print_function, unicode_literals +# Zxy 导入标准库模块 import json import os import re @@ -9,6 +10,7 @@ import shutil import threading import warnings +# Zxy 导入第三方库 import six from django.conf import settings from django.core.exceptions import ImproperlyConfigured @@ -34,36 +36,41 @@ from whoosh.qparser import QueryParser from whoosh.searching import ResultsPage from whoosh.writing import AsyncWriter +# Zxy 尝试导入 whoosh 库,如果失败则抛出依赖缺失异常 try: import whoosh except ImportError: raise MissingDependency( "The 'whoosh' backend requires the installation of 'Whoosh'. Please refer to the documentation.") -# Handle minimum requirement. +# Zxy 检查 whoosh 版本是否满足最低要求 if not hasattr(whoosh, '__version__') or whoosh.__version__ < (2, 5, 0): raise MissingDependency( "The 'whoosh' backend requires version 2.5.0 or greater.") -# Bubble up the correct error. - +# Zxy 定义一个正则表达式,用于匹配 ISO 8601 格式的日期时间字符串 DATETIME_REGEX = re.compile( '^(?P\d{4})-(?P\d{2})-(?P\d{2})T(?P\d{2}):(?P\d{2}):(?P\d{2})(\.\d{3,6}Z?)?$') +# Zxy 创建一个线程局部存储对象,用于在每个线程中存储独立的数据 LOCALS = threading.local() +# Zxy 初始化线程局部存储中的 RAM_STORE 为 None,用于存储内存索引 LOCALS.RAM_STORE = None +# Zxy 定义一个自定义的 HTML 格式化器,继承自 Whoosh 的 HtmlFormatter class WhooshHtmlFormatter(HtmlFormatter): """ This is a HtmlFormatter simpler than the whoosh.HtmlFormatter. We use it to have consistent results across backends. Specifically, Solr, Xapian and Elasticsearch are using this formatting. """ + # Zxy 定义高亮显示的 HTML 模板 template = '<%(tag)s>%(t)s' +# Zxy 定义 Whoosh 搜索后端类,继承自 Haystack 的 BaseSearchBackend class WhooshSearchBackend(BaseSearchBackend): - # Word reserved by Whoosh for special use. + # Zxy 定义 Whoosh 保留的关键字列表,这些关键字在查询中有特殊含义 RESERVED_WORDS = ( 'AND', 'NOT', @@ -71,7 +78,7 @@ class WhooshSearchBackend(BaseSearchBackend): 'TO', ) - # Characters reserved by Whoosh for special use. + # Zxy 定义 Whoosh 保留的字符列表,这些字符在查询中有特殊含义 # The '\\' must come first, so as not to overwrite the other slash # replacements. RESERVED_CHARACTERS = ( @@ -79,157 +86,214 @@ class WhooshSearchBackend(BaseSearchBackend): '[', ']', '^', '"', '~', '*', '?', ':', '.', ) + # Zxy 初始化方法,设置连接别名和连接选项 def __init__(self, connection_alias, **connection_options): + # Zxy 调用父类的初始化方法 super( WhooshSearchBackend, self).__init__( connection_alias, **connection_options) + # Zxy 标记后端设置尚未完成 self.setup_complete = False + # Zxy 默认使用文件存储 self.use_file_storage = True + # Zxy 设置 POST 请求的大小限制,默认为 128MB self.post_limit = getattr( connection_options, 'POST_LIMIT', 128 * 1024 * 1024) + # Zxy 从连接选项中获取索引文件存储路径 self.path = connection_options.get('PATH') + # Zxy 如果配置的存储类型不是 'file',则使用内存存储 if connection_options.get('STORAGE', 'file') != 'file': self.use_file_storage = False + # Zxy 如果使用文件存储但未提供路径,则抛出配置错误异常 if self.use_file_storage and not self.path: raise ImproperlyConfigured( "You must specify a 'PATH' in your settings for connection '%s'." % connection_alias) + # Zxy 获取 Haystack 的日志记录器 self.log = logging.getLogger('haystack') + + # Zxy 设置搜索引擎,延迟加载直到需要时才执行 def setup(self): """ Defers loading until needed. """ + # Zxy 导入 haystack 的连接管理器 from haystack import connections + # Zxy 标记是否为新创建的索引 new_index = False - # Make sure the index is there. + # Zxy 确保索引目录存在 if self.use_file_storage and not os.path.exists(self.path): + # Zxy 如果目录不存在,则创建它 os.makedirs(self.path) + # Zxy 标记这是一个新索引 new_index = True + # Zxy 检查索引目录是否可写 if self.use_file_storage and not os.access(self.path, os.W_OK): + # Zxy 如果不可写,则抛出 IO 错误 raise IOError( "The path to your Whoosh index '%s' is not writable for the current user/group." % self.path) + # Zxy 根据配置选择存储方式:文件存储或内存存储 if self.use_file_storage: + # Zxy 使用文件存储 self.storage = FileStorage(self.path) else: + # Zxy 声明使用全局的线程局部存储 global LOCALS + # Zxy 如果内存存储尚未初始化,则创建一个 if getattr(LOCALS, 'RAM_STORE', None) is None: LOCALS.RAM_STORE = RamStorage() + # Zxy 使用内存存储 self.storage = LOCALS.RAM_STORE + # Zxy 构建索引模式,并获取主内容字段名 self.content_field_name, self.schema = self.build_schema( connections[self.connection_alias].get_unified_index().all_searchfields()) + # Zxy 创建查询解析器,用于解析用户查询 self.parser = QueryParser(self.content_field_name, schema=self.schema) + # Zxy 根据是否为新索引来创建或打开索引 if new_index is True: + # Zxy 如果是新索引,则根据 schema 创建 self.index = self.storage.create_index(self.schema) else: + # Zxy 否则尝试打开现有索引 try: self.index = self.storage.open_index(schema=self.schema) except index.EmptyIndexError: + # Zxy 如果索引为空(可能刚创建目录但无文件),则创建新索引 self.index = self.storage.create_index(self.schema) + # Zxy 标记后端设置已完成 self.setup_complete = True + # Zxy 根据 Haystack 的搜索字段构建 Whoosh 的索引模式 def build_schema(self, fields): + # Zxy 初始化模式字段,包含 Haystack 内置的 ID、类型和模型 ID schema_fields = { ID: WHOOSH_ID(stored=True, unique=True), DJANGO_CT: WHOOSH_ID(stored=True), DJANGO_ID: WHOOSH_ID(stored=True), } - # Grab the number of keys that are hard-coded into Haystack. - # We'll use this to (possibly) fail slightly more gracefully later. + # Zxy 获取 Haystack 内置字段的数量,用于后续检查 initial_key_count = len(schema_fields) + # Zxy 初始化主内容字段名 content_field_name = '' + # Zxy 遍历所有搜索字段,根据字段类型转换为 Whoosh 字段 for field_name, field_class in fields.items(): + # Zxy 如果字段是多值字段 if field_class.is_multivalued: if field_class.indexed is False: + # Zxy 如果多值字段不被索引,使用 IDLIST 类型 schema_fields[field_class.index_fieldname] = IDLIST( stored=True, field_boost=field_class.boost) else: + # Zxy 如果多值字段被索引,使用 KEYWORD 类型 schema_fields[field_class.index_fieldname] = KEYWORD( stored=True, commas=True, scorable=True, field_boost=field_class.boost) + # Zxy 如果字段类型是日期或日期时间 elif field_class.field_type in ['date', 'datetime']: + # Zxy 使用 DATETIME 类型,并设置为可排序 schema_fields[field_class.index_fieldname] = DATETIME( stored=field_class.stored, sortable=True) + # Zxy 如果字段类型是整数 elif field_class.field_type == 'integer': + # Zxy 使用 NUMERIC 类型,并指定数字类型为整数 schema_fields[field_class.index_fieldname] = NUMERIC( stored=field_class.stored, numtype=int, field_boost=field_class.boost) + # Zxy 如果字段类型是浮点数 elif field_class.field_type == 'float': + # Zxy 使用 NUMERIC 类型,并指定数字类型为浮点数 schema_fields[field_class.index_fieldname] = NUMERIC( stored=field_class.stored, numtype=float, field_boost=field_class.boost) + # Zxy 如果字段类型是布尔值 elif field_class.field_type == 'boolean': + # Zxy 使用 BOOLEAN 类型 # Field boost isn't supported on BOOLEAN as of 1.8.2. schema_fields[field_class.index_fieldname] = BOOLEAN( stored=field_class.stored) + # Zxy 如果字段类型是 N-gram elif field_class.field_type == 'ngram': + # Zxy 使用 NGRAM 类型 schema_fields[field_class.index_fieldname] = NGRAM( minsize=3, maxsize=15, stored=field_class.stored, field_boost=field_class.boost) + # Zxy 如果字段类型是边 N-gram elif field_class.field_type == 'edge_ngram': + # Zxy 使用 NGRAMWORDS 类型,并设置为从词首开始 schema_fields[field_class.index_fieldname] = NGRAMWORDS(minsize=2, maxsize=15, at='start', stored=field_class.stored, field_boost=field_class.boost) else: + # Zxy 默认情况下,使用 TEXT 类型,并配置中文分词器 # 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) + # Zxy 如果字段被标记为文档主内容 if field_class.document is True: + # Zxy 记录主内容字段名 content_field_name = field_class.index_fieldname + # Zxy 启用拼写建议功能 schema_fields[field_class.index_fieldname].spelling = True - # Fail more gracefully than relying on the backend to die if no fields - # are found. + # Zxy 如果除了内置字段外没有找到任何其他字段,则优雅地报错 if len(schema_fields) <= initial_key_count: raise SearchBackendError( "No fields were found in any search_indexes. Please correct this before attempting to search.") + # Zxy 返回主内容字段名和构建好的 Schema 对象 return (content_field_name, Schema(**schema_fields)) + # Zxy 更新索引,将可迭代对象中的每个对象添加或更新到 Whoosh 索引中 def update(self, index, iterable, commit=True): + # Zxy 如果后端尚未初始化,则先进行设置 if not self.setup_complete: self.setup() + # Zxy 刷新索引以获取最新状态 self.index = self.index.refresh() + # Zxy 创建一个异步写入器,用于高效地写入索引 writer = AsyncWriter(self.index) + # Zxy 遍历所有需要更新的对象 for obj in iterable: try: + # Zxy 准备要索引的文档数据 doc = index.full_prepare(obj) except SkipDocument: + # Zxy 如果对象被标记为跳过,则记录调试信息 self.log.debug(u"Indexing for object `%s` skipped", obj) else: - # Really make sure it's unicode, because Whoosh won't have it any - # other way. + # Zxy 确保所有字段值都是 Whoosh 可以处理的格式 for key in doc: doc[key] = self._from_python(doc[key]) - # Document boosts aren't supported in Whoosh 2.5.0+. + # Zxy 文档级别的权重提升在 Whoosh 2.5.0+ 版本中不再支持 if 'boost' in doc: del doc['boost'] try: + # Zxy 使用写入器更新文档(如果存在则更新,否则创建) writer.update_document(**doc) except Exception as e: + # Zxy 如果未配置为静默失败,则重新抛出异常 if not self.silently_fail: raise - # We'll log the object identifier but won't include the actual object - # to avoid the possibility of that generating encoding errors while - # processing the log message: + # Zxy 记录错误信息,包含对象标识符,但不包含对象本身以避免编码问题 self.log.error( u"%s while preparing object for update" % e.__class__.__name__, @@ -239,60 +303,78 @@ class WhooshSearchBackend(BaseSearchBackend): "index": index, "object": get_identifier(obj)}}) + # Zxy 如果可迭代对象不为空,则提交更改 if len(iterable) > 0: # For now, commit no matter what, as we run into locking issues # otherwise. writer.commit() + # Zxy 从索引中移除一个对象 def remove(self, obj_or_string, commit=True): + # Zxy 如果后端尚未初始化,则先进行设置 if not self.setup_complete: self.setup() + # Zxy 刷新索引以获取最新状态 self.index = self.index.refresh() + # Zxy 获取对象的唯一标识符 whoosh_id = get_identifier(obj_or_string) try: + # Zxy 构建一个查询,根据 ID 查找文档并删除 self.index.delete_by_query( q=self.parser.parse( u'%s:"%s"' % (ID, whoosh_id))) except Exception as e: + # Zxy 如果未配置为静默失败,则重新抛出异常 if not self.silently_fail: raise + # Zxy 记录删除失败的错误 self.log.error( "Failed to remove document '%s' from Whoosh: %s", whoosh_id, e, exc_info=True) + # Zxy 清空索引,可以清空所有内容或指定模型的内容 def clear(self, models=None, commit=True): + # Zxy 如果后端尚未初始化,则先进行设置 if not self.setup_complete: self.setup() + # Zxy 刷新索引以获取最新状态 self.index = self.index.refresh() + # Zxy 如果提供了模型列表,则检查其类型 if models is not None: assert isinstance(models, (list, tuple)) try: if models is None: + # Zxy 如果没有指定模型,则删除整个索引 self.delete_index() else: + # Zxy 准备要删除的模型列表 models_to_delete = [] for model in models: + # Zxy 为每个模型构建查询字符串 models_to_delete.append( u"%s:%s" % (DJANGO_CT, get_model_ct(model))) + # Zxy 使用 OR 连接多个模型查询,并删除匹配的文档 self.index.delete_by_query( q=self.parser.parse( u" OR ".join(models_to_delete))) except Exception as e: + # Zxy 如果未配置为静默失败,则重新抛出异常 if not self.silently_fail: raise + # Zxy 根据是否指定模型,记录不同的错误信息 if models is not None: self.log.error( "Failed to clear Whoosh index of models '%s': %s", @@ -303,48 +385,60 @@ class WhooshSearchBackend(BaseSearchBackend): self.log.error( "Failed to clear Whoosh index: %s", e, exc_info=True) + # Zxy 物理删除整个索引 def delete_index(self): # Per the Whoosh mailing list, if wiping out everything from the index, # it's much more efficient to simply delete the index files. + # Zxy 如果使用文件存储,则直接删除索引目录 if self.use_file_storage and os.path.exists(self.path): shutil.rmtree(self.path) + # Zxy 如果使用内存存储,则清空存储 elif not self.use_file_storage: self.storage.clean() - # Recreate everything. + # Zxy 重新初始化后端,创建新的空索引 self.setup() + # Zxy 优化索引,合并索引段以提高搜索性能 def optimize(self): + # Zxy 如果后端尚未初始化,则先进行设置 if not self.setup_complete: self.setup() + # Zxy 刷新索引以获取最新状态 self.index = self.index.refresh() + # Zxy 执行 Whoosh 的优化操作 self.index.optimize() + # Zxy 根据偏移量计算 Whoosh 分页所需的页码和每页大小 def calculate_page(self, start_offset=0, end_offset=None): - # Prevent against Whoosh throwing an error. Requires an end_offset - # greater than 0. + # Zxy 防止 Whoosh 因 end_offset 小于等于 0 而抛出错误 if end_offset is not None and end_offset <= 0: end_offset = 1 - # Determine the page. + # Zxy 初始化页码 page_num = 0 + # Zxy 如果未指定结束偏移量,则设置一个很大的默认值 if end_offset is None: end_offset = 1000000 + # Zxy 如果未指定起始偏移量,则默认为 0 if start_offset is None: start_offset = 0 + # Zxy 计算每页的长度 page_length = end_offset - start_offset + # Zxy 如果页长度有效,则计算页码 if page_length and page_length > 0: page_num = int(start_offset / page_length) - # Increment because Whoosh uses 1-based page numbers. + # Zxy Whoosh 使用 1-based 页码,所以需要加 1 page_num += 1 return page_num, page_length + # Zxy 执行搜索查询的核心方法 @log_query def search( self, @@ -366,164 +460,183 @@ class WhooshSearchBackend(BaseSearchBackend): limit_to_registered_models=None, result_class=None, **kwargs): + # Zxy 如果后端尚未初始化,则先进行设置 if not self.setup_complete: self.setup() - # A zero length query should return no results. + # Zxy 空查询字符串应返回无结果 if len(query_string) == 0: return { 'results': [], 'hits': 0, } + # Zxy 确保查询字符串为正确的字符串类型 query_string = force_str(query_string) - # A one-character query (non-wildcard) gets nabbed by a stopwords - # filter and should yield zero results. + # Zxy 单个字符(非通配符)查询会被停用词过滤器拦截,应返回无结果 if len(query_string) <= 1 and query_string != u'*': return { 'results': [], 'hits': 0, } + # Zxy 初始化排序方向为非逆序 reverse = False + # Zxy 如果提供了排序字段 if sort_by is not None: - # Determine if we need to reverse the results and if Whoosh can - # handle what it's being asked to sort by. Reversing is an - # all-or-nothing action, unfortunately. + # Zxy 确定是否需要反转结果,以及 Whoosh 是否能处理排序 sort_by_list = [] reverse_counter = 0 + # Zxy 统计逆序排序字段的数量 for order_by in sort_by: if order_by.startswith('-'): reverse_counter += 1 + # Zxy Whoosh 要求所有排序字段的排序方向必须一致 if reverse_counter and reverse_counter != len(sort_by): raise SearchBackendError("Whoosh requires all order_by fields" " to use the same sort direction") + # Zxy 处理排序字段列表,去除 '-' 前缀并确定最终排序方向 for order_by in sort_by: if order_by.startswith('-'): sort_by_list.append(order_by[1:]) - if len(sort_by_list) == 1: reverse = True else: sort_by_list.append(order_by) - if len(sort_by_list) == 1: reverse = False + # Zxy Whoosh 的 search_page 方法只接受单个排序字段 sort_by = sort_by_list[0] + # Zxy Whoosh 后端不支持分面搜索,发出警告 if facets is not None: warnings.warn( "Whoosh does not handle faceting.", Warning, stacklevel=2) + # Zxy Whoosh 后端不支持日期分面,发出警告 if date_facets is not None: warnings.warn( "Whoosh does not handle date faceting.", Warning, stacklevel=2) + # Zxy Whoosh 后端不支持查询分面,发出警告 if query_facets is not None: warnings.warn( "Whoosh does not handle query faceting.", Warning, stacklevel=2) + # Zxy 初始化用于存储缩小范围后的结果 narrowed_results = None + # Zxy 刷新索引以获取最新状态 self.index = self.index.refresh() + # Zxy 确定是否限制搜索到已注册的模型 if limit_to_registered_models is None: limit_to_registered_models = getattr( settings, 'HAYSTACK_LIMIT_TO_REGISTERED_MODELS', True) + # Zxy 根据传入的模型或配置构建模型选择列表 if models and len(models): model_choices = sorted(get_model_ct(model) for model in models) elif limit_to_registered_models: - # Using narrow queries, limit the results to only models handled - # with the current routers. + # Zxy 使用缩小查询的方式,将结果限制在当前路由器处理的模型中 model_choices = self.build_models_list() else: model_choices = [] + # Zxy 如果存在模型选择,则将其添加到缩小查询中 if len(model_choices) > 0: if narrow_queries is None: narrow_queries = set() + # Zxy 构建一个 OR 查询来限制模型类型 narrow_queries.add(' OR '.join( ['%s:%s' % (DJANGO_CT, rm) for rm in model_choices])) + # Zxy 初始化缩小查询的搜索器 narrow_searcher = None + # Zxy 如果存在缩小查询,则执行它们以获取一个结果集过滤器 if narrow_queries is not None: - # Potentially expensive? I don't see another way to do it in - # Whoosh... + # Zxy 这个操作可能很昂贵,但 Whoosh 中没有其他方法 narrow_searcher = self.index.searcher() for nq in narrow_queries: recent_narrowed_results = narrow_searcher.search( self.parser.parse(force_str(nq)), limit=None) + # Zxy 如果任何一个缩小查询返回空结果,则直接返回空 if len(recent_narrowed_results) <= 0: return { 'results': [], 'hits': 0, } + # Zxy 将多个缩小查询的结果集进行交集过滤 if narrowed_results: narrowed_results.filter(recent_narrowed_results) else: narrowed_results = recent_narrowed_results + # Zxy 再次刷新索引以确保所有写入都可见 self.index = self.index.refresh() + # Zxy 如果索引中有文档,则执行搜索 if self.index.doc_count(): searcher = self.index.searcher() + # Zxy 解析查询字符串 parsed_query = self.parser.parse(query_string) - # In the event of an invalid/stopworded query, recover gracefully. + # Zxy 如果查询无效或被停用词过滤,则优雅地恢复 if parsed_query is None: return { 'results': [], 'hits': 0, } + # Zxy 计算分页参数 page_num, page_length = self.calculate_page( start_offset, end_offset) + # Zxy 准备搜索参数 search_kwargs = { 'pagelen': page_length, 'sortedby': sort_by, 'reverse': reverse, } - # Handle the case where the results have been narrowed. + # Zxy 如果存在缩小范围的结果,则将其作为过滤器 if narrowed_results is not None: search_kwargs['filter'] = narrowed_results try: + # Zxy 执行分页搜索 raw_page = searcher.search_page( parsed_query, page_num, **search_kwargs ) except ValueError: + # Zxy 如果页码无效,则返回空结果 if not self.silently_fail: raise - return { 'results': [], 'hits': 0, 'spelling_suggestion': None, } - # Because as of Whoosh 2.5.1, it will return the wrong page of - # results if you request something too high. :( + # Zxy 兼容 Whoosh 2.5.1 的 bug:请求过高的页码会返回错误的页 if raw_page.pagenum < page_num: return { 'results': [], @@ -531,19 +644,24 @@ class WhooshSearchBackend(BaseSearchBackend): 'spelling_suggestion': None, } + # Zxy 处理原始搜索结果,转换为 Haystack 的 SearchResult 对象 results = self._process_results( raw_page, highlight=highlight, query_string=query_string, spelling_query=spelling_query, result_class=result_class) + # Zxy 关闭主搜索器 searcher.close() + # Zxy 关闭缩小查询的搜索器 if hasattr(narrow_searcher, 'close'): narrow_searcher.close() return results else: + # Zxy 如果索引为空,但仍需处理拼写建议 + spelling_suggestion = None if self.include_spelling: if spelling_query: spelling_suggestion = self.create_spelling_suggestion( @@ -551,8 +669,6 @@ class WhooshSearchBackend(BaseSearchBackend): else: spelling_suggestion = self.create_spelling_suggestion( query_string) - else: - spelling_suggestion = None return { 'results': [], @@ -560,6 +676,7 @@ class WhooshSearchBackend(BaseSearchBackend): 'spelling_suggestion': spelling_suggestion, } + # Zxy 实现“更多类似于此”功能,根据给定模型实例查找相似文档 def more_like_this( self, model_instance, @@ -570,85 +687,108 @@ class WhooshSearchBackend(BaseSearchBackend): limit_to_registered_models=None, result_class=None, **kwargs): + # Zxy 如果后端尚未初始化,则先进行设置 if not self.setup_complete: self.setup() + # Zxy 获取模型的真实类,避免使用延迟加载的模型类 # Deferred models will have a different class ("RealClass_Deferred_fieldname") # which won't be in our registry: model_klass = model_instance._meta.concrete_model + # Zxy 获取主内容字段名,用于相似性分析 field_name = self.content_field_name + # Zxy 初始化缩小查询集合 narrow_queries = set() + # Zxy 初始化缩小范围后的结果集 narrowed_results = None + # Zxy 刷新索引以获取最新状态 self.index = self.index.refresh() + # Zxy 确定是否限制搜索到已注册的模型 if limit_to_registered_models is None: limit_to_registered_models = getattr( settings, 'HAYSTACK_LIMIT_TO_REGISTERED_MODELS', True) + # Zxy 根据传入的模型或配置构建模型选择列表 if models and len(models): model_choices = sorted(get_model_ct(model) for model in models) elif limit_to_registered_models: - # Using narrow queries, limit the results to only models handled - # with the current routers. + # Zxy 使用缩小查询的方式,将结果限制在当前路由器处理的模型中 model_choices = self.build_models_list() else: model_choices = [] + # Zxy 如果存在模型选择,则将其添加到缩小查询中 if len(model_choices) > 0: if narrow_queries is None: narrow_queries = set() + # Zxy 构建一个 OR 查询来限制模型类型 narrow_queries.add(' OR '.join( ['%s:%s' % (DJANGO_CT, rm) for rm in model_choices])) + # Zxy 如果提供了额外的查询字符串,则也添加到缩小查询中 if additional_query_string and additional_query_string != '*': narrow_queries.add(additional_query_string) + # Zxy 初始化缩小查询的搜索器 narrow_searcher = None + # Zxy 如果存在缩小查询,则执行它们以获取一个结果集过滤器 if narrow_queries is not None: - # Potentially expensive? I don't see another way to do it in - # Whoosh... + # Zxy 这个操作可能很昂贵,但 Whoosh 中没有其他方法 narrow_searcher = self.index.searcher() for nq in narrow_queries: recent_narrowed_results = narrow_searcher.search( self.parser.parse(force_str(nq)), limit=None) + # Zxy 如果任何一个缩小查询返回空结果,则直接返回空 if len(recent_narrowed_results) <= 0: return { 'results': [], 'hits': 0, } + # Zxy 将多个缩小查询的结果集进行交集过滤 if narrowed_results: narrowed_results.filter(recent_narrowed_results) else: narrowed_results = recent_narrowed_results + # Zxy 计算分页参数 page_num, page_length = self.calculate_page(start_offset, end_offset) + # Zxy 再次刷新索引以确保所有写入都可见 self.index = self.index.refresh() + # Zxy 初始化原始结果为空 raw_results = EmptyResults() + # Zxy 如果索引中有文档,则执行“更多类似于此”查询 if self.index.doc_count(): + # Zxy 构建一个查询以找到当前模型实例对应的索引文档 query = "%s:%s" % (ID, get_identifier(model_instance)) searcher = self.index.searcher() parsed_query = self.parser.parse(query) + # Zxy 搜索当前文档 results = searcher.search(parsed_query) + # Zxy 如果找到了当前文档,则调用其 more_like_this 方法 if len(results): + # Zxy 获取与当前文档相似的其他文档 raw_results = results[0].more_like_this( field_name, top=end_offset) - # Handle the case where the results have been narrowed. + # Zxy 如果存在缩小范围的结果,则将其作为过滤器应用于相似结果 if narrowed_results is not None and hasattr(raw_results, 'filter'): raw_results.filter(narrowed_results) try: + # Zxy 将原始结果集包装成分页对象 raw_page = ResultsPage(raw_results, page_num, page_length) except ValueError: + # Zxy 如果页码无效,则返回空结果 if not self.silently_fail: raise @@ -658,8 +798,7 @@ class WhooshSearchBackend(BaseSearchBackend): 'spelling_suggestion': None, } - # Because as of Whoosh 2.5.1, it will return the wrong page of - # results if you request something too high. :( + # Zxy 兼容 Whoosh 2.5.1 的 bug:请求过高的页码会返回错误的页 if raw_page.pagenum < page_num: return { 'results': [], @@ -667,14 +806,19 @@ class WhooshSearchBackend(BaseSearchBackend): 'spelling_suggestion': None, } + # Zxy 处理原始搜索结果,转换为 Haystack 的 SearchResult 对象 results = self._process_results(raw_page, result_class=result_class) + # Zxy 关闭主搜索器 searcher.close() + # Zxy 关闭缩小查询的搜索器 if hasattr(narrow_searcher, 'close'): narrow_searcher.close() return results + + # Zxy 处理 Whoosh 返回的原始搜索结果,转换为 Haystack 标准格式 def _process_results( self, raw_page, @@ -682,35 +826,48 @@ class WhooshSearchBackend(BaseSearchBackend): query_string='', spelling_query=None, result_class=None): + # Zxy 导入 haystack 连接管理器 from haystack import connections + # Zxy 初始化结果列表 results = [] - # It's important to grab the hits first before slicing. Otherwise, this - # can cause pagination failures. + # Zxy 在切片之前获取总命中数,这对于分页至关重要 hits = len(raw_page) + # Zxy 如果未指定结果类,则使用默认的 SearchResult if result_class is None: result_class = SearchResult + # Zxy 初始化分面和拼写建议 facets = {} spelling_suggestion = None + # Zxy 获取统一索引对象和已注册的模型列表 unified_index = connections[self.connection_alias].get_unified_index() indexed_models = unified_index.get_indexed_models() + # Zxy 遍历原始结果页中的每个文档 for doc_offset, raw_result in enumerate(raw_page): + # Zxy 获取文档的得分 score = raw_page.score(doc_offset) or 0 + # Zxy 从文档中解析出应用标签和模型名 app_label, model_name = raw_result[DJANGO_CT].split('.') + # Zxy 初始化额外字段字典 additional_fields = {} + # Zxy 根据应用标签和模型名获取模型类 model = haystack_get_model(app_label, model_name) + # Zxy 确保模型存在且已注册到索引 if model and model in indexed_models: + # Zxy 遍历文档中的所有字段 for key, value in raw_result.items(): + # Zxy 获取该模型对应的索引 index = unified_index.get_index(model) string_key = str(key) + # Zxy 如果字段在索引定义中,并且有转换方法 if string_key in index.fields and hasattr( index.fields[string_key], 'convert'): - # Special-cased due to the nature of KEYWORD fields. + # Zxy 特殊处理多值字段 if index.fields[string_key].is_multivalued: if value is None or len(value) == 0: additional_fields[string_key] = [] @@ -718,19 +875,26 @@ class WhooshSearchBackend(BaseSearchBackend): additional_fields[string_key] = value.split( ',') else: + # Zxy 使用索引字段定义的转换方法 additional_fields[string_key] = index.fields[string_key].convert( value) else: + # Zxy 否则使用通用的 Python 类型转换 additional_fields[string_key] = self._to_python(value) + # Zxy 删除 Haystack 内部字段,不返回给用户 del (additional_fields[DJANGO_CT]) del (additional_fields[DJANGO_ID]) + # Zxy 如果需要高亮显示 if highlight: + # Zxy 创建词干分析器和 HTML 格式化器 sa = StemmingAnalyzer() formatter = WhooshHtmlFormatter('em') + # Zxy 从查询字符串中提取词条 terms = [token.text for token in sa(query_string)] + # Zxy 调用 Whoosh 的高亮方法 whoosh_result = whoosh_highlight( additional_fields.get(self.content_field_name), terms, @@ -738,10 +902,12 @@ class WhooshSearchBackend(BaseSearchBackend): ContextFragmenter(), formatter ) + # Zxy 将高亮结果添加到额外字段中 additional_fields['highlighted'] = { self.content_field_name: [whoosh_result], } + # Zxy 创建 SearchResult 对象并添加到结果列表 result = result_class( app_label, model_name, @@ -750,8 +916,10 @@ class WhooshSearchBackend(BaseSearchBackend): **additional_fields) results.append(result) else: + # Zxy 如果模型未注册,则减少总命中数 hits -= 1 + # Zxy 如果启用了拼写建议 if self.include_spelling: if spelling_query: spelling_suggestion = self.create_spelling_suggestion( @@ -760,6 +928,7 @@ class WhooshSearchBackend(BaseSearchBackend): spelling_suggestion = self.create_spelling_suggestion( query_string) + # Zxy 返回包含结果、命中数、分面和拼写建议的字典 return { 'results': results, 'hits': hits, @@ -767,116 +936,140 @@ class WhooshSearchBackend(BaseSearchBackend): 'spelling_suggestion': spelling_suggestion, } + # Zxy 根据查询字符串创建拼写建议 def create_spelling_suggestion(self, query_string): + # Zxy 初始化拼写建议 spelling_suggestion = None + # Zxy 获取索引的读取器和校正器 reader = self.index.reader() corrector = reader.corrector(self.content_field_name) + # Zxy 清理查询字符串 cleaned_query = force_str(query_string) + # Zxy 如果查询字符串为空,直接返回 if not query_string: return spelling_suggestion - # Clean the string. + # Zxy 移除查询中的保留字 for rev_word in self.RESERVED_WORDS: cleaned_query = cleaned_query.replace(rev_word, '') + # Zxy 移除查询中的保留字符 for rev_char in self.RESERVED_CHARACTERS: cleaned_query = cleaned_query.replace(rev_char, '') - # Break it down. + # Zxy 将清理后的查询拆分为单词列表 query_words = cleaned_query.split() suggested_words = [] + # Zxy 为每个单词查找拼写建议 for word in query_words: suggestions = corrector.suggest(word, limit=1) if len(suggestions) > 0: suggested_words.append(suggestions[0]) + # Zxy 将建议的单词重新组合成字符串 spelling_suggestion = ' '.join(suggested_words) return spelling_suggestion + # Zxy 将 Python 值转换为 Whoosh 可用的字符串格式 def _from_python(self, value): """ Converts Python values to a string for Whoosh. Code courtesy of pysolr. """ + # Zxy 处理日期时间对象 if hasattr(value, 'strftime'): + # Zxy 如果只有日期没有时间,则将时间部分设为 0 if not hasattr(value, 'hour'): value = datetime(value.year, value.month, value.day, 0, 0, 0) + # Zxy 处理布尔值 elif isinstance(value, bool): if value: value = 'true' else: value = 'false' + # Zxy 处理列表或元组,用逗号连接 elif isinstance(value, (list, tuple)): value = u','.join([force_str(v) for v in value]) + # Zxy 处理数字,保持原样 elif isinstance(value, (six.integer_types, float)): # Leave it alone. pass else: + # Zxy 其他类型强制转换为字符串 value = force_str(value) return value - def _to_python(self, value): - """ - Converts values from Whoosh to native Python values. - - A port of the same method in pysolr, as they deal with data the same way. - """ - if value == 'true': - return True - elif value == 'false': - return False - - if value and isinstance(value, six.string_types): - possible_datetime = DATETIME_REGEX.search(value) - - if possible_datetime: - date_values = possible_datetime.groupdict() - - for dk, dv in date_values.items(): - date_values[dk] = int(dv) - - return datetime( - date_values['year'], - date_values['month'], - date_values['day'], - date_values['hour'], - date_values['minute'], - date_values['second']) - - try: - # Attempt to use json to load the values. - converted_value = json.loads(value) - - # Try to handle most built-in types. - if isinstance( - converted_value, - (list, - tuple, - set, - dict, - six.integer_types, - float, - complex)): - return converted_value - except BaseException: - # If it fails (SyntaxError or its ilk) or we don't trust it, - # continue on. - pass - - return value - - + # Zxy 将 Whoosh 的值转换为原生 Python 值 + def _to_python(self, value): + """ + Converts values from Whoosh to native Python values. + + A port of the same method in pysolr, as they deal with data the same way. + """ + # Zxy 处理布尔字符串 + if value == 'true': + return True + elif value == 'false': + return False + + # Zxy 尝试解析日期时间字符串 + if value and isinstance(value, six.string_types): + possible_datetime = DATETIME_REGEX.search(value) + + if possible_datetime: + date_values = possible_datetime.groupdict() + + for dk, dv in date_values.items(): + date_values[dk] = int(dv) + + return datetime( + date_values['year'], + date_values['month'], + date_values['day'], + date_values['hour'], + date_values['minute'], + date_values['second']) + + # Zxy 尝试使用 json 解复杂数据类型 + try: + # Attempt to use json to load the values. + converted_value = json.loads(value) + + # Try to handle most built-in types. + if isinstance( + converted_value, + (list, + tuple, + set, + dict, + six.integer_types, + float, + complex)): + return converted_value + except BaseException: + # If it fails (SyntaxError or its ilk) or we don't trust it, + # continue on. + pass + + # Zxy 如果都无法转换,则返回原始值 + return value + + # Zxy 定义 Whoosh 搜索查询类,继承自 Haystack 的 BaseSearchQuery class WhooshSearchQuery(BaseSearchQuery): + # Zxy 将日期时间对象转换为 Whoosh 查询所需的字符串格式 def _convert_datetime(self, date): + # Zxy 如果包含时间,则转换为完整格式 if hasattr(date, 'hour'): return force_str(date.strftime('%Y%m%d%H%M%S')) else: + # Zxy 如果只有日期,则补充零时间 return force_str(date.strftime('%Y%m%d000000')) + # Zxy 清理查询片段,转义 Whoosh 的保留字符 def clean(self, query_fragment): """ Provides a mechanism for sanitizing user input before presenting the @@ -886,13 +1079,17 @@ class WhooshSearchQuery(BaseSearchQuery): to escape reserved characters. Instead, the whole word should be quoted. """ + # Zxy 将查询片段按空格分割成单词 words = query_fragment.split() cleaned_words = [] + # Zxy 遍历每个单词进行清理 for word in words: + # Zxy 如果是保留字,则转为小写 if word in self.backend.RESERVED_WORDS: word = word.replace(word, word.lower()) + # Zxy 如果包含保留字符,则用单引号将整个单词括起来 for char in self.backend.RESERVED_CHARACTERS: if char in word: word = "'%s'" % word @@ -900,13 +1097,17 @@ class WhooshSearchQuery(BaseSearchQuery): cleaned_words.append(word) + # Zxy 将清理后的单词重新组合 return ' '.join(cleaned_words) + # Zxy 构建查询片段,根据字段、过滤类型和值生成 Whoosh 查询语法 def build_query_fragment(self, field, filter_type, value): + # Zxy 导入 haystack 连接管理器 from haystack import connections query_frag = '' is_datetime = False + # Zxy 如果值没有 input_type_name 属性,则进行类型推断 if not hasattr(value, 'input_type_name'): # Handle when we've got a ``ValuesListQuerySet``... if hasattr(value, 'values_list'): @@ -921,21 +1122,25 @@ class WhooshSearchQuery(BaseSearchQuery): else: value = PythonData(value) - # Prepare the query using the InputType. + # Zxy 使用 InputType 准备查询值 prepared_value = value.prepare(self) + # Zxy 如果准备好的值不是集合类型,则转换为 Whoosh 可用的格式 if not isinstance(prepared_value, (set, list, tuple)): # Then convert whatever we get back to what pysolr wants if needed. prepared_value = self.backend._from_python(prepared_value) # 'content' is a special reserved word, much like 'pk' in # Django's ORM layer. It indicates 'no special field'. + # Zxy 'content' 是特殊字段,代表所有可搜索内容 if field == 'content': index_fieldname = '' else: + # Zxy 获取字段在索引中的真实名称 index_fieldname = u'%s:' % connections[self._using].get_unified_index( ).get_index_fieldname(field) + # Zxy 定义不同过滤类型对应的 Whoosh 查询模板 filter_types = { 'content': '%s', 'contains': '*%s*', @@ -948,16 +1153,18 @@ class WhooshSearchQuery(BaseSearchQuery): 'lte': "[to %s]", 'fuzzy': u'%s~', } - + # Zxy 如果值不需要后处理,则直接使用 if value.post_process is False: query_frag = prepared_value else: + # Zxy 根据不同的过滤类型构建查询片段 if filter_type in [ 'content', 'contains', 'startswith', 'endswith', 'fuzzy']: + # Zxy 如果输入类型是精确匹配,则直接使用值 if value.input_type_name == 'exact': query_frag = prepared_value else: @@ -983,6 +1190,7 @@ class WhooshSearchQuery(BaseSearchQuery): query_frag = terms[0] else: query_frag = u"(%s)" % " AND ".join(terms) + # Zxy 处理 'in' 过滤类型 elif filter_type == 'in': in_options = [] @@ -1003,6 +1211,7 @@ class WhooshSearchQuery(BaseSearchQuery): in_options.append('%s' % pv) query_frag = "(%s)" % " OR ".join(in_options) + # Zxy 处理 'range' 过滤类型 elif filter_type == 'range': start = self.backend._from_python(prepared_value[0]) end = self.backend._from_python(prepared_value[1]) @@ -1014,6 +1223,7 @@ class WhooshSearchQuery(BaseSearchQuery): end = self._convert_datetime(end) query_frag = u"[%s to %s]" % (start, end) + # Zxy 处理 'exact' 过滤类型 elif filter_type == 'exact': if value.input_type_name == 'exact': query_frag = prepared_value @@ -1021,15 +1231,16 @@ class WhooshSearchQuery(BaseSearchQuery): prepared_value = Exact(prepared_value).prepare(self) query_frag = filter_types[filter_type] % prepared_value else: + # Zxy 处理其他类型(如 gt, gte, lt, lte) if is_datetime is True: prepared_value = self._convert_datetime(prepared_value) query_frag = filter_types[filter_type] % prepared_value + # Zxy 如果查询片段不为空且不是原始查询,则用括号括起来 if len(query_frag) and not isinstance(value, Raw): if not query_frag.startswith('(') and not query_frag.endswith(')'): query_frag = "(%s)" % query_frag - return u"%s%s" % (index_fieldname, query_frag) # if not filter_type in ('in', 'range'): @@ -1038,7 +1249,8 @@ class WhooshSearchQuery(BaseSearchQuery): # # until later... # value = self.backend._from_python(value) - +# Zxy 定义 Whoosh 引擎类,继承自 Haystack 的 BaseEngine class WhooshEngine(BaseEngine): + # Zxy 指定后端和查询类 backend = WhooshSearchBackend query = WhooshSearchQuery diff --git a/src/DjangoBlog-master/djangoblog/wsgi.py b/src/DjangoBlog-master/djangoblog/wsgi.py index 2295efd..730430e 100644 --- a/src/DjangoBlog-master/djangoblog/wsgi.py +++ b/src/DjangoBlog-master/djangoblog/wsgi.py @@ -7,10 +7,14 @@ For more information on this file, see https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ """ +# Zxy导入os模块,用于操作环境变量 import os +# Zxy从Django中导入get_wsgi_application函数,用于获取WSGI应用 from django.core.wsgi import get_wsgi_application +# Zxy设置环境变量DJANGO_SETTINGS_MODULE,指定Django项目的配置文件 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangoblog.settings") -application = get_wsgi_application() +# Zxy获取WSGI应用实例,用于部署 +application = get_wsgi_application() \ No newline at end of file