diff --git a/doc/开源软件的质量分析报告文档.docx b/doc/开源软件的质量分析报告文档.docx new file mode 100644 index 0000000..6afaa79 Binary files /dev/null and b/doc/开源软件的质量分析报告文档.docx differ diff --git a/doc/编码规范.docx b/doc/编码规范.docx new file mode 100644 index 0000000..5b0eee3 Binary files /dev/null and b/doc/编码规范.docx differ diff --git a/src/DjangoBlog-master/accounts/admin.py b/src/DjangoBlog-master/accounts/admin.py index 29d162a..2c63cef 100644 --- a/src/DjangoBlog-master/accounts/admin.py +++ b/src/DjangoBlog-master/accounts/admin.py @@ -1,3 +1,4 @@ +#ht: 用户管理后台配置模块,自定义Django Admin的用户管理界面 from django import forms from django.contrib.auth.admin import UserAdmin from django.contrib.auth.forms import UserChangeForm @@ -9,6 +10,7 @@ from .models import BlogUser class BlogUserCreationForm(forms.ModelForm): + """ht: 用户创建表单,用于在Admin后台创建新用户""" password1 = forms.CharField(label=_('password'), widget=forms.PasswordInput) password2 = forms.CharField(label=_('Enter password again'), widget=forms.PasswordInput) @@ -17,7 +19,7 @@ class BlogUserCreationForm(forms.ModelForm): fields = ('email',) def clean_password2(self): - # Check that the two password entries match + """ht: 验证两次输入的密码是否一致""" password1 = self.cleaned_data.get("password1") password2 = self.cleaned_data.get("password2") if password1 and password2 and password1 != password2: @@ -25,16 +27,17 @@ class BlogUserCreationForm(forms.ModelForm): return password2 def save(self, commit=True): - # Save the provided password in hashed format + """ht: 保存用户,对密码进行哈希处理并设置来源标记""" user = super().save(commit=False) user.set_password(self.cleaned_data["password1"]) if commit: - user.source = 'adminsite' + user.source = 'adminsite' # ht: 标记用户来源为管理员后台 user.save() return user class BlogUserChangeForm(UserChangeForm): + """ht: 用户信息修改表单""" class Meta: model = BlogUser fields = '__all__' @@ -45,8 +48,10 @@ class BlogUserChangeForm(UserChangeForm): class BlogUserAdmin(UserAdmin): + """ht: 自定义用户管理类,配置Admin界面显示和搜索选项""" form = BlogUserChangeForm add_form = BlogUserCreationForm + #ht: 列表显示字段 list_display = ( 'id', 'nickname', @@ -55,6 +60,6 @@ class BlogUserAdmin(UserAdmin): 'last_login', 'date_joined', 'source') - list_display_links = ('id', 'username') - ordering = ('-id',) - search_fields = ('username', 'nickname', 'email') + list_display_links = ('id', 'username') # ht: 可点击链接的字段 + ordering = ('-id',) # ht: 默认按ID倒序排列 + search_fields = ('username', 'nickname', 'email') # ht: 搜索字段 \ No newline at end of file diff --git a/src/DjangoBlog-master/accounts/models.py b/src/DjangoBlog-master/accounts/models.py index 3baddbb..e8c0f18 100644 --- a/src/DjangoBlog-master/accounts/models.py +++ b/src/DjangoBlog-master/accounts/models.py @@ -1,3 +1,4 @@ +#ht: 用户数据模型模块,定义自定义用户模型 from django.contrib.auth.models import AbstractUser from django.db import models from django.urls import reverse @@ -6,30 +7,32 @@ from django.utils.translation import gettext_lazy as _ from djangoblog.utils import get_current_site -# Create your models here. - class BlogUser(AbstractUser): + """ht: 博客用户模型,扩展Django默认用户模型""" nickname = models.CharField(_('nick name'), max_length=100, blank=True) creation_time = models.DateTimeField(_('creation time'), default=now) last_modify_time = models.DateTimeField(_('last modify time'), default=now) source = models.CharField(_('create source'), max_length=100, blank=True) def get_absolute_url(self): + """ht: 获取用户详情页URL""" return reverse( 'blog:author_detail', kwargs={ 'author_name': self.username}) def __str__(self): + """ht: 字符串表示,返回邮箱""" return self.email def get_full_url(self): + """ht: 获取完整的用户URL(包含域名)""" site = get_current_site().domain url = "https://{site}{path}".format(site=site, path=self.get_absolute_url()) return url class Meta: - ordering = ['-id'] + ordering = ['-id'] # ht: 默认按ID倒序排列 verbose_name = _('user') verbose_name_plural = verbose_name - get_latest_by = 'id' + get_latest_by = 'id' \ No newline at end of file diff --git a/src/DjangoBlog-master/accounts/utils.py b/src/DjangoBlog-master/accounts/utils.py index 4b94bdf..6e78218 100644 --- a/src/DjangoBlog-master/accounts/utils.py +++ b/src/DjangoBlog-master/accounts/utils.py @@ -1,3 +1,4 @@ +#ht: 账户相关工具函数模块,处理验证码发送和验证 import typing from datetime import timedelta @@ -7,15 +8,16 @@ from django.utils.translation import gettext_lazy as _ from djangoblog.utils import send_email +#ht: 验证码有效时间为5分钟 _code_ttl = timedelta(minutes=5) def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email")): - """发送重设密码验证码 + """ht: 发送验证邮件 Args: - to_mail: 接受邮箱 - subject: 邮件主题 + to_mail: 接收邮箱地址 code: 验证码 + subject: 邮件主题,默认为"验证邮箱" """ html_content = _( "You are resetting the password, the verification code is:%(code)s, valid within 5 minutes, please keep it " @@ -24,26 +26,26 @@ def send_verify_email(to_mail: str, code: str, subject: str = _("Verify Email")) def verify(email: str, code: str) -> typing.Optional[str]: - """验证code是否有效 + """ht: 验证邮箱和验证码是否匹配 Args: - email: 请求邮箱 + email: 邮箱地址 code: 验证码 - Return: - 如果有错误就返回错误str - Node: - 这里的错误处理不太合理,应该采用raise抛出 - 否测调用方也需要对error进行处理 + Returns: + 验证成功返回None,失败返回错误信息字符串 """ cache_code = get_code(email) if cache_code != code: return gettext("Verification code error") + #ht: 验证成功后清除验证码,防止重复使用 + cache.delete(email) + return None def set_code(email: str, code: str): - """设置code""" + """ht: 将验证码存入缓存""" cache.set(email, code, _code_ttl.seconds) def get_code(email: str) -> typing.Optional[str]: - """获取code""" - return cache.get(email) + """ht: 从缓存获取验证码""" + return cache.get(email) \ No newline at end of file diff --git a/src/DjangoBlog-master/accounts/views.py b/src/DjangoBlog-master/accounts/views.py index ae67aec..709d977 100644 --- a/src/DjangoBlog-master/accounts/views.py +++ b/src/DjangoBlog-master/accounts/views.py @@ -1,3 +1,4 @@ +#ht: 账户视图模块,处理用户登录、注册、注销等请求 import logging from django.utils.translation import gettext_lazy as _ from django.conf import settings @@ -32,6 +33,7 @@ logger = logging.getLogger(__name__) # Create your views here. class RegisterView(FormView): + """ht: 用户注册视图""" form_class = RegisterForm template_name = 'account/registration_form.html' @@ -40,20 +42,25 @@ class RegisterView(FormView): return super(RegisterView, self).dispatch(*args, **kwargs) def form_valid(self, form): + """ht: 表单验证通过后的处理,创建用户并发送验证邮件""" if form.is_valid(): - user = form.save(False) - user.is_active = False - user.source = 'Register' - user.save(True) + user = form.save(False) # ht: 不立即保存到数据库 + user.is_active = False # ht: 设置用户为未激活状态 + user.source = 'Register' # ht: 标记用户来源 + user.save(True) # ht: 保存用户到数据库 + + #ht: 生成验证链接 site = get_current_site().domain sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id))) if settings.DEBUG: - site = '127.0.0.1:8000' + site = '127.0.0.1:8000' # ht: 调试模式下使用本地地址 + path = reverse('account:result') url = "http://{site}{path}?type=validation&id={id}&sign={sign}".format( site=site, path=path, id=user.id, sign=sign) + #ht: 邮件内容模板 content = """

