From 0b382f5e0d7a43995179cfe52a15a2a816082c56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E7=91=9E=E8=90=8D?= <1909818175@qq.com> Date: Sat, 25 Oct 2025 15:43:29 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AC=AC=E5=85=AD=E5=91=A8=E4=B8=AA=E4=BA=BA?= =?UTF-8?q?=E5=AE=9E=E8=B7=B5=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- comments/forms.py | 14 +++++-- comments/models.py | 39 ++++++++++++++----- comments/tests.py | 97 ++++++++++++++++++++++------------------------ 3 files changed, 87 insertions(+), 63 deletions(-) diff --git a/comments/forms.py b/comments/forms.py index e83737d..2112458 100644 --- a/comments/forms.py +++ b/comments/forms.py @@ -1,13 +1,21 @@ +# 导入Django表单相关模块 from django import forms -from django.forms import ModelForm +from django.forms import ModelForm # 导入模型表单类,用于快速生成与模型对应的表单 +# 导入当前应用下的Comment模型,表单将基于该模型创建 from .models import Comment +# 定义评论表单类,继承自ModelForm(模型表单) class CommentForm(ModelForm): + # 定义父评论ID字段,用于实现评论回复功能 + # IntegerField:整数类型字段,存储父评论的ID + # widget=forms.HiddenInput:使用隐藏输入框,不在页面上显示但会随表单提交 + # required=False:该字段为非必填,顶级评论(无父评论)不需要填写 parent_comment_id = forms.IntegerField( widget=forms.HiddenInput, required=False) + # Meta类用于配置模型表单的元数据 class Meta: - model = Comment - fields = ['body'] + model = Comment # 指定表单对应的模型为Comment + fields = ['body'] # 指定需要在表单中显示的模型字段,这里只包含评论内容字段'body' \ No newline at end of file diff --git a/comments/models.py b/comments/models.py index 7c3bbc8..69b3cb7 100644 --- a/comments/models.py +++ b/comments/models.py @@ -1,39 +1,60 @@ +# 导入Django配置、数据库模型、时间工具和翻译工具 from django.conf import settings from django.db import models -from django.utils.timezone import now -from django.utils.translation import gettext_lazy as _ +from django.utils.timezone import now # 获取当前时区时间的工具 +from django.utils.translation import gettext_lazy as _ # 国际化翻译工具,用于多语言支持 +# 导入博客文章模型,评论将与文章关联 from blog.models import Article -# Create your models here. - +# 创建评论模型(数据库表结构的抽象) class Comment(models.Model): + # 评论正文:文本字段,最大长度300, verbose_name为'正文'(后台显示名称) body = models.TextField('正文', max_length=300) + + # 创建时间:日期时间字段,使用当前时区时间作为默认值,国际化翻译字段名为'creation time' creation_time = models.DateTimeField(_('creation time'), default=now) + + # 最后修改时间:日期时间字段,默认值为当前时间(后续可通过代码更新) last_modify_time = models.DateTimeField(_('last modify time'), default=now) + + # 评论作者:外键关联到Django内置用户模型(settings.AUTH_USER_MODEL) + # on_delete=models.CASCADE表示:如果用户被删除,其评论也会被级联删除 author = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_('author'), on_delete=models.CASCADE) + + # 关联文章:外键关联到博客文章模型(Article) + # on_delete=models.CASCADE表示:如果文章被删除,其下所有评论也会被级联删除 article = models.ForeignKey( Article, verbose_name=_('article'), on_delete=models.CASCADE) + + # 父评论:自关联外键,用于实现评论回复功能 + # 'self'表示关联到当前模型自身,blank=True和null=True允许为空(即顶级评论) + # 父评论被删除时,子评论也会被级联删除 parent_comment = models.ForeignKey( 'self', verbose_name=_('parent comment'), blank=True, null=True, on_delete=models.CASCADE) + + # 是否启用:布尔字段,默认不启用(可能用于评论审核功能) + # blank=False和null=False表示该字段必填且不能为NULL is_enable = models.BooleanField(_('enable'), default=False, blank=False, null=False) + # 模型元数据配置(影响模型的整体行为和显示) class Meta: - ordering = ['-id'] - verbose_name = _('comment') - verbose_name_plural = verbose_name - get_latest_by = 'id' + ordering = ['-id'] # 默认排序:按ID降序(最新评论在前) + verbose_name = _('comment') # 模型的单数显示名称(用于后台) + verbose_name_plural = verbose_name # 模型的复数显示名称(与单数相同) + get_latest_by = 'id' # 指定通过id字段获取最新记录 + # 模型的字符串表示:在后台和打印对象时显示评论正文 def __str__(self): - return self.body + return self.body \ No newline at end of file diff --git a/comments/tests.py b/comments/tests.py index 2a7f55f..26cc0a6 100644 --- a/comments/tests.py +++ b/comments/tests.py @@ -1,109 +1,104 @@ +# 导入Django测试相关工具 from django.test import Client, RequestFactory, TransactionTestCase -from django.urls import reverse +from django.urls import reverse # 用于反向解析URL -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 +# 导入相关模型和工具 +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 # 工具函数 -# Create your tests here. - +# 创建测试类,继承TransactionTestCase(支持事务的测试类,适合涉及数据库事务的测试) class CommentsTest(TransactionTestCase): def setUp(self): - self.client = Client() - self.factory = RequestFactory() + """测试前的初始化设置,每个测试方法执行前都会调用""" + self.client = Client() # 创建测试客户端,用于模拟用户请求 + self.factory = RequestFactory() # 创建请求工厂,用于构造请求对象 + + # 配置博客设置(评论需要审核) from blog.models import BlogSettings value = BlogSettings() - value.comment_need_review = True + 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() + """辅助方法:更新文章所有评论为启用状态(模拟审核通过)""" + comments = article.comment_set.all() # 获取文章的所有评论 for comment in comments: - comment.is_enable = True - comment.save() + 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.author = self.user # 关联作者 + article.category = category # 关联分类 + article.type = 'a' # 文章类型(假设'a'表示普通文章) + article.status = 'p' # 发布状态(假设'p'表示已发布) article.save() + # 获取评论提交的URL(反向解析评论提交视图) comment_url = reverse( 'comments:postcomment', kwargs={ - 'article_id': article.id}) + 'article_id': article.id}) # 传入文章ID参数 + # 测试发布第一条评论 response = self.client.post(comment_url, { - 'body': '123ffffffffff' + 'body': '123ffffffffff' # 评论内容 }) + # 验证评论提交后是否重定向(通常评论成功后会跳转到文章页) self.assertEqual(response.status_code, 302) + # 重新获取文章对象(从数据库刷新) article = Article.objects.get(pk=article.pk) + # 因为评论需要审核(初始is_enable=False),所以评论列表长度应为0 self.assertEqual(len(article.comment_list()), 0) + # 调用辅助方法,将评论设为启用(模拟审核通过) self.update_article_comment_status(article) - + # 此时评论列表应包含1条评论 self.assertEqual(len(article.comment_list()), 1) + # 测试发布第二条评论 response = self.client.post(comment_url, { 'body': '123ffffffffff', }) - self.assertEqual(response.status_code, 302) - + self.assertEqual(response.status_code, 302) # 验证重定向 article = Article.objects.get(pk=article.pk) self.update_article_comment_status(article) - self.assertEqual(len(article.comment_list()), 2) + self.assertEqual(len(article.comment_list()), 2) # 验证评论数量 + + # 测试回复评论功能 + # 获取第一条评论的ID作为父评论ID parent_comment_id = article.comment_list()[0].id + # 发布带Markdown格式的回复(测试富文本内容) response = self.client.post(comment_url, { 'body': ''' - # Title1 + # Title1 (Markdown标题) ```python - import os - ``` - - [url](https://www.lylinux.net/) - - [ddd](http://www.baidu.com) - - - ''', - 'parent_comment_id': parent_comment_id - }) - - 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) - tree = parse_commenttree(article.comment_list(), comment) - self.assertEqual(len(tree), 1) - data = show_comment_item(comment, True) - self.assertIsNotNone(data) - s = get_max_articleid_commentid() - self.assertIsNotNone(s) - - from comments.utils import send_comment_email - send_comment_email(comment) + import os # Markdown代码块 \ No newline at end of file