Squashed 'src/DjangoBlog/' changes from 1f969cc..408d19c

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
develop
dynastxu 3 months ago
parent 4e5377d894
commit 001bc85a81

@ -0,0 +1,7 @@
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5

@ -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):

@ -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

@ -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

@ -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

@ -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):

@ -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']

@ -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'

@ -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

@ -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)

@ -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('''

@ -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值

@ -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_typestoredboost 等属性的对象
: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

Loading…
Cancel
Save