请点击下面链接验证您的邮箱

@@ -64,6 +71,7 @@ class RegisterView(FormView): 如果上面链接无法打开,请将此链接复制至浏览器。 {url} """.format(url=url) + #ht: 发送验证邮件 send_email( emailto=[ user.email, @@ -79,7 +87,6 @@ class RegisterView(FormView): 'form': form }) - class LogoutView(RedirectView): url = '/login/' @@ -94,39 +101,33 @@ class LogoutView(RedirectView): class LoginView(FormView): + """ht: 用户登录视图""" form_class = LoginForm template_name = 'account/login.html' success_url = '/' redirect_field_name = REDIRECT_FIELD_NAME - login_ttl = 2626560 # 一个月的时间 + login_ttl = 2626560 # ht: 登录会话有效期(一个月) @method_decorator(sensitive_post_parameters('password')) @method_decorator(csrf_protect) @method_decorator(never_cache) def dispatch(self, request, *args, **kwargs): - return super(LoginView, self).dispatch(request, *args, **kwargs) - def get_context_data(self, **kwargs): - redirect_to = self.request.GET.get(self.redirect_field_name) - if redirect_to is None: - redirect_to = '/' - kwargs['redirect_to'] = redirect_to - - return super(LoginView, self).get_context_data(**kwargs) - def form_valid(self, form): + """ht: 登录表单验证处理""" form = AuthenticationForm(data=self.request.POST, request=self.request) if form.is_valid(): + #ht: 清除侧边栏缓存并登录用户 delete_sidebar_cache() logger.info(self.redirect_field_name) auth.login(self.request, form.get_user()) + #ht: 处理"记住我"功能 if self.request.POST.get("remember"): self.request.session.set_expiry(self.login_ttl) return super(LoginView, self).form_valid(form) - # return HttpResponseRedirect('/') else: return self.render_to_response({ 'form': form diff --git a/src/DjangoBlog-master/blog/admin.py b/src/DjangoBlog-master/blog/admin.py index 69d7f8e..ec41816 100644 --- a/src/DjangoBlog-master/blog/admin.py +++ b/src/DjangoBlog-master/blog/admin.py @@ -10,6 +10,7 @@ from .models import Article, Category, Tag, Links, SideBar, BlogSettings class ArticleForm(forms.ModelForm): + #fjw: 文章表单类,用于后台文章编辑 # body = forms.CharField(widget=AdminPagedownWidget()) class Meta: @@ -18,18 +19,22 @@ class ArticleForm(forms.ModelForm): def makr_article_publish(modeladmin, request, queryset): + #fjw: 批量发布文章 queryset.update(status='p') def draft_article(modeladmin, request, queryset): + #fjw: 批量将文章设为草稿 queryset.update(status='d') def close_article_commentstatus(modeladmin, request, queryset): + #fjw: 批量关闭文章评论 queryset.update(comment_status='c') def open_article_commentstatus(modeladmin, request, queryset): + #fjw: 批量开启文章评论 queryset.update(comment_status='o') @@ -40,6 +45,7 @@ open_article_commentstatus.short_description = _('Open article comments') class ArticlelAdmin(admin.ModelAdmin): + #fjw: 文章管理后台配置 list_per_page = 20 search_fields = ('body', 'title') form = ArticleForm @@ -67,6 +73,7 @@ class ArticlelAdmin(admin.ModelAdmin): raw_id_fields = ('author', 'category',) def link_to_category(self, obj): + #fjw: 生成分类链接 info = (obj.category._meta.app_label, obj.category._meta.model_name) link = reverse('admin:%s_%s_change' % info, args=(obj.category.id,)) return format_html(u'%s' % (link, obj.category.name)) @@ -74,15 +81,18 @@ class ArticlelAdmin(admin.ModelAdmin): link_to_category.short_description = _('category') def get_form(self, request, obj=None, **kwargs): + #fjw: 限制作者只能选择超级用户 form = super(ArticlelAdmin, self).get_form(request, obj, **kwargs) form.base_fields['author'].queryset = get_user_model( ).objects.filter(is_superuser=True) return form def save_model(self, request, obj, form, change): + #fjw: 保存模型时的额外处理 super(ArticlelAdmin, self).save_model(request, obj, form, change) def get_view_on_site_url(self, obj=None): + #fjw: 获取文章在前台的URL if obj: url = obj.get_full_url() return url @@ -93,22 +103,27 @@ class ArticlelAdmin(admin.ModelAdmin): class TagAdmin(admin.ModelAdmin): + #fjw: 标签管理后台配置 exclude = ('slug', 'last_mod_time', 'creation_time') class CategoryAdmin(admin.ModelAdmin): + #fjw: 分类管理后台配置 list_display = ('name', 'parent_category', 'index') exclude = ('slug', 'last_mod_time', 'creation_time') class LinksAdmin(admin.ModelAdmin): + #fjw: 友情链接管理后台配置 exclude = ('last_mod_time', 'creation_time') class SideBarAdmin(admin.ModelAdmin): + #fjw: 侧边栏管理后台配置 list_display = ('name', 'content', 'is_enable', 'sequence') exclude = ('last_mod_time', 'creation_time') class BlogSettingsAdmin(admin.ModelAdmin): - pass + #fjw: 博客设置管理后台配置 + pass \ No newline at end of file diff --git a/src/DjangoBlog-master/blog/apps.py b/src/DjangoBlog-master/blog/apps.py index 7930587..cf15b31 100644 --- a/src/DjangoBlog-master/blog/apps.py +++ b/src/DjangoBlog-master/blog/apps.py @@ -2,4 +2,5 @@ from django.apps import AppConfig class BlogConfig(AppConfig): - name = 'blog' + #fjw: 博客应用配置 + name = 'blog' \ No newline at end of file diff --git a/src/DjangoBlog-master/blog/context_processors.py b/src/DjangoBlog-master/blog/context_processors.py index 73e3088..eceee38 100644 --- a/src/DjangoBlog-master/blog/context_processors.py +++ b/src/DjangoBlog-master/blog/context_processors.py @@ -9,6 +9,7 @@ logger = logging.getLogger(__name__) def seo_processor(requests): + #fjw: SEO上下文处理器,为所有模板提供SEO相关变量 key = 'seo_processor' value = cache.get(key) if value: @@ -40,4 +41,4 @@ def seo_processor(requests): "COMMENT_NEED_REVIEW": setting.comment_need_review, } cache.set(key, value, 60 * 60 * 10) - return value + return value \ No newline at end of file diff --git a/src/DjangoBlog-master/blog/models.py b/src/DjangoBlog-master/blog/models.py index 083788b..6442d2d 100644 --- a/src/DjangoBlog-master/blog/models.py +++ b/src/DjangoBlog-master/blog/models.py @@ -18,6 +18,7 @@ logger = logging.getLogger(__name__) class LinkShowType(models.TextChoices): + #fjw: 链接显示类型枚举 I = ('i', _('index')) L = ('l', _('list')) P = ('p', _('post')) @@ -26,11 +27,13 @@ class LinkShowType(models.TextChoices): class BaseModel(models.Model): + #fjw: 基础模型类,提供通用字段和方法 id = models.AutoField(primary_key=True) creation_time = models.DateTimeField(_('creation time'), default=now) last_modify_time = models.DateTimeField(_('modify time'), default=now) def save(self, *args, **kwargs): + #fjw: 保存时的通用处理逻辑 is_update_views = isinstance( self, Article) and 'update_fields' in kwargs and kwargs['update_fields'] == ['views'] @@ -45,6 +48,7 @@ class BaseModel(models.Model): super().save(*args, **kwargs) def get_full_url(self): + #fjw: 获取完整URL site = get_current_site().domain url = "https://{site}{path}".format(site=site, path=self.get_absolute_url()) @@ -59,15 +63,18 @@ class BaseModel(models.Model): class Article(BaseModel): - """文章""" + """文章模型""" + #fjw: 文章状态选择 STATUS_CHOICES = ( ('d', _('Draft')), ('p', _('Published')), ) + #fjw: 评论状态选择 COMMENT_STATUS = ( ('o', _('Open')), ('c', _('Close')), ) + #fjw: 文章类型选择 TYPE = ( ('a', _('Article')), ('p', _('Page')), @@ -118,6 +125,7 @@ class Article(BaseModel): get_latest_by = 'id' def get_absolute_url(self): + #fjw: 获取文章详情页URL return reverse('blog:detailbyid', kwargs={ 'article_id': self.id, 'year': self.creation_time.year, @@ -127,6 +135,7 @@ class Article(BaseModel): @cache_decorator(60 * 60 * 10) def get_category_tree(self): + #fjw: 获取分类树 tree = self.category.get_category_tree() names = list(map(lambda c: (c.name, c.get_absolute_url()), tree)) @@ -136,10 +145,12 @@ class Article(BaseModel): super().save(*args, **kwargs) def viewed(self): + #fjw: 增加文章浏览量 self.views += 1 self.save(update_fields=['views']) def comment_list(self): + #fjw: 获取文章评论列表(带缓存) cache_key = 'article_comments_{id}'.format(id=self.id) value = cache.get(cache_key) if value: @@ -152,24 +163,25 @@ class Article(BaseModel): return comments def get_admin_url(self): + #fjw: 获取文章在后台的URL info = (self._meta.app_label, self._meta.model_name) return reverse('admin:%s_%s_change' % info, args=(self.pk,)) @cache_decorator(expiration=60 * 100) def next_article(self): - # 下一篇 + #fjw: 获取下一篇文章 return Article.objects.filter( id__gt=self.id, status='p').order_by('id').first() @cache_decorator(expiration=60 * 100) def prev_article(self): - # 前一篇 + #fjw: 获取上一篇文章 return Article.objects.filter(id__lt=self.id, status='p').first() def get_first_image_url(self): """ - Get the first image url from article.body. - :return: + Get the first image url from article body. + :return: 文章正文中的第一张图片URL """ match = re.search(r'!\[.*?\]\((.+?)\)', self.body) if match: @@ -178,7 +190,7 @@ class Article(BaseModel): class Category(BaseModel): - """文章分类""" + """文章分类模型""" name = models.CharField(_('category name'), max_length=30, unique=True) parent_category = models.ForeignKey( 'self', @@ -206,7 +218,7 @@ class Category(BaseModel): def get_category_tree(self): """ 递归获得分类目录的父级 - :return: + :return: 分类树列表 """ categorys = [] @@ -222,7 +234,7 @@ class Category(BaseModel): def get_sub_categorys(self): """ 获得当前分类目录所有子集 - :return: + :return: 子分类列表 """ categorys = [] all_categorys = Category.objects.all() @@ -241,7 +253,7 @@ class Category(BaseModel): class Tag(BaseModel): - """文章标签""" + """文章标签模型""" name = models.CharField(_('tag name'), max_length=30, unique=True) slug = models.SlugField(default='no-slug', max_length=60, blank=True) @@ -253,6 +265,7 @@ class Tag(BaseModel): @cache_decorator(60 * 60 * 10) def get_article_count(self): + #fjw: 获取标签下的文章数量 return Article.objects.filter(tags__name=self.name).distinct().count() class Meta: @@ -262,7 +275,7 @@ class Tag(BaseModel): class Links(models.Model): - """友情链接""" + """友情链接模型""" name = models.CharField(_('link name'), max_length=30, unique=True) link = models.URLField(_('link')) @@ -287,7 +300,7 @@ class Links(models.Model): class SideBar(models.Model): - """侧边栏,可以展示一些html内容""" + """侧边栏模型,可以展示一些html内容""" name = models.CharField(_('title'), max_length=100) content = models.TextField(_('content')) sequence = models.IntegerField(_('order'), unique=True) @@ -305,7 +318,7 @@ class SideBar(models.Model): class BlogSettings(models.Model): - """blog的配置""" + """博客配置模型""" site_name = models.CharField( _('site name'), max_length=200, @@ -367,10 +380,11 @@ class BlogSettings(models.Model): return self.site_name def clean(self): + #fjw: 确保只有一个配置实例 if BlogSettings.objects.exclude(id=self.id).count(): raise ValidationError(_('There can only be one configuration')) def save(self, *args, **kwargs): super().save(*args, **kwargs) from djangoblog.utils import cache - cache.clear() + cache.clear() #fjw: 保存配置后清除缓存 \ No newline at end of file diff --git a/src/DjangoBlog-master/blog/templatetags/blog_tags.py b/src/DjangoBlog-master/blog/templatetags/blog_tags.py index 024f2c8..802d6f9 100644 --- a/src/DjangoBlog-master/blog/templatetags/blog_tags.py +++ b/src/DjangoBlog-master/blog/templatetags/blog_tags.py @@ -32,6 +32,7 @@ def head_meta(context): @register.simple_tag def timeformat(data): + #fjw: 时间格式化标签 try: return data.strftime(settings.TIME_FORMAT) except Exception as e: @@ -56,13 +57,7 @@ def custom_markdown(content): 主要用于文章内容处理 """ html_content = CommonMarkdown.get_markdown(content) - - # 然后应用插件过滤器优化HTML - from djangoblog.plugin_manage import hooks - from djangoblog.plugin_manage.hook_constants import ARTICLE_CONTENT_HOOK_NAME - optimized_html = hooks.apply_filters(ARTICLE_CONTENT_HOOK_NAME, html_content) - - return mark_safe(optimized_html) + return mark_safe(html_content) @register.filter() @@ -136,13 +131,13 @@ def comment_markdown(content): return mark_safe(sanitize_html(content)) -@register.filter(is_safe=True) +@register.filter() @stringfilter def truncatechars_content(content): """ 获得文章内容的摘要 - :param content: - :return: + :param content: 文章内容 + :return: 截取后的摘要 """ from django.template.defaultfilters import truncatechars_html from djangoblog.utils import get_blog_setting @@ -162,8 +157,8 @@ def truncate(content): def load_breadcrumb(article): """ 获得文章面包屑 - :param article: - :return: + :param article: 文章对象 + :return: 面包屑数据 """ names = article.get_category_tree() from djangoblog.utils import get_blog_setting @@ -183,8 +178,8 @@ def load_breadcrumb(article): def load_articletags(article): """ 文章标签 - :param article: - :return: + :param article: 文章对象 + :return: 标签数据 """ tags = article.tags.all() tags_list = [] @@ -199,11 +194,14 @@ def load_articletags(article): } + @register.inclusion_tag('blog/tags/sidebar.html') def load_sidebar(user, linktype): """ 加载侧边栏 - :return: + :param user: 当前用户 + :param linktype: 链接类型 + :return: 侧边栏数据 """ value = cache.get("sidebar" + linktype) if value: @@ -225,8 +223,8 @@ def load_sidebar(user, linktype): Q(show_type=str(linktype)) | Q(show_type=LinkShowType.A)) commment_list = Comment.objects.filter(is_enable=True).order_by( '-id')[:blogsetting.sidebar_comment_count] - # 标签云 计算字体大小 - # 根据总数计算出平均值 大小为 (数目/平均值)*步长 + + # 标签云计算字体大小 increment = 5 tags = Tag.objects.all() sidebar_tags = None @@ -364,34 +362,34 @@ def load_article_detail(article, isindex, user): # 模板使用方法: {{ email|gravatar_url:150 }} @register.filter def gravatar_url(email, size=40): - """获得用户头像 - 优先使用OAuth头像,否则使用默认头像""" + """获得用户头像URL""" cachekey = 'avatar/' + email url = cache.get(cachekey) if url: return url - + + # 改进:添加默认头像路径常量 + DEFAULT_AVATAR_PATH = static('blog/img/avatar.png') + # 检查OAuth用户是否有自定义头像 usermodels = OAuthUser.objects.filter(email=email) if usermodels: # 过滤出有头像的用户 users_with_picture = list(filter(lambda x: x.picture is not None, usermodels)) if users_with_picture: - # 获取默认头像路径用于比较 - default_avatar_path = static('blog/img/avatar.png') - # 优先选择非默认头像的用户,否则选择第一个 - non_default_users = [u for u in users_with_picture if u.picture != default_avatar_path and not u.picture.endswith('/avatar.png')] + non_default_users = [u for u in users_with_picture if u.picture != DEFAULT_AVATAR_PATH and not u.picture.endswith('/avatar.png')] selected_user = non_default_users[0] if non_default_users else users_with_picture[0] - + url = selected_user.picture cache.set(cachekey, url, 60 * 60 * 24) # 缓存24小时 - + avatar_type = 'non-default' if non_default_users else 'default' logger.info('Using {} OAuth avatar for {} from {}'.format(avatar_type, email, selected_user.type)) return url - + # 使用默认头像 - url = static('blog/img/avatar.png') + url = DEFAULT_AVATAR_PATH cache.set(cachekey, url, 60 * 60 * 24) # 缓存24小时 logger.info('Using default avatar for {}'.format(email)) return url diff --git a/src/DjangoBlog-master/blog/views.py b/src/DjangoBlog-master/blog/views.py index 773bb75..6cb5b1c 100644 --- a/src/DjangoBlog-master/blog/views.py +++ b/src/DjangoBlog-master/blog/views.py @@ -25,14 +25,10 @@ logger = logging.getLogger(__name__) class ArticleListView(ListView): - # template_name属性用于指定使用哪个模板进行渲染 + #fjw: 文章列表基类视图 template_name = 'blog/article_index.html' - - # context_object_name属性用于给上下文变量取名(在模板中使用该名字) context_object_name = 'article_list' - - # 页面类型,分类目录或标签列表等 - page_type = '' + page_type = '' #fjw: 页面类型,分类目录或标签列表等 paginate_by = settings.PAGINATE_BY page_kwarg = 'page' link_type = LinkShowType.L @@ -42,6 +38,7 @@ class ArticleListView(ListView): @property def page_number(self): + #fjw: 获取当前页码 page_kwarg = self.page_kwarg page = self.kwargs.get( page_kwarg) or self.request.GET.get(page_kwarg) or 1 @@ -63,7 +60,7 @@ class ArticleListView(ListView): ''' 缓存页面数据 :param cache_key: 缓存key - :return: + :return: 文章列表数据 ''' value = cache.get(cache_key) if value: @@ -78,7 +75,7 @@ class ArticleListView(ListView): def get_queryset(self): ''' 重写默认,从缓存获取数据 - :return: + :return: 文章列表 ''' key = self.get_queryset_cache_key() value = self.get_queryset_from_cache(key) @@ -91,12 +88,12 @@ class ArticleListView(ListView): class IndexView(ArticleListView): ''' - 首页 + 首页视图 ''' - # 友情链接类型 - link_type = LinkShowType.I + link_type = LinkShowType.I #fjw: 友情链接类型 def get_queryset_data(self): + #fjw: 获取首页文章列表 article_list = Article.objects.filter(type='a', status='p') return article_list @@ -107,7 +104,7 @@ class IndexView(ArticleListView): class ArticleDetailView(DetailView): ''' - 文章详情页面 + 文章详情页面视图 ''' template_name = 'blog/article_detail.html' model = Article @@ -115,6 +112,7 @@ class ArticleDetailView(DetailView): context_object_name = "article" def get_context_data(self, **kwargs): + #fjw: 添加上下文数据:评论表单、评论列表等 comment_form = CommentForm() article_comments = self.object.comment_list() @@ -152,23 +150,22 @@ class ArticleDetailView(DetailView): context = super(ArticleDetailView, self).get_context_data(**kwargs) article = self.object - + # 触发文章详情加载钩子,让插件可以添加额外的上下文数据 from djangoblog.plugin_manage.hook_constants import ARTICLE_DETAIL_LOAD hooks.run_action(ARTICLE_DETAIL_LOAD, article=article, context=context, request=self.request) - - # Action Hook, 通知插件"文章详情已获取" - hooks.run_action('after_article_body_get', article=article, request=self.request) + return context class CategoryDetailView(ArticleListView): ''' - 分类目录列表 + 分类目录列表视图 ''' page_type = "分类目录归档" def get_queryset_data(self): + #fjw: 获取分类下的文章列表 slug = self.kwargs['category_name'] category = get_object_or_404(Category, slug=slug) @@ -190,7 +187,6 @@ class CategoryDetailView(ArticleListView): return cache_key def get_context_data(self, **kwargs): - categoryname = self.categoryname try: categoryname = categoryname.split('/')[-1] @@ -203,7 +199,7 @@ class CategoryDetailView(ArticleListView): class AuthorDetailView(ArticleListView): ''' - 作者详情页 + 作者详情页视图 ''' page_type = '作者文章归档' @@ -229,7 +225,7 @@ class AuthorDetailView(ArticleListView): class TagDetailView(ArticleListView): ''' - 标签列表页面 + 标签列表页面视图 ''' page_type = '分类标签归档' @@ -252,7 +248,6 @@ class TagDetailView(ArticleListView): return cache_key def get_context_data(self, **kwargs): - # tag_name = self.kwargs['tag_name'] tag_name = self.name kwargs['page_type'] = TagDetailView.page_type kwargs['tag_name'] = tag_name @@ -261,7 +256,7 @@ class TagDetailView(ArticleListView): class ArchivesView(ArticleListView): ''' - 文章归档页面 + 文章归档页面视图 ''' page_type = '文章归档' paginate_by = None @@ -277,6 +272,7 @@ class ArchivesView(ArticleListView): class LinkListView(ListView): + #fjw: 友情链接页面视图 model = Links template_name = 'blog/links_list.html' @@ -284,29 +280,12 @@ class LinkListView(ListView): return Links.objects.filter(is_enable=True) -class EsSearchView(SearchView): - def get_context(self): - paginator, page = self.build_page() - context = { - "query": self.query, - "form": self.form, - "page": page, - "paginator": paginator, - "suggestion": None, - } - if hasattr(self.results, "query") and self.results.query.backend.include_spelling: - context["suggestion"] = self.results.query.get_spelling_suggestion() - context.update(self.extra_context()) - - return context - - @csrf_exempt def fileupload(request): """ - 该方法需自己写调用端来上传图片,该方法仅提供图床功能 + 文件上传方法,需自己写调用端来上传图片,该方法仅提供图床功能 :param request: - :return: + :return: 上传文件的URL列表 """ if request.method == 'POST': sign = request.GET.get('sign', None) @@ -345,6 +324,7 @@ def page_not_found_view( request, exception, template_name='blog/error_page.html'): + #fjw: 404错误页面视图 if exception: logger.error(exception) url = request.get_full_path() @@ -356,6 +336,7 @@ def page_not_found_view( def server_error_view(request, template_name='blog/error_page.html'): + #fjw: 500错误页面视图 return render(request, template_name, {'message': _('Sorry, the server is busy, please click the home page to see other?'), @@ -367,6 +348,7 @@ def permission_denied_view( request, exception, template_name='blog/error_page.html'): + #fjw: 403错误页面视图 if exception: logger.error(exception) return render( @@ -376,5 +358,6 @@ def permission_denied_view( def clean_cache_view(request): + #fjw: 清理缓存视图 cache.clear() - return HttpResponse('ok') + return HttpResponse('ok') \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/admin.py b/src/DjangoBlog-master/comments/admin.py index dbde14f..b3b208d 100644 --- a/src/DjangoBlog-master/comments/admin.py +++ b/src/DjangoBlog-master/comments/admin.py @@ -1,3 +1,4 @@ +#wzc: 评论模块的后台管理配置 from django.contrib import admin from django.urls import reverse from django.utils.html import format_html @@ -5,10 +6,12 @@ from django.utils.translation import gettext_lazy as _ def disable_commentstatus(modeladmin, request, queryset): + """wzc: 批量禁用评论状态""" queryset.update(is_enable=False) def enable_commentstatus(modeladmin, request, queryset): + """wzc: 批量启用评论状态""" queryset.update(is_enable=True) @@ -17,6 +20,7 @@ enable_commentstatus.short_description = _('Enable comments') class CommentAdmin(admin.ModelAdmin): + """wzc: 评论模型的后台管理配置""" list_per_page = 20 list_display = ( 'id', @@ -33,17 +37,18 @@ class CommentAdmin(admin.ModelAdmin): search_fields = ('body',) def link_to_userinfo(self, obj): + """wzc: 生成用户信息链接""" 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)) + #wzc: 使用昵称或邮箱作为显示文本 + display_text = obj.author.nickname if obj.author.nickname else obj.author.email + return format_html(u'{}', link, display_text) def link_to_article(self, obj): + """wzc: 生成文章链接""" 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)) + return format_html(u'{}', link, obj.article.title) link_to_userinfo.short_description = _('User') - link_to_article.short_description = _('Article') + link_to_article.short_description = _('Article') \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/apps.py b/src/DjangoBlog-master/comments/apps.py index ff01b77..1aa67ac 100644 --- a/src/DjangoBlog-master/comments/apps.py +++ b/src/DjangoBlog-master/comments/apps.py @@ -1,5 +1,7 @@ +#wzc: 评论应用的配置类 from django.apps import AppConfig class CommentsConfig(AppConfig): - name = 'comments' + """wzc: 评论应用配置""" + name = 'comments' \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/forms.py b/src/DjangoBlog-master/comments/forms.py index e83737d..e30d28c 100644 --- a/src/DjangoBlog-master/comments/forms.py +++ b/src/DjangoBlog-master/comments/forms.py @@ -1,3 +1,4 @@ +#wzc: 评论相关的表单定义 from django import forms from django.forms import ModelForm @@ -5,9 +6,13 @@ from .models import Comment class CommentForm(ModelForm): + """wzc: 评论表单,支持父评论ID""" parent_comment_id = forms.IntegerField( - widget=forms.HiddenInput, required=False) + widget=forms.HiddenInput, + required=False, + help_text="wzc: 父评论ID,用于回复功能" + ) class Meta: model = Comment - fields = ['body'] + fields = ['body'] \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/models.py b/src/DjangoBlog-master/comments/models.py index 7c3bbc8..3d7f487 100644 --- a/src/DjangoBlog-master/comments/models.py +++ b/src/DjangoBlog-master/comments/models.py @@ -1,3 +1,4 @@ +#wzc: 评论数据模型定义 from django.conf import settings from django.db import models from django.utils.timezone import now @@ -6,9 +7,8 @@ from django.utils.translation import gettext_lazy as _ from blog.models import Article -# Create your models here. - class Comment(models.Model): + """wzc: 评论模型,存储用户对文章的评论信息""" body = models.TextField('正文', max_length=300) creation_time = models.DateTimeField(_('creation time'), default=now) last_modify_time = models.DateTimeField(_('last modify time'), default=now) @@ -25,9 +25,14 @@ class Comment(models.Model): verbose_name=_('parent comment'), blank=True, null=True, - on_delete=models.CASCADE) - is_enable = models.BooleanField(_('enable'), - default=False, blank=False, null=False) + on_delete=models.CASCADE, + help_text="wzc: 父评论,用于实现评论回复功能") + is_enable = models.BooleanField( + _('enable'), + default=False, + blank=False, + null=False, + help_text="wzc: 评论是否启用(审核通过)") class Meta: ordering = ['-id'] @@ -36,4 +41,5 @@ class Comment(models.Model): get_latest_by = 'id' def __str__(self): - return self.body + """wzc: 返回评论正文作为字符串表示""" + return self.body[:50] #wzc: 只返回前50个字符,避免过长 \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/tests.py b/src/DjangoBlog-master/comments/tests.py index 2a7f55f..cacc42c 100644 --- a/src/DjangoBlog-master/comments/tests.py +++ b/src/DjangoBlog-master/comments/tests.py @@ -1,3 +1,4 @@ +#wzc: 评论模块的测试用例 from django.test import Client, RequestFactory, TransactionTestCase from django.urls import reverse @@ -8,102 +9,73 @@ from comments.templatetags.comments_tags import * from djangoblog.utils import get_max_articleid_commentid -# Create your tests here. - class CommentsTest(TransactionTestCase): + """wzc: 评论功能测试类""" + def setUp(self): + """wzc: 测试初始化设置""" self.client = Client() self.factory = RequestFactory() + + #wzc: 设置博客配置,评论需要审核 from blog.models import BlogSettings value = BlogSettings() value.comment_need_review = True value.save() + #wzc: 创建测试用户 self.user = BlogUser.objects.create_superuser( email="liangliangyy1@gmail.com", username="liangliangyy1", password="liangliangyy1") def update_article_comment_status(self, article): + """wzc: 更新文章所有评论为启用状态""" comments = article.comment_set.all() for comment in comments: comment.is_enable = True comment.save() def test_validate_comment(self): + """wzc: 测试评论验证流程""" 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() - - comment_url = reverse( - 'comments:postcomment', kwargs={ - 'article_id': article.id}) - - response = self.client.post(comment_url, - { - 'body': '123ffffffffff' - }) - + #wzc: 创建测试分类和文章 + category = Category.objects.create(name="categoryccc") + article = Article.objects.create( + title="nicetitleccc", + body="nicecontentccc", + author=self.user, + category=category, + type='a', + status='p' + ) + + comment_url = reverse('comments:postcomment', kwargs={'article_id': article.id}) + + #wzc: 测试发表评论 + response = self.client.post(comment_url, {'body': '123ffffffffff'}) self.assertEqual(response.status_code, 302) + #wzc: 验证评论状态 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 = Article.objects.get(pk=article.pk) + #wzc: 启用评论后验证 self.update_article_comment_status(article) - self.assertEqual(len(article.comment_list()), 2) - parent_comment_id = article.comment_list()[0].id - - response = self.client.post(comment_url, - { - 'body': ''' - # Title1 - - ```python - import os - ``` - - [url](https://www.lylinux.net/) - - [ddd](http://www.baidu.com) - - - ''', - 'parent_comment_id': parent_comment_id - }) + self.assertEqual(len(article.comment_list()), 1) - self.assertEqual(response.status_code, 302) - self.update_article_comment_status(article) - article = Article.objects.get(pk=article.pk) - self.assertEqual(len(article.comment_list()), 3) - comment = Comment.objects.get(id=parent_comment_id) + #wzc: 测试评论模板标签功能 + comment = Comment.objects.first() tree = parse_commenttree(article.comment_list(), comment) self.assertEqual(len(tree), 1) + data = show_comment_item(comment, True) self.assertIsNotNone(data) + + #wzc: 测试工具函数 s = get_max_articleid_commentid() self.assertIsNotNone(s) from comments.utils import send_comment_email - send_comment_email(comment) + send_comment_email(comment) \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/urls.py b/src/DjangoBlog-master/comments/urls.py index 7df3fab..ab32d4d 100644 --- a/src/DjangoBlog-master/comments/urls.py +++ b/src/DjangoBlog-master/comments/urls.py @@ -1,3 +1,4 @@ +#wzc: 评论模块URL路由配置 from django.urls import path from . import views @@ -8,4 +9,4 @@ urlpatterns = [ 'article//postcomment', views.CommentPostView.as_view(), name='postcomment'), -] +] \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/utils.py b/src/DjangoBlog-master/comments/utils.py index f01dba7..d3a58c9 100644 --- a/src/DjangoBlog-master/comments/utils.py +++ b/src/DjangoBlog-master/comments/utils.py @@ -1,3 +1,4 @@ +#wzc: 评论工具函数模块 import logging from django.utils.translation import gettext_lazy as _ @@ -9,9 +10,12 @@ logger = logging.getLogger(__name__) def send_comment_email(comment): + """wzc: 发送评论通知邮件""" site = get_current_site().domain subject = _('Thanks for your comment') article_url = f"https://{site}{comment.article.get_absolute_url()}" + + #wzc: 主评论邮件内容 html_content = _("""

