diff --git a/src/DjangoBlog-master/djangoblog/__init__.py b/src/DjangoBlog-master/djangoblog/__init__.py index 1e205f40..c9794562 100644 --- a/src/DjangoBlog-master/djangoblog/__init__.py +++ b/src/DjangoBlog-master/djangoblog/__init__.py @@ -1 +1,4 @@ +# psr:设置默认的应用配置类 +# psr:该配置指向 djangoblog 应用下的 apps.py 文件中的 DjangoblogAppConfig 类 +# psr:Django 会使用这个配置类来加载应用的自定义设置和行为 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 f1204059..56fcb110 100644 --- a/src/DjangoBlog-master/djangoblog/admin_site.py +++ b/src/DjangoBlog-master/djangoblog/admin_site.py @@ -1,8 +1,10 @@ +#psr:导入Django管理站点相关模块和模型 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 +#psr:导入项目中各个应用的admin和models模块 from accounts.admin import * from blog.admin import * from blog.models import * @@ -16,14 +18,16 @@ from owntracks.models import * from servermanager.admin import * from servermanager.models import * - +#psr:自定义Django管理站点类,用于统一管理博客系统所有模型 class DjangoBlogAdminSite(AdminSite): + #psr:设置管理站点的头部标题 site_header = 'djangoblog administration' + #psr:设置管理站点的标题 site_title = 'djangoblog site admin' - + #psr:初始化管理站点,设置名称为'admin' def __init__(self, name='admin'): super().__init__(name) - + #psr:重写权限检查方法,只允许超级用户访问管理站点 def has_permission(self, request): return request.user.is_superuser @@ -37,9 +41,10 @@ class DjangoBlogAdminSite(AdminSite): # ] # return urls + my_urls - +#psr:创建DjangoBlog管理站点实例 admin_site = DjangoBlogAdminSite(name='admin') +#psr:注册博客相关的模型到管理站点 admin_site.register(Article, ArticlelAdmin) admin_site.register(Category, CategoryAdmin) admin_site.register(Tag, TagAdmin) @@ -47,18 +52,25 @@ admin_site.register(Links, LinksAdmin) admin_site.register(SideBar, SideBarAdmin) admin_site.register(BlogSettings, BlogSettingsAdmin) +#psr:注册服务器管理相关的模型到管理站点 admin_site.register(commands, CommandsAdmin) admin_site.register(EmailSendLog, EmailSendLogAdmin) +#psr:注册用户管理相关的模型到管理站点 admin_site.register(BlogUser, BlogUserAdmin) +#psr:注册评论相关的模型到管理站点 admin_site.register(Comment, CommentAdmin) +#psr:注册OAuth认证相关的模型到管理站点 admin_site.register(OAuthUser, OAuthUserAdmin) admin_site.register(OAuthConfig, OAuthConfigAdmin) +#psr:注册OwnTracks位置追踪相关的模型到管理站点 admin_site.register(OwnTrackLog, OwnTrackLogsAdmin) +#psr:注册Django自带的Site模型到管理站点 admin_site.register(Site, SiteAdmin) +#psr:注册Django自带的LogEntry模型到管理站点 admin_site.register(LogEntry, LogEntryAdmin) diff --git a/src/DjangoBlog-master/djangoblog/apps.py b/src/DjangoBlog-master/djangoblog/apps.py index d29e318a..f6e72668 100644 --- a/src/DjangoBlog-master/djangoblog/apps.py +++ b/src/DjangoBlog-master/djangoblog/apps.py @@ -1,11 +1,19 @@ +#psr:导入Django应用配置基类 from django.apps import AppConfig +#psr:定义DjangoBlog应用的配置类,继承自AppConfig class DjangoblogAppConfig(AppConfig): + #psr:设置默认的自动字段类型为BigAutoField default_auto_field = 'django.db.models.BigAutoField' + #psr:指定应用名称 name = 'djangoblog' + #psr:重写ready方法,在应用启动时执行初始化操作 def ready(self): + #psr:调用父类的ready方法 super().ready() - # Import and load plugins here - from .plugin_manage.loader import load_plugins + # Import and load plugins here + from .plugin_manage.loader import load_plugins #psr:导入并加载插件系统 + + #psr:执行插件加载函数 load_plugins() \ 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 393f441c..d0433388 100644 --- a/src/DjangoBlog-master/djangoblog/blog_signals.py +++ b/src/DjangoBlog-master/djangoblog/blog_signals.py @@ -1,3 +1,4 @@ +#psr 导入线程、日志、Django信号、邮件、模型信号等相关模块 import _thread import logging @@ -9,6 +10,7 @@ from django.core.mail import EmailMultiAlternatives from django.db.models.signals import post_save from django.dispatch import receiver +#psr 导入项目相关模块和工具函数 from comments.models import Comment from comments.utils import send_comment_email from djangoblog.spider_notify import SpiderNotify @@ -16,19 +18,24 @@ from djangoblog.utils import cache, expire_view_cache, delete_sidebar_cache, del from djangoblog.utils import get_current_site from oauth.models import OAuthUser +#psr 获取日志记录器实例 logger = logging.getLogger(__name__) +#psr 定义OAuth用户登录信号和发送邮件信号 oauth_user_login_signal = django.dispatch.Signal(['id']) send_email_signal = django.dispatch.Signal( ['emailto', 'title', 'content']) +#psr 发送邮件信号处理函数,用于发送HTML格式邮件 @receiver(send_email_signal) def send_email_signal_handler(sender, **kwargs): + #psr 从信号参数中获取邮件信息 emailto = kwargs['emailto'] title = kwargs['title'] content = kwargs['content'] + #psr 创建邮件消息对象并设置HTML内容类型 msg = EmailMultiAlternatives( title, content, @@ -36,6 +43,7 @@ def send_email_signal_handler(sender, **kwargs): to=emailto) msg.content_subtype = "html" + #psr 创建邮件发送日志记录 from servermanager.models import EmailSendLog log = EmailSendLog() log.title = title @@ -43,27 +51,35 @@ def send_email_signal_handler(sender, **kwargs): log.emailto = ','.join(emailto) try: + #psr 发送邮件并记录发送结果 result = msg.send() log.send_result = result > 0 except Exception as e: + #psr 处理邮件发送异常 logger.error(f"失败邮箱号: {emailto}, {e}") log.send_result = False + #psr 保存邮件发送日志 log.save() +#psr OAuth用户登录信号处理函数,用于处理用户头像等登录后操作 @receiver(oauth_user_login_signal) def oauth_user_login_signal_handler(sender, **kwargs): + #psr 获取OAuth用户信息 id = kwargs['id'] oauthuser = OAuthUser.objects.get(id=id) site = get_current_site().domain + #psr 如果用户头像不在当前站点,则保存用户头像到本地 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() + #psr 删除侧边栏缓存 delete_sidebar_cache() +#psr 模型保存后回调函数,用于处理模型保存后的缓存清理和通知操作 @receiver(post_save) def model_post_save_callback( sender, @@ -74,19 +90,24 @@ def model_post_save_callback( update_fields, **kwargs): clearcache = False + #psr 如果是日志条目则直接返回 if isinstance(instance, LogEntry): return + #psr 如果实例有获取完整URL的方法 if 'get_full_url' in dir(instance): is_update_views = update_fields == {'views'} + #psr 如果不是测试环境且不是仅更新浏览量,则通知搜索引擎 if not settings.TESTING and not is_update_views: try: notify_url = instance.get_full_url() SpiderNotify.baidu_notify([notify_url]) except Exception as ex: logger.error("notify sipder", ex) + #psr 如果不是仅更新浏览量则需要清理缓存 if not is_update_views: clearcache = True + #psr 如果是评论实例且已启用 if isinstance(instance, Comment): if instance.is_enable: path = instance.article.get_absolute_url() @@ -94,29 +115,38 @@ def model_post_save_callback( if site.find(':') > 0: site = site[0:site.find(':')] + #psr 过期文章详情页面缓存 expire_view_cache( path, servername=site, serverport=80, key_prefix='blogdetail') + #psr 删除SEO处理器缓存 if cache.get('seo_processor'): cache.delete('seo_processor') + #psr 删除文章评论缓存 comment_cache_key = 'article_comments_{id}'.format( id=instance.article.id) cache.delete(comment_cache_key) + #psr 删除侧边栏和视图缓存 delete_sidebar_cache() delete_view_cache('article_comments', [str(instance.article.pk)]) + #psr 异步发送评论邮件通知 _thread.start_new_thread(send_comment_email, (instance,)) + #psr 如果需要清理缓存则清空所有缓存 if clearcache: cache.clear() +#psr 用户登录和登出回调函数,用于处理用户认证相关缓存清理 @receiver(user_logged_in) @receiver(user_logged_out) def user_auth_callback(sender, request, user, **kwargs): + #psr 如果用户存在且有用户名 if user and user.username: logger.info(user) + #psr 删除侧边栏缓存 delete_sidebar_cache() # cache.clear() diff --git a/src/DjangoBlog-master/djangoblog/elasticsearch_backend.py b/src/DjangoBlog-master/djangoblog/elasticsearch_backend.py index 4afe4981..6c0c1fd6 100644 --- a/src/DjangoBlog-master/djangoblog/elasticsearch_backend.py +++ b/src/DjangoBlog-master/djangoblog/elasticsearch_backend.py @@ -1,3 +1,4 @@ +#psr:导入所需的编码处理、Elasticsearch查询、Haystack搜索框架等相关模块 from django.utils.encoding import force_str from elasticsearch_dsl import Q from haystack.backends import BaseEngine, BaseSearchBackend, BaseSearchQuery, log_query @@ -5,103 +6,144 @@ from haystack.forms import ModelSearchForm from haystack.models import SearchResult from haystack.utils import log as logging +#psr:导入博客应用的Elasticsearch文档和模型 from blog.documents import ArticleDocument, ArticleDocumentManager from blog.models import Article +#psr:获取日志记录器实例 logger = logging.getLogger(__name__) +#psr:定义Elasticsearch搜索后端类,继承自BaseSearchBackend class ElasticSearchBackend(BaseSearchBackend): + #psr:初始化Elasticsearch后端,设置连接别名和选项 def __init__(self, connection_alias, **connection_options): super( ElasticSearchBackend, self).__init__( connection_alias, **connection_options) + #psr:创建文章文档管理器实例 self.manager = ArticleDocumentManager() + #psr:启用拼写检查功能 self.include_spelling = True + #psr:获取模型数据并转换为文档格式 def _get_models(self, iterable): + #psr:如果传入迭代器为空则获取所有文章对象 models = iterable if iterable and iterable[0] else Article.objects.all() + #psr:将模型转换为Elasticsearch文档 docs = self.manager.convert_to_doc(models) return docs + #psr:创建索引,包括创建索引结构和重建文档数据 def _create(self, models): + #psr:创建Elasticsearch索引 self.manager.create_index() + #psr:获取模型文档 docs = self._get_models(models) + #psr:重建索引数据 self.manager.rebuild(docs) + #psr:删除指定模型数据 def _delete(self, models): + #psr:遍历模型列表并删除每个模型 for m in models: m.delete() return True + #psr:重建索引数据 def _rebuild(self, models): + #psr:如果模型为空则获取所有文章对象 models = models if models else Article.objects.all() + #psr:转换模型为文档格式 docs = self.manager.convert_to_doc(models) + #psr:更新文档数据 self.manager.update_docs(docs) + #psr:更新索引中的文档数据 def update(self, index, iterable, commit=True): - + #psr:获取模型文档 models = self._get_models(iterable) + #psr:更新文档到Elasticsearch self.manager.update_docs(models) + #psr:从索引中移除指定对象 def remove(self, obj_or_string): + #psr:获取模型文档并删除 models = self._get_models([obj_or_string]) self._delete(models) + #psr:清空索引数据 def clear(self, models=None, commit=True): + #psr:调用remove方法清空数据 self.remove(None) + #psr:静态方法,获取搜索建议词 @staticmethod def get_suggestion(query: str) -> str: """获取推荐词, 如果没有找到添加原搜索词""" + #psr:构建Elasticsearch搜索查询,使用term建议器 search = ArticleDocument.search() \ .query("match", body=query) \ .suggest('suggest_search', query, term={'field': 'body'}) \ .execute() + #psr:处理建议结果,提取建议词 keywords = [] for suggest in search.suggest.suggest_search: if suggest["options"]: + #psr:如果有建议选项则使用建议词 keywords.append(suggest["options"][0]["text"]) else: + #psr:如果没有建议选项则使用原词 keywords.append(suggest["text"]) + #psr:将关键词组合成字符串返回 return ' '.join(keywords) + #psr:执行搜索查询的核心方法 @log_query def search(self, query_string, **kwargs): + #psr:记录搜索查询日志 logger.info('search query_string:' + query_string) + #psr:获取分页偏移量参数 start_offset = kwargs.get('start_offset') end_offset = kwargs.get('end_offset') - # 推荐词搜索 + #psr:根据是否启用建议功能获取搜索建议词 if getattr(self, "is_suggest", None): suggestion = self.get_suggestion(query_string) else: suggestion = query_string + #psr:构建Elasticsearch布尔查询条件 q = Q('bool', should=[Q('match', body=suggestion), Q('match', title=suggestion)], minimum_should_match="70%") + #psr:构建完整的Elasticsearch搜索查询 search = ArticleDocument.search() \ .query('bool', filter=[q]) \ .filter('term', status='p') \ .filter('term', type='a') \ .source(False)[start_offset: end_offset] + #psr:执行搜索并获取结果 results = search.execute() + #psr:获取搜索结果总数 hits = results['hits'].total + #psr:处理原始搜索结果 raw_results = [] for raw_result in results['hits']['hits']: + #psr:设置应用标签和模型名称 app_label = 'blog' model_name = 'Article' additional_fields = {} + #psr:创建搜索结果对象 result_class = SearchResult result = result_class( @@ -111,9 +153,11 @@ class ElasticSearchBackend(BaseSearchBackend): raw_result['_score'], **additional_fields) raw_results.append(result) + #psr:初始化facet和拼写建议 facets = {} spelling_suggestion = None if query_string == suggestion else suggestion + #psr:返回搜索结果字典 return { 'results': raw_results, 'hits': hits, @@ -122,13 +166,17 @@ class ElasticSearchBackend(BaseSearchBackend): } +#psr:定义Elasticsearch搜索查询类,继承自BaseSearchQuery class ElasticSearchQuery(BaseSearchQuery): + #psr:转换日期时间格式 def _convert_datetime(self, date): + #psr:判断日期是否包含小时信息并格式化 if hasattr(date, 'hour'): return force_str(date.strftime('%Y%m%d%H%M%S')) else: return force_str(date.strftime('%Y%m%d000000')) + #psr:清理用户输入的查询字符串 def clean(self, query_fragment): """ Provides a mechanism for sanitizing user input before presenting the @@ -138,13 +186,17 @@ class ElasticSearchQuery(BaseSearchQuery): to escape reserved characters. Instead, the whole word should be quoted. """ + #psr:将查询字符串分割为单词 words = query_fragment.split() cleaned_words = [] + #psr:遍历每个单词进行清理处理 for word in words: + #psr:处理保留字 if word in self.backend.RESERVED_WORDS: word = word.replace(word, word.lower()) + #psr:处理保留字符,如果包含则用引号包围 for char in self.backend.RESERVED_CHARACTERS: if char in word: word = "'%s'" % word @@ -152,32 +204,46 @@ class ElasticSearchQuery(BaseSearchQuery): cleaned_words.append(word) + #psr:重新组合清理后的单词 return ' '.join(cleaned_words) + #psr:构建查询片段 def build_query_fragment(self, field, filter_type, value): + #psr:返回查询字符串 return value.query_string + #psr:获取搜索结果数量 def get_count(self): + #psr:获取搜索结果并返回长度 results = self.get_results() return len(results) if results else 0 + #psr:获取拼写建议 def get_spelling_suggestion(self, preferred_query=None): + #psr:返回拼写建议 return self._spelling_suggestion + #psr:构建查询参数 def build_params(self, spelling_query=None): + #psr:调用父类方法构建参数 kwargs = super(ElasticSearchQuery, self).build_params(spelling_query=spelling_query) return kwargs +#psr:定义Elasticsearch模型搜索表单类,继承自ModelSearchForm class ElasticSearchModelSearchForm(ModelSearchForm): + #psr:执行搜索操作 def search(self): - # 是否建议搜索 + #psr:根据表单数据设置是否启用建议功能 self.searchqueryset.query.backend.is_suggest = self.data.get("is_suggest") != "no" + #psr:调用父类搜索方法 sqs = super().search() return sqs +#psr:定义Elasticsearch搜索引擎类,继承自BaseEngine class ElasticSearchEngine(BaseEngine): + #psr:指定后端和查询类 backend = ElasticSearchBackend - query = ElasticSearchQuery + query = ElasticSearchQuery \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/logentryadmin.py b/src/DjangoBlog-master/djangoblog/logentryadmin.py index 2f6a5353..dcedb0f8 100644 --- a/src/DjangoBlog-master/djangoblog/logentryadmin.py +++ b/src/DjangoBlog-master/djangoblog/logentryadmin.py @@ -1,3 +1,4 @@ +#psr 导入Django管理后台、内容类型、URL处理等相关模块 from django.contrib import admin from django.contrib.admin.models import DELETION from django.contrib.contenttypes.models import ContentType @@ -8,20 +9,25 @@ from django.utils.safestring import mark_safe from django.utils.translation import gettext_lazy as _ +#psr 日志条目管理类,用于在Django管理后台中显示和管理管理员操作日志 class LogEntryAdmin(admin.ModelAdmin): + #psr 设置列表过滤器,按内容类型过滤日志 list_filter = [ 'content_type' ] + #psr 设置搜索字段,支持按对象表示和变更消息搜索 search_fields = [ 'object_repr', 'change_message' ] + #psr 设置列表显示链接字段 list_display_links = [ 'action_time', 'get_change_message', ] + #psr 设置列表显示字段 list_display = [ 'action_time', 'user_link', @@ -30,25 +36,32 @@ class LogEntryAdmin(admin.ModelAdmin): 'get_change_message', ] + #psr 禁止添加权限,日志条目只能由系统自动生成 def has_add_permission(self, request): return False + #psr 设置变更权限,只有超级用户或具有特定权限的用户才能查看,且不能通过POST请求修改 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' + #psr 禁止删除权限,日志条目不能被删除 def has_delete_permission(self, request, obj=None): return False + #psr 生成对象链接,将日志中的对象表示转换为可点击的管理页面链接 def object_link(self, obj): + #psr 转义对象表示字符串防止XSS攻击 object_link = escape(obj.object_repr) content_type = obj.content_type + #psr 如果不是删除操作且内容类型存在,则尝试生成链接 if obj.action_flag != DELETION and content_type is not None: # try returning an actual link instead of object repr string try: + #psr 构造管理页面URL并生成HTML链接 url = reverse( 'admin:{}_{}_change'.format(content_type.app_label, content_type.model), @@ -57,16 +70,22 @@ class LogEntryAdmin(admin.ModelAdmin): object_link = '{}'.format(url, object_link) except NoReverseMatch: pass + #psr 返回安全的HTML链接 return mark_safe(object_link) + #psr 设置对象链接字段的排序字段和显示名称 object_link.admin_order_field = 'object_repr' object_link.short_description = _('object') + #psr 生成用户链接,将日志中的用户转换为可点击的管理页面链接 def user_link(self, obj): + #psr 获取用户对象的内容类型 content_type = ContentType.objects.get_for_model(type(obj.user)) + #psr 转义用户字符串防止XSS攻击 user_link = escape(force_str(obj.user)) try: # try returning an actual link instead of object repr string + #psr 构造用户管理页面URL并生成HTML链接 url = reverse( 'admin:{}_{}_change'.format(content_type.app_label, content_type.model), @@ -75,15 +94,19 @@ class LogEntryAdmin(admin.ModelAdmin): user_link = '{}'.format(url, user_link) except NoReverseMatch: pass + #psr 返回安全的HTML链接 return mark_safe(user_link) + #psr 设置用户链接字段的排序字段和显示名称 user_link.admin_order_field = 'user' user_link.short_description = _('user') + #psr 重写查询集,预加载内容类型以提高查询性能 def get_queryset(self, request): queryset = super(LogEntryAdmin, self).get_queryset(request) return queryset.prefetch_related('content_type') + #psr 重写获取操作方法,移除删除选中项操作 def get_actions(self, request): actions = super(LogEntryAdmin, self).get_actions(request) if 'delete_selected' in actions: diff --git a/src/DjangoBlog-master/djangoblog/settings.py b/src/DjangoBlog-master/djangoblog/settings.py index cd85eb7f..1593c63f 100644 --- a/src/DjangoBlog-master/djangoblog/settings.py +++ b/src/DjangoBlog-master/djangoblog/settings.py @@ -8,40 +8,52 @@ https://docs.djangoproject.com/en/1.10/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/1.10/ref/settings/ +#psr Django博客项目的配置文件,包含数据库、缓存、安全、国际化等所有配置项 +#psr 该文件定义了Django应用的核心设置,包括应用列表、中间件、模板、数据库连接等 +#psr 更多信息请参考: https://docs.djangoproject.com/en/1.10/topics/settings/ +#psr 完整配置项列表请参考: https://docs.djangoproject.com/en/1.10/ref/settings/ """ +#psr 导入操作系统、系统路径、路径处理等相关模块 import os import sys from pathlib import Path +#psr 导入Django国际化翻译模块 from django.utils.translation import gettext_lazy as _ - +#psr 环境变量转换为布尔值的辅助函数 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'. +#psr 构建项目内部路径,BASE_DIR指向项目根目录 BASE_DIR = Path(__file__).resolve().parent.parent # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! +#psr Django安全密钥,用于加密签名,生产环境应从环境变量获取 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! +#psr 调试模式开关,生产环境应关闭 DEBUG = env_to_bool('DJANGO_DEBUG', True) # DEBUG = False +#psr 测试模式标识,用于判断是否在运行测试 TESTING = len(sys.argv) > 1 and sys.argv[1] == 'test' # ALLOWED_HOSTS = [] +#psr 允许访问的主机列表,*表示允许所有主机 ALLOWED_HOSTS = ['*', '127.0.0.1', 'example.com'] # django 4.0新增配置 +#psr CSRF信任的源列表,用于跨站请求伪造保护 CSRF_TRUSTED_ORIGINS = ['http://example.com'] # Application definition - +#psr 已安装的应用列表,包含Django核心应用和自定义应用 INSTALLED_APPS = [ # 'django.contrib.admin', 'django.contrib.admin.apps.SimpleAdminConfig', @@ -64,6 +76,7 @@ INSTALLED_APPS = [ 'djangoblog' ] +#psr 中间件配置列表,用于处理请求和响应 MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', @@ -81,8 +94,10 @@ MIDDLEWARE = [ 'blog.middleware.OnlineMiddleware' ] +#psr 根URL配置文件 ROOT_URLCONF = 'djangoblog.urls' +#psr 模板配置 TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', @@ -100,12 +115,13 @@ TEMPLATES = [ }, ] +#psr WSGI应用配置 WSGI_APPLICATION = 'djangoblog.wsgi.application' # Database # https://docs.djangoproject.com/en/1.10/ref/settings/#databases - +#psr 数据库配置,使用MySQL数据库 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', @@ -122,6 +138,7 @@ DATABASES = { # Password validation # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators +#psr 密码验证器配置 AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', @@ -137,15 +154,18 @@ AUTH_PASSWORD_VALIDATORS = [ }, ] +#psr 支持的语言列表 LANGUAGES = ( ('en', _('English')), ('zh-hans', _('Simplified Chinese')), ('zh-hant', _('Traditional Chinese')), ) +#psr 本地化文件路径 LOCALE_PATHS = ( os.path.join(BASE_DIR, 'locale'), ) +#psr 语言代码和时区设置 LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai' @@ -159,7 +179,7 @@ USE_TZ = False # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.10/howto/static-files/ - +#psr Haystack搜索配置,使用Whoosh搜索引擎 HAYSTACK_CONNECTIONS = { 'default': { 'ENGINE': 'djangoblog.whoosh_cn_backend.WhooshEngine', @@ -167,32 +187,41 @@ HAYSTACK_CONNECTIONS = { }, } # Automatically update searching index +#psr 实时更新搜索索引的信号处理器 HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor' # Allow user login with username and password +#psr 自定义认证后端,支持邮箱或用户名登录 AUTHENTICATION_BACKENDS = [ 'accounts.user_login_backend.EmailOrUsernameModelBackend'] STATIC_ROOT = os.path.join(BASE_DIR, 'collectedstatic') +#psr 静态文件配置 STATIC_URL = '/static/' STATICFILES = os.path.join(BASE_DIR, 'static') +#psr 自定义用户模型和登录URL AUTH_USER_MODEL = 'accounts.BlogUser' LOGIN_URL = '/login/' +#psr 时间格式配置 TIME_FORMAT = '%Y-%m-%d %H:%M:%S' DATE_TIME_FORMAT = '%Y-%m-%d' # bootstrap color styles +#psr Bootstrap颜色样式列表 BOOTSTRAP_COLOR_TYPES = [ 'default', 'primary', 'success', 'info', 'warning', 'danger' ] # paginate +#psr 分页配置,每页显示10条记录 PAGINATE_BY = 10 # http cache timeout +#psr HTTP缓存超时时间(秒) CACHE_CONTROL_MAX_AGE = 2592000 # cache setting +#psr 缓存配置,使用本地内存缓存 CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', @@ -201,6 +230,7 @@ CACHES = { } } # 使用redis作为缓存 +#psr 如果配置了Redis URL,则使用Redis作为缓存后端 if os.environ.get("DJANGO_REDIS_URL"): CACHES = { 'default': { @@ -209,11 +239,14 @@ if os.environ.get("DJANGO_REDIS_URL"): } } +#psr 站点ID配置 SITE_ID = 1 +#psr 百度搜索引擎通知URL BAIDU_NOTIFY_URL = os.environ.get('DJANGO_BAIDU_NOTIFY_URL') \ or 'http://data.zz.baidu.com/urls?site=https://www.lylinux.net&token=1uAOGrMsUm5syDGn' # Email: +#psr 邮件配置,使用SMTP后端 EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_USE_TLS = env_to_bool('DJANGO_EMAIL_TLS', False) EMAIL_USE_SSL = env_to_bool('DJANGO_EMAIL_SSL', True) @@ -224,15 +257,19 @@ EMAIL_HOST_PASSWORD = os.environ.get('DJANGO_EMAIL_PASSWORD') DEFAULT_FROM_EMAIL = EMAIL_HOST_USER SERVER_EMAIL = EMAIL_HOST_USER # Setting debug=false did NOT handle except email notifications +#psr 管理员邮箱配置,用于接收错误通知 ADMINS = [('admin', os.environ.get('DJANGO_ADMIN_EMAIL') or 'admin@admin.com')] # WX ADMIN password(Two times md5) +#psr 微信管理员密码(两次MD5加密) WXADMIN = os.environ.get( 'DJANGO_WXADMIN_PASSWORD') or '995F03AC401D6CABABAEF756FC4D43C7' +#psr 日志路径配置 LOG_PATH = os.path.join(BASE_DIR, 'logs') if not os.path.exists(LOG_PATH): os.makedirs(LOG_PATH, exist_ok=True) +#psr 日志配置 LOGGING = { 'version': 1, 'disable_existing_loggers': False, @@ -294,16 +331,18 @@ LOGGING = { } } +#psr 静态文件查找器配置 STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', # other 'compressor.finders.CompressorFinder', ) +#psr 启用资源压缩 COMPRESS_ENABLED = True # COMPRESS_OFFLINE = True - +#psr CSS和JS压缩过滤器配置 COMPRESS_CSS_FILTERS = [ # creates absolute urls from relative ones 'compressor.filters.css_default.CssAbsoluteFilter', @@ -314,12 +353,16 @@ COMPRESS_JS_FILTERS = [ 'compressor.filters.jsmin.JSMinFilter' ] +#psr 媒体文件配置 MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads') MEDIA_URL = '/media/' +#psr X-Frame-Options安全头配置 X_FRAME_OPTIONS = 'SAMEORIGIN' +#psr 默认自动字段配置 DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +#psr 如果配置了Elasticsearch主机,则使用Elasticsearch作为搜索引擎 if os.environ.get('DJANGO_ELASTICSEARCH_HOST'): ELASTICSEARCH_DSL = { 'default': { @@ -333,6 +376,7 @@ if os.environ.get('DJANGO_ELASTICSEARCH_HOST'): } # Plugin System +#psr 插件系统配置,定义插件目录和激活的插件列表 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 8b7d4460..e9072ca9 100644 --- a/src/DjangoBlog-master/djangoblog/sitemap.py +++ b/src/DjangoBlog-master/djangoblog/sitemap.py @@ -1,59 +1,85 @@ +#psr 导入Django站点地图相关模块和项目模型 from django.contrib.sitemaps import Sitemap from django.urls import reverse from blog.models import Article, Category, Tag +#psr 静态页面站点地图类,用于生成网站首页等静态页面的sitemap class StaticViewSitemap(Sitemap): + #psr 设置页面优先级为0.5 priority = 0.5 + #psr 设置页面更新频率为每日 changefreq = 'daily' + #psr 返回需要包含在sitemap中的URL名称列表 def items(self): return ['blog:index', ] + #psr 根据URL名称生成实际的URL路径 def location(self, item): return reverse(item) +#psr 文章站点地图类,用于生成已发布文章页面的sitemap class ArticleSiteMap(Sitemap): + #psr 设置文章页面更新频率为每月 changefreq = "monthly" + #psr 设置文章页面优先级为0.6 priority = "0.6" + #psr 返回所有已发布状态的文章对象 def items(self): return Article.objects.filter(status='p') + #psr 返回文章对象的最后修改时间 def lastmod(self, obj): return obj.last_modify_time +#psr 分类站点地图类,用于生成文章分类页面的sitemap class CategorySiteMap(Sitemap): + #psr 设置分类页面更新频率为每周 changefreq = "Weekly" + #psr 设置分类页面优先级为0.6 priority = "0.6" + #psr 返回所有分类对象 def items(self): return Category.objects.all() + #psr 返回分类对象的最后修改时间 def lastmod(self, obj): return obj.last_modify_time +#psr 标签站点地图类,用于生成文章标签页面的sitemap class TagSiteMap(Sitemap): + #psr 设置标签页面更新频率为每周 changefreq = "Weekly" + #psr 设置标签页面优先级为0.3 priority = "0.3" + #psr 返回所有标签对象 def items(self): return Tag.objects.all() + #psr 返回标签对象的最后修改时间 def lastmod(self, obj): return obj.last_modify_time +#psr 用户站点地图类,用于生成用户页面的sitemap class UserSiteMap(Sitemap): + #psr 设置用户页面更新频率为每周 changefreq = "Weekly" + #psr 设置用户页面优先级为0.3 priority = "0.3" + #psr 返回所有文章作者的去重列表 def items(self): return list(set(map(lambda x: x.author, Article.objects.all()))) + #psr 返回用户对象的注册时间 def lastmod(self, obj): return obj.date_joined diff --git a/src/DjangoBlog-master/djangoblog/spider_notify.py b/src/DjangoBlog-master/djangoblog/spider_notify.py index 7b909e96..f49b9c15 100644 --- a/src/DjangoBlog-master/djangoblog/spider_notify.py +++ b/src/DjangoBlog-master/djangoblog/spider_notify.py @@ -1,21 +1,31 @@ +#psr 导入日志记录和HTTP请求相关模块 import logging import requests from django.conf import settings +#psr 获取日志记录器实例 logger = logging.getLogger(__name__) +#psr 蜘蛛通知类,用于向搜索引擎推送网站更新通知 class SpiderNotify(): + #psr 静态方法,向百度搜索引擎推送URL更新通知 @staticmethod def baidu_notify(urls): try: + #psr 将URL列表转换为换行分隔的字符串格式 data = '\n'.join(urls) + #psr 发送POST请求到百度通知URL result = requests.post(settings.BAIDU_NOTIFY_URL, data=data) + #psr 记录百度返回的响应结果 logger.info(result.text) except Exception as e: + #psr 记录推送过程中出现的异常错误 logger.error(e) + #psr 静态方法,通用通知方法,调用百度通知功能 @staticmethod def notify(url): + #psr 调用百度通知方法推送URL SpiderNotify.baidu_notify(url) diff --git a/src/DjangoBlog-master/djangoblog/tests.py b/src/DjangoBlog-master/djangoblog/tests.py index 01237d9a..bd5ad94b 100644 --- a/src/DjangoBlog-master/djangoblog/tests.py +++ b/src/DjangoBlog-master/djangoblog/tests.py @@ -1,15 +1,21 @@ +#psr 导入Django测试基类和项目工具函数模块 from django.test import TestCase from djangoblog.utils import * +#psr Django博客应用的测试类,用于测试项目中的工具函数 class DjangoBlogTest(TestCase): + #psr 测试初始化方法,在每个测试方法执行前运行 def setUp(self): pass + #psr 测试工具函数功能的测试方法 def test_utils(self): + #psr 测试SHA256哈希函数,验证字符串'test'的哈希值不为空 md5 = get_sha256('test') self.assertIsNotNone(md5) + #psr 测试Markdown转换功能,验证包含标题、代码块和链接的Markdown文本能正确转换 c = CommonMarkdown.get_markdown(''' # Title1 @@ -24,6 +30,7 @@ class DjangoBlogTest(TestCase): ''') self.assertIsNotNone(c) + #psr 测试字典转URL参数功能,验证字典能正确转换为URL查询字符串 d = { 'd': 'key1', 'd2': 'key2' diff --git a/src/DjangoBlog-master/djangoblog/urls.py b/src/DjangoBlog-master/djangoblog/urls.py index 4aae58a6..65a915a8 100644 --- a/src/DjangoBlog-master/djangoblog/urls.py +++ b/src/DjangoBlog-master/djangoblog/urls.py @@ -1,6 +1,7 @@ +#psr Django博客项目的URL配置文件,定义了整个应用的路由映射关系 """djangoblog URL Configuration -The `urlpatterns` list routes URLs to views. For more information please see: +The [urlpatterns](file://d:\PSR_projects\src\PSR_pro01\urls.py#L19-L21) list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/1.10/topics/http/urls/ Examples: Function views @@ -13,6 +14,7 @@ Including another URLconf 1. Import the include() function: from django.conf.urls import url, include 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) """ +#psr 导入Django配置、国际化、静态文件、站点地图等相关模块 from django.conf import settings from django.conf.urls.i18n import i18n_patterns from django.conf.urls.static import static @@ -21,12 +23,14 @@ from django.urls import path, include from django.urls import re_path from haystack.views import search_view_factory +#psr 导入自定义视图、管理站点、搜索表单和Feed类 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 ArticleSiteMap, CategorySiteMap, StaticViewSitemap, TagSiteMap, UserSiteMap +#psr 定义站点地图配置,包含文章、分类、标签、用户和静态页面的sitemap sitemaps = { 'blog': ArticleSiteMap, @@ -36,29 +40,44 @@ sitemaps = { 'static': StaticViewSitemap } +#psr 定义自定义错误处理视图 handler404 = 'blog.views.page_not_found_view' handler500 = 'blog.views.server_error_view' handle403 = 'blog.views.permission_denied_view' +#psr 定义基础URL模式,包含国际化URL urlpatterns = [ path('i18n/', include('django.conf.urls.i18n')), ] +#psr 使用国际化模式定义主要URL路由 urlpatterns += i18n_patterns( + #psr 管理后台路由 re_path(r'^admin/', admin_site.urls), + #psr 博客应用路由 re_path(r'', include('blog.urls', namespace='blog')), + #psr Markdown编辑器路由 re_path(r'mdeditor/', include('mdeditor.urls')), + #psr 评论应用路由 re_path(r'', include('comments.urls', namespace='comment')), + #psr 账户应用路由 re_path(r'', include('accounts.urls', namespace='account')), + #psr OAuth认证路由 re_path(r'', include('oauth.urls', namespace='oauth')), + #psr 站点地图路由 re_path(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps}, name='django.contrib.sitemaps.views.sitemap'), + #psr RSS Feed路由 re_path(r'^feed/$', DjangoBlogFeed()), re_path(r'^rss/$', DjangoBlogFeed()), + #psr 搜索路由,使用Elasticsearch搜索视图 re_path('^search', search_view_factory(view_class=EsSearchView, form_class=ElasticSearchModelSearchForm), name='search'), + #psr 服务器管理路由 re_path(r'', include('servermanager.urls', namespace='servermanager')), + #psr OwnTracks位置追踪路由 re_path(r'', include('owntracks.urls', namespace='owntracks')) , prefix_default_language=False) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) +#psr 在调试模式下添加媒体文件路由 if settings.DEBUG: urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/src/DjangoBlog-master/djangoblog/utils.py b/src/DjangoBlog-master/djangoblog/utils.py index 57f63dca..51ff0fd9 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 - +#psr:导入日志、操作系统、随机数、字符串、UUID、哈希等相关标准库模块 import logging import os import random @@ -9,6 +9,7 @@ import string import uuid from hashlib import sha256 +#psr:导入第三方库和Django相关模块 import bleach import markdown import requests @@ -17,45 +18,59 @@ from django.contrib.sites.models import Site from django.core.cache import cache from django.templatetags.static import static +#psr:获取日志记录器实例 logger = logging.getLogger(__name__) +#psr:获取最新的文章ID和评论ID def get_max_articleid_commentid(): from blog.models import Article from comments.models import Comment + #psr:返回最新文章和评论的主键值 return (Article.objects.latest().pk, Comment.objects.latest().pk) +#psr:计算字符串的SHA256哈希值 def get_sha256(str): + #psr:创建SHA256哈希对象并计算摘要 m = sha256(str.encode('utf-8')) return m.hexdigest() +#psr:缓存装饰器,用于缓存函数执行结果 def cache_decorator(expiration=3 * 60): + #psr:装饰器包装函数 def wrapper(func): + #psr:新的函数实现缓存逻辑 def news(*args, **kwargs): try: + #psr:尝试从视图获取缓存键 view = args[0] key = view.get_cache_key() except: key = None + #psr:如果无法获取缓存键则生成唯一键 if not key: unique_str = repr((func, args, kwargs)) m = sha256(unique_str.encode('utf-8')) key = m.hexdigest() + #psr:从缓存中获取值 value = cache.get(key) if value is not None: # logger.info('cache_decorator get cache:%s key:%s' % (func.__name__, key)) + #psr:处理默认缓存值 if str(value) == '__default_cache_value__': return None else: return value else: + #psr:缓存未命中,执行函数并设置缓存 logger.debug( 'cache_decorator set cache:%s key:%s' % (func.__name__, key)) value = func(*args, **kwargs) + #psr:根据函数返回值设置缓存 if value is None: cache.set(key, '__default_cache_value__', expiration) else: @@ -67,6 +82,7 @@ def cache_decorator(expiration=3 * 60): return wrapper +#psr:刷新视图缓存 def expire_view_cache(path, servername, serverport, key_prefix=None): ''' 刷新视图缓存 @@ -76,13 +92,16 @@ def expire_view_cache(path, servername, serverport, key_prefix=None): :param key_prefix:前缀 :return:是否成功 ''' + #psr:导入Django HTTP请求和缓存相关模块 from django.http import HttpRequest from django.utils.cache import get_cache_key + #psr:创建HTTP请求对象并设置相关属性 request = HttpRequest() request.META = {'SERVER_NAME': servername, 'SERVER_PORT': serverport} request.path = path + #psr:获取缓存键并删除对应缓存 key = get_cache_key(request, key_prefix=key_prefix, cache=cache) if key: logger.info('expire_view_cache:get key:{path}'.format(path=path)) @@ -92,15 +111,20 @@ def expire_view_cache(path, servername, serverport, key_prefix=None): return False +#psr:使用缓存装饰器缓存当前站点信息 @cache_decorator() def get_current_site(): + #psr:获取当前站点对象 site = Site.objects.get_current() return site +#psr:Markdown处理工具类 class CommonMarkdown: + #psr:静态方法,转换Markdown文本为HTML并生成目录 @staticmethod def _convert_markdown(value): + #psr:创建Markdown处理器并配置扩展 md = markdown.Markdown( extensions=[ 'extra', @@ -109,23 +133,30 @@ class CommonMarkdown: 'tables', ] ) + #psr:转换Markdown并获取正文和目录 body = md.convert(value) toc = md.toc return body, toc + #psr:静态方法,获取带目录的Markdown HTML @staticmethod def get_markdown_with_toc(value): + #psr:调用转换方法获取正文和目录 body, toc = CommonMarkdown._convert_markdown(value) return body, toc + #psr:静态方法,获取不带目录的Markdown HTML @staticmethod def get_markdown(value): + #psr:调用转换方法只返回正文 body, toc = CommonMarkdown._convert_markdown(value) return body +#psr:发送邮件函数 def send_email(emailto, title, content): from djangoblog.blog_signals import send_email_signal + #psr:发送邮件信号 send_email_signal.send( send_email.__class__, emailto=emailto, @@ -133,25 +164,33 @@ def send_email(emailto, title, content): content=content) +#psr:生成6位随机数字验证码 def generate_code() -> str: """生成随机数验证码""" + #psr:从数字中随机采样6个字符组成验证码 return ''.join(random.sample(string.digits, 6)) +#psr:将字典转换为URL查询字符串 def parse_dict_to_url(dict): from urllib.parse import quote + #psr:对字典键值对进行URL编码并拼接 url = '&'.join(['{}={}'.format(quote(k, safe='/'), quote(v, safe='/')) for k, v in dict.items()]) return url +#psr:获取博客设置信息 def get_blog_setting(): + #psr:先从缓存中获取设置 value = cache.get('get_blog_setting') if value: return value else: + #psr:缓存未命中则从数据库获取或创建默认设置 from blog.models import BlogSettings if not BlogSettings.objects.count(): + #psr:创建默认博客设置 setting = BlogSettings() setting.site_name = 'djangoblog' setting.site_description = '基于Django的博客系统' @@ -167,12 +206,14 @@ def get_blog_setting(): setting.show_gongan_code = False setting.comment_need_review = False setting.save() + #psr:获取第一个设置对象并缓存 value = BlogSettings.objects.first() logger.info('set cache get_blog_setting') cache.set('get_blog_setting', value) return value +#psr:保存用户头像到本地 def save_user_avatar(url): ''' 保存用户头像 @@ -182,40 +223,54 @@ def save_user_avatar(url): logger.info(url) try: + #psr:设置头像保存基础目录 basedir = os.path.join(settings.STATICFILES, 'avatar') + #psr:下载头像图片 rsp = requests.get(url, timeout=2) if rsp.status_code == 200: + #psr:创建目录如果不存在 if not os.path.exists(basedir): os.makedirs(basedir) + #psr:判断是否为图片文件并获取扩展名 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' + #psr:生成唯一文件名并保存文件 save_filename = str(uuid.uuid4().hex) + ext logger.info('保存用户头像:' + basedir + save_filename) with open(os.path.join(basedir, save_filename), 'wb+') as file: file.write(rsp.content) + #psr:返回静态文件路径 return static('avatar/' + save_filename) except Exception as e: + #psr:处理异常并返回默认头像 logger.error(e) return static('blog/img/avatar.png') +#psr:删除侧边栏缓存 def delete_sidebar_cache(): from blog.models import LinkShowType + #psr:生成侧边栏缓存键列表 keys = ["sidebar" + x for x in LinkShowType.values] + #psr:遍历删除每个缓存键 for k in keys: logger.info('delete sidebar key:' + k) cache.delete(k) +#psr:删除模板片段缓存 def delete_view_cache(prefix, keys): from django.core.cache.utils import make_template_fragment_key + #psr:生成模板片段缓存键并删除 key = make_template_fragment_key(prefix, keys) cache.delete(key) +#psr:获取资源URL路径 def get_resource_url(): + #psr:优先使用配置的静态URL,否则根据当前站点生成 if settings.STATIC_URL: return settings.STATIC_URL else: @@ -223,10 +278,14 @@ def get_resource_url(): return 'http://' + site.domain + '/static/' +#psr:定义允许的HTML标签 ALLOWED_TAGS = ['a', 'abbr', 'acronym', 'b', 'blockquote', 'code', 'em', 'i', 'li', 'ol', 'pre', 'strong', 'ul', 'h1', 'h2', 'p'] +#psr:定义允许的HTML属性 ALLOWED_ATTRIBUTES = {'a': ['href', 'title'], 'abbr': ['title'], 'acronym': ['title']} +#psr:清理HTML内容,只保留允许的标签和属性 def sanitize_html(html): - return bleach.clean(html, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES) + #psr:使用bleach库清理HTML + 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 04e3f7fd..375057e6 100644 --- a/src/DjangoBlog-master/djangoblog/whoosh_cn_backend.py +++ b/src/DjangoBlog-master/djangoblog/whoosh_cn_backend.py @@ -1,7 +1,9 @@ # encoding: utf-8 +# Handle minimum requirement. from __future__ import absolute_import, division, print_function, unicode_literals +# Handle minimum requirement. import json import os import re @@ -9,6 +11,7 @@ import shutil import threading import warnings +# Handle minimum requirement. import six from django.conf import settings from django.core.exceptions import ImproperlyConfigured @@ -34,6 +37,7 @@ from whoosh.qparser import QueryParser from whoosh.searching import ResultsPage from whoosh.writing import AsyncWriter +#psr:尝试导入whoosh库,如果失败,则抛出依赖缺失异常 try: import whoosh except ImportError: @@ -41,29 +45,33 @@ except ImportError: "The 'whoosh' backend requires the installation of 'Whoosh'. Please refer to the documentation.") # Handle minimum requirement. +#psr:检查whoosh版本要求,必须大于等于2.5.0 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. - +#psr:定义日期时间正则表达式模式 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?)?$') +#psr:创建线程本地存储对象 LOCALS = threading.local() LOCALS.RAM_STORE = None - +#psr:自定义HTML格式化器类,用于高亮显示搜索结果 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. """ + #psr:定义HTML模板格式 template = '<%(tag)s>%(t)s' - +#psr:定义Whoosh搜索后端类,继承自BaseSearchBackend class WhooshSearchBackend(BaseSearchBackend): # Word reserved by Whoosh for special use. + #psr:定义Whoosh保留关键字 RESERVED_WORDS = ( 'AND', 'NOT', @@ -74,35 +82,45 @@ class WhooshSearchBackend(BaseSearchBackend): # Characters reserved by Whoosh for special use. # The '\\' must come first, so as not to overwrite the other slash # replacements. + #psr:定义Whoosh保留字符 RESERVED_CHARACTERS = ( '\\', '+', '-', '&&', '||', '!', '(', ')', '{', '}', '[', ']', '^', '"', '~', '*', '?', ':', '.', ) + #psr:初始化Whoosh搜索后端 def __init__(self, connection_alias, **connection_options): super( WhooshSearchBackend, self).__init__( connection_alias, **connection_options) + #psr:标记设置是否完成 self.setup_complete = False + #psr:是否使用文件存储 self.use_file_storage = True + #psr:POST请求限制大小 self.post_limit = getattr( connection_options, 'POST_LIMIT', 128 * 1024 * 1024) + #psr:索引文件路径 self.path = connection_options.get('PATH') + #psr:根据配置决定存储类型 if connection_options.get('STORAGE', 'file') != 'file': self.use_file_storage = False - + + #psr:验证文件存储配置 if self.use_file_storage and not self.path: raise ImproperlyConfigured( "You must specify a 'PATH' in your settings for connection '%s'." % connection_alias) + #psr:获取日志记录器 self.log = logging.getLogger('haystack') + #psr:设置Whoosh索引环境 def setup(self): """ Defers loading until needed. @@ -111,15 +129,18 @@ class WhooshSearchBackend(BaseSearchBackend): new_index = False # Make sure the index is there. + #psr:创建索引目录如果不存在 if self.use_file_storage and not os.path.exists(self.path): os.makedirs(self.path) new_index = True + #psr:检查索引目录写入权限 if self.use_file_storage and not os.access(self.path, os.W_OK): raise IOError( "The path to your Whoosh index '%s' is not writable for the current user/group." % self.path) + #psr:根据配置选择存储方式 if self.use_file_storage: self.storage = FileStorage(self.path) else: @@ -130,10 +151,12 @@ class WhooshSearchBackend(BaseSearchBackend): self.storage = LOCALS.RAM_STORE + #psr:构建索引模式和解析器 self.content_field_name, self.schema = self.build_schema( connections[self.connection_alias].get_unified_index().all_searchfields()) self.parser = QueryParser(self.content_field_name, schema=self.schema) + #psr:创建或打开索引 if new_index is True: self.index = self.storage.create_index(self.schema) else: @@ -142,9 +165,12 @@ class WhooshSearchBackend(BaseSearchBackend): except index.EmptyIndexError: self.index = self.storage.create_index(self.schema) + #psr:标记设置完成 self.setup_complete = True + #psr:构建Whoosh索引模式 def build_schema(self, fields): + #psr:定义基本模式字段 schema_fields = { ID: WHOOSH_ID(stored=True, unique=True), DJANGO_CT: WHOOSH_ID(stored=True), @@ -152,11 +178,14 @@ class WhooshSearchBackend(BaseSearchBackend): } # Grab the number of keys that are hard-coded into Haystack. # We'll use this to (possibly) fail slightly more gracefully later. + #psr:获取初始键数量 initial_key_count = len(schema_fields) content_field_name = '' + #psr:根据字段类型构建相应的模式字段 for field_name, field_class in fields.items(): if field_class.is_multivalued: + #psr:处理多值字段 if field_class.indexed is False: schema_fields[field_class.index_fieldname] = IDLIST( stored=True, field_boost=field_class.boost) @@ -164,48 +193,62 @@ class WhooshSearchBackend(BaseSearchBackend): schema_fields[field_class.index_fieldname] = KEYWORD( stored=True, commas=True, scorable=True, field_boost=field_class.boost) elif field_class.field_type in ['date', 'datetime']: + #psr:处理日期时间字段 schema_fields[field_class.index_fieldname] = DATETIME( stored=field_class.stored, sortable=True) elif field_class.field_type == 'integer': + #psr:处理整数字段 schema_fields[field_class.index_fieldname] = NUMERIC( stored=field_class.stored, numtype=int, field_boost=field_class.boost) elif field_class.field_type == 'float': + #psr:处理浮点数字段 schema_fields[field_class.index_fieldname] = NUMERIC( stored=field_class.stored, numtype=float, field_boost=field_class.boost) elif field_class.field_type == 'boolean': + #psr:处理布尔字段 # Field boost isn't supported on BOOLEAN as of 1.8.2. schema_fields[field_class.index_fieldname] = BOOLEAN( stored=field_class.stored) elif field_class.field_type == 'ngram': + #psr:处理ngram字段 schema_fields[field_class.index_fieldname] = NGRAM( minsize=3, maxsize=15, stored=field_class.stored, field_boost=field_class.boost) elif field_class.field_type == 'edge_ngram': + #psr:处理边缘ngram字段 schema_fields[field_class.index_fieldname] = NGRAMWORDS(minsize=2, maxsize=15, at='start', stored=field_class.stored, field_boost=field_class.boost) else: + #psr:处理文本字段,使用中文分析器 # 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) + #psr:处理文档字段 if field_class.document is True: content_field_name = field_class.index_fieldname schema_fields[field_class.index_fieldname].spelling = True # Fail more gracefully than relying on the backend to die if no fields # are found. + #psr:验证是否有字段被添加到模式中 if len(schema_fields) <= initial_key_count: raise SearchBackendError( "No fields were found in any search_indexes. Please correct this before attempting to search.") + #psr:返回内容字段名和构建好的模式 return (content_field_name, Schema(**schema_fields)) + #psr:更新索引中的文档 def update(self, index, iterable, commit=True): + #psr:如果设置未完成则先进行设置 if not self.setup_complete: self.setup() + #psr:刷新索引并创建异步写入器 self.index = self.index.refresh() writer = AsyncWriter(self.index) + #psr:遍历可迭代对象准备并更新文档 for obj in iterable: try: doc = index.full_prepare(obj) @@ -214,22 +257,27 @@ class WhooshSearchBackend(BaseSearchBackend): else: # Really make sure it's unicode, because Whoosh won't have it any # other way. + #psr:确保文档内容都是unicode格式 for key in doc: doc[key] = self._from_python(doc[key]) # Document boosts aren't supported in Whoosh 2.5.0+. + #psr:删除文档提升字段(Whoosh 2.5.0+不支持) if 'boost' in doc: del doc['boost'] try: + #psr:更新文档 writer.update_document(**doc) except Exception as e: + #psr:处理异常情况 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: + #psr:记录错误日志 self.log.error( u"%s while preparing object for update" % e.__class__.__name__, @@ -239,43 +287,54 @@ class WhooshSearchBackend(BaseSearchBackend): "index": index, "object": get_identifier(obj)}}) + #psr:如果有数据则提交更改 if len(iterable) > 0: # For now, commit no matter what, as we run into locking issues # otherwise. writer.commit() + #psr:从索引中移除对象 def remove(self, obj_or_string, commit=True): + #psr:如果设置未完成则先进行设置 if not self.setup_complete: self.setup() + #psr:刷新索引并获取对象标识符 self.index = self.index.refresh() whoosh_id = get_identifier(obj_or_string) try: + #psr:通过查询删除文档 self.index.delete_by_query( q=self.parser.parse( - u'%s:"%s"' % - (ID, whoosh_id))) + u'%s:"%s"' % (ID, whoosh_id))) except Exception as e: + #psr:处理异常情况 if not self.silently_fail: raise + #psr:记录错误日志 self.log.error( "Failed to remove document '%s' from Whoosh: %s", whoosh_id, e, exc_info=True) + #psr:清空索引 def clear(self, models=None, commit=True): + #psr:如果设置未完成则先进行设置 if not self.setup_complete: self.setup() + #psr:刷新索引 self.index = self.index.refresh() + #psr:验证模型参数 if models is not None: assert isinstance(models, (list, tuple)) try: + #psr:根据参数决定是删除整个索引还是特定模型 if models is None: self.delete_index() else: @@ -283,16 +342,17 @@ class WhooshSearchBackend(BaseSearchBackend): for model in models: models_to_delete.append( - u"%s:%s" % - (DJANGO_CT, get_model_ct(model))) + u"%s:%s" % (DJANGO_CT, get_model_ct(model))) self.index.delete_by_query( q=self.parser.parse( u" OR ".join(models_to_delete))) except Exception as e: + #psr:处理异常情况 if not self.silently_fail: raise + #psr:记录错误日志 if models is not None: self.log.error( "Failed to clear Whoosh index of models '%s': %s", @@ -303,31 +363,34 @@ class WhooshSearchBackend(BaseSearchBackend): self.log.error( "Failed to clear Whoosh index: %s", e, exc_info=True) + #psr:删除整个索引 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. + #psr:根据存储类型删除索引文件 if self.use_file_storage and os.path.exists(self.path): shutil.rmtree(self.path) elif not self.use_file_storage: self.storage.clean() - # Recreate everything. + #psr:重新创建索引环境 self.setup() + #psr:优化索引 def optimize(self): + #psr:如果设置未完成则先进行设置 if not self.setup_complete: self.setup() + #psr:刷新索引并执行优化 self.index = self.index.refresh() self.index.optimize() + #psr:计算分页参数 def calculate_page(self, start_offset=0, end_offset=None): - # Prevent against Whoosh throwing an error. Requires an end_offset - # greater than 0. + #psr:防止Whoosh抛出错误,确保结束偏移量大于0 if end_offset is not None and end_offset <= 0: end_offset = 1 - # Determine the page. + #psr:确定页码 page_num = 0 if end_offset is None: @@ -341,10 +404,11 @@ class WhooshSearchBackend(BaseSearchBackend): if page_length and page_length > 0: page_num = int(start_offset / page_length) - # Increment because Whoosh uses 1-based page numbers. + #psr:增加页码因为Whoosh使用1基页码 page_num += 1 return page_num, page_length + #psr:执行搜索查询 @log_query def search( self, @@ -366,10 +430,11 @@ class WhooshSearchBackend(BaseSearchBackend): limit_to_registered_models=None, result_class=None, **kwargs): + #psr:如果设置未完成则先进行设置 if not self.setup_complete: self.setup() - # A zero length query should return no results. + #psr:处理零长度查询 if len(query_string) == 0: return { 'results': [], @@ -378,8 +443,7 @@ class WhooshSearchBackend(BaseSearchBackend): query_string = force_str(query_string) - # A one-character query (non-wildcard) gets nabbed by a stopwords - # filter and should yield zero results. + #psr:处理单字符查询 if len(query_string) <= 1 and query_string != u'*': return { 'results': [], @@ -388,6 +452,7 @@ class WhooshSearchBackend(BaseSearchBackend): reverse = False + #psr:处理排序参数 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 @@ -417,6 +482,7 @@ class WhooshSearchBackend(BaseSearchBackend): sort_by = sort_by_list[0] + #psr:处理分面搜索警告 if facets is not None: warnings.warn( "Whoosh does not handle faceting.", @@ -438,6 +504,7 @@ class WhooshSearchBackend(BaseSearchBackend): narrowed_results = None self.index = self.index.refresh() + #psr:处理模型限制参数 if limit_to_registered_models is None: limit_to_registered_models = getattr( settings, 'HAYSTACK_LIMIT_TO_REGISTERED_MODELS', True) @@ -451,6 +518,7 @@ class WhooshSearchBackend(BaseSearchBackend): else: model_choices = [] + #psr:构建模型选择查询 if len(model_choices) > 0: if narrow_queries is None: narrow_queries = set() @@ -460,6 +528,7 @@ class WhooshSearchBackend(BaseSearchBackend): narrow_searcher = None + #psr:处理窄化查询 if narrow_queries is not None: # Potentially expensive? I don't see another way to do it in # Whoosh... @@ -482,6 +551,7 @@ class WhooshSearchBackend(BaseSearchBackend): self.index = self.index.refresh() + #psr:执行实际搜索 if self.index.doc_count(): searcher = self.index.searcher() parsed_query = self.parser.parse(query_string) @@ -496,6 +566,7 @@ class WhooshSearchBackend(BaseSearchBackend): page_num, page_length = self.calculate_page( start_offset, end_offset) + #psr:设置搜索参数 search_kwargs = { 'pagelen': page_length, 'sortedby': sort_by, @@ -507,12 +578,14 @@ class WhooshSearchBackend(BaseSearchBackend): search_kwargs['filter'] = narrowed_results try: + #psr:执行搜索页面查询 raw_page = searcher.search_page( parsed_query, page_num, **search_kwargs ) except ValueError: + #psr:处理数值错误异常 if not self.silently_fail: raise @@ -531,6 +604,7 @@ class WhooshSearchBackend(BaseSearchBackend): 'spelling_suggestion': None, } + #psr:处理搜索结果 results = self._process_results( raw_page, highlight=highlight, @@ -544,6 +618,7 @@ class WhooshSearchBackend(BaseSearchBackend): return results else: + #psr:处理空索引情况 if self.include_spelling: if spelling_query: spelling_suggestion = self.create_spelling_suggestion( @@ -560,6 +635,7 @@ class WhooshSearchBackend(BaseSearchBackend): 'spelling_suggestion': spelling_suggestion, } + #psr:相似文档搜索 def more_like_this( self, model_instance, @@ -570,6 +646,7 @@ class WhooshSearchBackend(BaseSearchBackend): limit_to_registered_models=None, result_class=None, **kwargs): + #psr:如果设置未完成则先进行设置 if not self.setup_complete: self.setup() @@ -582,6 +659,7 @@ class WhooshSearchBackend(BaseSearchBackend): narrowed_results = None self.index = self.index.refresh() + #psr:处理模型限制参数 if limit_to_registered_models is None: limit_to_registered_models = getattr( settings, 'HAYSTACK_LIMIT_TO_REGISTERED_MODELS', True) @@ -595,6 +673,7 @@ class WhooshSearchBackend(BaseSearchBackend): else: model_choices = [] + #psr:构建模型选择查询 if len(model_choices) > 0: if narrow_queries is None: narrow_queries = set() @@ -602,11 +681,13 @@ class WhooshSearchBackend(BaseSearchBackend): narrow_queries.add(' OR '.join( ['%s:%s' % (DJANGO_CT, rm) for rm in model_choices])) + #psr:添加附加查询字符串 if additional_query_string and additional_query_string != '*': narrow_queries.add(additional_query_string) narrow_searcher = None + #psr:处理窄化查询 if narrow_queries is not None: # Potentially expensive? I don't see another way to do it in # Whoosh... @@ -627,11 +708,13 @@ class WhooshSearchBackend(BaseSearchBackend): else: narrowed_results = recent_narrowed_results + #psr:计算分页参数 page_num, page_length = self.calculate_page(start_offset, end_offset) self.index = self.index.refresh() raw_results = EmptyResults() + #psr:执行相似文档搜索 if self.index.doc_count(): query = "%s:%s" % (ID, get_identifier(model_instance)) searcher = self.index.searcher() @@ -647,8 +730,10 @@ class WhooshSearchBackend(BaseSearchBackend): raw_results.filter(narrowed_results) try: + #psr:创建结果页面 raw_page = ResultsPage(raw_results, page_num, page_length) except ValueError: + #psr:处理数值错误异常 if not self.silently_fail: raise @@ -667,6 +752,7 @@ class WhooshSearchBackend(BaseSearchBackend): 'spelling_suggestion': None, } + #psr:处理搜索结果 results = self._process_results(raw_page, result_class=result_class) searcher.close() @@ -675,6 +761,7 @@ class WhooshSearchBackend(BaseSearchBackend): return results + #psr:处理搜索结果 def _process_results( self, raw_page, @@ -689,6 +776,7 @@ class WhooshSearchBackend(BaseSearchBackend): # can cause pagination failures. hits = len(raw_page) + #psr:设置结果类 if result_class is None: result_class = SearchResult @@ -697,6 +785,7 @@ class WhooshSearchBackend(BaseSearchBackend): unified_index = connections[self.connection_alias].get_unified_index() indexed_models = unified_index.get_indexed_models() + #psr:遍历原始结果处理每条记录 for doc_offset, raw_result in enumerate(raw_page): score = raw_page.score(doc_offset) or 0 app_label, model_name = raw_result[DJANGO_CT].split('.') @@ -704,6 +793,7 @@ class WhooshSearchBackend(BaseSearchBackend): model = haystack_get_model(app_label, model_name) if model and model in indexed_models: + #psr:处理字段数据 for key, value in raw_result.items(): index = unified_index.get_index(model) string_key = str(key) @@ -723,9 +813,11 @@ class WhooshSearchBackend(BaseSearchBackend): else: additional_fields[string_key] = self._to_python(value) + #psr:删除不需要的字段 del (additional_fields[DJANGO_CT]) del (additional_fields[DJANGO_ID]) + #psr:处理高亮显示 if highlight: sa = StemmingAnalyzer() formatter = WhooshHtmlFormatter('em') @@ -742,6 +834,7 @@ class WhooshSearchBackend(BaseSearchBackend): self.content_field_name: [whoosh_result], } + #psr:创建搜索结果对象 result = result_class( app_label, model_name, @@ -752,6 +845,7 @@ class WhooshSearchBackend(BaseSearchBackend): else: hits -= 1 + #psr:处理拼写建议 if self.include_spelling: if spelling_query: spelling_suggestion = self.create_spelling_suggestion( @@ -760,6 +854,7 @@ class WhooshSearchBackend(BaseSearchBackend): spelling_suggestion = self.create_spelling_suggestion( query_string) + #psr:返回处理后的结果 return { 'results': results, 'hits': hits, @@ -767,6 +862,7 @@ class WhooshSearchBackend(BaseSearchBackend): 'spelling_suggestion': spelling_suggestion, } + #psr:创建拼写建议 def create_spelling_suggestion(self, query_string): spelling_suggestion = None reader = self.index.reader() @@ -787,6 +883,7 @@ class WhooshSearchBackend(BaseSearchBackend): query_words = cleaned_query.split() suggested_words = [] + #psr:为每个查询词生成建议 for word in query_words: suggestions = corrector.suggest(word, limit=1) @@ -796,6 +893,7 @@ class WhooshSearchBackend(BaseSearchBackend): spelling_suggestion = ' '.join(suggested_words) return spelling_suggestion + #psr:将Python值转换为Whoosh字符串 def _from_python(self, value): """ Converts Python values to a string for Whoosh. @@ -819,6 +917,7 @@ class WhooshSearchBackend(BaseSearchBackend): value = force_str(value) return value + #psr:将Whoosh值转换为Python原生值 def _to_python(self, value): """ Converts values from Whoosh to native Python values. @@ -870,13 +969,16 @@ class WhooshSearchBackend(BaseSearchBackend): return value +#psr:定义Whoosh搜索查询类,继承自BaseSearchQuery class WhooshSearchQuery(BaseSearchQuery): + #psr:转换日期时间格式 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')) + #psr:清理查询片段 def clean(self, query_fragment): """ Provides a mechanism for sanitizing user input before presenting the @@ -902,11 +1004,13 @@ class WhooshSearchQuery(BaseSearchQuery): return ' '.join(cleaned_words) + #psr:构建查询片段 def build_query_fragment(self, field, filter_type, value): from haystack import connections query_frag = '' is_datetime = False + #psr:处理不同类型的值 if not hasattr(value, 'input_type_name'): # Handle when we've got a ``ValuesListQuerySet``... if hasattr(value, 'values_list'): @@ -936,6 +1040,7 @@ class WhooshSearchQuery(BaseSearchQuery): index_fieldname = u'%s:' % connections[self._using].get_unified_index( ).get_index_fieldname(field) + #psr:定义过滤类型映射 filter_types = { 'content': '%s', 'contains': '*%s*', @@ -949,6 +1054,7 @@ class WhooshSearchQuery(BaseSearchQuery): 'fuzzy': u'%s~', } + #psr:根据不同过滤类型构建查询片段 if value.post_process is False: query_frag = prepared_value else: @@ -1026,19 +1132,15 @@ class WhooshSearchQuery(BaseSearchQuery): query_frag = filter_types[filter_type] % prepared_value + #psr:格式化查询片段 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'): - # # 'in' is a bit of a special case, as we don't want to - # # convert a valid list/tuple to string. Defer handling it - # # until later... - # value = self.backend._from_python(value) - - +#psr:定义Whoosh搜索引擎类,继承自BaseEngine class WhooshEngine(BaseEngine): + #psr:指定后端和查询类 backend = WhooshSearchBackend query = WhooshSearchQuery diff --git a/src/DjangoBlog-master/djangoblog/wsgi.py b/src/DjangoBlog-master/djangoblog/wsgi.py index 2295efd5..ea2fefee 100644 --- a/src/DjangoBlog-master/djangoblog/wsgi.py +++ b/src/DjangoBlog-master/djangoblog/wsgi.py @@ -1,8 +1,11 @@ """ + #psr:WSGI配置文件,用于部署Django应用程序 WSGI config for djangoblog project. +#psr:该文件将WSGI可调用对象暴露为模块级变量application It exposes the WSGI callable as a module-level variable named ``application``. +#psr:更多信息参考Django官方文档中的部署指南 For more information on this file, see https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ """ @@ -11,6 +14,8 @@ import os from django.core.wsgi import get_wsgi_application +#psr:设置默认的Django设置模块环境变量 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangoblog.settings") +#psr:获取WSGI应用程序实例,用于处理HTTP请求 application = get_wsgi_application()