diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8b978ac --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +*.DS_Store diff --git a/src/.DS_Store b/src/.DS_Store deleted file mode 100644 index f24a7dc..0000000 Binary files a/src/.DS_Store and /dev/null differ diff --git a/src/DjangoBlog-master/comments/admin.py b/src/DjangoBlog-master/comments/admin.py deleted file mode 100644 index 770ff73..0000000 --- a/src/DjangoBlog-master/comments/admin.py +++ /dev/null @@ -1,68 +0,0 @@ -from django.contrib import admin -from django.urls import reverse -from django.utils.html import format_html -from django.utils.translation import gettext_lazy as _ - -# 批量禁用评论的admin动作 -def disable_commentstatus(modeladmin, request, queryset): - queryset.update(is_enable=False) - -# 批量启用评论的admin动作 -def enable_commentstatus(modeladmin, request, queryset): - queryset.update(is_enable=True) - -# 设置admin动作在后台显示的名称 -disable_commentstatus.short_description = _('Disable comments') -enable_commentstatus.short_description = _('Enable comments') - -# 评论模型的后台管理配置 -class CommentAdmin(admin.ModelAdmin): - list_per_page = 20# 每页显示20条记录 - # 列表页显示的字段 - list_display = ( - 'id', - 'body',# 评论内容 - 'link_to_userinfo',# 自定义:链接到用户信息 - 'link_to_article',# 自定义:链接到文章 - 'is_enable',# 是否启用 - 'creation_time' # 创建时间 - ) - # 可点击跳转的字段 - list_display_links = ('id', 'body', 'is_enable') - - # 侧边栏过滤器 - list_filter = ('is_enable',) - - # 表单中排除的字段 - exclude = ('creation_time', 'last_modify_time') - - # 批量动作 - actions = [disable_commentstatus, enable_commentstatus] - - # 外键字段使用原始ID输入框(避免下拉列表性能问题) - raw_id_fields = ('author', 'article') - # 搜索字段 - search_fields = ('body',) - - # 自定义方法:显示用户链接 - def link_to_userinfo(self, obj): - # 获取用户模型的admin URL信息 - info = (obj.author._meta.app_label, obj.author._meta.model_name) - link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,)) - - # 显示用户昵称或邮箱 - return format_html( - u'%s' % - (link, obj.author.nickname if obj.author.nickname else obj.author.email)) - - #自定义方法:显示文章链接 - def link_to_article(self, obj): - # 获取文章模型的admin URL信息 - info = (obj.article._meta.app_label, obj.article._meta.model_name) - link = reverse('admin:%s_%s_change' % info, args=(obj.article.id,)) - return format_html( - u'%s' % (link, obj.article.title)) - - # 设置自定义方法的显示名称 - link_to_userinfo.short_description = _('User') - link_to_article.short_description = _('Article') diff --git a/src/DjangoBlog-master/comments/apps.py b/src/DjangoBlog-master/comments/apps.py deleted file mode 100644 index 72cc0d8..0000000 --- a/src/DjangoBlog-master/comments/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - -#评论应用的配置类 -class CommentsConfig(AppConfig): - name = 'comments'# 指定应用名称为'comments',对应安装的应用名 diff --git a/src/DjangoBlog-master/comments/forms.py b/src/DjangoBlog-master/comments/forms.py deleted file mode 100644 index ef27205..0000000 --- a/src/DjangoBlog-master/comments/forms.py +++ /dev/null @@ -1,16 +0,0 @@ -from django import forms # 导入Django表单模块 -from django.forms import ModelForm # 导入模型表单基类 - -from .models import Comment # 从当前应用导入Comment模型 - - -#评论表单类 -class CommentForm(ModelForm): - # 父评论ID(用于回复功能),隐藏输入框,非必填 - # 该字段不在模型中,仅用于表单处理嵌套评论 - parent_comment_id = forms.IntegerField( - widget=forms.HiddenInput, required=False) - - class Meta: - model = Comment # 指定关联的模型 - fields = ['body'] # 表单只包含body字段(评论内容) \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/migrations/0001_initial.py b/src/DjangoBlog-master/comments/migrations/0001_initial.py deleted file mode 100644 index 16348e5..0000000 --- a/src/DjangoBlog-master/comments/migrations/0001_initial.py +++ /dev/null @@ -1,49 +0,0 @@ -# Generated by Django 4.1.7 on 2023-03-02 07:14 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone - -# 数据库迁移类:创建评论模型 -class Migration(migrations.Migration): - - initial = True# 标记为comments应用的初始迁移 - # 依赖项:需要先执行blog的初始迁移和用户模型 - dependencies = [ - ('blog', '0001_initial'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - # 数据库操作列表 - operations = [ - # 创建Comment模型 - migrations.CreateModel( - name='Comment',# 模型名称 - fields=[ - # 主键ID,自增大数据类型 - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - # 评论正文,文本类型,最大长度300字符 - ('body', models.TextField(max_length=300, verbose_name='正文')), - # 创建时间,默认当前时间 - ('created_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='创建时间')), - # 最后修改时间,默认当前时间 - ('last_mod_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='修改时间')), - # 是否启用/显示,布尔类型,默认True - ('is_enable', models.BooleanField(default=True, verbose_name='是否显示')), - # 外键:关联文章,级联删除 - ('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='文章')), - # 外键:关联用户(评论作者),级联删除 - ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='作者')), - # 自关联外键:上级评论(用于嵌套评论),可为空 - ('parent_comment', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='comments.comment', verbose_name='上级评论')), - ], - # 模型元数据配置 - options={ - 'verbose_name': '评论',# 单数显示名称 - 'verbose_name_plural': '评论',# 复数显示名称 - 'ordering': ['-id'],# 按ID降序排列 - 'get_latest_by': 'id',# 获取最新记录的字段 - }, - ), - ] diff --git a/src/DjangoBlog-master/comments/migrations/0002_alter_comment_is_enable.py b/src/DjangoBlog-master/comments/migrations/0002_alter_comment_is_enable.py deleted file mode 100644 index 2a1a653..0000000 --- a/src/DjangoBlog-master/comments/migrations/0002_alter_comment_is_enable.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 4.1.7 on 2023-04-24 13:48 - -from django.db import migrations, models - -# 数据库迁移:修改评论模型字段默认值 -class Migration(migrations.Migration): - # 依赖项:需要先执行comments应用的0001_initial迁移 - dependencies = [ - ('comments', '0001_initial'), - ] - # 数据库操作列表 - operations = [ - # 修改is_enable字段:将默认值从True改为False(评论默认不显示) - migrations.AlterField( - model_name='comment',#模型名称 - name='is_enable',#字段名称 - field=models.BooleanField(default=False, verbose_name='是否显示'),# 新字段定义 - ), - ] diff --git a/src/DjangoBlog-master/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py b/src/DjangoBlog-master/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py deleted file mode 100644 index d10e316..0000000 --- a/src/DjangoBlog-master/comments/migrations/0003_alter_comment_options_remove_comment_created_time_and_more.py +++ /dev/null @@ -1,69 +0,0 @@ -# Generated by Django 4.2.5 on 2023-09-06 13:13 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone - -# 数据库迁移:评论模型字段重命名及国际化调整 -class Migration(migrations.Migration): - # 依赖项:需要user模型、blog的0005迁移及comments的0002迁移 - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('blog', '0005_alter_article_options_alter_category_options_and_more'), - ('comments', '0002_alter_comment_is_enable'), - ] - # 数据库操作列表 - operations = [ - # 修改模型选项:将verbose_name从中文改为英文 - migrations.AlterModelOptions( - name='comment', - options={'get_latest_by': 'id', 'ordering': ['-id'], 'verbose_name': 'comment', 'verbose_name_plural': 'comment'}, - ), - # 移除旧字段:删除created_time字段 - migrations.RemoveField( - model_name='comment', - name='created_time', - ), - # 移除旧字段:删除last_mod_time字段 - migrations.RemoveField( - model_name='comment', - name='last_mod_time', - ), - # 添加新字段:creation_time(替代created_time) - migrations.AddField( - model_name='comment', - name='creation_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='creation time'), - ), - # 添加新字段:last_modify_time(替代last_mod_time) - migrations.AddField( - model_name='comment', - name='last_modify_time', - field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='last modify time'), - ), - # 修改字段:article的verbose_name国际化 - migrations.AlterField( - model_name='comment', - name='article', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.article', verbose_name='article'), - ), - # 修改字段:author的verbose_name国际化 - migrations.AlterField( - model_name='comment', - name='author', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='author'), - ), - #修改字段:is_enable的verbose_name国际化 - migrations.AlterField( - model_name='comment', - name='is_enable', - field=models.BooleanField(default=False, verbose_name='enable'), - ), - # 修改字段:parent_comment的verbose_name国际化 - migrations.AlterField( - model_name='comment', - name='parent_comment', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='comments.comment', verbose_name='parent comment'), - ), - ] diff --git a/src/DjangoBlog-master/comments/models.py b/src/DjangoBlog-master/comments/models.py deleted file mode 100644 index 03b3565..0000000 --- a/src/DjangoBlog-master/comments/models.py +++ /dev/null @@ -1,52 +0,0 @@ -from django.conf import settings # 导入Django设置 -from django.db import models # 导入模型模块 -from django.utils.timezone import now # 导入当前时间函数 -from django.utils.translation import gettext_lazy as _ # 导入国际化翻译函数 - -from blog.models import Article # 导入文章模型 - - -# 评论模型 -class Comment(models.Model): - # 评论正文,最大长度300字符 - body = models.TextField('正文', max_length=300) - - # 评论创建时间,默认当前时间 - creation_time = models.DateTimeField(_('creation time'), default=now) - - # 最后修改时间,默认当前时间 - last_modify_time = models.DateTimeField(_('last modify time'), default=now) - - # 外键:评论作者,关联用户模型,级联删除 - author = models.ForeignKey( - settings.AUTH_USER_MODEL, - verbose_name=_('author'), - on_delete=models.CASCADE) - - # 外键:评论所属文章,关联文章模型,级联删除 - article = models.ForeignKey( - Article, - verbose_name=_('article'), - on_delete=models.CASCADE) - - # 自关联外键:父评论(用于实现嵌套评论),可为空 - parent_comment = models.ForeignKey( - 'self', - verbose_name=_('parent comment'), - blank=True, - null=True, - on_delete=models.CASCADE) - - #是否启用/显示评论,默认False(需审核) - is_enable = models.BooleanField(_('enable'), - default=False, blank=False, null=False) - - class Meta: - ordering = ['-id'] # 按ID降序排列 - verbose_name = _('comment') # 单数名称 - verbose_name_plural = verbose_name # 复数名称 - get_latest_by = 'id' # 获取最新评论的字段 - - # 返回评论正文作为字符串表示 - def __str__(self): - return self.body \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/templatetags/comments_tags.py b/src/DjangoBlog-master/comments/templatetags/comments_tags.py deleted file mode 100644 index 297bddf..0000000 --- a/src/DjangoBlog-master/comments/templatetags/comments_tags.py +++ /dev/null @@ -1,60 +0,0 @@ -from django import template - -register = template.Library() - - -@register.simple_tag -def parse_commenttree(commentlist, comment): - """ - 递归获取指定评论的所有子评论(包括嵌套的子评论) - - 用法: {% parse_commenttree article_comments comment as childcomments %} - - 参数: - commentlist: 所有评论的QuerySet(通常包含文章的所有评论) - comment: 当前要查询的父评论对象 - 返回: - list: 包含所有子评论的列表(按层级顺序排列) - """ - datas = [] - - def parse(c): - """ - 内部递归函数,用于遍历评论树 - - 参数: - c: 当前正在处理的父评论 - """ - # 获取当前评论的所有启用的子评论 - childs = commentlist.filter(parent_comment=c, is_enable=True) - - # 遍历子评论 - for child in childs: - # 将子评论添加到结果列表 - datas.append(child) - # 递归处理该子评论,查找它的子评论 - parse(child) - - # 从传入的评论开始递归解析 - parse(comment) - return datas - - -@register.inclusion_tag('comments/tags/comment_item.html') -def show_comment_item(comment, ischild): - """ - 渲染单个评论项的包含标签 - - 参数: - comment: 要显示的评论对象 - ischild: 布尔值,表示是否为子评论(True)还是父评论(False) - 返回: - dict: 传递给模板的数据字典 - """ - # 根据是否为子评论设置深度值(用于控制前端缩进或样式) - depth = 1 if ischild else 2 - - return { - 'comment_item': comment, # 评论对象本身 - 'depth': depth # 评论深度级别 - } \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/tests.py b/src/DjangoBlog-master/comments/tests.py deleted file mode 100644 index 61899c3..0000000 --- a/src/DjangoBlog-master/comments/tests.py +++ /dev/null @@ -1,85 +0,0 @@ -from django.test import Client, RequestFactory, TransactionTestCase -from django.urls import reverse - -from accounts.models import BlogUser -from blog.models import Category, Article -from comments.models import Comment -from comments.templatetags.comments_tags import * -from djangoblog.utils import get_max_articleid_commentid - - -# 评论功能测试类(使用数据库事务隔离) -class CommentsTest(TransactionTestCase): - def setUp(self): - # 初始化测试客户端和请求工厂 - self.client = Client() - self.factory = RequestFactory() - - # 设置评论需要审核才能显示 - from blog.models import BlogSettings - value = BlogSettings() - value.comment_need_review = True - value.save() - - # 创建超级用户用于登录测试 - self.user = BlogUser.objects.create_superuser( - email="liangliangyy1@gmail.com", - username="liangliangyy1", - password="liangliangyy1") - - def update_article_comment_status(self, article): - """批量启用文章下的所有评论""" - comments = article.comment_set.all() - for comment in comments: - comment.is_enable = True - comment.save() - - def test_validate_comment(self): - # 登录测试用户 - self.client.login(username='liangliangyy1', password='liangliangyy1') - - # 创建测试分类 - category = Category() - category.name = "categoryccc" - category.save() - - # 创建测试文章 - article = Article() - article.title = "nicetitleccc" - article.body = "nicecontentccc" - article.author = self.user - article.category = category - article.type = 'a' - article.status = 'p' - article.save() - - # 获取评论提交的URL - comment_url = reverse( - 'comments:postcomment', kwargs={ - 'article_id': article.id}) - - # 测试发布第一条评论 - response = self.client.post(comment_url, - { - 'body': '123ffffffffff' - }) - - self.assertEqual(response.status_code, 302) # 验证重定向 - - #断言:评论未审核时不显示 - article = Article.objects.get(pk=article.pk) - self.assertEqual(len(article.comment_list()), 0) - - # 审核通过所有评论 - self.update_article_comment_status(article) - self.assertEqual(len(article.comment_list()), 1) # 验证评论已显示 - - # 测试发布第二条评论 - response = self.client.post(comment_url, - { - 'body': '123ffffffffff', - }) - - self.assertEqual(response.status_code, 302) - - article \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/urls.py b/src/DjangoBlog-master/comments/urls.py deleted file mode 100644 index 2c00c6e..0000000 --- a/src/DjangoBlog-master/comments/urls.py +++ /dev/null @@ -1,15 +0,0 @@ -from django.urls import path - -from . import views - -# 设置应用命名空间,用于URL反向解析 -app_name = "comments" -# URL模式列表 -urlpatterns = [ -#文章评论提交接口 - # 捕获整数类型article_id参数,映射到CommentPostView视图 - path( - 'article//postcomment', - views.CommentPostView.as_view(), - name='postcomment'),# URL名称,模板中可通过{% url 'comments:postcomment' article_id %}调用 -] diff --git a/src/DjangoBlog-master/comments/utils.py b/src/DjangoBlog-master/comments/utils.py deleted file mode 100644 index 91629f0..0000000 --- a/src/DjangoBlog-master/comments/utils.py +++ /dev/null @@ -1,43 +0,0 @@ -import logging - -from django.utils.translation import gettext_lazy as _ - -from djangoblog.utils import get_current_site -from djangoblog.utils import send_email - -logger = logging.getLogger(__name__) # 获取当前模块的日志记录器 - - -def send_comment_email(comment): - """发送评论相关邮件的主函数""" - site = get_current_site().domain# 获取当前站点域名 - subject = _('Thanks for your comment')# 邮件标题(国际化) - article_url = f"https://{site}{comment.article.get_absolute_url()}" # 构建文章完整URL - - # 1. 给评论作者发送感谢邮件 - html_content = _("""

