更新: 删除旧DjangoBlog-master,替换为新DjangoBlog项目

master
周潇潇 2 weeks ago
parent 54b4de7c17
commit 9b4fa3827e

2
.gitignore vendored

@ -0,0 +1,2 @@
.DS_Store
*.DS_Store

BIN
src/.DS_Store vendored

Binary file not shown.

@ -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'<a href="%s">%s</a>' %
(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'<a href="%s">%s</a>' % (link, obj.article.title))
# 设置自定义方法的显示名称
link_to_userinfo.short_description = _('User')
link_to_article.short_description = _('Article')

@ -1,5 +0,0 @@
from django.apps import AppConfig
#评论应用的配置类
class CommentsConfig(AppConfig):
name = 'comments'# 指定应用名称为'comments',对应安装的应用名

@ -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字段评论内容

@ -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',# 获取最新记录的字段
},
),
]

@ -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='是否显示'),# 新字段定义
),
]

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

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

@ -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 # 评论深度级别
}

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

@ -1,15 +0,0 @@
from django.urls import path
from . import views
# 设置应用命名空间用于URL反向解析
app_name = "comments"
# URL模式列表
urlpatterns = [
#文章评论提交接口
# 捕获整数类型article_id参数映射到CommentPostView视图
path(
'article/<int:article_id>/postcomment',
views.CommentPostView.as_view(),
name='postcomment'),# URL名称模板中可通过{% url 'comments:postcomment' article_id %}调用
]

@ -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 = _("""<p>Thank you very much for your comments on this site</p>
You can visit <a href="%(article_url)s" rel="bookmark">%(article_title)s</a>
to review your comments,
Thank you again!
<br />
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 <a href="%(article_url)s" rel="bookmark">%(article_title)s</a><br/> has
received a reply. <br/> %(comment_body)s
<br/>
go check it out!
<br/>
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)#记录邮件发送异常

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

@ -1,2 +0,0 @@
# Zxy指定默认的应用配置类
default_app_config = 'djangoblog.apps.DjangoblogAppConfig'

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

@ -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加载插件

@ -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清理所有缓存暂时注释

@ -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查询类

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

@ -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 = '<a href="{}">{}</a>'.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 = '<a href="{}">{}</a>'.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

@ -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插件版本
}

@ -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文章内容处理的钩子名称

@ -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返回处理后的值

@ -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记录加载失败日志

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

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

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

@ -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断言返回值不为空

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

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

File diff suppressed because it is too large Load Diff

@ -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()
Loading…
Cancel
Save