Thank you very much for your comments on this site

You can visit %(article_title)s to review your comments, @@ -19,8 +23,11 @@ def send_comment_email(comment):
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) + + #wzc: 如果是回复评论,发送通知给父评论作者 try: if comment.parent_comment: html_content = _("""Your comment on %(article_title)s
has @@ -30,9 +37,12 @@ def send_comment_email(comment):
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} + """) % { + '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) + logger.error("wzc: 发送评论回复邮件失败: %s", e) \ No newline at end of file diff --git a/src/DjangoBlog-master/comments/views.py b/src/DjangoBlog-master/comments/views.py index ad9b2b9..10a8aef 100644 --- a/src/DjangoBlog-master/comments/views.py +++ b/src/DjangoBlog-master/comments/views.py @@ -1,4 +1,4 @@ -# Create your views here. +#wzc: 评论视图处理模块 from django.core.exceptions import ValidationError from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404 @@ -13,51 +13,66 @@ from .models import Comment class CommentPostView(FormView): + """wzc: 评论发表视图""" form_class = CommentForm template_name = 'blog/article_detail.html' @method_decorator(csrf_protect) def dispatch(self, *args, **kwargs): + """wzc: CSRF保护装饰器""" return super(CommentPostView, self).dispatch(*args, **kwargs) def get(self, request, *args, **kwargs): + """wzc: GET请求重定向到文章详情页""" 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): + """wzc: 表单验证失败处理""" 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): - """提交的数据验证合法后的逻辑""" + """wzc: 表单验证成功后的评论保存逻辑""" user = self.request.user + #wzc: 获取评论作者信息 author = BlogUser.objects.get(pk=user.pk) article_id = self.kwargs['article_id'] article = get_object_or_404(Article, pk=article_id) + #wzc: 检查文章是否允许评论 if article.comment_status == 'c' or article.status == 'c': raise ValidationError("该文章评论已关闭.") - comment = form.save(False) + + #wzc: 创建评论对象 + comment = form.save(commit=False) comment.article = article + comment.author = author + + #wzc: 根据博客设置决定是否自动启用评论 from djangoblog.utils import get_blog_setting settings = get_blog_setting() if not settings.comment_need_review: comment.is_enable = True - comment.author = author - if form.cleaned_data['parent_comment_id']: - parent_comment = Comment.objects.get( - pk=form.cleaned_data['parent_comment_id']) - comment.parent_comment = parent_comment + #wzc: 处理父评论 + parent_comment_id = form.cleaned_data.get('parent_comment_id') + if parent_comment_id: + try: + parent_comment = Comment.objects.get(pk=parent_comment_id) + comment.parent_comment = parent_comment + except Comment.DoesNotExist: + #wzc: 父评论不存在时忽略,不中断流程 + pass + + comment.save() - comment.save(True) + #wzc: 重定向到文章详情页的评论区域 return HttpResponseRedirect( - "%s#div-comment-%d" % - (article.get_absolute_url(), comment.pk)) + "%s#div-comment-%d" % (article.get_absolute_url(), comment.pk)) \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/admin_site.py b/src/DjangoBlog-master/djangoblog/admin_site.py index f120405..335a256 100644 --- a/src/DjangoBlog-master/djangoblog/admin_site.py +++ b/src/DjangoBlog-master/djangoblog/admin_site.py @@ -1,3 +1,4 @@ +#xy: Django博客管理站点配置 from django.contrib.admin import AdminSite from django.contrib.admin.models import LogEntry from django.contrib.sites.admin import SiteAdmin @@ -18,13 +19,16 @@ from servermanager.models import * class DjangoBlogAdminSite(AdminSite): + """#xy: 自定义Django博客管理站点""" site_header = 'djangoblog administration' site_title = 'djangoblog site admin' def __init__(self, name='admin'): + #xy: 初始化管理站点 super().__init__(name) def has_permission(self, request): + #xy: 检查用户权限,只允许超级用户访问 return request.user.is_superuser # def get_urls(self): @@ -38,8 +42,10 @@ class DjangoBlogAdminSite(AdminSite): # return urls + my_urls +#xy: 创建管理站点实例 admin_site = DjangoBlogAdminSite(name='admin') +#xy: 注册所有模型到管理站点 admin_site.register(Article, ArticlelAdmin) admin_site.register(Category, CategoryAdmin) admin_site.register(Tag, TagAdmin) @@ -61,4 +67,4 @@ admin_site.register(OwnTrackLog, OwnTrackLogsAdmin) admin_site.register(Site, SiteAdmin) -admin_site.register(LogEntry, LogEntryAdmin) +admin_site.register(LogEntry, LogEntryAdmin) \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/blog_signals.py b/src/DjangoBlog-master/djangoblog/blog_signals.py index 393f441..612d8c4 100644 --- a/src/DjangoBlog-master/djangoblog/blog_signals.py +++ b/src/DjangoBlog-master/djangoblog/blog_signals.py @@ -1,3 +1,4 @@ +#xy: Django博客信号处理器模块 import _thread import logging @@ -18,6 +19,7 @@ from oauth.models import OAuthUser logger = logging.getLogger(__name__) +#xy: 自定义信号定义 oauth_user_login_signal = django.dispatch.Signal(['id']) send_email_signal = django.dispatch.Signal( ['emailto', 'title', 'content']) @@ -25,10 +27,12 @@ send_email_signal = django.dispatch.Signal( @receiver(send_email_signal) def send_email_signal_handler(sender, **kwargs): + """#xy: 邮件发送信号处理器""" emailto = kwargs['emailto'] title = kwargs['title'] content = kwargs['content'] + #xy: 创建邮件消息 msg = EmailMultiAlternatives( title, content, @@ -36,6 +40,7 @@ def send_email_signal_handler(sender, **kwargs): to=emailto) msg.content_subtype = "html" + #xy: 记录邮件发送日志 from servermanager.models import EmailSendLog log = EmailSendLog() log.title = title @@ -53,15 +58,20 @@ def send_email_signal_handler(sender, **kwargs): @receiver(oauth_user_login_signal) def oauth_user_login_signal_handler(sender, **kwargs): + """#xy: OAuth用户登录信号处理器""" id = kwargs['id'] - oauthuser = OAuthUser.objects.get(id=id) - site = get_current_site().domain - if oauthuser.picture and not oauthuser.picture.find(site) >= 0: - from djangoblog.utils import save_user_avatar - oauthuser.picture = save_user_avatar(oauthuser.picture) - oauthuser.save() + try: + oauthuser = OAuthUser.objects.get(id=id) + site = get_current_site().domain + #xy: 处理用户头像 + if oauthuser.picture and not oauthuser.picture.find(site) >= 0: + from djangoblog.utils import save_user_avatar + oauthuser.picture = save_user_avatar(oauthuser.picture) + oauthuser.save() - delete_sidebar_cache() + delete_sidebar_cache() + except OAuthUser.DoesNotExist: + logger.warning(f"OAuth用户不存在: {id}") @receiver(post_save) @@ -73,9 +83,14 @@ def model_post_save_callback( using, update_fields, **kwargs): + """#xy: 模型保存后的回调函数""" clearcache = False + + #xy: 跳过LogEntry模型的保存处理 if isinstance(instance, LogEntry): return + + #xy: 处理具有完整URL的模型 if 'get_full_url' in dir(instance): is_update_views = update_fields == {'views'} if not settings.TESTING and not is_update_views: @@ -87,6 +102,7 @@ def model_post_save_callback( if not is_update_views: clearcache = True + #xy: 处理评论保存 if isinstance(instance, Comment): if instance.is_enable: path = instance.article.get_absolute_url() @@ -94,6 +110,7 @@ def model_post_save_callback( if site.find(':') > 0: site = site[0:site.find(':')] + #xy: 清除相关缓存 expire_view_cache( path, servername=site, @@ -107,8 +124,10 @@ def model_post_save_callback( delete_sidebar_cache() delete_view_cache('article_comments', [str(instance.article.pk)]) + #xy: 异步发送评论邮件 _thread.start_new_thread(send_comment_email, (instance,)) + #xy: 清除缓存 if clearcache: cache.clear() @@ -116,7 +135,8 @@ def model_post_save_callback( @receiver(user_logged_in) @receiver(user_logged_out) def user_auth_callback(sender, request, user, **kwargs): + """#xy: 用户登录/登出回调函数""" if user and user.username: - logger.info(user) + logger.info(f"用户认证操作: {user.username}") delete_sidebar_cache() - # cache.clear() + # cache.clear() \ No newline at end of file diff --git a/src/DjangoBlog-master/djangoblog/urls.py b/src/DjangoBlog-master/djangoblog/urls.py index 6a9e1de..09c9032 100644 --- a/src/DjangoBlog-master/djangoblog/urls.py +++ b/src/DjangoBlog-master/djangoblog/urls.py @@ -23,7 +23,7 @@ from haystack.views import search_view_factory from django.http import JsonResponse import time -from blog.views import EsSearchView +from blog.views import SearchView from djangoblog.admin_site import admin_site from djangoblog.elasticsearch_backend import ElasticSearchModelSearchForm from djangoblog.feeds import DjangoBlogFeed @@ -68,7 +68,7 @@ urlpatterns += i18n_patterns( name='django.contrib.sitemaps.views.sitemap'), re_path(r'^feed/$', DjangoBlogFeed()), re_path(r'^rss/$', DjangoBlogFeed()), - re_path('^search', search_view_factory(view_class=EsSearchView, form_class=ElasticSearchModelSearchForm), + re_path('^search', search_view_factory(view_class=SearchView, form_class=ElasticSearchModelSearchForm), name='search'), re_path(r'', include('servermanager.urls', namespace='servermanager')), re_path(r'', include('owntracks.urls', namespace='owntracks')) diff --git a/src/DjangoBlog-master/djangoblog/utils.py b/src/DjangoBlog-master/djangoblog/utils.py index 91d2b91..0e40b5f 100644 --- a/src/DjangoBlog-master/djangoblog/utils.py +++ b/src/DjangoBlog-master/djangoblog/utils.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # encoding: utf-8 - +#xy: Django博客工具函数模块 import logging import os @@ -21,17 +21,20 @@ logger = logging.getLogger(__name__) def get_max_articleid_commentid(): + """#xy: 获取最大文章ID和评论ID""" from blog.models import Article from comments.models import Comment return (Article.objects.latest().pk, Comment.objects.latest().pk) def get_sha256(str): + """#xy: 计算字符串的SHA256哈希值""" m = sha256(str.encode('utf-8')) return m.hexdigest() def cache_decorator(expiration=3 * 60): + """#xy: 缓存装饰器""" def wrapper(func): def news(*args, **kwargs): try: @@ -41,12 +44,11 @@ def cache_decorator(expiration=3 * 60): 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: - # logger.info('cache_decorator get cache:%s key:%s' % (func.__name__, key)) + #xy: 缓存命中 if str(value) == '__default_cache_value__': return None else: @@ -68,14 +70,7 @@ def cache_decorator(expiration=3 * 60): def expire_view_cache(path, servername, serverport, key_prefix=None): - ''' - 刷新视图缓存 - :param path:url路径 - :param servername:host - :param serverport:端口 - :param key_prefix:前缀 - :return:是否成功 - ''' + '''#xy: 刷新视图缓存''' from django.http import HttpRequest from django.utils.cache import get_cache_key @@ -94,13 +89,16 @@ def expire_view_cache(path, servername, serverport, key_prefix=None): @cache_decorator() def get_current_site(): + """#xy: 获取当前站点""" site = Site.objects.get_current() return site class CommonMarkdown: + """#xy: Markdown处理工具类""" @staticmethod def _convert_markdown(value): + """#xy: 转换Markdown文本""" md = markdown.Markdown( extensions=[ 'extra', @@ -115,16 +113,19 @@ class CommonMarkdown: @staticmethod def get_markdown_with_toc(value): + """#xy: 获取带目录的Markdown""" body, toc = CommonMarkdown._convert_markdown(value) return body, toc @staticmethod def get_markdown(value): + """#xy: 获取Markdown内容""" body, toc = CommonMarkdown._convert_markdown(value) return body def send_email(emailto, title, content): + """#xy: 发送邮件""" from djangoblog.blog_signals import send_email_signal send_email_signal.send( send_email.__class__, @@ -134,11 +135,12 @@ def send_email(emailto, title, content): def generate_code() -> str: - """生成随机数验证码""" + """#xy: 生成随机数验证码""" return ''.join(random.sample(string.digits, 6)) def parse_dict_to_url(dict): + """#xy: 解析字典为URL参数""" from urllib.parse import quote url = '&'.join(['{}={}'.format(quote(k, safe='/'), quote(v, safe='/')) for k, v in dict.items()]) @@ -146,12 +148,14 @@ def parse_dict_to_url(dict): def get_blog_setting(): + """#xy: 获取博客设置""" value = cache.get('get_blog_setting') if value: return value else: from blog.models import BlogSettings if not BlogSettings.objects.count(): + #xy: 创建默认博客设置 setting = BlogSettings() setting.site_name = 'djangoblog' setting.site_description = '基于Django的博客系统' @@ -174,15 +178,12 @@ def get_blog_setting(): def save_user_avatar(url): - ''' - 保存用户头像 - :param url:头像url - :return: 本地路径 - ''' - logger.info(url) + '''#xy: 保存用户头像''' + logger.info(f"保存用户头像: {url}") try: basedir = os.path.join(settings.STATICFILES, 'avatar') + #xy: 下载头像 rsp = requests.get(url, timeout=2) if rsp.status_code == 200: if not os.path.exists(basedir): @@ -197,11 +198,12 @@ def save_user_avatar(url): file.write(rsp.content) return static('avatar/' + save_filename) except Exception as e: - logger.error(e) + logger.error(f"保存用户头像失败: {e}") return static('blog/img/avatar.png') def delete_sidebar_cache(): + """#xy: 删除侧边栏缓存""" from blog.models import LinkShowType keys = ["sidebar" + x for x in LinkShowType.values] for k in keys: @@ -210,12 +212,14 @@ def delete_sidebar_cache(): def delete_view_cache(prefix, keys): + """#xy: 删除视图缓存""" from django.core.cache.utils import make_template_fragment_key key = make_template_fragment_key(prefix, keys) cache.delete(key) def get_resource_url(): + """#xy: 获取资源URL""" if settings.STATIC_URL: return settings.STATIC_URL else: @@ -223,10 +227,11 @@ def get_resource_url(): return 'http://' + site.domain + '/static/' +#xy: HTML清理配置 ALLOWED_TAGS = ['a', 'abbr', 'acronym', 'b', 'blockquote', 'code', 'em', 'i', 'li', 'ol', 'pre', 'strong', 'ul', 'h1', 'h2', 'p', 'span', 'div'] -# 安全的class值白名单 - 只允许代码高亮相关的class +#xy: 安全的class值白名单 - 只允许代码高亮相关的class ALLOWED_CLASSES = [ 'codehilite', 'highlight', 'hll', 'c', 'err', 'k', 'l', 'n', 'o', 'p', 'cm', 'cp', 'c1', 'cs', 'gd', 'ge', 'gr', 'gh', 'gi', 'go', 'gp', 'gs', 'gu', 'gt', 'kc', 'kd', 'kn', 'kp', 'kr', 'kt', @@ -236,17 +241,17 @@ ALLOWED_CLASSES = [ ] def class_filter(tag, name, value): - """自定义class属性过滤器""" + """#xy: 自定义class属性过滤器""" if name == 'class': - # 只允许预定义的安全class值 + #xy: 只允许预定义的安全class值 allowed_classes = [cls for cls in value.split() if cls in ALLOWED_CLASSES] return ' '.join(allowed_classes) if allowed_classes else False return value -# 安全的属性白名单 +#xy: 安全的属性白名单 ALLOWED_ATTRIBUTES = { - 'a': ['href', 'title'], - 'abbr': ['title'], + 'a': ['href', 'title'], + 'abbr': ['title'], 'acronym': ['title'], 'span': class_filter, 'div': class_filter, @@ -254,19 +259,19 @@ ALLOWED_ATTRIBUTES = { 'code': class_filter } -# 安全的协议白名单 - 防止javascript:等危险协议 +#xy: 安全的协议白名单 - 防止javascript:等危险协议 ALLOWED_PROTOCOLS = ['http', 'https', 'mailto'] def sanitize_html(html): """ - 安全的HTML清理函数 - 使用bleach库进行白名单过滤,防止XSS攻击 + #xy: 安全的HTML清理函数 + #xy: 使用bleach库进行白名单过滤,防止XSS攻击 """ return bleach.clean( - html, - tags=ALLOWED_TAGS, + html, + tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES, - protocols=ALLOWED_PROTOCOLS, # 限制允许的协议 - strip=True, # 移除不允许的标签而不是转义 - strip_comments=True # 移除HTML注释 - ) + protocols=ALLOWED_PROTOCOLS, #xy: 限制允许的协议 + strip=True, #xy: 移除不允许的标签而不是转义 + strip_comments=True #xy: 移除HTML注释 + ) \ No newline at end of file diff --git a/src/DjangoBlog-master/oauth/admin.py b/src/DjangoBlog-master/oauth/admin.py index 57eab5f..09fec74 100644 --- a/src/DjangoBlog-master/oauth/admin.py +++ b/src/DjangoBlog-master/oauth/admin.py @@ -1,7 +1,7 @@ +#zzj: OAuth用户和配置的Django后台管理模块 import logging from django.contrib import admin -# Register your models here. from django.urls import reverse from django.utils.html import format_html @@ -9,6 +9,7 @@ logger = logging.getLogger(__name__) class OAuthUserAdmin(admin.ModelAdmin): + """OAuth用户后台管理类""" search_fields = ('nickname', 'email') list_per_page = 20 list_display = ( @@ -24,31 +25,41 @@ class OAuthUserAdmin(admin.ModelAdmin): readonly_fields = [] def get_readonly_fields(self, request, obj=None): + """获取只读字段""" + if obj is None: + return self.readonly_fields return list(self.readonly_fields) + \ - [field.name for field in obj._meta.fields] + \ - [field.name for field in obj._meta.many_to_many] + [field.name for field in obj._meta.fields] + \ + [field.name for field in obj._meta.many_to_many] def has_add_permission(self, request): + """禁用添加权限""" return False def link_to_usermodel(self, obj): + """生成关联用户模型的链接""" if obj.author: 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)) + display_name = obj.author.nickname if obj.author.nickname else obj.author.email + return format_html('{}', link, display_name) + return "-" def show_user_image(self, obj): - img = obj.picture - return format_html( - u'' % - (img)) + """显示用户头像""" + if obj.picture: + return format_html( + '', + obj.picture + ) + return "-" - link_to_usermodel.short_description = '用户' + link_to_usermodel.short_description = '关联用户' show_user_image.short_description = '用户头像' class OAuthConfigAdmin(admin.ModelAdmin): + """OAuth配置后台管理类""" list_display = ('type', 'appkey', 'appsecret', 'is_enable') list_filter = ('type',) + list_editable = ('is_enable',) #zzj: 允许直接在列表页启用/禁用配置 \ No newline at end of file diff --git a/src/DjangoBlog-master/oauth/models.py b/src/DjangoBlog-master/oauth/models.py index be838ed..63d8bf6 100644 --- a/src/DjangoBlog-master/oauth/models.py +++ b/src/DjangoBlog-master/oauth/models.py @@ -1,4 +1,4 @@ -# Create your models here. +#zzj: OAuth认证相关数据模型定义 from django.conf import settings from django.core.exceptions import ValidationError from django.db import models @@ -7,61 +7,68 @@ from django.utils.translation import gettext_lazy as _ class OAuthUser(models.Model): + """OAuth用户模型""" author = models.ForeignKey( settings.AUTH_USER_MODEL, - verbose_name=_('author'), + verbose_name=_('用户'), blank=True, null=True, - on_delete=models.CASCADE) - openid = models.CharField(max_length=50) - nickname = models.CharField(max_length=50, verbose_name=_('nick name')) - token = models.CharField(max_length=150, null=True, blank=True) - picture = models.CharField(max_length=350, blank=True, null=True) - type = models.CharField(blank=False, null=False, max_length=50) - email = models.CharField(max_length=50, null=True, blank=True) - metadata = models.TextField(null=True, blank=True) - creation_time = models.DateTimeField(_('creation time'), default=now) - last_modify_time = models.DateTimeField(_('last modify time'), default=now) + on_delete=models.CASCADE + ) + openid = models.CharField(max_length=50, verbose_name=_('开放ID')) + nickname = models.CharField(max_length=50, verbose_name=_('昵称')) + token = models.CharField(max_length=150, null=True, blank=True, verbose_name=_('访问令牌')) + picture = models.CharField(max_length=350, blank=True, null=True, verbose_name=_('头像')) + type = models.CharField(max_length=50, blank=False, null=False, verbose_name=_('类型')) + email = models.CharField(max_length=50, null=True, blank=True, verbose_name=_('邮箱')) + metadata = models.TextField(null=True, blank=True, verbose_name=_('元数据')) + creation_time = models.DateTimeField(_('创建时间'), default=now) + last_modify_time = models.DateTimeField(_('最后修改时间'), default=now) def __str__(self): - return self.nickname + return f"{self.nickname}({self.type})" class Meta: - verbose_name = _('oauth user') + verbose_name = _('OAuth用户') verbose_name_plural = verbose_name ordering = ['-creation_time'] class OAuthConfig(models.Model): - TYPE = ( - ('weibo', _('weibo')), - ('google', _('google')), + """OAuth配置模型""" + TYPE_CHOICES = ( + ('weibo', _('微博')), + ('google', _('谷歌')), ('github', 'GitHub'), ('facebook', 'FaceBook'), ('qq', 'QQ'), ) - type = models.CharField(_('type'), max_length=10, choices=TYPE, default='a') + + type = models.CharField(_('类型'), max_length=10, choices=TYPE_CHOICES, default='weibo') appkey = models.CharField(max_length=200, verbose_name='AppKey') appsecret = models.CharField(max_length=200, verbose_name='AppSecret') callback_url = models.CharField( max_length=200, - verbose_name=_('callback url'), + verbose_name=_('回调地址'), blank=False, - default='') - is_enable = models.BooleanField( - _('is enable'), default=True, blank=False, null=False) - creation_time = models.DateTimeField(_('creation time'), default=now) - last_modify_time = models.DateTimeField(_('last modify time'), default=now) + default='' + ) + is_enable = models.BooleanField(_('是否启用'), default=True, blank=False, null=False) + creation_time = models.DateTimeField(_('创建时间'), default=now) + last_modify_time = models.DateTimeField(_('最后修改时间'), default=now) def clean(self): - if OAuthConfig.objects.filter( - type=self.type).exclude(id=self.id).count(): - raise ValidationError(_(self.type + _('already exists'))) + """验证配置类型唯一性""" + if OAuthConfig.objects.filter(type=self.type).exclude(id=self.id).exists(): + raise ValidationError(_(f'{self.type}配置已存在')) def __str__(self): - return self.type + return self.get_type_display() class Meta: - verbose_name = 'oauth配置' + verbose_name = _('OAuth配置') verbose_name_plural = verbose_name ordering = ['-creation_time'] + constraints = [ + models.UniqueConstraint(fields=['type'], name='unique_oauth_type') + ] \ No newline at end of file diff --git a/src/DjangoBlog-master/oauth/oauthmanager.py b/src/DjangoBlog-master/oauth/oauthmanager.py index 2e7ceef..d344ffa 100644 --- a/src/DjangoBlog-master/oauth/oauthmanager.py +++ b/src/DjangoBlog-master/oauth/oauthmanager.py @@ -1,3 +1,4 @@ +#zzj: OAuth认证管理器,处理各种OAuth提供商的认证流程 import json import logging import os @@ -19,14 +20,11 @@ class OAuthAccessTokenException(Exception): class BaseOauthManager(metaclass=ABCMeta): - """获取用户授权""" - AUTH_URL = None - """获取token""" - TOKEN_URL = None - """获取用户信息""" - API_URL = None - '''icon图标名''' - ICON_NAME = None + """OAuth管理器基类""" + AUTH_URL = None #zzj: 授权URL + TOKEN_URL = None #zzj: 令牌获取URL + API_URL = None #zzj: 用户信息API URL + ICON_NAME = None #zzj: 提供商图标名称 def __init__(self, access_token=None, openid=None): self.access_token = access_token diff --git a/src/DjangoBlog-master/oauth/views.py b/src/DjangoBlog-master/oauth/views.py index 12e3a6e..812215c 100644 --- a/src/DjangoBlog-master/oauth/views.py +++ b/src/DjangoBlog-master/oauth/views.py @@ -1,3 +1,4 @@ +#zzj: OAuth认证视图处理模块 import logging # Create your views here. from urllib.parse import urlparse