Thank you very much for your comments on this site

- You can visit %(article_title)s - to review your comments, - Thank you again! -
- If the link above cannot be opened, please copy this link to your browser. - %(article_url)s""") % {'article_url': article_url, 'article_title': comment.article.title} - tomail = comment.author.email# 收件人邮箱 - send_email([tomail], subject, html_content) - - # 2. 如果是回复评论,给被回复者发送通知邮件 - try: - if comment.parent_comment: - html_content = _("""Your comment on %(article_title)s
has - received a reply.
%(comment_body)s -
- go check it out! -
- If the link above cannot be opened, please copy this link to your browser. - %(article_url)s - """) % {'article_url': article_url, 'article_title': comment.article.title, - 'comment_body': comment.parent_comment.body} - tomail = comment.parent_comment.author.email # 被回复者邮箱 - send_email([tomail], subject, html_content) - except Exception as e: - logger.error(e)#记录邮件发送异常 diff --git a/src/DjangoBlog-master/comments/views.py b/src/DjangoBlog-master/comments/views.py deleted file mode 100644 index 1c6d6c7..0000000 --- a/src/DjangoBlog-master/comments/views.py +++ /dev/null @@ -1,72 +0,0 @@ -# Create your views here. -from django.core.exceptions import ValidationError -from django.http import HttpResponseRedirect -from django.shortcuts import get_object_or_404 -from django.utils.decorators import method_decorator -from django.views.decorators.csrf import csrf_protect -from django.views.generic.edit import FormView - -from accounts.models import BlogUser -from blog.models import Article -from .forms import CommentForm -from .models import Comment - -# 评论提交处理视图 -class CommentPostView(FormView): - form_class = CommentForm# 指定使用的表单类 - template_name = 'blog/article_detail.html'# 表单无效时渲染的模板 - - #为视图添加CSRF保护 - @method_decorator(csrf_protect) - def dispatch(self, *args, **kwargs): - return super(CommentPostView, self).dispatch(*args, **kwargs) - - # 处理GET请求:直接访问评论提交URL时重定向到文章详情页评论区 - def get(self, request, *args, **kwargs): - article_id = self.kwargs['article_id'] - article = get_object_or_404(Article, pk=article_id) - url = article.get_absolute_url() - return HttpResponseRedirect(url + "#comments") - - # 表单验证失败时的处理:重新渲染文章详情页并显示表单错误 - def form_invalid(self, form): - article_id = self.kwargs['article_id'] - article = get_object_or_404(Article, pk=article_id) - - return self.render_to_response({ - 'form': form, - 'article': article - }) - - # 表单验证成功后的处理:保存评论并发送通知邮件 - def form_valid(self, form): - """提交的数据验证合法后的逻辑""" - user = self.request.user# 获取当前登录用户 - author = BlogUser.objects.get(pk=user.pk)# 获取完整的用户信息 - article_id = self.kwargs['article_id'] - article = get_object_or_404(Article, pk=article_id) - - # 检查文章是否允许评论(评论状态或文章状态为关闭时抛出异常) - if article.comment_status == 'c' or article.status == 'c': - raise ValidationError("该文章评论已关闭.") - # 创建评论实例但不保存到数据库 - comment = form.save(False) - comment.article = article - # 获取站点配置,判断评论是否需要审核 - from djangoblog.utils import get_blog_setting - settings = get_blog_setting() - if not settings.comment_need_review: - comment.is_enable = True# 无需审核则直接启用评论 - comment.author = author# 设置评论作者 - - # 处理父评论ID:如果存在则设置为回复评论 - if form.cleaned_data['parent_comment_id']: - parent_comment = Comment.objects.get( - pk=form.cleaned_data['parent_comment_id']) - comment.parent_comment = parent_comment - # 保存评论到数据库 - comment.save(True) - # 重定向到文章详情页并定位到该评论锚点位置 - return HttpResponseRedirect( - "%s#div-comment-%d" % - (article.get_absolute_url(), comment.pk)) diff --git a/src/DjangoBlog-master/djangoblog/__init__.py b/src/DjangoBlog-master/djangoblog/__init__.py deleted file mode 100644 index 3279070..0000000 --- a/src/DjangoBlog-master/djangoblog/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# Zxy指定默认的应用配置类 -default_app_config = 'djangoblog.apps.DjangoblogAppConfig' diff --git a/src/DjangoBlog-master/djangoblog/admin_site.py b/src/DjangoBlog-master/djangoblog/admin_site.py deleted file mode 100644 index a0cb357..0000000 --- a/src/DjangoBlog-master/djangoblog/admin_site.py +++ /dev/null @@ -1,60 +0,0 @@ -# Zxy导入 Django 的 AdminSite 类和其他相关模块 -from django.contrib.admin import AdminSite -from django.contrib.admin.models import LogEntry -from django.contrib.sites.admin import SiteAdmin -from django.contrib.sites.models import Site - -# Zxy导入自定义模块和模型 -from accounts.admin import * -from blog.admin import * -from blog.models import * -from comments.admin import * -from comments.models import * -from djangoblog.logentryadmin import LogEntryAdmin -from oauth.admin import * -from oauth.models import * -from owntracks.admin import * -from owntracks.models import * -from servermanager.admin import * -from servermanager.models import * - -# Zxy定义自定义 AdminSite 类 -class DjangoBlogAdminSite(AdminSite): - # 自定义站点标题和头部 - site_header = 'djangoblog administration' - site_title = 'djangoblog site admin' - - # 初始化方法 - def __init__(self, name='admin'): - super().__init__(name) - - # 定义权限检查方法,仅允许超级用户访问 - def has_permission(self, request): - return request.user.is_superuser - -# Zxy创建自定义 AdminSite 实例 -admin_site = DjangoBlogAdminSite(name='admin') - -# Zxy注册模型到自定义 AdminSite -admin_site.register(Article, ArticleAdmin) -admin_site.register(Category, CategoryAdmin) -admin_site.register(Tag, TagAdmin) -admin_site.register(Links, LinksAdmin) -admin_site.register(SideBar, SideBarAdmin) -admin_site.register(BlogSettings, BlogSettingsAdmin) - -admin_site.register(commands, CommandsAdmin) -admin_site.register(EmailSendLog, EmailSendLogAdmin) - -admin_site.register(BlogUser, BlogUserAdmin) - -admin_site.register(Comment, CommentAdmin) - -admin_site.register(OAuthUser, OAuthUserAdmin) -admin_site.register(OAuthConfig, OAuthConfigAdmin) - -admin_site.register(OwnTrackLog, OwnTrackLogsAdmin) - -admin_site.register(Site, SiteAdmin) - -admin_site.register(LogEntry, LogEntryAdmin) \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/apps.py b/src/DjangoBlog-master/djangoblog/apps.py deleted file mode 100644 index f902735..0000000 --- a/src/DjangoBlog-master/djangoblog/apps.py +++ /dev/null @@ -1,16 +0,0 @@ -# Zxy导入Django的AppConfig模块 -from django.apps import AppConfig - -# Zxy定义Djangoblog应用的配置类 -class DjangoblogAppConfig(AppConfig): - # Zxy设置默认的自动字段类型 - default_auto_field = 'django.db.models.BigAutoField' - # Zxy应用名称 - name = 'djangoblog' - - # Zxy重写ready方法,用于初始化应用 - def ready(self): - super().ready() # Zxy调用父类的ready方法 - # Zxy导入并加载插件 - from .plugin_manage.loader import load_plugins - load_plugins() # Zxy加载插件 \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/blog_signals.py b/src/DjangoBlog-master/djangoblog/blog_signals.py deleted file mode 100644 index 9ba3376..0000000 --- a/src/DjangoBlog-master/djangoblog/blog_signals.py +++ /dev/null @@ -1,133 +0,0 @@ -# Zxy导入线程模块 -import _thread -# Zxy导入日志模块 -import logging - -# Zxy导入Django的信号模块 -import django.dispatch -# Zxy导入Django的配置模块 -from django.conf import settings -# Zxy导入Django的LogEntry模型 -from django.contrib.admin.models import LogEntry -# Zxy导入用户登录和登出信号 -from django.contrib.auth.signals import user_logged_in, user_logged_out -# Zxy导入邮件发送模块 -from django.core.mail import EmailMultiAlternatives -# Zxy导入模型保存信号 -from django.db.models.signals import post_save -# Zxy导入信号接收器 -from django.dispatch import receiver - -# Zxy导入评论模型 -from comments.models import Comment -# Zxy导入发送评论邮件的工具函数 -from comments.utils import send_comment_email -# Zxy导入爬虫通知工具 -from djangoblog.spider_notify import SpiderNotify -# Zxy导入缓存和缓存清理工具 -from djangoblog.utils import cache, expire_view_cache, delete_sidebar_cache, delete_view_cache -# Zxy导入获取当前站点的工具函数 -from djangoblog.utils import get_current_site -# Zxy导入OAuth用户模型 -from oauth.models import OAuthUser - -# Zxy获取日志记录器 -logger = logging.getLogger(__name__) - -# Zxy定义OAuth用户登录信号 -oauth_user_login_signal = django.dispatch.Signal(['id']) -# Zxy定义发送邮件信号 -send_email_signal = django.dispatch.Signal(['emailto', 'title', 'content']) - -# Zxy定义发送邮件信号的处理函数 -@receiver(send_email_signal) -def send_email_signal_handler(sender, **kwargs): - emailto = kwargs['emailto'] # Zxy收件人 - title = kwargs['title'] # Zxy邮件标题 - content = kwargs['content'] # Zxy邮件内容 - - msg = EmailMultiAlternatives( # Zxy创建邮件对象 - title, - content, - from_email=settings.DEFAULT_FROM_EMAIL, # Zxy发件人 - to=emailto # Zxy收件人 - ) - msg.content_subtype = "html" # Zxy设置邮件内容类型为HTML - - from servermanager.models import EmailSendLog # Zxy导入邮件发送日志模型 - log = EmailSendLog() # Zxy创建日志记录 - log.title = title - log.content = content - log.emailto = ','.join(emailto) # Zxy记录收件人 - - try: - result = msg.send() # Zxy发送邮件 - log.send_result = result > 0 # Zxy记录发送结果 - except Exception as e: - logger.error(f"失败邮箱号: {emailto}, {e}") # Zxy记录错误信息 - log.send_result = False - log.save() # Zxy保存日志 - -# Zxy定义OAuth用户登录信号的处理函数 -@receiver(oauth_user_login_signal) -def oauth_user_login_signal_handler(sender, **kwargs): - id = kwargs['id'] # Zxy获取用户ID - oauthuser = OAuthUser.objects.get(id=id) # Zxy获取OAuth用户 - site = get_current_site().domain # Zxy获取当前站点域名 - if oauthuser.picture and not oauthuser.picture.find(site) >= 0: # Zxy检查头像URL是否包含当前站点域名 - from djangoblog.utils import save_user_avatar # Zxy导入保存头像的工具函数 - oauthuser.picture = save_user_avatar(oauthuser.picture) # Zxy保存用户头像 - oauthuser.save() # Zxy保存用户信息 - - delete_sidebar_cache() # Zxy删除侧边栏缓存 - -# Zxy定义模型保存后的回调函数 -@receiver(post_save) -def model_post_save_callback(sender, instance, created, raw, using, update_fields, **kwargs): - clearcache = False # Zxy标记是否需要清理缓存 - if isinstance(instance, LogEntry): # Zxy如果是LogEntry模型,直接返回 - return - if 'get_full_url' in dir(instance): # Zxy检查是否有获取完整URL的方法 - is_update_views = update_fields == {'views'} # Zxy检查是否是更新浏览次数 - if not settings.TESTING and not is_update_views: # Zxy如果不是测试环境且不是更新浏览次数 - try: - notify_url = instance.get_full_url() # Zxy获取完整URL - SpiderNotify.baidu_notify([notify_url]) # Zxy通知百度爬虫 - except Exception as ex: - logger.error("notify spider", ex) # Zxy记录错误信息 - if not is_update_views: - clearcache = True # Zxy标记清理缓存 - - if isinstance(instance, Comment): # Zxy如果是评论模型 - if instance.is_enable: # Zxy检查评论是否启用 - path = instance.article.get_absolute_url() # Zxy获取文章的绝对URL - site = get_current_site().domain # Zxy获取当前站点域名 - if site.find(':') > 0: # Zxy去除端口号 - site = site[0:site.find(':')] - - expire_view_cache( # Zxy清理视图缓存 - path, - servername=site, - serverport=80, - key_prefix='blogdetail' - ) - if cache.get('seo_processor'): # Zxy清理SEO处理器缓存 - cache.delete('seo_processor') - comment_cache_key = 'article_comments_{id}'.format(id=instance.article.id) # Zxy生成评论缓存键 - cache.delete(comment_cache_key) # Zxy清理评论缓存 - delete_sidebar_cache() # Zxy清理侧边栏缓存 - delete_view_cache('article_comments', [str(instance.article.pk)]) # Zxy清理文章评论缓存 - - _thread.start_new_thread(send_comment_email, (instance,)) # Zxy异步发送评论邮件 - - if clearcache: # Zxy清理缓存 - cache.clear() - -# Zxy定义用户登录和登出的回调函数 -@receiver(user_logged_in) -@receiver(user_logged_out) -def user_auth_callback(sender, request, user, **kwargs): - if user and user.username: # Zxy检查用户是否登录 - logger.info(user) # Zxy记录用户信息 - delete_sidebar_cache() # Zxy清理侧边栏缓存 - # cache.clear() # Zxy清理所有缓存(暂时注释) \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/elasticsearch_backend.py b/src/DjangoBlog-master/djangoblog/elasticsearch_backend.py deleted file mode 100644 index 55a9b89..0000000 --- a/src/DjangoBlog-master/djangoblog/elasticsearch_backend.py +++ /dev/null @@ -1,196 +0,0 @@ -# Zxy导入Django的编码工具 -from django.utils.encoding import force_str -# Zxy导入Elasticsearch DSL的查询构造器 -from elasticsearch_dsl import Q -# Zxy导入Haystack的搜索后端模块 -from haystack.backends import BaseEngine, BaseSearchBackend, BaseSearchQuery, log_query -# Zxy导入Haystack的表单模块 -from haystack.forms import ModelSearchForm -# Zxy导入Haystack的搜索结果模型 -from haystack.models import SearchResult -# Zxy导入Haystack的日志工具 -from haystack.utils import log as logging - -# Zxy导入项目中的Elasticsearch文档和管理器 -from blog.documents import ArticleDocument, ArticleDocumentManager -# Zxy导入项目中的文章模型 -from blog.models import Article - -# Zxy获取日志记录器 -logger = logging.getLogger(__name__) - -# Zxy定义Elasticsearch搜索后端类 -class ElasticSearchBackend(BaseSearchBackend): - # Zxy初始化方法 - def __init__(self, connection_alias, **connection_options): - super(ElasticSearchBackend, self).__init__(connection_alias, **connection_options) - self.manager = ArticleDocumentManager() # Zxy初始化文档管理器 - self.include_spelling = True # Zxy启用拼写建议 - - # Zxy获取模型数据并转换为文档 - def _get_models(self, iterable): - models = iterable if iterable and iterable[0] else Article.objects.all() # Zxy获取模型数据 - docs = self.manager.convert_to_doc(models) # Zxy转换为Elasticsearch文档 - return docs - - # Zxy创建索引并重建数据 - def _create(self, models): - self.manager.create_index() # Zxy创建索引 - docs = self._get_models(models) # Zxy获取文档 - self.manager.rebuild(docs) # Zxy重建索引数据 - - # Zxy删除模型数据 - def _delete(self, models): - for m in models: - m.delete() # Zxy删除模型实例 - return True - - # Zxy重建索引数据 - def _rebuild(self, models): - models = models if models else Article.objects.all() # Zxy获取模型数据 - docs = self.manager.convert_to_doc(models) # Zxy转换为文档 - self.manager.update_docs(docs) # Zxy更新索引数据 - - # Zxy更新索引 - def update(self, index, iterable, commit=True): - models = self._get_models(iterable) # Zxy获取文档 - self.manager.update_docs(models) # Zxy更新索引 - - # Zxy删除文档 - def remove(self, obj_or_string): - models = self._get_models([obj_or_string]) # Zxy获取文档 - self._delete(models) # Zxy删除文档 - - # Zxy清空索引 - def clear(self, models=None, commit=True): - self.remove(None) # Zxy删除所有文档 - - # Zxy获取拼写建议 - @staticmethod - def get_suggestion(query: str) -> str: - """获取推荐词,如果没有找到则返回原搜索词""" - search = ArticleDocument.search() \ - .query("match", body=query) \ - .suggest('suggest_search', query, term={'field': 'body'}) \ - .execute() - - keywords = [] - for suggest in search.suggest.suggest_search: - if suggest["options"]: # Zxy检查是否有建议选项 - keywords.append(suggest["options"][0]["text"]) # Zxy添加建议词 - else: - keywords.append(suggest["text"]) # Zxy添加原搜索词 - - return ' '.join(keywords) # Zxy返回拼写建议 - - # Zxy执行搜索查询 - @log_query - def search(self, query_string, **kwargs): - logger.info('search query_string:' + query_string) # Zxy记录搜索查询 - - start_offset = kwargs.get('start_offset') # Zxy获取起始偏移量 - end_offset = kwargs.get('end_offset') # Zxy获取结束偏移量 - - # Zxy检查是否启用拼写建议 - if getattr(self, "is_suggest", None): - suggestion = self.get_suggestion(query_string) # Zxy获取拼写建议 - else: - suggestion = query_string # Zxy使用原搜索词 - - q = Q('bool', # Zxy构造布尔查询 - should=[Q('match', body=suggestion), Q('match', title=suggestion)], # Zxy匹配标题或正文 - minimum_should_match="70%") # Zxy至少匹配70% - - search = ArticleDocument.search() \ - .query('bool', filter=[q]) \ - .filter('term', status='p') \ - .filter('term', type='a') \ - .source(False)[start_offset: end_offset] # Zxy执行搜索 - - results = search.execute() # Zxy执行搜索查询 - hits = results['hits'].total # Zxy获取总匹配数 - raw_results = [] # Zxy初始化结果列表 - - for raw_result in results['hits']['hits']: # Zxy遍历搜索结果 - app_label = 'blog' # Zxy应用标签 - model_name = 'Article' # Zxy模型名称 - additional_fields = {} # Zxy额外字段 - - result_class = SearchResult # Zxy搜索结果类 - - result = result_class( # Zxy创建搜索结果对象 - app_label, - model_name, - raw_result['_id'], - raw_result['_score'], - **additional_fields - ) - raw_results.append(result) # Zxy添加到结果列表 - - facets = {} # Zxy初始化分面信息 - spelling_suggestion = None if query_string == suggestion else suggestion # Zxy拼写建议 - - return { - 'results': raw_results, # Zxy返回搜索结果 - 'hits': hits, # Zxy返回匹配数 - 'facets': facets, # Zxy返回分面信息 - 'spelling_suggestion': spelling_suggestion, # Zxy返回拼写建议 - } - -# Zxy定义Elasticsearch搜索查询类 -class ElasticSearchQuery(BaseSearchQuery): - # Zxy转换日期时间格式 - def _convert_datetime(self, date): - if hasattr(date, 'hour'): - return force_str(date.strftime('%Y%m%d%H%M%S')) - else: - return force_str(date.strftime('%Y%m%d000000')) - - # Zxy清理用户输入 - def clean(self, query_fragment): - words = query_fragment.split() - cleaned_words = [] - - for word in words: - if word in self.backend.RESERVED_WORDS: - word = word.replace(word, word.lower()) - - for char in self.backend.RESERVED_CHARACTERS: - if char in word: - word = "'%s'" % word - break - - cleaned_words.append(word) - - return ' '.join(cleaned_words) - - # Zxy构建查询片段 - def build_query_fragment(self, field, filter_type, value): - return value.query_string - - # Zxy获取结果数量 - def get_count(self): - results = self.get_results() - return len(results) if results else 0 - - # Zxy获取拼写建议 - def get_spelling_suggestion(self, preferred_query=None): - return self._spelling_suggestion - - # Zxy构建查询参数 - def build_params(self, spelling_query=None): - kwargs = super(ElasticSearchQuery, self).build_params(spelling_query=spelling_query) - return kwargs - -# Zxy定义Elasticsearch模型搜索表单 -class ElasticSearchModelSearchForm(ModelSearchForm): - # Zxy执行搜索 - def search(self): - self.searchqueryset.query.backend.is_suggest = self.data.get("is_suggest") != "no" # Zxy检查是否启用拼写建议 - sqs = super().search() # Zxy调用父类搜索方法 - return sqs - -# Zxy定义Elasticsearch搜索引擎 -class ElasticSearchEngine(BaseEngine): - backend = ElasticSearchBackend # Zxy后端类 - query = ElasticSearchQuery # Zxy查询类 \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/feeds.py b/src/DjangoBlog-master/djangoblog/feeds.py deleted file mode 100644 index c7afbe3..0000000 --- a/src/DjangoBlog-master/djangoblog/feeds.py +++ /dev/null @@ -1,54 +0,0 @@ -# Zxy导入Django的用户模型 -from django.contrib.auth import get_user_model -# Zxy导入Django的Feed视图 -from django.contrib.syndication.views import Feed -# Zxy导入Django的时区工具 -from django.utils import timezone -# Zxy导入RSS 2.0 Feed生成器 -from django.utils.feedgenerator import Rss201rev2Feed - -# Zxy导入项目中的文章模型 -from blog.models import Article -# Zxy导入Markdown工具 -from djangoblog.utils import CommonMarkdown - -# Zxy定义Django博客Feed -class DjangoBlogFeed(Feed): - feed_type = Rss201rev2Feed # Zxy使用RSS 2.0格式 - - description = '大巧无工,重剑无锋.' # ZxyFeed描述 - title = "且听风吟 大巧无工,重剑无锋." # ZxyFeed标题 - link = "/feed/" # ZxyFeed链接 - - # Zxy获取作者名称 - def author_name(self): - return get_user_model().objects.first().nickname - - # Zxy获取作者链接 - def author_link(self): - return get_user_model().objects.first().get_absolute_url() - - # Zxy获取Feed项 - def items(self): - return Article.objects.filter(type='a', status='p').order_by('-pub_time')[:5] # Zxy获取最近5篇已发布的文章 - - # Zxy获取Feed项标题 - def item_title(self, item): - return item.title - - # Zxy获取Feed项描述 - def item_description(self, item): - return CommonMarkdown.get_markdown(item.body) # Zxy将文章内容转换为Markdown格式 - - # Zxy获取Feed版权信息 - def feed_copyright(self): - now = timezone.now() - return "Copyright© {year} 且听风吟".format(year=now.year) # Zxy动态生成版权年份 - - # Zxy获取Feed项链接 - def item_link(self, item): - return item.get_absolute_url() - - # Zxy获取Feed项GUID - def item_guid(self, item): - return \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/logentryadmin.py b/src/DjangoBlog-master/djangoblog/logentryadmin.py deleted file mode 100644 index e1b4bd3..0000000 --- a/src/DjangoBlog-master/djangoblog/logentryadmin.py +++ /dev/null @@ -1,88 +0,0 @@ -# Zxy导入Django的admin模块 -from django.contrib import admin -# Zxy导入Django的LogEntry模型 -from django.contrib.admin.models import DELETION -# Zxy导入Django的内容类型模型 -from django.contrib.contenttypes.models import ContentType -# Zxy导入Django的URL工具 -from django.urls import reverse, NoReverseMatch -# Zxy导入Django的编码工具 -from django.utils.encoding import force_str -# Zxy导入Django的HTML工具 -from django.utils.html import escape -# Zxy导入Django的安全字符串工具 -from django.utils.safestring import mark_safe -# Zxy导入Django的翻译工具 -from django.utils.translation import gettext_lazy as _ - -# Zxy定义LogEntryAdmin类 -class LogEntryAdmin(admin.ModelAdmin): - list_filter = ['content_type'] # Zxy按内容类型过滤 - search_fields = ['object_repr', 'change_message'] # Zxy搜索字段 - - list_display_links = ['action_time', 'get_change_message'] # Zxy显示链接的字段 - list_display = ['action_time', 'user_link', 'content_type', 'object_link', 'get_change_message'] # Zxy显示字段 - - # Zxy检查是否有添加权限 - def has_add_permission(self, request): - return False - - # Zxy检查是否有修改权限 - def has_change_permission(self, request, obj=None): - return ( - request.user.is_superuser or - request.user.has_perm('admin.change_logentry') - ) and request.method != 'POST' - - # Zxy检查是否有删除权限 - def has_delete_permission(self, request, obj=None): - return False - - # Zxy获取对象链接 - def object_link(self, obj): - object_link = escape(obj.object_repr) # Zxy转义对象表示 - content_type = obj.content_type - - if obj.action_flag != DELETION and content_type is not None: # Zxy检查是否为删除操作 - try: - url = reverse( # Zxy生成反向URL - 'admin:{}_{}_change'.format(content_type.app_label, content_type.model), - args=[obj.object_id] - ) - object_link = '{}'.format(url, object_link) # Zxy生成链接 - except NoReverseMatch: - pass - return mark_safe(object_link) # Zxy标记为安全字符串 - - object_link.admin_order_field = 'object_repr' # Zxy排序字段 - object_link.short_description = _('object') # Zxy字段描述 - - # Zxy获取用户链接 - def user_link(self, obj): - content_type = ContentType.objects.get_for_model(type(obj.user)) # Zxy获取用户的内容类型 - user_link = escape(force_str(obj.user)) # Zxy转义用户表示 - - try: - url = reverse( # Zxy生成反向URL - 'admin:{}_{}_change'.format(content_type.app_label, content_type.model), - args=[obj.user.pk] - ) - user_link = '{}'.format(url, user_link) # Zxy生成链接 - except NoReverseMatch: - pass - return mark_safe(user_link) # Zxy标记为安全字符串 - - user_link.admin_order_field = 'user' # Zxy排序字段 - user_link.short_description = _('user') # Zxy字段描述 - - # Zxy获取查询集 - def get_queryset(self, request): - queryset = super(LogEntryAdmin, self).get_queryset(request) - return queryset.prefetch_related('content_type') # Zxy预加载内容类型 - - # Zxy获取操作 - def get_actions(self, request): - actions = super(LogEntryAdmin, self).get_actions(request) - if 'delete_selected' in actions: # Zxy移除删除操作 - del actions['delete_selected'] - return actions \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/plugin_manage/base_plugin.py b/src/DjangoBlog-master/djangoblog/plugin_manage/base_plugin.py deleted file mode 100644 index e87d7df..0000000 --- a/src/DjangoBlog-master/djangoblog/plugin_manage/base_plugin.py +++ /dev/null @@ -1,48 +0,0 @@ -# Zxy导入日志模块 -import logging - -# Zxy获取日志记录器 -logger = logging.getLogger(__name__) - -# Zxy定义基础插件类 -class BasePlugin: - # Zxy插件元数据 - PLUGIN_NAME = None # Zxy插件名称 - PLUGIN_DESCRIPTION = None # Zxy插件描述 - PLUGIN_VERSION = None # Zxy插件版本 - - # Zxy初始化插件 - def __init__(self): - # Zxy检查插件元数据是否已定义 - if not all([self.PLUGIN_NAME, self.PLUGIN_DESCRIPTION, self.PLUGIN_VERSION]): - raise ValueError("Plugin metadata (PLUGIN_NAME, PLUGIN_DESCRIPTION, PLUGIN_VERSION) must be defined.") - self.init_plugin() # Zxy调用初始化方法 - self.register_hooks() # Zxy注册钩子 - - # Zxy插件初始化逻辑 - def init_plugin(self): - """ - 插件初始化逻辑 - 子类可以重写此方法来实现特定的初始化操作 - """ - logger.info(f'{self.PLUGIN_NAME} initialized.') # Zxy记录插件初始化日志 - - # Zxy注册插件钩子 - def register_hooks(self): - """ - 注册插件钩子 - 子类可以重写此方法来注册特定的钩子 - """ - pass - - # Zxy获取插件信息 - def get_plugin_info(self): - """ - 获取插件信息 - :return: 包含插件元数据的字典 - """ - return { - 'name': self.PLUGIN_NAME, # Zxy插件名称 - 'description': self.PLUGIN_DESCRIPTION, # Zxy插件描述 - 'version': self.PLUGIN_VERSION # Zxy插件版本 - } \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/plugin_manage/hook_constants.py b/src/DjangoBlog-master/djangoblog/plugin_manage/hook_constants.py deleted file mode 100644 index cabaed1..0000000 --- a/src/DjangoBlog-master/djangoblog/plugin_manage/hook_constants.py +++ /dev/null @@ -1,8 +0,0 @@ -# Zxy定义文章相关的钩子常量 -ARTICLE_DETAIL_LOAD = 'article_detail_load' # Zxy文章详情加载时触发的钩子 -ARTICLE_CREATE = 'article_create' # Zxy文章创建时触发的钩子 -ARTICLE_UPDATE = 'article_update' # Zxy文章更新时触发的钩子 -ARTICLE_DELETE = 'article_delete' # Zxy文章删除时触发的钩子 - -# Zxy定义文章内容处理的钩子名称 -ARTICLE_CONTENT_HOOK_NAME = "the_content" # Zxy文章内容处理的钩子名称 \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/plugin_manage/hooks.py b/src/DjangoBlog-master/djangoblog/plugin_manage/hooks.py deleted file mode 100644 index 75317ab..0000000 --- a/src/DjangoBlog-master/djangoblog/plugin_manage/hooks.py +++ /dev/null @@ -1,47 +0,0 @@ -# Zxy导入日志模块 -import logging - -# Zxy获取日志记录器 -logger = logging.getLogger(__name__) - -# Zxy定义全局钩子字典 -_hooks = {} - -# Zxy注册钩子回调 -def register(hook_name: str, callback: callable): - """ - 注册一个钩子回调。 - """ - if hook_name not in _hooks: # Zxy检查钩子是否已存在 - _hooks[hook_name] = [] # Zxy初始化钩子列表 - _hooks[hook_name].append(callback) # Zxy添加回调到钩子列表 - logger.debug(f"Registered hook '{hook_name}' with callback '{callback.__name__}'") # Zxy记录注册日志 - -# Zxy执行Action Hook -def run_action(hook_name: str, *args, **kwargs): - """ - 执行一个 Action Hook。 - 它会按顺序执行所有注册到该钩子上的回调函数。 - """ - if hook_name in _hooks: # Zxy检查钩子是否存在 - logger.debug(f"Running action hook '{hook_name}'") # Zxy记录执行日志 - for callback in _hooks[hook_name]: # Zxy遍历钩子回调 - try: - callback(*args, **kwargs) # Zxy执行回调 - except Exception as e: - logger.error(f"Error running action hook '{hook_name}' callback '{callback.__name__}': {e}", exc_info=True) # Zxy记录错误日志 - -# Zxy执行Filter Hook -def apply_filters(hook_name: str, value, *args, **kwargs): - """ - 执行一个 Filter Hook。 - 它会把 value 依次传递给所有注册的回调函数进行处理。 - """ - if hook_name in _hooks: # Zxy检查钩子是否存在 - logger.debug(f"Applying filter hook '{hook_name}'") # Zxy记录执行日志 - for callback in _hooks[hook_name]: # Zxy遍历钩子回调 - try: - value = callback(value, *args, **kwargs) # Zxy调用回调处理值 - except Exception as e: - logger.error(f"Error applying filter hook '{hook_name}' callback '{callback.__name__}': {e}", exc_info=True) # Zxy记录错误日志 - return value # Zxy返回处理后的值 \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/plugin_manage/loader.py b/src/DjangoBlog-master/djangoblog/plugin_manage/loader.py deleted file mode 100644 index 9ee7372..0000000 --- a/src/DjangoBlog-master/djangoblog/plugin_manage/loader.py +++ /dev/null @@ -1,24 +0,0 @@ -# Zxy导入os模块,用于操作路径 -import os -# Zxy导入日志模块 -import logging -# Zxy导入Django的配置模块 -from django.conf import settings - -# Zxy获取日志记录器 -logger = logging.getLogger(__name__) - -# Zxy动态加载插件 -def load_plugins(): - """ - Dynamically loads and initializes plugins from the 'plugins' directory. - This function is intended to be called when the Django app registry is ready. - """ - for plugin_name in settings.ACTIVE_PLUGINS: # Zxy遍历激活的插件 - plugin_path = os.path.join(settings.PLUGINS_DIR, plugin_name) # Zxy获取插件路径 - if os.path.isdir(plugin_path) and os.path.exists(os.path.join(plugin_path, 'plugin.py')): # Zxy检查插件目录和文件 - try: - __import__(f'plugins.{plugin_name}.plugin') # Zxy动态导入插件模块 - logger.info(f"Successfully loaded plugin: {plugin_name}") # Zxy记录加载成功日志 - except ImportError as e: - logger.error(f"Failed to import plugin: {plugin_name}", exc_info=e) # Zxy记录加载失败日志 \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/settings.py b/src/DjangoBlog-master/djangoblog/settings.py deleted file mode 100644 index 943c243..0000000 --- a/src/DjangoBlog-master/djangoblog/settings.py +++ /dev/null @@ -1,344 +0,0 @@ -""" -Django settings for djangoblog project. - -Generated by 'django-admin startproject' using Django 1.10.2. - -For more information on this file, see -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/ -""" -import os -import sys -from pathlib import Path - -from django.utils.translation import gettext_lazy as _ - - -# Zxy环境变量布尔值转换函数:将环境变量转换为布尔值 -def env_to_bool(env, default): - str_val = os.environ.get(env) - return default if str_val is None else str_val == 'True' - - -# Zxy项目根目录:获取项目的绝对路径 -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! -SECRET_KEY = os.environ.get( - 'DJANGO_SECRET_KEY') or 'n9ceqv38)#&mwuat@(mjb_p%em$e8$qyr#fw9ot!=ba6lijx-6' -# Zxy调试模式:从环境变量获取或默认为 True -DEBUG = env_to_bool('DJANGO_DEBUG', True) -# Zxy测试模式检测:检查是否运行测试 -TESTING = len(sys.argv) > 1 and sys.argv[1] == 'test' - -# Zxy允许的主机名:允许访问的域名列表 -ALLOWED_HOSTS = ['*', '127.0.0.1', 'example.com'] -# ZxyCSRF 可信来源:允许的 CSRF 来源 -CSRF_TRUSTED_ORIGINS = ['http://example.com'] -# Application definition - -# Zxy应用程序定义:定义项目中使用的 Django 应用程序 -INSTALLED_APPS = [ - # 默认的 Django 应用程序 - 'django.contrib.admin.apps.SimpleAdminConfig', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django.contrib.sites', - 'django.contrib.sitemaps', - # 第三方和自定义应用程序 - 'mdeditor', - 'haystack', - 'blog', - 'accounts', - 'comments', - 'oauth', - 'servermanager', - 'owntracks', - 'compressor', - 'djangoblog' -] -# Zxy中间件配置:定义请求处理的中间件 -MIDDLEWARE = [ - - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.locale.LocaleMiddleware', - 'django.middleware.gzip.GZipMiddleware', - # 'django.middleware.cache.UpdateCacheMiddleware', - 'django.middleware.common.CommonMiddleware', - # 'django.middleware.cache.FetchFromCacheMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'django.middleware.http.ConditionalGetMiddleware', - 'blog.middleware.OnlineMiddleware' -] -# ZxyURL 配置:定义项目的 URL 配置文件 -ROOT_URLCONF = 'djangoblog.urls' -# Zxy模板配置:配置 Django 模板引擎 -TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [os.path.join(BASE_DIR, 'templates')], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - 'blog.context_processors.seo_processor' - ], - }, - }, -] -# ZxyWSGI 应用程序:定义 WSGI 应用程序入口 -WSGI_APPLICATION = 'djangoblog.wsgi.application' - -# Database -# https://docs.djangoproject.com/en/1.10/ref/settings/#databases - -# Zxy数据库配置:配置数据库连接 -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': 'djangoblog', - 'USER': 'root', - 'PASSWORD': '123456', - 'HOST': '127.0.0.1', - 'PORT': 3306, - } -} - -# Password validation -# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators -# Zxy密码验证器:配置密码验证规则 -AUTH_PASSWORD_VALIDATORS = [ - { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', - }, -] -# Zxy语言和时区配置:配置语言和时区 -LANGUAGES = ( - ('en', _('English')), - ('zh-hans', _('Simplified Chinese')), - ('zh-hant', _('Traditional Chinese')), -) -LOCALE_PATHS = ( - os.path.join(BASE_DIR, 'locale'), -) - -LANGUAGE_CODE = 'zh-hans' - -TIME_ZONE = 'Asia/Shanghai' - -USE_I18N = True - -USE_L10N = True - -USE_TZ = False - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/1.10/howto/static-files/ - -# ZxyHaystack 搜索配置:配置 Haystack 搜索引擎 -HAYSTACK_CONNECTIONS = { - 'default': { - 'ENGINE': 'djangoblog.whoosh_cn_backend.WhooshEngine', - 'PATH': os.path.join(os.path.dirname(__file__), 'whoosh_index'), - }, -} -# Automatically update searching index -HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor' -# Allow user login with username and password -AUTHENTICATION_BACKENDS = [ - 'accounts.user_login_backend.EmailOrUsernameModelBackend'] -# Zxy静态文件配置:配置静态文件路径 -STATIC_ROOT = os.path.join(BASE_DIR, 'collectedstatic') - -STATIC_URL = '/static/' -STATICFILES = os.path.join(BASE_DIR, 'static') -# Zxy用户模型配置:定义自定义用户模型和登录 URL -AUTH_USER_MODEL = 'accounts.BlogUser' -LOGIN_URL = '/login/' - -TIME_FORMAT = '%Y-%m-%d %H:%M:%S' -DATE_TIME_FORMAT = '%Y-%m-%d' - -# bootstrap color styles -BOOTSTRAP_COLOR_TYPES = [ - 'default', 'primary', 'success', 'info', 'warning', 'danger' -] - -# paginate -PAGINATE_BY = 10 -# http cache timeout -CACHE_CONTROL_MAX_AGE = 2592000 -# cache setting -# Zxy缓存配置:配置缓存后端 -CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', - 'TIMEOUT': 10800, - 'LOCATION': 'unique-snowflake', - } -} -# 使用redis作为缓存 -if os.environ.get("DJANGO_REDIS_URL"): - CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.redis.RedisCache', - 'LOCATION': f'redis://{os.environ.get("DJANGO_REDIS_URL")}', - } - } -# Zxy站点 ID:定义当前站点的 ID -SITE_ID = 1 -BAIDU_NOTIFY_URL = os.environ.get('DJANGO_BAIDU_NOTIFY_URL') \ - or 'http://data.zz.baidu.com/urls?site=https://www.lylinux.net&token=1uAOGrMsUm5syDGn' - -# Email: -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) -EMAIL_HOST = os.environ.get('DJANGO_EMAIL_HOST') or 'smtp.mxhichina.com' -EMAIL_PORT = int(os.environ.get('DJANGO_EMAIL_PORT') or 465) -EMAIL_HOST_USER = os.environ.get('DJANGO_EMAIL_USER') -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 -ADMINS = [('admin', os.environ.get('DJANGO_ADMIN_EMAIL') or 'admin@admin.com')] -# WX ADMIN password(Two times md5) -WXADMIN = os.environ.get( - 'DJANGO_WXADMIN_PASSWORD') or '995F03AC401D6CABABAEF756FC4D43C7' - -LOG_PATH = os.path.join(BASE_DIR, 'logs') -if not os.path.exists(LOG_PATH): - os.makedirs(LOG_PATH, exist_ok=True) -# Zxy日志配置:配置日志记录 -LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'root': { - 'level': 'INFO', - 'handlers': ['console', 'log_file'], - }, - 'formatters': { - 'verbose': { - 'format': '[%(asctime)s] %(levelname)s [%(name)s.%(funcName)s:%(lineno)d %(module)s] %(message)s', - } - }, - 'filters': { - 'require_debug_false': { - '()': 'django.utils.log.RequireDebugFalse', - }, - 'require_debug_true': { - '()': 'django.utils.log.RequireDebugTrue', - }, - }, - 'handlers': { - 'log_file': { - 'level': 'INFO', - 'class': 'logging.handlers.TimedRotatingFileHandler', - 'filename': os.path.join(LOG_PATH, 'djangoblog.log'), - 'when': 'D', - 'formatter': 'verbose', - 'interval': 1, - 'delay': True, - 'backupCount': 5, - 'encoding': 'utf-8' - }, - 'console': { - 'level': 'DEBUG', - 'filters': ['require_debug_true'], - 'class': 'logging.StreamHandler', - 'formatter': 'verbose' - }, - 'null': { - 'class': 'logging.NullHandler', - }, - 'mail_admins': { - 'level': 'ERROR', - 'filters': ['require_debug_false'], - 'class': 'django.utils.log.AdminEmailHandler' - } - }, - 'loggers': { - 'djangoblog': { - 'handlers': ['log_file', 'console'], - 'level': 'INFO', - 'propagate': True, - }, - 'django.request': { - 'handlers': ['mail_admins'], - 'level': 'ERROR', - 'propagate': False, - } - } -} -# Zxy压缩配置:配置静态文件压缩 -STATICFILES_FINDERS = ( - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', - # other - 'compressor.finders.CompressorFinder', -) -COMPRESS_ENABLED = True -# COMPRESS_OFFLINE = True - - -COMPRESS_CSS_FILTERS = [ - # creates absolute urls from relative ones - 'compressor.filters.css_default.CssAbsoluteFilter', - # css minimizer - 'compressor.filters.cssmin.CSSMinFilter' -] -COMPRESS_JS_FILTERS = [ - 'compressor.filters.jsmin.JSMinFilter' -] -# Zxy媒体文件配置:配置媒体文件路径 -MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads') -MEDIA_URL = '/media/' -X_FRAME_OPTIONS = 'SAMEORIGIN' - -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' -# ZxyElasticsearch 配置:配置 Elasticsearch 搜索引擎 -if os.environ.get('DJANGO_ELASTICSEARCH_HOST'): - ELASTICSEARCH_DSL = { - 'default': { - 'hosts': os.environ.get('DJANGO_ELASTICSEARCH_HOST') - }, - } - HAYSTACK_CONNECTIONS = { - 'default': { - 'ENGINE': 'djangoblog.elasticsearch_backend.ElasticSearchEngine', - }, - } - -# Zxy插件系统配置:定义插件目录和激活的插件 -PLUGINS_DIR = BASE_DIR / 'plugins' -ACTIVE_PLUGINS = [ - 'article_copyright', - 'reading_time', - 'external_links', - 'view_count', - 'seo_optimizer' -] \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/sitemap.py b/src/DjangoBlog-master/djangoblog/sitemap.py deleted file mode 100644 index 986f1ef..0000000 --- a/src/DjangoBlog-master/djangoblog/sitemap.py +++ /dev/null @@ -1,70 +0,0 @@ -# Zxy导入 Django 的 Sitemap 模块和其他相关模块 -from django.contrib.sitemaps import Sitemap -from django.urls import reverse - -from blog.models import Article, Category, Tag - -# Zxy定义静态页面的 Sitemap -class StaticViewSitemap(Sitemap): - priority = 0.5 # Zxy设置优先级 - changefreq = 'daily' # Zxy设置更新频率 - - def items(self): - # Zxy返回静态页面的 URL 名称 - return ['blog:index', ] - - def location(self, item): - # Zxy通过 URL 名称生成完整的 URL - return reverse(item) - -# Zxy定义文章的 Sitemap -class ArticleSiteMap(Sitemap): - changefreq = "monthly" # Zxy文章更新频率为每月 - priority = "0.6" # Zxy文章优先级 - - def items(self): - # Zxy返回所有已发布的文章 - return Article.objects.filter(status='p') - - def lastmod(self, obj): - # Zxy返回文章的最后修改时间 - return obj.last_modify_time - -# Zxy定义分类的 Sitemap -class CategorySiteMap(Sitemap): - changefreq = "Weekly" # Zxy分类更新频率为每周 - priority = "0.6" # Zxy分类优先级 - - def items(self): - # Zxy返回所有分类 - return Category.objects.all() - - def lastmod(self, obj): - # Zxy返回分类的最后修改时间 - return obj.last_modify_time - -# Zxy定义标签的 Sitemap -class TagSiteMap(Sitemap): - changefreq = "Weekly" # Zxy标签更新频率为每周 - priority = "0.3" # Zxy标签优先级 - - def items(self): - # Zxy返回所有标签 - return Tag.objects.all() - - def lastmod(self, obj): - # Zxy返回标签的最后修改时间 - return obj.last_modify_time - -# Zxy定义用户的 Sitemap -class UserSiteMap(Sitemap): - changefreq = "Weekly" # Zxy用户更新频率为每周 - priority = "0.3" # Zxy用户优先级 - - def items(self): - # Zxy返回所有用户的作者列表(去重) - return list(set(map(lambda x: x.author, Article.objects.all()))) - - def lastmod(self, obj): - # Zxy返回用户的注册时间 - return obj.date_joined \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/spider_notify.py b/src/DjangoBlog-master/djangoblog/spider_notify.py deleted file mode 100644 index 20fed14..0000000 --- a/src/DjangoBlog-master/djangoblog/spider_notify.py +++ /dev/null @@ -1,26 +0,0 @@ -# Zxy导入日志模块 -import logging - -# Zxy导入 requests 模块用于发送 HTTP 请求 -import requests -from django.conf import settings - -# Zxy获取日志记录器 -logger = logging.getLogger(__name__) - -# Zxy定义爬虫通知类 -class SpiderNotify(): - @staticmethod - def baidu_notify(urls): - # Zxy向百度站长平台发送 URL 提交请求 - try: - data = '\n'.join(urls) # Zxy将 URL 列表拼接为字符串 - result = requests.post(settings.BAIDU_NOTIFY_URL, data=data) # Zxy发送 POST 请求 - logger.info(result.text) # Zxy记录响应内容 - except Exception as e: - logger.error(e) # Zxy记录异常信息 - - @staticmethod - def notify(url): - # Zxy调用百度通知方法 - SpiderNotify.baidu_notify(url) \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/tests.py b/src/DjangoBlog-master/djangoblog/tests.py deleted file mode 100644 index 83af4c1..0000000 --- a/src/DjangoBlog-master/djangoblog/tests.py +++ /dev/null @@ -1,38 +0,0 @@ -# Zxy导入 Django 的测试模块 -from django.test import TestCase - -# Zxy导入项目中的工具函数 -from djangoblog.utils import * - -# Zxy定义测试类 -class DjangoBlogTest(TestCase): - def setUp(self): - # Zxy测试初始化方法(暂无内容) - pass - - def test_utils(self): - # Zxy测试工具函数 - md5 = get_sha256('test') # Zxy测试 SHA256 函数 - self.assertIsNotNone(md5) # Zxy断言返回值不为空 - - c = CommonMarkdown.get_markdown(''' # Zxy测试 Markdown 转换 - # Title1 - - ```python - import os - ``` - - [url](https://www.lylinux.net/) - - [ddd](http://www.baidu.com) - - - ''') - self.assertIsNotNone(c) # Zxy断言返回值不为空 - - d = { - 'd': 'key1', - 'd2': 'key2' - } - data = parse_dict_to_url(d) # Zxy测试字典转 URL 函数 - self.assertIsNotNone(data) # Zxy断言返回值不为空 \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/urls.py b/src/DjangoBlog-master/djangoblog/urls.py deleted file mode 100644 index f158317..0000000 --- a/src/DjangoBlog-master/djangoblog/urls.py +++ /dev/null @@ -1,68 +0,0 @@ -# Zxy定义项目的 URL 配置 -"""djangoblog URL Configuration - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/1.10/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') -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')) -""" -from django.conf import settings -from django.conf.urls.i18n import i18n_patterns -from django.conf.urls.static import static -from django.contrib.sitemaps.views import sitemap -from django.urls import path, include -from django.urls import re_path -from haystack.views import search_view_factory - -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 - -# Zxy定义站点地图 -sitemaps = { - 'blog': ArticleSiteMap, - 'Category': CategorySiteMap, - 'Tag': TagSiteMap, - 'User': UserSiteMap, - 'static': StaticViewSitemap -} - -# Zxy定义 404、500 和 403 错误页面的视图 -handler404 = 'blog.views.page_not_found_view' -handler500 = 'blog.views.server_error_view' -handle403 = 'blog.views.permission_denied_view' - -# Zxy定义 URL 模式 -urlpatterns = [ - path('i18n/', include('django.conf.urls.i18n')), # Zxy国际化语言切换 -] -urlpatterns += i18n_patterns( - re_path(r'^admin/', admin_site.urls), # Zxy自定义 Admin 站点 - re_path(r'', include('blog.urls', namespace='blog')), # Zxy博客应用的 URL - re_path(r'mdeditor/', include('mdeditor.urls')), # Zxy Markdown 编辑器的 URL - re_path(r'', include('comments.urls', namespace='comment')), # Zxy评论应用的 URL - re_path(r'', include('accounts.urls', namespace='account')), # Zxy用户账户的 URL - re_path(r'', include('oauth.urls', namespace='oauth')), # Zxy OAuth 应用的 URL - re_path(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps}, # Zxy站点地图 - name='django.contrib.sitemaps.views.sitemap'), - re_path(r'^feed/$', DjangoBlogFeed()), # Zxy RSS 订阅 - re_path(r'^rss/$', DjangoBlogFeed()), # Zxy RSS 订阅 - re_path('^search', search_view_factory(view_class=EsSearchView, form_class=ElasticSearchModelSearchForm), # Zxy搜索功能 - name='search'), - re_path(r'', include('servermanager.urls', namespace='servermanager')), # Zxy服务器管理应用的 URL - re_path(r'', include('owntracks.urls', namespace='owntracks')), # Zxy位置跟踪应用的 URL - prefix_default_language=False -) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) # Zxy静态文件的 URL -if settings.DEBUG: - urlpatterns += static(settings.MEDIA_URL, # Zxy媒体文件的 URL - document_root=settings.MEDIA_ROOT) \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/utils.py b/src/DjangoBlog-master/djangoblog/utils.py deleted file mode 100644 index 90cfa42..0000000 --- a/src/DjangoBlog-master/djangoblog/utils.py +++ /dev/null @@ -1,198 +0,0 @@ -#!/usr/bin/env python -# encoding: utf-8 - -# Zxy导入日志模块 -import logging -import os -import random -import string -import uuid -from hashlib import sha256 - -import bleach -import markdown -import requests -from django.conf import settings -from django.contrib.sites.models import Site -from django.core.cache import cache -from django.templatetags.static import static - -# Zxy获取日志记录器 -logger = logging.getLogger(__name__) - -# Zxy获取文章和评论的最大 ID -def get_max_articleid_commentid(): - from blog.models import Article - from comments.models import Comment - return (Article.objects.latest().pk, Comment.objects.latest().pk) - -# Zxy计算字符串的 SHA256 值 -def get_sha256(str): - m = sha256(str.encode('utf-8')) - return m.hexdigest() - -# Zxy缓存装饰器,用于缓存函数的返回值 -def cache_decorator(expiration=3 * 60): - def wrapper(func): - def news(*args, **kwargs): - try: - view = args[0] - key = view.get_cache_key() - except: - key = None - if not key: - unique_str = repr((func, args, kwargs)) - m = sha256(unique_str.encode('utf-8')) - key = m.hexdigest() - value = cache.get(key) - if value is not None: - if str(value) == '__default_cache_value__': - return None - else: - return value - else: - logger.debug('cache_decorator set cache:%s key:%s' % (func.__name__, key)) - value = func(*args, **kwargs) - if value is None: - cache.set(key, '__default_cache_value__', expiration) - else: - cache.set(key, value, expiration) - return value - return news - return wrapper - -# Zxy刷新视图缓存 -def expire_view_cache(path, servername, serverport, key_prefix=None): - from django.http import HttpRequest - from django.utils.cache import get_cache_key - - request = HttpRequest() - request.META = {'SERVER_NAME': servername, 'SERVER_PORT': serverport} - request.path = path - - key = get_cache_key(request, key_prefix=key_prefix, cache=cache) - if key: - logger.info('expire_view_cache:get key:{path}'.format(path=path)) - if cache.get(key): - cache.delete(key) - return True - return False - -# Zxy获取当前站点 -@cache_decorator() -def get_current_site(): - site = Site.objects.get_current() - return site - -# Zxy定义 CommonMarkdown 类,用于处理 Markdown 转换 -class CommonMarkdown: - @staticmethod - def _convert_markdown(value): - md = markdown.Markdown(extensions=['extra', 'codehilite', 'toc', 'tables']) - body = md.convert(value) - toc = md.toc - return body, toc - - @staticmethod - def get_markdown_with_toc(value): - body, toc = CommonMarkdown._convert_markdown(value) - return body, toc - - @staticmethod - def get_markdown(value): - body, toc = CommonMarkdown._convert_markdown(value) - return body - -# Zxy发送邮件 -def send_email(emailto, title, content): - from djangoblog.blog_signals import send_email_signal - send_email_signal.send(send_email.__class__, emailto=emailto, title=title, content=content) - -# Zxy生成随机验证码 -def generate_code() -> str: - return ''.join(random.sample(string.digits, 6)) - -# Zxy将字典转换为 URL 参数 -def parse_dict_to_url(dict): - from urllib.parse import quote - url = '&'.join(['{}={}'.format(quote(k, safe='/'), quote(v, safe='/')) for k, v in dict.items()]) - return url - -# Zxy获取博客设置 -def get_blog_setting(): - value = cache.get('get_blog_setting') - if value: - return value - else: - from blog.models import BlogSettings - if not BlogSettings.objects.count(): - setting = BlogSettings() - setting.site_name = 'djangoblog' - setting.site_description = '基于Django的博客系统' - setting.site_seo_description = '基于Django的博客系统' - setting.site_keywords = 'Django,Python' - setting.article_sub_length = 300 - setting.sidebar_article_count = 10 - setting.sidebar_comment_count = 5 - setting.show_google_adsense = False - setting.open_site_comment = True - setting.analytics_code = '' - setting.beian_code = '' - setting.show_gongan_code = False - setting.comment_need_review = False - setting.save() - value = BlogSettings.objects.first() - logger.info('set cache get_blog_setting') - cache.set('get_blog_setting', value) - return value - -# Zxy保存用户头像 -def save_user_avatar(url): - logger.info(url) - try: - basedir = os.path.join(settings.STATICFILES, 'avatar') - rsp = requests.get(url, timeout=2) - if rsp.status_code == 200: - if not os.path.exists(basedir): - os.makedirs(basedir) - image_extensions = ['.jpg', '.png', 'jpeg', '.gif'] - isimage = len([i for i in image_extensions if url.endswith(i)]) > 0 - ext = os.path.splitext(url)[1] if isimage else '.jpg' - 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) - return static('avatar/' + save_filename) - except Exception as e: - logger.error(e) - return static('blog/img/avatar.png') - -# Zxy删除侧边栏缓存 -def delete_sidebar_cache(): - from blog.models import LinkShowType - keys = ["sidebar" + x for x in LinkShowType.values] - for k in keys: - logger.info('delete sidebar key:' + k) - cache.delete(k) - -# Zxy删除视图缓存 -def delete_view_cache(prefix, keys): - from django.core.cache.utils import make_template_fragment_key - key = make_template_fragment_key(prefix, keys) - cache.delete(key) - -# Zxy获取资源 URL -def get_resource_url(): - if settings.STATIC_URL: - return settings.STATIC_URL - else: - site = get_current_site() - return 'http://' + site.domain + '/static/' - -# Zxy定义允许的 HTML 标签和属性 -ALLOWED_TAGS = ['a', 'abbr', 'acronym', 'b', 'blockquote', 'code', 'em', 'i', 'li', 'ol', 'pre', 'strong', 'ul', 'h1', 'h2', 'p'] -ALLOWED_ATTRIBUTES = {'a': ['href', 'title'], 'abbr': ['title'], 'acronym': ['title']} - -# Zxy清理 HTML 内容 -def sanitize_html(html): - return bleach.clean(html, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES) \ 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 deleted file mode 100644 index 994247c..0000000 --- a/src/DjangoBlog-master/djangoblog/whoosh_cn_backend.py +++ /dev/null @@ -1,1256 +0,0 @@ -# Zxy 导入未来支持模块,确保代码在 Python 2 和 3 中的兼容性 -# encoding: utf-8 -from __future__ import absolute_import, division, print_function, unicode_literals - -# Zxy 导入标准库模块 -import json -import os -import re -import shutil -import threading -import warnings - -# Zxy 导入第三方库 -import six -from django.conf import settings -from django.core.exceptions import ImproperlyConfigured -from datetime import datetime -from django.utils.encoding import force_str -from haystack.backends import BaseEngine, BaseSearchBackend, BaseSearchQuery, EmptyResults, log_query -from haystack.constants import DJANGO_CT, DJANGO_ID, ID -from haystack.exceptions import MissingDependency, SearchBackendError, SkipDocument -from haystack.inputs import Clean, Exact, PythonData, Raw -from haystack.models import SearchResult -from haystack.utils import get_identifier, get_model_ct -from haystack.utils import log as logging -from haystack.utils.app_loading import haystack_get_model -from jieba.analyse import ChineseAnalyzer -from whoosh import index -from whoosh.analysis import StemmingAnalyzer -from whoosh.fields import BOOLEAN, DATETIME, IDLIST, KEYWORD, NGRAM, NGRAMWORDS, NUMERIC, Schema, TEXT -from whoosh.fields import ID as WHOOSH_ID -from whoosh.filedb.filestore import FileStorage, RamStorage -from whoosh.highlight import ContextFragmenter, HtmlFormatter -from whoosh.highlight import highlight as whoosh_highlight -from whoosh.qparser import QueryParser -from whoosh.searching import ResultsPage -from whoosh.writing import AsyncWriter - -# Zxy 尝试导入 whoosh 库,如果失败则抛出依赖缺失异常 -try: - import whoosh -except ImportError: - raise MissingDependency( - "The 'whoosh' backend requires the installation of 'Whoosh'. Please refer to the documentation.") - -# Zxy 检查 whoosh 版本是否满足最低要求 -if not hasattr(whoosh, '__version__') or whoosh.__version__ < (2, 5, 0): - raise MissingDependency( - "The 'whoosh' backend requires version 2.5.0 or greater.") - -# Zxy 定义一个正则表达式,用于匹配 ISO 8601 格式的日期时间字符串 -DATETIME_REGEX = re.compile( - '^(?P\d{4})-(?P\d{2})-(?P\d{2})T(?P\d{2}):(?P\d{2}):(?P\d{2})(\.\d{3,6}Z?)?$') -# Zxy 创建一个线程局部存储对象,用于在每个线程中存储独立的数据 -LOCALS = threading.local() -# Zxy 初始化线程局部存储中的 RAM_STORE 为 None,用于存储内存索引 -LOCALS.RAM_STORE = None - - -# Zxy 定义一个自定义的 HTML 格式化器,继承自 Whoosh 的 HtmlFormatter -class WhooshHtmlFormatter(HtmlFormatter): - """ - This is a HtmlFormatter simpler than the whoosh.HtmlFormatter. - We use it to have consistent results across backends. Specifically, - Solr, Xapian and Elasticsearch are using this formatting. - """ - # Zxy 定义高亮显示的 HTML 模板 - template = '<%(tag)s>%(t)s' - - -# Zxy 定义 Whoosh 搜索后端类,继承自 Haystack 的 BaseSearchBackend -class WhooshSearchBackend(BaseSearchBackend): - # Zxy 定义 Whoosh 保留的关键字列表,这些关键字在查询中有特殊含义 - RESERVED_WORDS = ( - 'AND', - 'NOT', - 'OR', - 'TO', - ) - - # Zxy 定义 Whoosh 保留的字符列表,这些字符在查询中有特殊含义 - # The '\\' must come first, so as not to overwrite the other slash - # replacements. - RESERVED_CHARACTERS = ( - '\\', '+', '-', '&&', '||', '!', '(', ')', '{', '}', - '[', ']', '^', '"', '~', '*', '?', ':', '.', - ) - - # Zxy 初始化方法,设置连接别名和连接选项 - def __init__(self, connection_alias, **connection_options): - # Zxy 调用父类的初始化方法 - super( - WhooshSearchBackend, - self).__init__( - connection_alias, - **connection_options) - # Zxy 标记后端设置尚未完成 - self.setup_complete = False - # Zxy 默认使用文件存储 - self.use_file_storage = True - # Zxy 设置 POST 请求的大小限制,默认为 128MB - self.post_limit = getattr( - connection_options, - 'POST_LIMIT', - 128 * 1024 * 1024) - # Zxy 从连接选项中获取索引文件存储路径 - self.path = connection_options.get('PATH') - - # Zxy 如果配置的存储类型不是 'file',则使用内存存储 - if connection_options.get('STORAGE', 'file') != 'file': - self.use_file_storage = False - - # Zxy 如果使用文件存储但未提供路径,则抛出配置错误异常 - if self.use_file_storage and not self.path: - raise ImproperlyConfigured( - "You must specify a 'PATH' in your settings for connection '%s'." % - connection_alias) - - # Zxy 获取 Haystack 的日志记录器 - self.log = logging.getLogger('haystack') - - - # Zxy 设置搜索引擎,延迟加载直到需要时才执行 - def setup(self): - """ - Defers loading until needed. - """ - # Zxy 导入 haystack 的连接管理器 - from haystack import connections - # Zxy 标记是否为新创建的索引 - new_index = False - - # Zxy 确保索引目录存在 - if self.use_file_storage and not os.path.exists(self.path): - # Zxy 如果目录不存在,则创建它 - os.makedirs(self.path) - # Zxy 标记这是一个新索引 - new_index = True - - # Zxy 检查索引目录是否可写 - if self.use_file_storage and not os.access(self.path, os.W_OK): - # Zxy 如果不可写,则抛出 IO 错误 - raise IOError( - "The path to your Whoosh index '%s' is not writable for the current user/group." % - self.path) - - # Zxy 根据配置选择存储方式:文件存储或内存存储 - if self.use_file_storage: - # Zxy 使用文件存储 - self.storage = FileStorage(self.path) - else: - # Zxy 声明使用全局的线程局部存储 - global LOCALS - - # Zxy 如果内存存储尚未初始化,则创建一个 - if getattr(LOCALS, 'RAM_STORE', None) is None: - LOCALS.RAM_STORE = RamStorage() - - # Zxy 使用内存存储 - self.storage = LOCALS.RAM_STORE - - # Zxy 构建索引模式,并获取主内容字段名 - self.content_field_name, self.schema = self.build_schema( - connections[self.connection_alias].get_unified_index().all_searchfields()) - # Zxy 创建查询解析器,用于解析用户查询 - self.parser = QueryParser(self.content_field_name, schema=self.schema) - - # Zxy 根据是否为新索引来创建或打开索引 - if new_index is True: - # Zxy 如果是新索引,则根据 schema 创建 - self.index = self.storage.create_index(self.schema) - else: - # Zxy 否则尝试打开现有索引 - try: - self.index = self.storage.open_index(schema=self.schema) - except index.EmptyIndexError: - # Zxy 如果索引为空(可能刚创建目录但无文件),则创建新索引 - self.index = self.storage.create_index(self.schema) - - # Zxy 标记后端设置已完成 - self.setup_complete = True - - # Zxy 根据 Haystack 的搜索字段构建 Whoosh 的索引模式 - def build_schema(self, fields): - # Zxy 初始化模式字段,包含 Haystack 内置的 ID、类型和模型 ID - schema_fields = { - ID: WHOOSH_ID(stored=True, unique=True), - DJANGO_CT: WHOOSH_ID(stored=True), - DJANGO_ID: WHOOSH_ID(stored=True), - } - # Zxy 获取 Haystack 内置字段的数量,用于后续检查 - initial_key_count = len(schema_fields) - # Zxy 初始化主内容字段名 - content_field_name = '' - - # Zxy 遍历所有搜索字段,根据字段类型转换为 Whoosh 字段 - for field_name, field_class in fields.items(): - # Zxy 如果字段是多值字段 - if field_class.is_multivalued: - if field_class.indexed is False: - # Zxy 如果多值字段不被索引,使用 IDLIST 类型 - schema_fields[field_class.index_fieldname] = IDLIST( - stored=True, field_boost=field_class.boost) - else: - # Zxy 如果多值字段被索引,使用 KEYWORD 类型 - schema_fields[field_class.index_fieldname] = KEYWORD( - stored=True, commas=True, scorable=True, field_boost=field_class.boost) - # Zxy 如果字段类型是日期或日期时间 - elif field_class.field_type in ['date', 'datetime']: - # Zxy 使用 DATETIME 类型,并设置为可排序 - schema_fields[field_class.index_fieldname] = DATETIME( - stored=field_class.stored, sortable=True) - # Zxy 如果字段类型是整数 - elif field_class.field_type == 'integer': - # Zxy 使用 NUMERIC 类型,并指定数字类型为整数 - schema_fields[field_class.index_fieldname] = NUMERIC( - stored=field_class.stored, numtype=int, field_boost=field_class.boost) - # Zxy 如果字段类型是浮点数 - elif field_class.field_type == 'float': - # Zxy 使用 NUMERIC 类型,并指定数字类型为浮点数 - schema_fields[field_class.index_fieldname] = NUMERIC( - stored=field_class.stored, numtype=float, field_boost=field_class.boost) - # Zxy 如果字段类型是布尔值 - elif field_class.field_type == 'boolean': - # Zxy 使用 BOOLEAN 类型 - # Field boost isn't supported on BOOLEAN as of 1.8.2. - schema_fields[field_class.index_fieldname] = BOOLEAN( - stored=field_class.stored) - # Zxy 如果字段类型是 N-gram - elif field_class.field_type == 'ngram': - # Zxy 使用 NGRAM 类型 - schema_fields[field_class.index_fieldname] = NGRAM( - minsize=3, maxsize=15, stored=field_class.stored, field_boost=field_class.boost) - # Zxy 如果字段类型是边 N-gram - elif field_class.field_type == 'edge_ngram': - # Zxy 使用 NGRAMWORDS 类型,并设置为从词首开始 - schema_fields[field_class.index_fieldname] = NGRAMWORDS(minsize=2, maxsize=15, at='start', - stored=field_class.stored, - field_boost=field_class.boost) - else: - # Zxy 默认情况下,使用 TEXT 类型,并配置中文分词器 - # schema_fields[field_class.index_fieldname] = TEXT(stored=True, analyzer=StemmingAnalyzer(), field_boost=field_class.boost, sortable=True) - schema_fields[field_class.index_fieldname] = TEXT( - stored=True, analyzer=ChineseAnalyzer(), field_boost=field_class.boost, sortable=True) - # Zxy 如果字段被标记为文档主内容 - if field_class.document is True: - # Zxy 记录主内容字段名 - content_field_name = field_class.index_fieldname - # Zxy 启用拼写建议功能 - schema_fields[field_class.index_fieldname].spelling = True - - # Zxy 如果除了内置字段外没有找到任何其他字段,则优雅地报错 - if len(schema_fields) <= initial_key_count: - raise SearchBackendError( - "No fields were found in any search_indexes. Please correct this before attempting to search.") - - # Zxy 返回主内容字段名和构建好的 Schema 对象 - return (content_field_name, Schema(**schema_fields)) - - # Zxy 更新索引,将可迭代对象中的每个对象添加或更新到 Whoosh 索引中 - def update(self, index, iterable, commit=True): - # Zxy 如果后端尚未初始化,则先进行设置 - if not self.setup_complete: - self.setup() - - # Zxy 刷新索引以获取最新状态 - self.index = self.index.refresh() - # Zxy 创建一个异步写入器,用于高效地写入索引 - writer = AsyncWriter(self.index) - - # Zxy 遍历所有需要更新的对象 - for obj in iterable: - try: - # Zxy 准备要索引的文档数据 - doc = index.full_prepare(obj) - except SkipDocument: - # Zxy 如果对象被标记为跳过,则记录调试信息 - self.log.debug(u"Indexing for object `%s` skipped", obj) - else: - # Zxy 确保所有字段值都是 Whoosh 可以处理的格式 - for key in doc: - doc[key] = self._from_python(doc[key]) - - # Zxy 文档级别的权重提升在 Whoosh 2.5.0+ 版本中不再支持 - if 'boost' in doc: - del doc['boost'] - - try: - # Zxy 使用写入器更新文档(如果存在则更新,否则创建) - writer.update_document(**doc) - except Exception as e: - # Zxy 如果未配置为静默失败,则重新抛出异常 - if not self.silently_fail: - raise - - # Zxy 记录错误信息,包含对象标识符,但不包含对象本身以避免编码问题 - self.log.error( - u"%s while preparing object for update" % - e.__class__.__name__, - exc_info=True, - extra={ - "data": { - "index": index, - "object": get_identifier(obj)}}) - - # Zxy 如果可迭代对象不为空,则提交更改 - if len(iterable) > 0: - # For now, commit no matter what, as we run into locking issues - # otherwise. - writer.commit() - - # Zxy 从索引中移除一个对象 - def remove(self, obj_or_string, commit=True): - # Zxy 如果后端尚未初始化,则先进行设置 - if not self.setup_complete: - self.setup() - - # Zxy 刷新索引以获取最新状态 - self.index = self.index.refresh() - # Zxy 获取对象的唯一标识符 - whoosh_id = get_identifier(obj_or_string) - - try: - # Zxy 构建一个查询,根据 ID 查找文档并删除 - self.index.delete_by_query( - q=self.parser.parse( - u'%s:"%s"' % - (ID, whoosh_id))) - except Exception as e: - # Zxy 如果未配置为静默失败,则重新抛出异常 - if not self.silently_fail: - raise - - # Zxy 记录删除失败的错误 - self.log.error( - "Failed to remove document '%s' from Whoosh: %s", - whoosh_id, - e, - exc_info=True) - - # Zxy 清空索引,可以清空所有内容或指定模型的内容 - def clear(self, models=None, commit=True): - # Zxy 如果后端尚未初始化,则先进行设置 - if not self.setup_complete: - self.setup() - - # Zxy 刷新索引以获取最新状态 - self.index = self.index.refresh() - - # Zxy 如果提供了模型列表,则检查其类型 - if models is not None: - assert isinstance(models, (list, tuple)) - - try: - if models is None: - # Zxy 如果没有指定模型,则删除整个索引 - self.delete_index() - else: - # Zxy 准备要删除的模型列表 - models_to_delete = [] - - for model in models: - # Zxy 为每个模型构建查询字符串 - models_to_delete.append( - u"%s:%s" % - (DJANGO_CT, get_model_ct(model))) - - # Zxy 使用 OR 连接多个模型查询,并删除匹配的文档 - self.index.delete_by_query( - q=self.parser.parse( - u" OR ".join(models_to_delete))) - except Exception as e: - # Zxy 如果未配置为静默失败,则重新抛出异常 - if not self.silently_fail: - raise - - # Zxy 根据是否指定模型,记录不同的错误信息 - if models is not None: - self.log.error( - "Failed to clear Whoosh index of models '%s': %s", - ','.join(models_to_delete), - e, - exc_info=True) - else: - self.log.error( - "Failed to clear Whoosh index: %s", e, exc_info=True) - - # Zxy 物理删除整个索引 - def delete_index(self): - # Per the Whoosh mailing list, if wiping out everything from the index, - # it's much more efficient to simply delete the index files. - # Zxy 如果使用文件存储,则直接删除索引目录 - if self.use_file_storage and os.path.exists(self.path): - shutil.rmtree(self.path) - # Zxy 如果使用内存存储,则清空存储 - elif not self.use_file_storage: - self.storage.clean() - - # Zxy 重新初始化后端,创建新的空索引 - self.setup() - - # Zxy 优化索引,合并索引段以提高搜索性能 - def optimize(self): - # Zxy 如果后端尚未初始化,则先进行设置 - if not self.setup_complete: - self.setup() - - # Zxy 刷新索引以获取最新状态 - self.index = self.index.refresh() - # Zxy 执行 Whoosh 的优化操作 - self.index.optimize() - - # Zxy 根据偏移量计算 Whoosh 分页所需的页码和每页大小 - def calculate_page(self, start_offset=0, end_offset=None): - # Zxy 防止 Whoosh 因 end_offset 小于等于 0 而抛出错误 - if end_offset is not None and end_offset <= 0: - end_offset = 1 - - # Zxy 初始化页码 - page_num = 0 - - # Zxy 如果未指定结束偏移量,则设置一个很大的默认值 - if end_offset is None: - end_offset = 1000000 - - # Zxy 如果未指定起始偏移量,则默认为 0 - if start_offset is None: - start_offset = 0 - - # Zxy 计算每页的长度 - page_length = end_offset - start_offset - - # Zxy 如果页长度有效,则计算页码 - if page_length and page_length > 0: - page_num = int(start_offset / page_length) - - # Zxy Whoosh 使用 1-based 页码,所以需要加 1 - page_num += 1 - return page_num, page_length - - # Zxy 执行搜索查询的核心方法 - @log_query - def search( - self, - query_string, - sort_by=None, - start_offset=0, - end_offset=None, - fields='', - highlight=False, - facets=None, - date_facets=None, - query_facets=None, - narrow_queries=None, - spelling_query=None, - within=None, - dwithin=None, - distance_point=None, - models=None, - limit_to_registered_models=None, - result_class=None, - **kwargs): - # Zxy 如果后端尚未初始化,则先进行设置 - if not self.setup_complete: - self.setup() - - # Zxy 空查询字符串应返回无结果 - if len(query_string) == 0: - return { - 'results': [], - 'hits': 0, - } - - # Zxy 确保查询字符串为正确的字符串类型 - query_string = force_str(query_string) - - # Zxy 单个字符(非通配符)查询会被停用词过滤器拦截,应返回无结果 - if len(query_string) <= 1 and query_string != u'*': - return { - 'results': [], - 'hits': 0, - } - - # Zxy 初始化排序方向为非逆序 - reverse = False - - # Zxy 如果提供了排序字段 - if sort_by is not None: - # Zxy 确定是否需要反转结果,以及 Whoosh 是否能处理排序 - sort_by_list = [] - reverse_counter = 0 - - # Zxy 统计逆序排序字段的数量 - for order_by in sort_by: - if order_by.startswith('-'): - reverse_counter += 1 - - # Zxy Whoosh 要求所有排序字段的排序方向必须一致 - if reverse_counter and reverse_counter != len(sort_by): - raise SearchBackendError("Whoosh requires all order_by fields" - " to use the same sort direction") - - # Zxy 处理排序字段列表,去除 '-' 前缀并确定最终排序方向 - for order_by in sort_by: - if order_by.startswith('-'): - sort_by_list.append(order_by[1:]) - if len(sort_by_list) == 1: - reverse = True - else: - sort_by_list.append(order_by) - if len(sort_by_list) == 1: - reverse = False - - # Zxy Whoosh 的 search_page 方法只接受单个排序字段 - sort_by = sort_by_list[0] - - # Zxy Whoosh 后端不支持分面搜索,发出警告 - if facets is not None: - warnings.warn( - "Whoosh does not handle faceting.", - Warning, - stacklevel=2) - - # Zxy Whoosh 后端不支持日期分面,发出警告 - if date_facets is not None: - warnings.warn( - "Whoosh does not handle date faceting.", - Warning, - stacklevel=2) - - # Zxy Whoosh 后端不支持查询分面,发出警告 - if query_facets is not None: - warnings.warn( - "Whoosh does not handle query faceting.", - Warning, - stacklevel=2) - - # Zxy 初始化用于存储缩小范围后的结果 - narrowed_results = None - # Zxy 刷新索引以获取最新状态 - self.index = self.index.refresh() - - # Zxy 确定是否限制搜索到已注册的模型 - if limit_to_registered_models is None: - limit_to_registered_models = getattr( - settings, 'HAYSTACK_LIMIT_TO_REGISTERED_MODELS', True) - - # Zxy 根据传入的模型或配置构建模型选择列表 - if models and len(models): - model_choices = sorted(get_model_ct(model) for model in models) - elif limit_to_registered_models: - # Zxy 使用缩小查询的方式,将结果限制在当前路由器处理的模型中 - model_choices = self.build_models_list() - else: - model_choices = [] - - # Zxy 如果存在模型选择,则将其添加到缩小查询中 - if len(model_choices) > 0: - if narrow_queries is None: - narrow_queries = set() - - # Zxy 构建一个 OR 查询来限制模型类型 - narrow_queries.add(' OR '.join( - ['%s:%s' % (DJANGO_CT, rm) for rm in model_choices])) - - # Zxy 初始化缩小查询的搜索器 - narrow_searcher = None - - # Zxy 如果存在缩小查询,则执行它们以获取一个结果集过滤器 - if narrow_queries is not None: - # Zxy 这个操作可能很昂贵,但 Whoosh 中没有其他方法 - narrow_searcher = self.index.searcher() - - for nq in narrow_queries: - recent_narrowed_results = narrow_searcher.search( - self.parser.parse(force_str(nq)), limit=None) - - # Zxy 如果任何一个缩小查询返回空结果,则直接返回空 - if len(recent_narrowed_results) <= 0: - return { - 'results': [], - 'hits': 0, - } - - # Zxy 将多个缩小查询的结果集进行交集过滤 - if narrowed_results: - narrowed_results.filter(recent_narrowed_results) - else: - narrowed_results = recent_narrowed_results - - # Zxy 再次刷新索引以确保所有写入都可见 - self.index = self.index.refresh() - - # Zxy 如果索引中有文档,则执行搜索 - if self.index.doc_count(): - searcher = self.index.searcher() - # Zxy 解析查询字符串 - parsed_query = self.parser.parse(query_string) - - # Zxy 如果查询无效或被停用词过滤,则优雅地恢复 - if parsed_query is None: - return { - 'results': [], - 'hits': 0, - } - - # Zxy 计算分页参数 - page_num, page_length = self.calculate_page( - start_offset, end_offset) - - # Zxy 准备搜索参数 - search_kwargs = { - 'pagelen': page_length, - 'sortedby': sort_by, - 'reverse': reverse, - } - - # Zxy 如果存在缩小范围的结果,则将其作为过滤器 - if narrowed_results is not None: - search_kwargs['filter'] = narrowed_results - - try: - # Zxy 执行分页搜索 - raw_page = searcher.search_page( - parsed_query, - page_num, - **search_kwargs - ) - except ValueError: - # Zxy 如果页码无效,则返回空结果 - if not self.silently_fail: - raise - return { - 'results': [], - 'hits': 0, - 'spelling_suggestion': None, - } - - # Zxy 兼容 Whoosh 2.5.1 的 bug:请求过高的页码会返回错误的页 - if raw_page.pagenum < page_num: - return { - 'results': [], - 'hits': 0, - 'spelling_suggestion': None, - } - - # Zxy 处理原始搜索结果,转换为 Haystack 的 SearchResult 对象 - results = self._process_results( - raw_page, - highlight=highlight, - query_string=query_string, - spelling_query=spelling_query, - result_class=result_class) - # Zxy 关闭主搜索器 - searcher.close() - - # Zxy 关闭缩小查询的搜索器 - if hasattr(narrow_searcher, 'close'): - narrow_searcher.close() - - return results - else: - # Zxy 如果索引为空,但仍需处理拼写建议 - spelling_suggestion = None - if self.include_spelling: - if spelling_query: - spelling_suggestion = self.create_spelling_suggestion( - spelling_query) - else: - spelling_suggestion = self.create_spelling_suggestion( - query_string) - - return { - 'results': [], - 'hits': 0, - 'spelling_suggestion': spelling_suggestion, - } - - # Zxy 实现“更多类似于此”功能,根据给定模型实例查找相似文档 - def more_like_this( - self, - model_instance, - additional_query_string=None, - start_offset=0, - end_offset=None, - models=None, - limit_to_registered_models=None, - result_class=None, - **kwargs): - # Zxy 如果后端尚未初始化,则先进行设置 - if not self.setup_complete: - self.setup() - - # Zxy 获取模型的真实类,避免使用延迟加载的模型类 - # Deferred models will have a different class ("RealClass_Deferred_fieldname") - # which won't be in our registry: - model_klass = model_instance._meta.concrete_model - - # Zxy 获取主内容字段名,用于相似性分析 - field_name = self.content_field_name - # Zxy 初始化缩小查询集合 - narrow_queries = set() - # Zxy 初始化缩小范围后的结果集 - narrowed_results = None - # Zxy 刷新索引以获取最新状态 - self.index = self.index.refresh() - - # Zxy 确定是否限制搜索到已注册的模型 - if limit_to_registered_models is None: - limit_to_registered_models = getattr( - settings, 'HAYSTACK_LIMIT_TO_REGISTERED_MODELS', True) - - # Zxy 根据传入的模型或配置构建模型选择列表 - if models and len(models): - model_choices = sorted(get_model_ct(model) for model in models) - elif limit_to_registered_models: - # Zxy 使用缩小查询的方式,将结果限制在当前路由器处理的模型中 - model_choices = self.build_models_list() - else: - model_choices = [] - - # Zxy 如果存在模型选择,则将其添加到缩小查询中 - if len(model_choices) > 0: - if narrow_queries is None: - narrow_queries = set() - - # Zxy 构建一个 OR 查询来限制模型类型 - narrow_queries.add(' OR '.join( - ['%s:%s' % (DJANGO_CT, rm) for rm in model_choices])) - - # Zxy 如果提供了额外的查询字符串,则也添加到缩小查询中 - if additional_query_string and additional_query_string != '*': - narrow_queries.add(additional_query_string) - - # Zxy 初始化缩小查询的搜索器 - narrow_searcher = None - - # Zxy 如果存在缩小查询,则执行它们以获取一个结果集过滤器 - if narrow_queries is not None: - # Zxy 这个操作可能很昂贵,但 Whoosh 中没有其他方法 - narrow_searcher = self.index.searcher() - - for nq in narrow_queries: - recent_narrowed_results = narrow_searcher.search( - self.parser.parse(force_str(nq)), limit=None) - - # Zxy 如果任何一个缩小查询返回空结果,则直接返回空 - if len(recent_narrowed_results) <= 0: - return { - 'results': [], - 'hits': 0, - } - - # Zxy 将多个缩小查询的结果集进行交集过滤 - if narrowed_results: - narrowed_results.filter(recent_narrowed_results) - else: - narrowed_results = recent_narrowed_results - - # Zxy 计算分页参数 - page_num, page_length = self.calculate_page(start_offset, end_offset) - - # Zxy 再次刷新索引以确保所有写入都可见 - self.index = self.index.refresh() - # Zxy 初始化原始结果为空 - raw_results = EmptyResults() - - # Zxy 如果索引中有文档,则执行“更多类似于此”查询 - if self.index.doc_count(): - # Zxy 构建一个查询以找到当前模型实例对应的索引文档 - query = "%s:%s" % (ID, get_identifier(model_instance)) - searcher = self.index.searcher() - parsed_query = self.parser.parse(query) - # Zxy 搜索当前文档 - results = searcher.search(parsed_query) - - # Zxy 如果找到了当前文档,则调用其 more_like_this 方法 - if len(results): - # Zxy 获取与当前文档相似的其他文档 - raw_results = results[0].more_like_this( - field_name, top=end_offset) - - # Zxy 如果存在缩小范围的结果,则将其作为过滤器应用于相似结果 - if narrowed_results is not None and hasattr(raw_results, 'filter'): - raw_results.filter(narrowed_results) - - try: - # Zxy 将原始结果集包装成分页对象 - raw_page = ResultsPage(raw_results, page_num, page_length) - except ValueError: - # Zxy 如果页码无效,则返回空结果 - if not self.silently_fail: - raise - - return { - 'results': [], - 'hits': 0, - 'spelling_suggestion': None, - } - - # Zxy 兼容 Whoosh 2.5.1 的 bug:请求过高的页码会返回错误的页 - if raw_page.pagenum < page_num: - return { - 'results': [], - 'hits': 0, - 'spelling_suggestion': None, - } - - # Zxy 处理原始搜索结果,转换为 Haystack 的 SearchResult 对象 - results = self._process_results(raw_page, result_class=result_class) - # Zxy 关闭主搜索器 - searcher.close() - - # Zxy 关闭缩小查询的搜索器 - if hasattr(narrow_searcher, 'close'): - narrow_searcher.close() - - return results - - - # Zxy 处理 Whoosh 返回的原始搜索结果,转换为 Haystack 标准格式 - def _process_results( - self, - raw_page, - highlight=False, - query_string='', - spelling_query=None, - result_class=None): - # Zxy 导入 haystack 连接管理器 - from haystack import connections - # Zxy 初始化结果列表 - results = [] - - # Zxy 在切片之前获取总命中数,这对于分页至关重要 - hits = len(raw_page) - - # Zxy 如果未指定结果类,则使用默认的 SearchResult - if result_class is None: - result_class = SearchResult - - # Zxy 初始化分面和拼写建议 - facets = {} - spelling_suggestion = None - # Zxy 获取统一索引对象和已注册的模型列表 - unified_index = connections[self.connection_alias].get_unified_index() - indexed_models = unified_index.get_indexed_models() - - # Zxy 遍历原始结果页中的每个文档 - for doc_offset, raw_result in enumerate(raw_page): - # Zxy 获取文档的得分 - score = raw_page.score(doc_offset) or 0 - # Zxy 从文档中解析出应用标签和模型名 - app_label, model_name = raw_result[DJANGO_CT].split('.') - # Zxy 初始化额外字段字典 - additional_fields = {} - # Zxy 根据应用标签和模型名获取模型类 - model = haystack_get_model(app_label, model_name) - - # Zxy 确保模型存在且已注册到索引 - if model and model in indexed_models: - # Zxy 遍历文档中的所有字段 - for key, value in raw_result.items(): - # Zxy 获取该模型对应的索引 - index = unified_index.get_index(model) - string_key = str(key) - - # Zxy 如果字段在索引定义中,并且有转换方法 - if string_key in index.fields and hasattr( - index.fields[string_key], 'convert'): - # Zxy 特殊处理多值字段 - if index.fields[string_key].is_multivalued: - if value is None or len(value) == 0: - additional_fields[string_key] = [] - else: - additional_fields[string_key] = value.split( - ',') - else: - # Zxy 使用索引字段定义的转换方法 - additional_fields[string_key] = index.fields[string_key].convert( - value) - else: - # Zxy 否则使用通用的 Python 类型转换 - additional_fields[string_key] = self._to_python(value) - - # Zxy 删除 Haystack 内部字段,不返回给用户 - del (additional_fields[DJANGO_CT]) - del (additional_fields[DJANGO_ID]) - - # Zxy 如果需要高亮显示 - if highlight: - # Zxy 创建词干分析器和 HTML 格式化器 - sa = StemmingAnalyzer() - formatter = WhooshHtmlFormatter('em') - # Zxy 从查询字符串中提取词条 - terms = [token.text for token in sa(query_string)] - - # Zxy 调用 Whoosh 的高亮方法 - whoosh_result = whoosh_highlight( - additional_fields.get(self.content_field_name), - terms, - sa, - ContextFragmenter(), - formatter - ) - # Zxy 将高亮结果添加到额外字段中 - additional_fields['highlighted'] = { - self.content_field_name: [whoosh_result], - } - - # Zxy 创建 SearchResult 对象并添加到结果列表 - result = result_class( - app_label, - model_name, - raw_result[DJANGO_ID], - score, - **additional_fields) - results.append(result) - else: - # Zxy 如果模型未注册,则减少总命中数 - hits -= 1 - - # Zxy 如果启用了拼写建议 - if self.include_spelling: - if spelling_query: - spelling_suggestion = self.create_spelling_suggestion( - spelling_query) - else: - spelling_suggestion = self.create_spelling_suggestion( - query_string) - - # Zxy 返回包含结果、命中数、分面和拼写建议的字典 - return { - 'results': results, - 'hits': hits, - 'facets': facets, - 'spelling_suggestion': spelling_suggestion, - } - - # Zxy 根据查询字符串创建拼写建议 - def create_spelling_suggestion(self, query_string): - # Zxy 初始化拼写建议 - spelling_suggestion = None - # Zxy 获取索引的读取器和校正器 - reader = self.index.reader() - corrector = reader.corrector(self.content_field_name) - # Zxy 清理查询字符串 - cleaned_query = force_str(query_string) - - # Zxy 如果查询字符串为空,直接返回 - if not query_string: - return spelling_suggestion - - # Zxy 移除查询中的保留字 - for rev_word in self.RESERVED_WORDS: - cleaned_query = cleaned_query.replace(rev_word, '') - - # Zxy 移除查询中的保留字符 - for rev_char in self.RESERVED_CHARACTERS: - cleaned_query = cleaned_query.replace(rev_char, '') - - # Zxy 将清理后的查询拆分为单词列表 - query_words = cleaned_query.split() - suggested_words = [] - - # Zxy 为每个单词查找拼写建议 - for word in query_words: - suggestions = corrector.suggest(word, limit=1) - - if len(suggestions) > 0: - suggested_words.append(suggestions[0]) - - # Zxy 将建议的单词重新组合成字符串 - spelling_suggestion = ' '.join(suggested_words) - return spelling_suggestion - - # Zxy 将 Python 值转换为 Whoosh 可用的字符串格式 - def _from_python(self, value): - """ - Converts Python values to a string for Whoosh. - - Code courtesy of pysolr. - """ - # Zxy 处理日期时间对象 - if hasattr(value, 'strftime'): - # Zxy 如果只有日期没有时间,则将时间部分设为 0 - if not hasattr(value, 'hour'): - value = datetime(value.year, value.month, value.day, 0, 0, 0) - # Zxy 处理布尔值 - elif isinstance(value, bool): - if value: - value = 'true' - else: - value = 'false' - # Zxy 处理列表或元组,用逗号连接 - elif isinstance(value, (list, tuple)): - value = u','.join([force_str(v) for v in value]) - # Zxy 处理数字,保持原样 - elif isinstance(value, (six.integer_types, float)): - # Leave it alone. - pass - else: - # Zxy 其他类型强制转换为字符串 - value = force_str(value) - return value - - # Zxy 将 Whoosh 的值转换为原生 Python 值 - def _to_python(self, value): - """ - Converts values from Whoosh to native Python values. - - A port of the same method in pysolr, as they deal with data the same way. - """ - # Zxy 处理布尔字符串 - if value == 'true': - return True - elif value == 'false': - return False - - # Zxy 尝试解析日期时间字符串 - if value and isinstance(value, six.string_types): - possible_datetime = DATETIME_REGEX.search(value) - - if possible_datetime: - date_values = possible_datetime.groupdict() - - for dk, dv in date_values.items(): - date_values[dk] = int(dv) - - return datetime( - date_values['year'], - date_values['month'], - date_values['day'], - date_values['hour'], - date_values['minute'], - date_values['second']) - - # Zxy 尝试使用 json 解复杂数据类型 - try: - # Attempt to use json to load the values. - converted_value = json.loads(value) - - # Try to handle most built-in types. - if isinstance( - converted_value, - (list, - tuple, - set, - dict, - six.integer_types, - float, - complex)): - return converted_value - except BaseException: - # If it fails (SyntaxError or its ilk) or we don't trust it, - # continue on. - pass - - # Zxy 如果都无法转换,则返回原始值 - return value - - # Zxy 定义 Whoosh 搜索查询类,继承自 Haystack 的 BaseSearchQuery -class WhooshSearchQuery(BaseSearchQuery): - # Zxy 将日期时间对象转换为 Whoosh 查询所需的字符串格式 - def _convert_datetime(self, date): - # Zxy 如果包含时间,则转换为完整格式 - if hasattr(date, 'hour'): - return force_str(date.strftime('%Y%m%d%H%M%S')) - else: - # Zxy 如果只有日期,则补充零时间 - return force_str(date.strftime('%Y%m%d000000')) - - # Zxy 清理查询片段,转义 Whoosh 的保留字符 - def clean(self, query_fragment): - """ - Provides a mechanism for sanitizing user input before presenting the - value to the backend. - - Whoosh 1.X differs here in that you can no longer use a backslash - to escape reserved characters. Instead, the whole word should be - quoted. - """ - # Zxy 将查询片段按空格分割成单词 - words = query_fragment.split() - cleaned_words = [] - - # Zxy 遍历每个单词进行清理 - for word in words: - # Zxy 如果是保留字,则转为小写 - if word in self.backend.RESERVED_WORDS: - word = word.replace(word, word.lower()) - - # Zxy 如果包含保留字符,则用单引号将整个单词括起来 - for char in self.backend.RESERVED_CHARACTERS: - if char in word: - word = "'%s'" % word - break - - cleaned_words.append(word) - - # Zxy 将清理后的单词重新组合 - return ' '.join(cleaned_words) - - # Zxy 构建查询片段,根据字段、过滤类型和值生成 Whoosh 查询语法 - def build_query_fragment(self, field, filter_type, value): - # Zxy 导入 haystack 连接管理器 - from haystack import connections - query_frag = '' - is_datetime = False - - # Zxy 如果值没有 input_type_name 属性,则进行类型推断 - if not hasattr(value, 'input_type_name'): - # Handle when we've got a ``ValuesListQuerySet``... - if hasattr(value, 'values_list'): - value = list(value) - - if hasattr(value, 'strftime'): - is_datetime = True - - if isinstance(value, six.string_types) and value != ' ': - # It's not an ``InputType``. Assume ``Clean``. - value = Clean(value) - else: - value = PythonData(value) - - # Zxy 使用 InputType 准备查询值 - prepared_value = value.prepare(self) - - # Zxy 如果准备好的值不是集合类型,则转换为 Whoosh 可用的格式 - if not isinstance(prepared_value, (set, list, tuple)): - # Then convert whatever we get back to what pysolr wants if needed. - prepared_value = self.backend._from_python(prepared_value) - - # 'content' is a special reserved word, much like 'pk' in - # Django's ORM layer. It indicates 'no special field'. - # Zxy 'content' 是特殊字段,代表所有可搜索内容 - if field == 'content': - index_fieldname = '' - else: - # Zxy 获取字段在索引中的真实名称 - index_fieldname = u'%s:' % connections[self._using].get_unified_index( - ).get_index_fieldname(field) - - # Zxy 定义不同过滤类型对应的 Whoosh 查询模板 - filter_types = { - 'content': '%s', - 'contains': '*%s*', - 'endswith': "*%s", - 'startswith': "%s*", - 'exact': '%s', - 'gt': "{%s to}", - 'gte': "[%s to]", - 'lt': "{to %s}", - 'lte': "[to %s]", - 'fuzzy': u'%s~', - } - # Zxy 如果值不需要后处理,则直接使用 - if value.post_process is False: - query_frag = prepared_value - else: - # Zxy 根据不同的过滤类型构建查询片段 - if filter_type in [ - 'content', - 'contains', - 'startswith', - 'endswith', - 'fuzzy']: - # Zxy 如果输入类型是精确匹配,则直接使用值 - if value.input_type_name == 'exact': - query_frag = prepared_value - else: - # Iterate over terms & incorportate the converted form of - # each into the query. - terms = [] - - if isinstance(prepared_value, six.string_types): - possible_values = prepared_value.split(' ') - else: - if is_datetime is True: - prepared_value = self._convert_datetime( - prepared_value) - - possible_values = [prepared_value] - - for possible_value in possible_values: - terms.append( - filter_types[filter_type] % - self.backend._from_python(possible_value)) - - if len(terms) == 1: - query_frag = terms[0] - else: - query_frag = u"(%s)" % " AND ".join(terms) - # Zxy 处理 'in' 过滤类型 - elif filter_type == 'in': - in_options = [] - - for possible_value in prepared_value: - is_datetime = False - - if hasattr(possible_value, 'strftime'): - is_datetime = True - - pv = self.backend._from_python(possible_value) - - if is_datetime is True: - pv = self._convert_datetime(pv) - - if isinstance(pv, six.string_types) and not is_datetime: - in_options.append('"%s"' % pv) - else: - in_options.append('%s' % pv) - - query_frag = "(%s)" % " OR ".join(in_options) - # Zxy 处理 'range' 过滤类型 - elif filter_type == 'range': - start = self.backend._from_python(prepared_value[0]) - end = self.backend._from_python(prepared_value[1]) - - if hasattr(prepared_value[0], 'strftime'): - start = self._convert_datetime(start) - - if hasattr(prepared_value[1], 'strftime'): - end = self._convert_datetime(end) - - query_frag = u"[%s to %s]" % (start, end) - # Zxy 处理 'exact' 过滤类型 - elif filter_type == 'exact': - if value.input_type_name == 'exact': - query_frag = prepared_value - else: - prepared_value = Exact(prepared_value).prepare(self) - query_frag = filter_types[filter_type] % prepared_value - else: - # Zxy 处理其他类型(如 gt, gte, lt, lte) - if is_datetime is True: - prepared_value = self._convert_datetime(prepared_value) - - query_frag = filter_types[filter_type] % prepared_value - - # Zxy 如果查询片段不为空且不是原始查询,则用括号括起来 - if len(query_frag) and not isinstance(value, Raw): - if not query_frag.startswith('(') and not query_frag.endswith(')'): - query_frag = "(%s)" % query_frag - return u"%s%s" % (index_fieldname, query_frag) - - # if not filter_type in ('in', 'range'): - # # '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) - -# Zxy 定义 Whoosh 引擎类,继承自 Haystack 的 BaseEngine -class WhooshEngine(BaseEngine): - # Zxy 指定后端和查询类 - backend = WhooshSearchBackend - query = WhooshSearchQuery diff --git a/src/DjangoBlog-master/djangoblog/wsgi.py b/src/DjangoBlog-master/djangoblog/wsgi.py deleted file mode 100644 index 730430e..0000000 --- a/src/DjangoBlog-master/djangoblog/wsgi.py +++ /dev/null @@ -1,20 +0,0 @@ -""" -WSGI config for djangoblog project. - -It exposes the WSGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ -""" - -# Zxy导入os模块,用于操作环境变量 -import os - -# Zxy从Django中导入get_wsgi_application函数,用于获取WSGI应用 -from django.core.wsgi import get_wsgi_application - -# Zxy设置环境变量DJANGO_SETTINGS_MODULE,指定Django项目的配置文件 -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangoblog.settings") - -# Zxy获取WSGI应用实例,用于部署 -application = get_wsgi_application() \ No newline at end of file