From 001bc85a8157f3742d79d513e5300bcf3b975edb Mon Sep 17 00:00:00 2001 From: dynastxu <151742029+ETOofficial@users.noreply.github.com> Date: Sat, 8 Nov 2025 22:36:04 +0800 Subject: [PATCH] Squashed 'src/DjangoBlog/' changes from 1f969cc..408d19c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 408d19c docs(djangoblog): 为多个模块添加详细的中文文档注释 6210d05 docs(djangoblog):详细的为多个模块添加中文文档注释 55861e8 Merge remote-tracking branch 'DjangoBlog/master' into g3f-CodeEdit da8074f Add Dependabot configuration for Python packages git-subtree-dir: src/DjangoBlog git-subtree-split: 408d19c92424299380d0b2c7825106a14d10d683 --- .github/dependabot.yml | 7 + djangoblog/admin_site.py | 53 ++++++++ djangoblog/apps.py | 36 +++++ djangoblog/blog_signals.py | 56 ++++++++ djangoblog/elasticsearch_backend.py | 160 ++++++++++++++++++++++ djangoblog/feeds.py | 80 +++++++++++ djangoblog/logentryadmin.py | 74 +++++++++++ djangoblog/settings.py | 11 ++ djangoblog/sitemap.py | 145 ++++++++++++++++++++ djangoblog/spider_notify.py | 36 +++++ djangoblog/tests.py | 33 +++++ djangoblog/utils.py | 197 ++++++++++++++++++++++++++++ djangoblog/whoosh_cn_backend.py | 182 +++++++++++++++++++++++++ 13 files changed, 1070 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..a13b873 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 5 diff --git a/djangoblog/admin_site.py b/djangoblog/admin_site.py index f120405..fdf91e7 100644 --- a/djangoblog/admin_site.py +++ b/djangoblog/admin_site.py @@ -1,3 +1,31 @@ +"""`xjj` + +DjangoBlog 管理站点配置 +======== + +该模块定义了 DjangoBlog 项目的自定义管理站点配置。它继承自 Django 的默认 AdminSite, +并注册了项目中所有需要在管理后台进行管理的模型。 + +主要功能: +~~~~~~~~ +- 创建自定义管理站点 +- 设置管理站点的标题和头部显示文本 +- 限制只有超级用户才能访问管理站点 +- 注册各个应用中的模型到管理站点 + +注册的模型包括: +~~~~~~~~ +- 博客相关:文章、分类、标签、链接、侧边栏、博客设置 +- 用户相关:博客用户 +- 评论相关:评论 +- OAuth 相关: OAuth 用户、 OAuth 配置 +- 其他:命令、邮件发送日志、位置跟踪日志、站点信息、管理日志 + +使用方式: +~~~~~~~~ +通过 admin_site 实例来访问自定义的管理站点。 +""" + from django.contrib.admin import AdminSite from django.contrib.admin.models import LogEntry from django.contrib.sites.admin import SiteAdmin @@ -18,13 +46,38 @@ from servermanager.models import * class DjangoBlogAdminSite(AdminSite): + """`xjj` + + 自定义 Django 管理站点类 + + 继承自 Django 默认的 AdminSite ,用于为 DjangoBlog 项目提供定制化的管理后台 + """ site_header = 'djangoblog administration' site_title = 'djangoblog site admin' def __init__(self, name='admin'): + """`xjj` + + 初始化管理站点 + + Args: + name (str): 管理站点的名称,默认为 'admin' + """ super().__init__(name) def has_permission(self, request): + """`xjj` + + 检查用户是否有访问管理站点的权限 + + 重写了父类的方法,只允许超级用户访问管理站点 + + Args: + request: HTTP 请求对象 + + Returns: + bool: 如果用户是超级用户则返回 **True**,否则返回 **False** + """ return request.user.is_superuser # def get_urls(self): diff --git a/djangoblog/apps.py b/djangoblog/apps.py index d29e318..4d03a67 100644 --- a/djangoblog/apps.py +++ b/djangoblog/apps.py @@ -1,10 +1,46 @@ +"""`xjj` + +Django 应用配置模块 +======== + +该模块定义了 djangoblog 应用的配置类,负责应用的初始化设置和插件加载。 +当 Django 项目启动时,会自动调用配置类中的 ``ready()`` 方法来完成必要的初始化工作, +包括加载自定义插件等操作。 + +配置项说明: +~~~~~~~~ +- ``default_auto_field``: 设置默认的主键字段类型为 ``BigAutoField`` +- ``name``: 指定应用名称为 'djangoblog' + +初始化逻辑: +~~~~~~~~ +- 在 ``ready()`` 方法中导入并执行插件加载函数 ``load_plugins()`` +""" + from django.apps import AppConfig class DjangoblogAppConfig(AppConfig): + """`xjj` + + Django 博客应用配置类 + + 该类继承自 Django 的 AppConfig ,用于配置 jangoblog 应用的基本信息和初始化操作。 + 主要负责设置默认的自动字段类型和应用名称,并在应用准备就绪时加载插件。 + """ default_auto_field = 'django.db.models.BigAutoField' name = 'djangoblog' def ready(self): + """`xjj` + + 应用准备就绪时的回调方法 + + 当 Django 应用完全加载后调用此方法,主要用于执行应用级别的初始化操作。 + 此方法会导入并加载所有已注册的插件。 + + Returns: + None + """ super().ready() # Import and load plugins here from .plugin_manage.loader import load_plugins diff --git a/djangoblog/blog_signals.py b/djangoblog/blog_signals.py index 393f441..779a267 100644 --- a/djangoblog/blog_signals.py +++ b/djangoblog/blog_signals.py @@ -1,3 +1,24 @@ +"""`xjj` + +博客信号处理模块 +======== + +该模块负责处理 Django 博客应用中的各种信号,包括: + - 邮件发送信号处理 + - OAuth 用户登录信号处理 + - 模型保存后的回调处理 + - 用户登录 / 登出事件处理 + +主要功能: +~~~~~~~~ +1. ``send_email_signal_handler``: 处理发送邮件信号,支持 HTML 格式邮件发送并记录发送日志 +2. ``oauth_user_login_signal_handler``: 处理 OAuth 用户登录,包括用户头像保存等操作 +3. ``model_post_save_callback``: 模型保存后的通用回调处理,包括搜索引擎通知、缓存清理等 +4. ``user_auth_callback``: 用户认证状态变更处理,主要负责清理侧边栏缓存 + +该模块通过 Django 信号机制实现解耦,确保各功能模块的独立性。 +""" + import _thread import logging @@ -25,6 +46,18 @@ send_email_signal = django.dispatch.Signal( @receiver(send_email_signal) def send_email_signal_handler(sender, **kwargs): + """`xjj` + + 发送邮件信号的处理函数 + + Args: + sender: 信号发送者 + **kwargs: 包含邮件相关信息的字典参数 + + - emailto (list): 收件人邮箱列表 + - title (str): 邮件标题 + - content (str): 邮件内容 + """ emailto = kwargs['emailto'] title = kwargs['title'] content = kwargs['content'] @@ -53,6 +86,16 @@ def send_email_signal_handler(sender, **kwargs): @receiver(oauth_user_login_signal) def oauth_user_login_signal_handler(sender, **kwargs): + """`xjj` + + OAuth 用户登录信号处理函数 + + 当 OAuth 用户登录时,该函数会被触发执行相关处理逻辑,包括更新用户头像和清除侧边栏缓存。 + + Args: + sender: 信号发送者 + **kwargs: 包含信号传递的额外参数,必须包含 ``id`` 键表示 OAuth 用户的 ID + """ id = kwargs['id'] oauthuser = OAuthUser.objects.get(id=id) site = get_current_site().domain @@ -73,6 +116,19 @@ def model_post_save_callback( using, update_fields, **kwargs): + """`xjj` + + 模型保存后的回调函数,用于处理缓存清理和搜索引擎通知等操作 + + Args: + sender: 发送信号的模型类 + instance: 保存的模型实例 + created: 布尔值,指示实例是新建还是更新 + raw: 布尔值,指示实例是否从序列化数据加载 + using: 数据库别名 + update_fields: 更新字段集合,如果为 None 表示所有字段都被更新 + **kwargs: 其他关键字参数 + """ clearcache = False if isinstance(instance, LogEntry): return diff --git a/djangoblog/elasticsearch_backend.py b/djangoblog/elasticsearch_backend.py index 4afe498..f22ab2c 100644 --- a/djangoblog/elasticsearch_backend.py +++ b/djangoblog/elasticsearch_backend.py @@ -1,3 +1,24 @@ +"""`xjj` + +用于 Django 博客搜索的 Elasticsearch 后端 +======== + +该模块实现了基于 Elasticsearch 的搜索后端,用于博客文章的全文搜索功能。 +主要包含以下组件: + +- ``ElasticSearchBackend``: 核心搜索后端实现,处理文档的创建、更新、删除和搜索操作 +- ``ElasticSearchQuery``: 搜索查询处理类,负责构建和执行搜索查询 +- ``ElasticSearchModelSearchForm``: 搜索表单类,处理用户搜索输入和选项 +- ``ElasticSearchEngine``: 搜索引擎类,整合后端和查询组件 + +功能特性: +~~~~~~~~ +- 支持文章内容和标题的全文搜索 +- 提供搜索建议和拼写纠正功能 +- 支持搜索结果分页和过滤 +- 集成 Haystack 搜索框架 +""" + from django.utils.encoding import force_str from elasticsearch_dsl import Q from haystack.backends import BaseEngine, BaseSearchBackend, BaseSearchQuery, log_query @@ -12,6 +33,14 @@ logger = logging.getLogger(__name__) class ElasticSearchBackend(BaseSearchBackend): + """`xjj` + + ElasticSearch 搜索后端实现类,用于与 Elasticsearch 进行交互,提供搜索、更新、删除等操作。 + + :param connection_alias: 连接别名,用于标识不同的搜索引擎连接。 + :param connection_options: 其他连接选项,以关键字参数形式传入。 + """ + def __init__(self, connection_alias, **connection_options): super( ElasticSearchBackend, @@ -22,35 +51,81 @@ class ElasticSearchBackend(BaseSearchBackend): self.include_spelling = True def _get_models(self, iterable): + """`xjj` + + 获取模型对象并转换为文档格式。 + + :param iterable: 可迭代的模型对象列表,若为空则默认获取所有 Article 对象。 + :return: 转换后的文档对象列表。 + """ models = iterable if iterable and iterable[0] else Article.objects.all() docs = self.manager.convert_to_doc(models) return docs def _create(self, models): + """`xjj` + + 创建索引并将模型数据重建到 Elasticsearch 中。 + + :param models: 模型对象列表。 + """ self.manager.create_index() docs = self._get_models(models) self.manager.rebuild(docs) def _delete(self, models): + """`xjj` + + 删除指定模型对象。 + + :param models: 待删除的模型对象列表。 + :return: 始终返回 True。 + """ for m in models: m.delete() return True def _rebuild(self, models): + """`xjj` + + 重建指定模型的索引。 + + :param models: 模型对象列表,如果为空则默认获取所有 Article 对象。 + """ models = models if models else Article.objects.all() docs = self.manager.convert_to_doc(models) self.manager.update_docs(docs) def update(self, index, iterable, commit=True): + """`xjj` + + 更新索引中的文档。 + :param index: 索引名称。 + :param iterable: 待更新的模型对象可迭代对象。 + :param commit: 是否提交更改。 + """ models = self._get_models(iterable) self.manager.update_docs(models) def remove(self, obj_or_string): + """`xjj` + + 从索引中移除指定对象。 + + :param obj_or_string: 待移除的对象或字符串标识。 + """ models = self._get_models([obj_or_string]) self._delete(models) def clear(self, models=None, commit=True): + """`xjj` + + 清空索引中的内容。 + + :param models: 待清除的模型对象(未使用)。 + :param commit: 是否提交更改(未使用)。 + """ self.remove(None) @staticmethod @@ -73,6 +148,14 @@ class ElasticSearchBackend(BaseSearchBackend): @log_query def search(self, query_string, **kwargs): + """`xjj` + + 执行搜索操作,支持推荐词搜索和分页。 + + :param query_string: 查询关键词字符串。 + :param kwargs: 其他参数,包括 start_offset 和 end_offset 用于分页。 + :return: 包含搜索结果、命中数、 facet 信息及拼写建议的字典。 + """ logger.info('search query_string:' + query_string) start_offset = kwargs.get('start_offset') @@ -124,6 +207,16 @@ class ElasticSearchBackend(BaseSearchBackend): class ElasticSearchQuery(BaseSearchQuery): def _convert_datetime(self, date): + """`xjj` + + 将日期时间对象转换为字符串格式 + + Args: + date: 日期时间对象 + + Returns: + str: 格式化后的日期时间字符串,包含小时分钟秒或默认为 000000 + """ if hasattr(date, 'hour'): return force_str(date.strftime('%Y%m%d%H%M%S')) else: @@ -155,23 +248,79 @@ class ElasticSearchQuery(BaseSearchQuery): return ' '.join(cleaned_words) def build_query_fragment(self, field, filter_type, value): + """`xjj` + + 构建查询片段 + + Args: + field: 查询字段 + filter_type: 过滤器类型 + value: 查询值对象 + + Returns: + str: 查询字符串 + """ return value.query_string def get_count(self): + """`xjj` + + 获取查询结果的数量 + + Returns: + int: 结果数量,如果没有结果则返回0 + """ results = self.get_results() return len(results) if results else 0 def get_spelling_suggestion(self, preferred_query=None): + """`xjj` + + 获取拼写建议 + + Args: + preferred_query (str, optional): 首选查询字符串 + + Returns: + str: 拼写建议 + """ return self._spelling_suggestion def build_params(self, spelling_query=None): + """`xjj` + + 构建查询参数 + + Args: + spelling_query (str, optional): 拼写查询字符串 + + Returns: + dict: 构建的查询参数字典 + """ kwargs = super(ElasticSearchQuery, self).build_params(spelling_query=spelling_query) return kwargs class ElasticSearchModelSearchForm(ModelSearchForm): + """`xjj` + + ElasticSearch 模型搜索表单类 + + 该类继承自 ``ModelSearchForm`` ,用于处理 ElasticSearch 相关的搜索功能, + 特别是支持建议搜索功能的配置。 + """ def search(self): + """`xjj` + + 执行搜索操作 + + 该方法首先根据表单数据配置是否启用建议搜索功能, + 然后调用父类的 ``search`` 方法执行实际的搜索操作。 + + Returns: + SearchQuerySet: 搜索结果查询集 + """ # 是否建议搜索 self.searchqueryset.query.backend.is_suggest = self.data.get("is_suggest") != "no" sqs = super().search() @@ -179,5 +328,16 @@ class ElasticSearchModelSearchForm(ModelSearchForm): class ElasticSearchEngine(BaseEngine): + """`xjj` + + ElasticSearch 搜索引擎引擎类 + + 该类继承自 ``BaseEngine`` ,用于实现 ElasticSearch 搜索引擎的核心功能, + 包含后端存储和查询处理的配置。 + + Attributes: + backend: ElasticSearch 后端存储类,用于数据的存储和检索 + query: ElasticSearch 查询类,用于构建和执行搜索查询 + """ backend = ElasticSearchBackend query = ElasticSearchQuery diff --git a/djangoblog/feeds.py b/djangoblog/feeds.py index 8c4e851..cb5bcb3 100644 --- a/djangoblog/feeds.py +++ b/djangoblog/feeds.py @@ -1,3 +1,18 @@ +"""`xjj` + +DjangoBlog RSS 订阅模块 +======== + +该模块实现了博客文章的 RSS 订阅功能,基于 Django 的 syndication 框架。 +提供文章标题、内容、发布链接等信息的 RSS 输出,方便读者订阅博客更新。 + +主要功能: +~~~~~~~~ +- 生成 RSS 2.0 格式的订阅源 +- 提供最新的 5 篇已发布文章信息 +- 支持文章标题、内容、作者、版权等元数据展示 +""" + from django.contrib.auth import get_user_model from django.contrib.syndication.views import Feed from django.utils import timezone @@ -8,6 +23,13 @@ from djangoblog.utils import CommonMarkdown class DjangoBlogFeed(Feed): + """`xjj` + + Django 博客 RSS 订阅源类 + + 该类继承自 Django 的 ``Feed`` 类,用于生成博客文章的 RSS 订阅源。 + 提供博客文章的 RSS feed 功能,包含文章标题、描述、作者信息等。 + """ feed_type = Rss201rev2Feed description = '大巧无工,重剑无锋.' @@ -15,25 +37,83 @@ class DjangoBlogFeed(Feed): link = "/feed/" def author_name(self): + """`xjj` + + 获取 RSS 订阅源的作者名称 + + Returns: + str: 博客第一个用户的昵称,如果不存在则返回 None + """ return get_user_model().objects.first().nickname def author_link(self): + """`xjj` + + 获取 RSS 订阅源作者的链接地址 + + Returns: + str: 博客第一个用户的绝对 URL 地址 + """ return get_user_model().objects.first().get_absolute_url() def items(self): + """`xjj` + + 获取 RSS 订阅源的文章列表 + + Returns: + QuerySet: 按发布时间倒序排列的前 5 篇已发布文章 + """ return Article.objects.filter(type='a', status='p').order_by('-pub_time')[:5] def item_title(self, item): + """`xjj` + + 获取单篇文章的标题 + + Args: + item (Article): 文章对象 + + Returns: + str: 文章的标题 + """ return item.title def item_description(self, item): + """`xjj` + + 获取单篇文章的描述内容 + + Args: + item (Article): 文章对象 + + Returns: + str: 文章正文内容转换为 Markdown 格式后的 HTML + """ return CommonMarkdown.get_markdown(item.body) def feed_copyright(self): + """`xjj` + + 获取 RSS 订阅源的版权信息 + + Returns: + str: 包含当前年份的版权声明 + """ now = timezone.now() return "Copyright© {year} 且听风吟".format(year=now.year) def item_link(self, item): + """`xjj` + + 获取单篇文章的链接地址 + + Args: + item (Article): 文章对象 + + Returns: + str: 文章的绝对URL地址 + """ return item.get_absolute_url() def item_guid(self, item): diff --git a/djangoblog/logentryadmin.py b/djangoblog/logentryadmin.py index 2f6a535..3b1f6de 100644 --- a/djangoblog/logentryadmin.py +++ b/djangoblog/logentryadmin.py @@ -1,3 +1,20 @@ +"""`xjj` + +Django Admin 日志条目管理模块 +======== + +该模块提供了对 Django 管理后台操作日志的可视化管理界面。 +主要功能包括: + +- 显示所有管理操作的日志记录,包括创建、修改和删除操作 +- 提供日志记录的搜索和过滤功能 +- 在列表中显示指向相关对象和用户的链接 +- 限制权限以防止意外修改日志数据 + +该模块扩展了 Django 的内置 LogEntry 模型管理界面, +增强了日志查看体验并保护日志不被修改。 +""" + from django.contrib import admin from django.contrib.admin.models import DELETION from django.contrib.contenttypes.models import ContentType @@ -9,6 +26,12 @@ from django.utils.translation import gettext_lazy as _ class LogEntryAdmin(admin.ModelAdmin): + """`xjj` + + 管理后台中用于展示和管理日志条目( LogEntry )的 ``ModelAdmin`` 类。 + + 该类自定义了日志条目的展示方式、过滤条件、搜索字段以及权限控制等。 + """ list_filter = [ 'content_type' ] @@ -34,6 +57,17 @@ class LogEntryAdmin(admin.ModelAdmin): return False def has_change_permission(self, request, obj=None): + """`xjj` + + 控制用户是否有权限修改日志条目。 + + Args: + request (HttpRequest): 当前的 HTTP 请求对象。 + obj (LogEntry, optional): 要检查权限的具体日志条目对象。 + + Returns: + bool: 如果用户是超级用户或具有 ``admin.change_logentry`` 权限,并且请求方法不是 **POST** ,则返回 **True**。 + """ return ( request.user.is_superuser or request.user.has_perm('admin.change_logentry') @@ -43,6 +77,16 @@ class LogEntryAdmin(admin.ModelAdmin): return False def object_link(self, obj): + """`xjj` + + 生成一个指向相关对象的链接(如果可能),否则返回对象的字符串表示。 + + Args: + obj (LogEntry): 日志条目实例。 + + Return: + str: 包含HTML链接或纯文本的对象表示。 + """ object_link = escape(obj.object_repr) content_type = obj.content_type @@ -63,6 +107,16 @@ class LogEntryAdmin(admin.ModelAdmin): object_link.short_description = _('object') def user_link(self, obj): + """`xjj` + + 生成一个指向用户编辑页面的链接。 + + Args: + obj (LogEntry): 日志条目实例。 + + Returns: + str: 包含HTML链接的用户名称。 + """ content_type = ContentType.objects.get_for_model(type(obj.user)) user_link = escape(force_str(obj.user)) try: @@ -81,10 +135,30 @@ class LogEntryAdmin(admin.ModelAdmin): user_link.short_description = _('user') def get_queryset(self, request): + """`xjj` + + 获取查询集并进行优化,预加载 ``content_type`` 以减少数据库查询。 + + Args: + request (HttpRequest): 当前的 HTTP 请求对象。 + + Returns: + QuerySet: 经过 ``prefetch_related`` 优化的查询集。 + """ queryset = super(LogEntryAdmin, self).get_queryset(request) return queryset.prefetch_related('content_type') def get_actions(self, request): + """`xjj` + + 获取可用的操作列表,并移除“删除选中项”操作。 + + Args: + request (HttpRequest): 当前的 HTTP 请求对象。 + + Returns: + dict: 可用的操作字典。 + """ actions = super(LogEntryAdmin, self).get_actions(request) if 'delete_selected' in actions: del actions['delete_selected'] diff --git a/djangoblog/settings.py b/djangoblog/settings.py index b755d24..5a10d78 100644 --- a/djangoblog/settings.py +++ b/djangoblog/settings.py @@ -17,6 +17,17 @@ from django.utils.translation import gettext_lazy as _ def env_to_bool(env, default): + """`xjj` + + 将环境变量转换为布尔值 + + Args: + env (str): 环境变量名称 + default (bool): 当环境变量不存在时返回的默认值 + + Returns: + bool: 环境变量的布尔值表示,如果环境变量不存在则返回默认值 + """ str_val = os.environ.get(env) return default if str_val is None else str_val == 'True' diff --git a/djangoblog/sitemap.py b/djangoblog/sitemap.py index 8b7d446..4fc8b78 100644 --- a/djangoblog/sitemap.py +++ b/djangoblog/sitemap.py @@ -1,3 +1,21 @@ +"""`xjj` + +Django博客站点地图模块 +======== + +该模块定义了网站的 sitemap 配置,用于帮助搜索引擎更好地抓取网站内容。 +包含了以下几种类型的sitemap: + +1. 静态页面站点地图 (``StaticViewSitemap``) - 包含网站首页等静态页面 +2. 文章站点地图 (``ArticleSiteMap``) - 包含所有已发布的文章页面 +3. 分类站点地图 (``CategorySiteMap``) - 包含所有文章分类页面 +4. 标签站点地图 (``TagSiteMap``) - 包含所有标签页面 +5. 用户站点地图 (``UserSiteMap``) - 包含所有作者页面 + +每个站点地图类都继承自Django的Sitemap基类,并根据不同的内容类型设置了相应的 +更新频率 (``changefreq``) 和优先级 (``priority``) 参数。 +""" + from django.contrib.sitemaps import Sitemap from django.urls import reverse @@ -5,55 +23,182 @@ from blog.models import Article, Category, Tag class StaticViewSitemap(Sitemap): + """`xjj` + + 静态视图站点地图类 + ~~~~~~~~ + + 该类用于生成静态页面的 sitemap 信息,继承自 Django 的 ``Sitemap`` 类 + """ priority = 0.5 changefreq = 'daily' def items(self): + """`xjj` + + 获取站点地图项列表 + + 返回包含 URL 名称的列表,这些 URL 将被包含在 sitemap 中 + + Returns: + list: 包含 URL 名称的列表 + """ return ['blog:index', ] def location(self, item): + """`xjj` + + 根据 URL 名称获取实际的 URL 路径 + + 通过 Django 的 ``reverse`` 函数将 URL 名称转换为实际的 URL 路径 + + Args: + item (str): URL 名称 + + Returns: + str: 对应的 URL 路径 + """ return reverse(item) class ArticleSiteMap(Sitemap): + """`xjj` + + 文章站点地图类,用于生成文章页面的 ``sitemap.xml`` 文件 + + 该类继承自 Django 的 ``Sitemap`` 类,定义了文章页面在站点地图中的相关属性 + 和数据获取方法,便于搜索引擎爬虫发现和索引文章内容。 + """ changefreq = "monthly" priority = "0.6" def items(self): + """`xjj` + + 获取 sitemap 中包含的所有文章对象列表 + + Returns: + QuerySet: 包含所有已发布状态文章的查询集 + """ return Article.objects.filter(status='p') def lastmod(self, obj): + """`xjj` + + 获取指定文章对象的最后修改时间 + + Args: + obj: 文章对象实例 + + Returns: + datetime: 文章的最后修改时间 + """ return obj.last_modify_time class CategorySiteMap(Sitemap): + """`xjj` + + 分类站点地图类 + ~~~~~~~~ + + 该类用于生成网站分类页面的 sitemap 信息,继承自 Django 的 ``Sitemap`` 类 + """ changefreq = "Weekly" priority = "0.6" def items(self): + """`xjj` + + 获取所有分类对象 + + Returns: + QuerySet: 包含所有 Category 对象的查询集 + """ return Category.objects.all() def lastmod(self, obj): + """`xjj` + + 获取分类对象的最后修改时间 + + Args: + obj (Category): 分类对象实例 + + Returns: + datetime: 分类的最后修改时间 + """ return obj.last_modify_time class TagSiteMap(Sitemap): + """`xjj` + + 标签站点地图类,用于生成标签页面的 sitemap.xml 文件 + + 该类继承自 Django 的 Sitemap 类,定义了标签页面在站点地图中的相关属性 + 和数据获取方法。 + """ changefreq = "Weekly" priority = "0.3" def items(self): + """`xjj` + + 获取所有标签对象列表 + + Returns: + QuerySet: 包含所有 Tag 对象的查询集 + """ return Tag.objects.all() def lastmod(self, obj): + """`xjj` + + 获取指定标签对象的最后修改时间 + + Args: + obj (Tag): 标签对象实例 + + Returns: + datetime: 标签对象的最后修改时间 + """ return obj.last_modify_time class UserSiteMap(Sitemap): + """`xjj` + + 用户站点地图类,用于生成用户页面的 sitemap.xml 文件 + + Attributes: + changefreq (str): 页面更新频率,设置为 `Weekly` + priority (str): 页面优先级,设置为 `0.3` + """ changefreq = "Weekly" priority = "0.3" def items(self): + """`xjj` + + 获取所有需要包含在 sitemap 中的用户对象列表 + + 通过获取所有文章的作者来构建用户列表,并去重 + + Returns: + list: 包含所有文章作者用户的去重列表 + """ return list(set(map(lambda x: x.author, Article.objects.all()))) def lastmod(self, obj): + """`xjj` + + 获取指定用户对象的最后修改时间 + + Args: + obj: 用户对象 + + Returns: + datetime: 用户的注册时间,作为最后修改时间 + """ return obj.date_joined diff --git a/djangoblog/spider_notify.py b/djangoblog/spider_notify.py index 7b909e9..bfe1d09 100644 --- a/djangoblog/spider_notify.py +++ b/djangoblog/spider_notify.py @@ -1,3 +1,23 @@ +"""`xjj` + +Spider Notify Module +======== + +此模块提供了向搜索引擎通知网站更新的功能,主要用于 SEO 优化。 +当前支持百度搜索引擎的主动推送功能。 + +主要功能: +~~~~~~~~ +- 向百度搜索引擎主动推送网站URL更新 +- 记录推送结果和异常日志 + +依赖: +~~~~~~~~ +- ``requests``: 用于发送 HTTP 请求 +- ``django.conf.settings``: 获取配置参数 +- ``logging``: 记录日志信息 +""" + import logging import requests @@ -7,8 +27,18 @@ logger = logging.getLogger(__name__) class SpiderNotify(): + """`xjj` + + 爬虫通知类,用于向搜索引擎提交 URL 通知 + """ @staticmethod def baidu_notify(urls): + """`xjj` + + 向百度搜索引擎提交 URL 通知 + + :param urls: 要提交的 URL 列表 + """ try: data = '\n'.join(urls) result = requests.post(settings.BAIDU_NOTIFY_URL, data=data) @@ -18,4 +48,10 @@ class SpiderNotify(): @staticmethod def notify(url): + """`xjj` + + 通用通知方法,调用百度通知接口 + + :param url: 要通知的 URL 列表 + """ SpiderNotify.baidu_notify(url) diff --git a/djangoblog/tests.py b/djangoblog/tests.py index 01237d9..2113af2 100644 --- a/djangoblog/tests.py +++ b/djangoblog/tests.py @@ -1,13 +1,46 @@ +"""`xjj` + +DjangoBlog 测试模块 +======== + +该模块包含对 DjangoBlog 应用各个功能组件的单元测试, +主要用于验证工具函数、模型、视图等的正确性和稳定性。 + +当前测试类: + - ``DjangoBlogTest``: 主要测试工具函数的功能正确性 + +测试覆盖的工具函数包括: + - ``get_sha256``: 测试 SHA256 哈希值生成功能 + - ``CommonMarkdown.get_markdown``: 测试 Markdown 文本转换为 HTML 的功能 + - ``parse_dict_to_url``: 测试字典转换为 URL 查询参数字符串的功能 +""" + from django.test import TestCase from djangoblog.utils import * class DjangoBlogTest(TestCase): + """`xjj` + + Django 博客测试类 + + 用于测试博客应用的各种功能模块 + 继承自 Django 的 ``TestCase`` 类 + """ def setUp(self): pass def test_utils(self): + """`xjj` + + 测试工具函数功能 + + 验证以下功能: + 1. SHA256哈希函数 + 2. Markdown文本转换功能 + 3. 字典转URL参数功能 + """ md5 = get_sha256('test') self.assertIsNotNone(md5) c = CommonMarkdown.get_markdown(''' diff --git a/djangoblog/utils.py b/djangoblog/utils.py index 91d2b91..045264e 100644 --- a/djangoblog/utils.py +++ b/djangoblog/utils.py @@ -1,5 +1,21 @@ #!/usr/bin/env python # encoding: utf-8 +"""`xjj` + +DjangoBlog 工具模块 +======== + +本模块包含项目中使用的各种通用工具函数和辅助类,主要功能包括: + - 缓存装饰器和缓存管理 + - Markdown 内容转换和处理 + - 邮件发送功能 + - 网站设置管理 + - 用户头像处理 + - HTML安全过滤 + - URL和数据处理工具 + +该模块为整个 DjangoBlog 项目提供基础工具支持,被多个应用组件调用。 +""" import logging @@ -21,19 +37,73 @@ logger = logging.getLogger(__name__) def get_max_articleid_commentid(): + """`xjj` + + 获取最新的文章 ID 和评论 ID + + 该函数从数据库中查询最新创建的文章和评论记录, + 并返回它们的主键值。 + + Returns: + tuple: 包含两个元素的元组 + + - 第一个元素是最新文章的主键 (pk) + - 第二个元素是最新评论的主键 (pk) + """ from blog.models import Article from comments.models import Comment return (Article.objects.latest().pk, Comment.objects.latest().pk) def get_sha256(str): + """`xjj` + + 计算字符串的 SHA256 哈希值 + + Args: + str (str): 需要计算哈希值的输入字符串 + + Returns: + str: 输入字符串的 SHA256 哈希值,以十六进制字符串形式表示 + """ m = sha256(str.encode('utf-8')) return m.hexdigest() def cache_decorator(expiration=3 * 60): + """`xjj` + + 缓存装饰器工厂函数,用于创建具有指定过期时间的缓存装饰器 + + Args: + expiration (int): 缓存过期时间,单位为秒,默认为 3 分钟 + + Returns: + function: 返回一个装饰器函数 ``wrapper`` + """ def wrapper(func): + """`xjj` + + 装饰器函数,用于包装需要缓存的函数 + + Args: + func (function): 被装饰的函数 + + Returns: + function: 返回包装后的函数 ``news`` + """ def news(*args, **kwargs): + """`xjj` + + 实际执行缓存逻辑的函数 + + Args: + *args: 被装饰函数的位置参数 + **kwargs: 被装饰函数的关键字参数 + + Returns: + 任意类型: 返回被装饰函数的执行结果或缓存的结果 + """ try: view = args[0] key = view.get_cache_key() @@ -94,13 +164,47 @@ def expire_view_cache(path, servername, serverport, key_prefix=None): @cache_decorator() def get_current_site(): + """ + 获取当前站点信息 + + 该函数通过 Django Sites 框架获取当前站点对象,并使用缓存装饰器进行性能优化。 + 函数会自动识别请求上下文中的站点信息,避免重复查询数据库。 + + Returns: + Site: 返回当前站点对象,包含站点的基本信息如域名、名称等 + + 注意: + - 依赖 Django Sites 框架 + - 使用了缓存机制提高性能 + - 需要确保 SITE_ID 设置正确 + """ site = Site.objects.get_current() return site class CommonMarkdown: + """`xjj` + + Markdown 处理工具类 + ~~~~~~~~ + + 提供 Markdown 文本转换为 HTML 的功能,支持目录生成、代码高亮等扩展功能 + """ @staticmethod def _convert_markdown(value): + """`xjj` + + 将 Markdown 文本转换为 HTML 并生成目录 + + Args: + value (str): 需要转换的 Markdown 格式文本 + + Returns: + tuple: 包含两个元素的元组 + + - str: 转换后的 HTML 正文内容 + - str: 生成的目录 HTML 内容 + """ md = markdown.Markdown( extensions=[ 'extra', @@ -115,16 +219,50 @@ class CommonMarkdown: @staticmethod def get_markdown_with_toc(value): + """`xjj` + + 转换 Markdown 文本并返回正文和目录 + + Args: + value (str): 需要转换的 Markdown 格式文本 + + Returns: + tuple: 包含两个元素的元组 + + - str: 转换后的 HTML 正文内容 + - str: 生成的目录 HTML 内容 + """ body, toc = CommonMarkdown._convert_markdown(value) return body, toc @staticmethod def get_markdown(value): + """`xjj` + + 转换 Markdown 文本并只返回正文内容 + + Args: + value (str): 需要转换的 Markdown 格式文本 + + Returns: + str: 转换后的 HTML 正文内容 + """ body, toc = CommonMarkdown._convert_markdown(value) return body def send_email(emailto, title, content): + """`xjj` + + 发送邮件函数 + + 该函数通过触发 Django 信号来发送邮件,实际的邮件发送逻辑在信号处理器中实现。 + + Args: + emailto (str): 收件人邮箱地址 + title (str): 邮件标题 + content (str): 邮件内容 + """ from djangoblog.blog_signals import send_email_signal send_email_signal.send( send_email.__class__, @@ -139,6 +277,16 @@ def generate_code() -> str: def parse_dict_to_url(dict): + """`xjj` + + 将字典转换为 URL 查询字符串格式 + + Args: + dict (dict): 包含键值对的字典,用于生成URL查询参数 + + Returns: + str: 格式化后的 URL 查询字符串,格式为 `key1=value1&key2=value2...` + """ from urllib.parse import quote url = '&'.join(['{}={}'.format(quote(k, safe='/'), quote(v, safe='/')) for k, v in dict.items()]) @@ -146,6 +294,16 @@ def parse_dict_to_url(dict): def get_blog_setting(): + """`xjj` + + 获取博客设置信息 + + 该函数首先尝试从缓存中获取博客设置,如果缓存中不存在则从数据库中获取。 + 如果数据库中没有博客设置记录,则创建一条默认配置记录。 + + Returns: + BlogSettings: 博客设置对象,包含网站名称、描述、 SEO 设置、文章显示配置等信息 + """ value = cache.get('get_blog_setting') if value: return value @@ -202,6 +360,13 @@ def save_user_avatar(url): def delete_sidebar_cache(): + """`xjj` + + 删除侧边栏缓存 + + 该函数用于删除所有侧边栏相关的缓存数据。通过遍历 ``LinkShowType`` 枚举值, + 构造对应的缓存键名并逐一删除。 + """ from blog.models import LinkShowType keys = ["sidebar" + x for x in LinkShowType.values] for k in keys: @@ -210,12 +375,30 @@ def delete_sidebar_cache(): def delete_view_cache(prefix, keys): + """`xjj` + + 删除视图缓存 + + 该函数用于删除 Django 模板片段缓存,通过构建缓存键并从缓存中删除对应的数据。 + + Args: + prefix (str): 模板片段缓存的前缀名称 + keys (list): 用于生成缓存键的变量列表 + """ from django.core.cache.utils import make_template_fragment_key key = make_template_fragment_key(prefix, keys) cache.delete(key) def get_resource_url(): + """`xjj` + 获取资源文件的 URL 地址 + + 该函数优先返回配置中的静态资源 URL ,如果未配置则根据当前站点信息构造默认的静态资源 URL + + Returns: + str: 静态资源的完整 URL 地址 + """ if settings.STATIC_URL: return settings.STATIC_URL else: @@ -236,6 +419,20 @@ ALLOWED_CLASSES = [ ] def class_filter(tag, name, value): + """`xjj` + + 自定义 class 属性过滤器 + + 该函数用于过滤 HTML 标签的 class 属性值,只允许预定义的安全 class 值通过 + + Args: + tag: HTML 标签对象 + name: 属性名称 + value: 属性值 + + Returns: + str | Any : 过滤后的 class 属性值,如果没有任何允许的 class 则返回 **False**。如果不是 class 属性,则返回原始值 + """ """自定义class属性过滤器""" if name == 'class': # 只允许预定义的安全class值 diff --git a/djangoblog/whoosh_cn_backend.py b/djangoblog/whoosh_cn_backend.py index 04e3f7f..ac6cd4e 100644 --- a/djangoblog/whoosh_cn_backend.py +++ b/djangoblog/whoosh_cn_backend.py @@ -1,4 +1,25 @@ # encoding: utf-8 +"""`xjj` + +Whoosh 中文搜索后端模块 + +该模块为 Django Haystack 提供了一个基于 Whoosh 的中文搜索引擎后端实现。 +主要特性包括: + +- 支持中文分词搜索(使用 jieba 分词器) +- 实现了完整的搜索后端功能,包括索引创建、更新、删除和查询 +- 支持多种数据类型的字段映射(文本、数字、日期、布尔值等) +- 提供搜索结果高亮显示功能 +- 支持搜索建议和拼写纠正 +- 支持多模型搜索和过滤 +- 实现了 more_like_this 相似文档查找功能 + +依赖: +~~~~~~~~ +- Whoosh >= 2.5.0 +- jieba (用于中文分词) +- Django Haystack +""" from __future__ import absolute_import, division, print_function, unicode_literals @@ -145,6 +166,18 @@ class WhooshSearchBackend(BaseSearchBackend): self.setup_complete = True def build_schema(self, fields): + """`xjj` + + 构建用于搜索索引的 Whoosh Schema 对象。 + + 根据传入的字段定义创建对应的 Whoosh 字段类型,并最终构建一个完整的 Schema 实例。 + + :param fields: 包含字段名与字段配置对象的字典。键是字段名称,值是一个具有 field_type、stored、boost 等属性的对象。 + + :return: 第一个元素是文档内容字段名,第二个是构建好的 Whoosh Schema 对象。 + + :exception SearchBackendError: 当没有找到任何字段时抛出异常。 + """ schema_fields = { ID: WHOOSH_ID(stored=True, unique=True), DJANGO_CT: WHOOSH_ID(stored=True), @@ -200,6 +233,17 @@ class WhooshSearchBackend(BaseSearchBackend): return (content_field_name, Schema(**schema_fields)) def update(self, index, iterable, commit=True): + """`xjj` + + 更新索引中的文档数据。 + + 遍历可迭代对象中的模型实例,将其转换成可用于索引的数据格式并更新到索引中。 + + Args: + index (SearchIndex): Haystack 的索引类实例。 + iterable (iterable): 要被索引的模型实例集合。 + commit (bool): 是否在更新后提交更改,默认为 **True**。 + """ if not self.setup_complete: self.setup() @@ -245,6 +289,14 @@ class WhooshSearchBackend(BaseSearchBackend): writer.commit() def remove(self, obj_or_string, commit=True): + """`xjj` + + 从索引中删除指定文档。 + + Args: + obj_or_string (Model | str): 要删除的模型实例或其唯一标识符字符串。 + commit (bool): 是否立即提交更改,默认为 **True**。 + """ if not self.setup_complete: self.setup() @@ -267,6 +319,14 @@ class WhooshSearchBackend(BaseSearchBackend): exc_info=True) def clear(self, models=None, commit=True): + """`xjj` + + 清除整个索引或特定模型类型的索引数据。 + + Args: + models (list | tuple): 可选,要清除的模型列表。 + commit (bool): 是否立即提交更改,默认为 **True**。 + """ if not self.setup_complete: self.setup() @@ -304,6 +364,10 @@ class WhooshSearchBackend(BaseSearchBackend): "Failed to clear Whoosh index: %s", e, exc_info=True) def delete_index(self): + """`xjj` + + 完全删除当前索引目录下的文件并重新初始化索引结构。 + """ # Per the Whoosh mailing list, if wiping out everything from the index, # it's much more efficient to simply delete the index files. if self.use_file_storage and os.path.exists(self.path): @@ -315,6 +379,10 @@ class WhooshSearchBackend(BaseSearchBackend): self.setup() def optimize(self): + """`xjj` + + 对现有索引执行优化操作,提升查询性能。 + """ if not self.setup_complete: self.setup() @@ -322,6 +390,17 @@ class WhooshSearchBackend(BaseSearchBackend): self.index.optimize() def calculate_page(self, start_offset=0, end_offset=None): + """`xjj` + + 根据偏移量计算分页信息。 + + Args: + start_offset (int): 查询起始位置。 + end_offset (int): 查询结束位置。 + + Returns: + tuple: 分别表示页码和每页长度。 + """ # Prevent against Whoosh throwing an error. Requires an end_offset # greater than 0. if end_offset is not None and end_offset <= 0: @@ -366,6 +445,33 @@ class WhooshSearchBackend(BaseSearchBackend): limit_to_registered_models=None, result_class=None, **kwargs): + """`xjj` + + 执行全文检索操作。 + + Args: + query_string (str): 用户输入的查询语句。 + sort_by (list): 排序字段列表。 + start_offset (int): 结果偏移起点。 + end_offset (int): 结果偏移终点。 + fields (str): 指定返回字段。 + highlight (bool): 是否启用高亮显示。 + facets (dict): 分面统计参数。 + date_facets (dict): 时间范围分面参数。 + query_facets (dict): 自定义查询分面参数。 + narrow_queries (set): 过滤条件集合。 + spelling_query (str): 用于拼写建议的原始查询。 + within (tuple): 地理位置过滤参数。 + dwithin (tuple): 距离范围内地理查询参数。 + distance_point (tuple): 中心点坐标。 + models (list): 限定搜索模型列表。 + limit_to_registered_models (bool): 是否仅限注册过的模型。 + result_class (class): 自定义结果封装类。 + **kwargs: 其他扩展参数。 + + Returns: + dict: 包括匹配结果、命中数、拼写建议等信息。 + """ if not self.setup_complete: self.setup() @@ -570,6 +676,23 @@ class WhooshSearchBackend(BaseSearchBackend): limit_to_registered_models=None, result_class=None, **kwargs): + """`xjj` + + 查找与给定模型实例相似的内容。 + + Args: + model_instance (Model): 目标模型实例。 + additional_query_string (str): 补充查询语句。 + start_offset (int): 起始偏移。 + end_offset (int): 终止偏移。 + models (list): 限制查找的模型列表。 + limit_to_registered_models (bool): 是否只考虑已注册模型。 + result_class (class): 自定义结果类。 + **kwargs: 扩展参数。 + + Returns: + dict: 包含相似文档的结果集。 + """ if not self.setup_complete: self.setup() @@ -682,6 +805,20 @@ class WhooshSearchBackend(BaseSearchBackend): query_string='', spelling_query=None, result_class=None): + """`xjj` + + 处理原始搜索结果,转换为标准输出格式。 + + Args: + raw_page (ResultsPage): 来自 Whoosh 的原始页面结果。 + highlight (bool): 是否需要高亮关键词。 + query_string (str): 查询语句。 + spelling_query (str): 用于拼写的查询语句。 + result_class (class): 自定义结果包装类。 + + Returns: + dict: 标准化的搜索响应结构。 + """ from haystack import connections results = [] @@ -768,6 +905,16 @@ class WhooshSearchBackend(BaseSearchBackend): } def create_spelling_suggestion(self, query_string): + """`xjj` + + 生成针对用户查询的拼写修正建议。 + + Args: + query_string (str): 输入的查询语句。 + + Returns: + str: 拼写修正后的建议语句。 + """ spelling_suggestion = None reader = self.index.reader() corrector = reader.corrector(self.content_field_name) @@ -872,6 +1019,16 @@ class WhooshSearchBackend(BaseSearchBackend): class WhooshSearchQuery(BaseSearchQuery): def _convert_datetime(self, date): + """`xjj` + + 将日期时间对象转换为 Whoosh 可识别的字符串格式。 + + Args: + date: 日期或日期时间对象。 + + Returns: + str: 格式化后的日期时间字符串,格式为 `YYYYMMDDHHMMSS` 。 + """ if hasattr(date, 'hour'): return force_str(date.strftime('%Y%m%d%H%M%S')) else: @@ -903,6 +1060,20 @@ class WhooshSearchQuery(BaseSearchQuery): return ' '.join(cleaned_words) def build_query_fragment(self, field, filter_type, value): + """`xjj` + + 构建单个查询片段。 + + 根据字段名、过滤类型和值构建相应的查询表达式片段,用于组合成完整的搜索查询。 + + Args: + field (str): 要查询的字段名。 + filter_type (str): 过滤类型,如 `contains` 、 `exact` 、 `startswith` 等。 + value: 要查询的值,可以是字符串、列表或其他数据类型。 + + Returns: + str: 构建好的查询片段字符串。 + """ from haystack import connections query_frag = '' is_datetime = False @@ -1040,5 +1211,16 @@ class WhooshSearchQuery(BaseSearchQuery): class WhooshEngine(BaseEngine): + """`xjj` + + Whoosh 搜索引擎引擎类 + + 该类继承自 ``BaseEngine``,用于集成 Whoosh 搜索功能, + 包含搜索后端和查询处理的相关配置。 + + Attributes: + backend: 搜索后端类,设置为 `WhooshSearchBackend` + query: 查询类,设置为 `WhooshSearchQuery` + """ backend = WhooshSearchBackend query = WhooshSearchQuery