diff --git a/src/DjangoBlog/comments/admin.py b/src/DjangoBlog/comments/admin.py index 1e9bc73..9a0e75a 100644 --- a/src/DjangoBlog/comments/admin.py +++ b/src/DjangoBlog/comments/admin.py @@ -1,49 +1,81 @@ +# zy: 评论管理模块 - 配置Django后台管理中评论模型的显示和操作功能 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 _ +# zy: 禁用评论状态 - 管理员动作函数,用于批量禁用评论 def disable_commentstatus(queryset): + # zy: 将选中的评论集的is_enable字段更新为False(禁用) queryset.update(is_enable=False) +# zy: 启用评论状态 - 管理员动作函数,用于批量启用评论 def enable_commentstatus(queryset): + # zy: 将选中的评论集的is_enable字段更新为True(启用) queryset.update(is_enable=True) +# zy: 设置动作在后台显示的描述文字(支持国际化) disable_commentstatus.short_description = _('Disable comments') enable_commentstatus.short_description = _('Enable comments') +# zy: 评论管理类 - 自定义Django后台的评论管理界面 class CommentAdmin(admin.ModelAdmin): + # zy: 设置列表页每页显示20条评论 list_per_page = 20 + + # zy: 定义列表页显示的字段列 list_display = ( - 'id', - 'body', - 'link_to_userinfo', - 'link_to_article', - 'is_enable', - 'creation_time') + 'id', # zy: 评论ID + 'body', # zy: 评论内容 + 'link_to_userinfo', # zy: 自定义方法 - 用户信息链接 + 'link_to_article', # zy: 自定义方法 - 文章链接 + 'is_enable', # zy: 是否启用状态 + 'creation_time' # zy: 创建时间 + ) + + # zy: 设置哪些字段可以作为链接点击进入编辑页 list_display_links = ('id', 'body', 'is_enable') - list_filter = ('is_enable',) + + # zy: 设置右侧过滤侧边栏的过滤条件 + list_filter = ('is_enable',) # zy: 按启用状态过滤 + + # zy: 排除在编辑表单中显示的字段(系统自动管理的字段) exclude = ('creation_time', 'last_modify_time') + + # zy: 定义批量操作动作列表 actions = [disable_commentstatus, enable_commentstatus] + + # zy: 设置使用弹出窗口选择关联对象的字段(优化性能) raw_id_fields = ('author', 'article') - search_fields = ('body',) + # zy: 设置可搜索的字段 + search_fields = ('body',) # zy: 按评论内容搜索 + + # zy: 自定义方法 - 生成用户信息链接 def link_to_userinfo(self, obj): + # zy: 获取作者模型的app名称和模型名称 info = (obj.author._meta.app_label, obj.author._meta.model_name) + # zy: 生成作者编辑页面的URL link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,)) + # zy: 返回HTML链接,显示用户昵称或邮箱 return format_html( u'%s' % (link, obj.author.nickname if obj.author.nickname else obj.author.email)) + # zy: 自定义方法 - 生成文章链接 def link_to_article(self, obj): + # zy: 获取文章模型的app名称和模型名称 info = (obj.article._meta.app_label, obj.article._meta.model_name) + # zy: 生成文章编辑页面的URL link = reverse('admin:%s_%s_change' % info, args=(obj.article.id,)) + # zy: 返回HTML链接,显示文章标题 return format_html( u'%s' % (link, obj.article.title)) + # zy: 设置自定义方法在列表页显示的列标题(支持国际化) 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/comments/apps.py b/src/DjangoBlog/comments/apps.py index ff01b77..a6fbd56 100644 --- a/src/DjangoBlog/comments/apps.py +++ b/src/DjangoBlog/comments/apps.py @@ -1,5 +1,8 @@ +# zy: 评论应用配置模块 - 定义comments应用的配置信息 from django.apps import AppConfig - +# zy: 评论应用配置类 - 继承自Django的AppConfig基类 class CommentsConfig(AppConfig): + # zy: 应用名称 - 指定Django内部使用的应用标识 + # 这个名称应该与INSTALLED_APPS中的名称一致 name = 'comments' diff --git a/src/DjangoBlog/comments/forms.py b/src/DjangoBlog/comments/forms.py index e83737d..8f04b50 100644 --- a/src/DjangoBlog/comments/forms.py +++ b/src/DjangoBlog/comments/forms.py @@ -1,13 +1,17 @@ +# zy: 评论表单模块 - 定义评论相关的表单类和验证逻辑 from django import forms from django.forms import ModelForm - +# zy: 导入评论模型,用于创建基于模型的自定义表单 from .models import Comment - +# zy: 评论表单类 - 继承自ModelForm,用于处理评论的创建和验证 class CommentForm(ModelForm): + # zy: 父评论ID字段 - 用于实现评论回复功能 + # 这是一个隐藏字段,不在页面显示,用于记录回复的父评论 parent_comment_id = forms.IntegerField( widget=forms.HiddenInput, required=False) + # zy: 表单元数据配置类 - 定义表单与模型的关联关系 class Meta: model = Comment fields = ['body'] diff --git a/src/DjangoBlog/comments/models.py b/src/DjangoBlog/comments/models.py index 7c3bbc8..12e7509 100644 --- a/src/DjangoBlog/comments/models.py +++ b/src/DjangoBlog/comments/models.py @@ -1,38 +1,44 @@ +# zy: 评论数据模型模块 - 定义评论的数据结构和数据库表结构 from django.conf import settings from django.db import models from django.utils.timezone import now from django.utils.translation import gettext_lazy as _ - +# zy: 导入博客文章模型,用于建立评论与文章的关联 from blog.models import Article -# Create your models here. +# zy: 评论数据模型类 - 继承自Django的Model基类,对应数据库中的评论表 class Comment(models.Model): body = models.TextField('正文', max_length=300) creation_time = models.DateTimeField(_('creation time'), default=now) last_modify_time = models.DateTimeField(_('last modify time'), default=now) + # zy: 作者外键字段 - 关联到用户模型,删除用户时级联删除其评论 author = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_('author'), on_delete=models.CASCADE) + # zy: 文章外键字段 - 关联到博客文章模型,删除文章时级联删除相关评论 article = models.ForeignKey( Article, verbose_name=_('article'), on_delete=models.CASCADE) + # zy: 父评论自关联字段 - 实现评论回复功能,允许评论有父评论 parent_comment = models.ForeignKey( 'self', verbose_name=_('parent comment'), blank=True, null=True, on_delete=models.CASCADE) + # zy: 启用状态字段 - 控制评论是否显示,用于评论审核机制 is_enable = models.BooleanField(_('enable'), default=False, blank=False, null=False) + # zy: 模型元数据配置类 - 定义模型的数据库和行为配置 class Meta: - ordering = ['-id'] - verbose_name = _('comment') - verbose_name_plural = verbose_name + ordering = ['-id'] # zy: 默认排序规则 - 按ID降序排列,新的评论显示在前面 + verbose_name = _('comment') # zy: 模型在Admin中的单数显示名称 + verbose_name_plural = verbose_name # zy: 模型在Admin中的复数显示名称 get_latest_by = 'id' def __str__(self): diff --git a/src/DjangoBlog/comments/tests.py b/src/DjangoBlog/comments/tests.py index 32d872c..406641e 100644 --- a/src/DjangoBlog/comments/tests.py +++ b/src/DjangoBlog/comments/tests.py @@ -1,6 +1,7 @@ +# zy: 评论模块测试文件 - 测试评论功能的各项业务逻辑 from django.test import Client, RequestFactory, TransactionTestCase from django.urls import reverse - +# zy: 导入相关模型类,用于测试数据准备 from accounts.models import BlogUser from blog.models import Category, Article from comments.models import Comment @@ -8,22 +9,26 @@ from comments.templatetags.comments_tags import * from djangoblog.utils import get_max_articleid_commentid -# Create your tests here. +# zy: 评论功能测试类 - 测试评论的创建、验证、回复等功能 class CommentsTest(TransactionTestCase): + # zy: 测试初始化方法 - 在每个测试方法执行前运行,准备测试数据 def setUp(self): - self.client = Client() - self.factory = RequestFactory() + self.client = Client() # zy: 创建测试客户端,用于模拟HTTP请求 + self.factory = RequestFactory() # zy: 创建请求工厂,用于构建请求对象 + # zy: 配置博客设置,设置评论需要审核 from blog.models import BlogSettings value = BlogSettings() value.comment_need_review = True value.save() + # zy: 创建测试超级用户,用于登录和权限测试 self.user = BlogUser.objects.create_superuser( email="liangliangyy1@gmail.com", username="liangliangyy1", password="liangliangyy1") + # zy: 辅助方法 - 更新文章评论的审核状态为已通过 @staticmethod def update_article_comment_status(article): comments = article.comment_set.all() @@ -31,80 +36,110 @@ class CommentsTest(TransactionTestCase): comment.is_enable = True comment.save() + # zy: 测试评论验证功能 - 测试评论的创建、回复和显示 def test_validate_comment(self): self.client.login(username='liangliangyy1', password='liangliangyy1') + # zy: 创建测试分类 category = Category() category.name = "categoryccc" category.save() + # zy: 创建测试文章 article = Article() article.title = "nicetitleccc" article.body = "nicecontentccc" - article.author = self.user - article.category = category - article.type = 'a' - article.status = 'p' + article.author = self.user # zy: 设置文章作者为测试用户 + article.category = category # zy: 设置文章分类 + article.type = 'a' # zy: 设置文章类型 + article.status = 'p' # zy: 设置文章状态为发布 article.save() + # zy: 获取发表评论的URL地址 comment_url = reverse( 'comments:postcomment', kwargs={ 'article_id': article.id}) + # zy: 测试1: 发表第一条评论 response = self.client.post(comment_url, { - 'body': '123ffffffffff' + 'body': '123ffffffffff' # zy: 评论内容 }) + # zy: 断言响应状态码为302(重定向),表示评论提交成功 self.assertEqual(response.status_code, 302) + # zy: 重新从数据库获取文章对象,确保获取最新数据 article = Article.objects.get(pk=article.pk) + # zy: 断言评论列表为空(因为评论需要审核,默认不显示) self.assertEqual(len(article.comment_list()), 0) + + # zy: 更新评论状态为已审核通过 self.update_article_comment_status(article) + # zy: 断言现在评论列表中有1条评论 self.assertEqual(len(article.comment_list()), 1) + # zy: 测试2: 发表第二条评论 response = self.client.post(comment_url, { - 'body': '123ffffffffff', + 'body': '123ffffffffff', # zy: 第二条评论内容 }) + # zy: 断言响应状态码为302(重定向) self.assertEqual(response.status_code, 302) + # zy: 重新获取文章并更新评论状态 article = Article.objects.get(pk=article.pk) self.update_article_comment_status(article) + # zy: 断言现在有2条评论 self.assertEqual(len(article.comment_list()), 2) + + # zy: 获取第一条评论的ID,用于回复测试 parent_comment_id = article.comment_list()[0].id + # zy: 测试3: 发表带格式的回复评论 response = self.client.post(comment_url, { 'body': ''' - # Title1 + # Title1 - ```python - import os - ``` + ```python + import os + ``` - [url](https://www.lylinux.net/) + [url](https://www.lylinux.net/) - [ddd](http://www.baidu.com) + [ddd](http://www.baidu.com) - ''', - 'parent_comment_id': parent_comment_id + ''', # zy: 测试包含Markdown格式的评论内容 + 'parent_comment_id': parent_comment_id # zy: 设置父评论ID,表示这是回复 }) + # zy: 断言响应状态码为302(重定向) self.assertEqual(response.status_code, 302) + + # zy: 更新评论状态并重新获取文章 self.update_article_comment_status(article) article = Article.objects.get(pk=article.pk) + # zy: 断言现在有3条评论(2条顶级评论 + 1条回复) self.assertEqual(len(article.comment_list()), 3) + + # zy: 测试评论树解析功能 comment = Comment.objects.get(id=parent_comment_id) + # zy: 解析评论树结构,检查回复关系 tree = parse_commenttree(article.comment_list(), comment) - self.assertEqual(len(tree), 1) + self.assertEqual(len(tree), 1) # zy: 断言评论树解析正确 + + # zy: 测试评论项显示功能 data = show_comment_item(comment, True) - self.assertIsNotNone(data) + self.assertIsNotNone(data) # zy: 断言评论项数据不为空 + + # zy: 测试获取最大文章ID和评论ID的工具函数 s = get_max_articleid_commentid() - self.assertIsNotNone(s) + self.assertIsNotNone(s) # zy: 断言返回值不为空 + # zy: 测试评论邮件发送功能 from comments.utils import send_comment_email - send_comment_email(comment) + send_comment_email(comment) # zy: 发送评论通知邮件 \ No newline at end of file diff --git a/src/DjangoBlog/comments/urls.py b/src/DjangoBlog/comments/urls.py index 7df3fab..8b922d4 100644 --- a/src/DjangoBlog/comments/urls.py +++ b/src/DjangoBlog/comments/urls.py @@ -1,11 +1,17 @@ +# zy: 评论模块URL配置 - 定义评论相关的URL路由和视图映射 from django.urls import path +# zy: 导入评论视图模块,包含评论相关的视图类 from . import views +# zy: 定义应用命名空间 - 用于URL反向解析时避免命名冲突 app_name = "comments" + +# zy: URL模式列表 - 定义评论模块的所有URL路由规则 urlpatterns = [ + # zy: 发表评论URL路由 - 处理用户发表评论的请求 path( - 'article//postcomment', - views.CommentPostView.as_view(), - name='postcomment'), -] + 'article//postcomment', # zy: URL模式,包含文章ID参数 + views.CommentPostView.as_view(), # zy: 关联的基于类的视图 + name='postcomment'), # zy: URL名称,用于反向解析 +] \ No newline at end of file diff --git a/src/DjangoBlog/comments/utils.py b/src/DjangoBlog/comments/utils.py index f01dba7..0f9397c 100644 --- a/src/DjangoBlog/comments/utils.py +++ b/src/DjangoBlog/comments/utils.py @@ -1,13 +1,14 @@ -import logging +import logging# zy: 评论邮件通知模块 - 处理评论相关的邮件发送功能 from django.utils.translation import gettext_lazy as _ -from djangoblog.utils import get_current_site +from djangoblog.utils import get_current_site# zy: 导入项目工具函数,用于获取站点信息和发送邮件 from djangoblog.utils import send_email +# zy: 创建日志记录器,用于记录邮件发送过程中的错误 logger = logging.getLogger(__name__) - +# zy: 发送评论邮件函数 - 处理评论发表和回复的邮件通知 def send_comment_email(comment): site = get_current_site().domain subject = _('Thanks for your comment') diff --git a/src/DjangoBlog/comments/views.py b/src/DjangoBlog/comments/views.py index ad9b2b9..6bae385 100644 --- a/src/DjangoBlog/comments/views.py +++ b/src/DjangoBlog/comments/views.py @@ -1,4 +1,4 @@ -# Create your views here. +# zy: 评论视图模块 - 处理评论相关的视图逻辑和请求处理 from django.core.exceptions import ValidationError from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404 @@ -6,26 +6,30 @@ from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_protect from django.views.generic.edit import FormView +# zy: 导入相关模型类 from accounts.models import BlogUser from blog.models import Article from .forms import CommentForm from .models import Comment - +# zy: 评论发表视图类 - 基于FormView处理评论提交 class CommentPostView(FormView): form_class = CommentForm template_name = 'blog/article_detail.html' + # zy: 添加CSRF保护装饰器,防止跨站请求伪造攻击 @method_decorator(csrf_protect) def dispatch(self, *args, **kwargs): return super(CommentPostView, self).dispatch(*args, **kwargs) + # zy: 处理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") + # zy: 表单验证失败时的处理逻辑 def form_invalid(self, form): article_id = self.kwargs['article_id'] article = get_object_or_404(Article, pk=article_id) @@ -35,6 +39,7 @@ class CommentPostView(FormView): 'article': article }) + # zy: 表单验证成功时的处理逻辑 - 保存评论数据 def form_valid(self, form): """提交的数据验证合法后的逻辑""" user = self.request.user @@ -42,6 +47,7 @@ class CommentPostView(FormView): article_id = self.kwargs['article_id'] article = get_object_or_404(Article, pk=article_id) + # zy: 检查文章是否允许评论 if article.comment_status == 'c' or article.status == 'c': raise ValidationError("该文章评论已关闭.") comment = form.save(False) @@ -52,6 +58,7 @@ class CommentPostView(FormView): comment.is_enable = True comment.author = author + # zy: 处理评论回复逻辑 if form.cleaned_data['parent_comment_id']: parent_comment = Comment.objects.get( pk=form.cleaned_data['parent_comment_id'])