diff --git a/doc/鞠泆男9-10周作业(1).docx b/doc/鞠泆男9-10周作业(1).docx new file mode 100644 index 0000000..c149915 Binary files /dev/null and b/doc/鞠泆男9-10周作业(1).docx differ diff --git a/src/django-master/comments/admin.py b/src/django-master/comments/admin.py index 6380066..1613008 100644 --- a/src/django-master/comments/admin.py +++ b/src/django-master/comments/admin.py @@ -1,12 +1,22 @@ +<<<<<<< HEAD # 导入Django Admin核心模块和辅助工具 from django.contrib import admin # Django Admin管理后台核心模块 from django.urls import reverse # 用于生成Django内部URL(反转URL) from django.utils.html import format_html # 用于生成安全的HTML代码(防止XSS攻击) from django.utils.translation import gettext_lazy as _ # 用于国际化翻译(支持多语言) +======= +# jyn: 该模块用于配置 Django 管理后台的评论(Comment)模型管理界面 +# jyn: 功能包括:自定义评论启用/禁用批量操作、列表页展示字段配置、关联对象跳转链接、界面显示优化等 +from django.contrib import admin +from django.urls import reverse # jyn: 用于反向解析 admin 后台的模型编辑页面 URL +from django.utils.html import format_html # jyn: 用于安全生成 HTML 标签,避免 XSS 风险 +from django.utils.translation import gettext_lazy as _ # jyn: 用于国际化翻译,支持多语言显示 +>>>>>>> JYN_branch def disable_commentstatus(modeladmin, request, queryset): """ +<<<<<<< HEAD 自定义Admin批量操作:批量禁用选中的评论 参数说明: - modeladmin:当前关联的Admin模型类实例 @@ -15,10 +25,19 @@ def disable_commentstatus(modeladmin, request, queryset): """ # 批量更新选中评论的is_enable字段为False(禁用状态) queryset.update(is_enable=False) +======= + jyn: 批量禁用评论的自定义操作函数 + :param modeladmin: 关联的 ModelAdmin 实例 + :param request: 当前请求对象 + :param queryset: 选中的评论记录集合 + """ + queryset.update(is_enable=False) # jyn: 将选中评论的 is_enable 字段设为 False,实现禁用 +>>>>>>> JYN_branch def enable_commentstatus(modeladmin, request, queryset): """ +<<<<<<< HEAD 自定义Admin批量操作:批量启用选中的评论 参数与disable_commentstatus一致,功能相反 """ @@ -50,10 +69,41 @@ class CommentAdmin(admin.ModelAdmin): list_filter = ('is_enable',) # 列表页右侧筛选器:按“是否启用”筛选评论 exclude = ('creation_time', 'last_modify_time') # 编辑/添加评论时,隐藏的字段(不允许手动修改) actions = [disable_commentstatus, enable_commentstatus] # 列表页支持的批量操作(绑定上面定义的两个函数) +======= + jyn: 批量启用评论的自定义操作函数 + :param modeladmin: 关联的 ModelAdmin 实例 + :param request: 当前请求对象 + :param queryset: 选中的评论记录集合 + """ + queryset.update(is_enable=True) # jyn: 将选中评论的 is_enable 字段设为 True,实现启用 + + +# jyn: 为批量操作函数设置后台显示名称(支持国际化) +disable_commentstatus.short_description = _('Disable comments') +enable_commentstatus.short_description = _('Enable comments') + + +class CommentAdmin(admin.ModelAdmin): + """jyn: 评论模型(Comment)的 Admin 配置类,控制后台展示和操作逻辑""" + list_per_page = 20 # jyn: 列表页每页显示 20 条评论记录 + list_display = ( + 'id', # jyn: 评论 ID + 'body', # jyn: 评论内容 + 'link_to_userinfo', # jyn: 自定义字段,显示评论作者跳转链接 + 'link_to_article', # jyn: 自定义字段,显示关联文章跳转链接 + 'is_enable', # jyn: 评论启用状态(True/False) + 'creation_time' # jyn: 评论创建时间 + ) + list_display_links = ('id', 'body', 'is_enable') # jyn: 列表页中可点击跳转编辑页的字段 + list_filter = ('is_enable',) # jyn: 右侧筛选器,按启用状态筛选评论 + exclude = ('creation_time', 'last_modify_time') # jyn: 编辑页隐藏的字段(自动生成,无需手动修改) + actions = [disable_commentstatus, enable_commentstatus] # jyn: 注册批量操作函数 +>>>>>>> JYN_branch # 2. 自定义列表页字段:生成“评论作者”的跳转链接 def link_to_userinfo(self, obj): """ +<<<<<<< HEAD obj:当前循环的评论对象(每条评论对应一个obj) 返回值:带有HTML链接的作者名称(点击跳转到作者的Admin编辑页) """ @@ -65,12 +115,26 @@ class CommentAdmin(admin.ModelAdmin): # 注:原代码中HTML标签内href属性缺失值(应改为href="%s"),此处按正确逻辑补充 return format_html( u'%s' % +======= + jyn: 自定义列表字段,生成评论作者的 admin 编辑页跳转链接 + :param obj: 当前 Comment 模型实例 + :return: 包含跳转链接的 HTML 字符串 + """ + # jyn: 获取作者模型(UserInfo)的 app 名称和模型名称,用于反向解析 URL + info = (obj.author._meta.app_label, obj.author._meta.model_name) + # jyn: 反向解析作者模型的编辑页 URL,传入作者 ID 作为参数 + link = reverse('admin:%s_%s_change' % info, args=(obj.author.id,)) + # jyn: 生成 HTML 链接,优先显示昵称,无昵称则显示邮箱 + return format_html( + u'%s' % +>>>>>>> JYN_branch (link, obj.author.nickname if obj.author.nickname else obj.author.email) ) # 3. 自定义列表页字段:生成“评论所属文章”的跳转链接 def link_to_article(self, obj): """ +<<<<<<< HEAD 逻辑与link_to_userinfo类似,生成文章的Admin编辑页跳转链接 """ # 获取评论所属文章模型的元数据 @@ -84,4 +148,22 @@ class CommentAdmin(admin.ModelAdmin): # 4. 为自定义字段设置在Admin界面显示的名称(支持国际化) link_to_userinfo.short_description = _('User') # 自定义字段“link_to_userinfo”显示为“用户” - link_to_article.short_description = _('Article') # 自定义字段“link_to_article”显示为“文章” \ No newline at end of file + link_to_article.short_description = _('Article') # 自定义字段“link_to_article”显示为“文章” +======= + jyn: 自定义列表字段,生成关联文章的 admin 编辑页跳转链接 + :param obj: 当前 Comment 模型实例 + :return: 包含跳转链接的 HTML 字符串 + """ + # jyn: 获取文章模型(Article)的 app 名称和模型名称 + info = (obj.article._meta.app_label, obj.article._meta.model_name) + # jyn: 反向解析文章模型的编辑页 URL,传入文章 ID 作为参数 + link = reverse('admin:%s_%s_change' % info, args=(obj.article.id,)) + # jyn: 生成 HTML 链接,显示文章标题 + return format_html( + u'%s' % (link, obj.article.title) + ) + + # jyn: 为自定义列表字段设置后台显示名称(支持国际化) + link_to_userinfo.short_description = _('User') + link_to_article.short_description = _('Article') +>>>>>>> JYN_branch diff --git a/src/django-master/comments/apps.py b/src/django-master/comments/apps.py index 182ec3c..3af20f9 100644 --- a/src/django-master/comments/apps.py +++ b/src/django-master/comments/apps.py @@ -1,3 +1,4 @@ +<<<<<<< HEAD # 导入Django的App配置基类:所有应用的配置类都需继承此类 from django.apps import AppConfig @@ -16,3 +17,13 @@ class CommentsConfig(AppConfig): ======= >>>>>>> ZYY_branch +======= +# jyn: 评论应用(comments)的配置类,用于定义应用元数据和初始化行为 +from django.apps import AppConfig # jyn: Django 内置的应用配置基类,所有应用配置需继承此类 + + +class CommentsConfig(AppConfig): + """jyn: comments 应用的核心配置类,指定应用名称等关键信息""" + name = 'comments' # jyn: 应用唯一标识名称,与项目目录名一致,Django 通过该名称识别应用 + +>>>>>>> JYN_branch diff --git a/src/django-master/comments/forms.py b/src/django-master/comments/forms.py index db7b07c..4f6f95a 100644 --- a/src/django-master/comments/forms.py +++ b/src/django-master/comments/forms.py @@ -1,3 +1,4 @@ +<<<<<<< HEAD # 导入Django表单核心模块 from django import forms # Django表单基础模块,提供表单字段、验证等功能 from django.forms import ModelForm # 模型表单类,可快速将模型转换为表单(减少重复代码) @@ -12,6 +13,17 @@ class CommentForm(ModelForm): 核心作用:生成前端评论提交表单,并关联Comment模型处理数据存储 """ # 1. 自定义额外字段:父评论ID(用于实现评论回复功能) +======= +# jyn: 评论功能的表单配置模块,定义前端提交评论的表单结构和字段规则 +from django import forms +from django.forms import ModelForm # jyn: Django 内置模型表单基类,自动关联模型字段生成表单 +from .models import Comment # jyn: 导入评论核心模型,表单与模型数据联动 + + +class CommentForm(ModelForm): + """jyn: 评论提交表单类,继承 ModelForm 实现模型与表单的自动映射""" + # jyn: 父评论 ID 字段,隐藏输入(用于回复功能),非必填(首次评论无父评论) +>>>>>>> JYN_branch parent_comment_id = forms.IntegerField( widget=forms.HiddenInput, # 表单控件:隐藏输入框(前端不显示,仅用于传递数据) required=False # 是否必填:False表示允许为空(普通评论无父评论,回复评论时才传值) @@ -19,7 +31,12 @@ class CommentForm(ModelForm): # 2. Meta类:模型表单的核心配置(关联模型、指定字段等) class Meta: +<<<<<<< HEAD model = Comment # 关联的模型:当前表单与Comment模型绑定 fields = ['body'] # 表单需显示/处理的模型字段:仅包含评论内容(body字段) # 注:Comment模型中其他字段(如author、article、creation_time等) - # 通常由后端自动填充(如从登录态获取author),无需前端用户输入 \ No newline at end of file + # 通常由后端自动填充(如从登录态获取author),无需前端用户输入 +======= + model = Comment # jyn: 关联的核心模型,表单数据同步至该模型 + fields = ['body'] # jyn: 前端需提交的字段(评论内容),自动生成对应表单控件 +>>>>>>> JYN_branch diff --git a/src/django-master/comments/models.py b/src/django-master/comments/models.py index a3bb081..558a95c 100644 --- a/src/django-master/comments/models.py +++ b/src/django-master/comments/models.py @@ -1,3 +1,4 @@ +<<<<<<< HEAD # 导入Django核心模块:配置、数据库模型、时间工具、国际化 from django.conf import settings # 导入项目配置(用于获取自定义用户模型) from django.db import models # Django数据库模型基类,所有模型需继承models.Model @@ -55,18 +56,74 @@ class Comment(models.Model): default=False, # 默认禁用(需管理员审核后启用,防止垃圾评论) blank=False, # 表单中不允许为空 null=False # 数据库中不允许为空 +======= +# jyn: 评论功能核心数据模型模块,定义评论的数据库结构、关联关系及基础配置 +from django.conf import settings # jyn: 导入 Django 项目配置,用于获取自定义用户模型 +from django.db import models # jyn: Django 数据库模型基类,所有数据模型需继承此类 +from django.utils.timezone import now # jyn: 获取当前时区时间,用于时间字段默认值 +from django.utils.translation import gettext_lazy as _ # jyn: 国际化翻译支持 +from blog.models import Article # jyn: 导入博客文章模型,建立评论与文章的关联 + + +class Comment(models.Model): + """jyn: 评论数据模型,存储评论核心信息,关联用户、文章及父评论(支持回复功能)""" + # jyn: 评论正文,文本字段,最大长度限制 300 字符 + body = models.TextField('正文', max_length=300) + # jyn: 评论创建时间,默认值为当前时间,支持国际化显示 + creation_time = models.DateTimeField(_('creation time'), default=now) + # jyn: 评论最后修改时间,默认值为当前时间,支持国际化显示 + last_modify_time = models.DateTimeField(_('last modify time'), default=now) + # jyn: 关联评论作者(用户模型),级联删除(用户删除时评论同步删除),支持国际化 + author = models.ForeignKey( + settings.AUTH_USER_MODEL, + verbose_name=_('author'), + on_delete=models.CASCADE + ) + # jyn: 关联所属文章,级联删除(文章删除时评论同步删除),支持国际化 + article = models.ForeignKey( + Article, + verbose_name=_('article'), + on_delete=models.CASCADE + ) + # jyn: 关联父评论(自关联),支持回复功能,允许为空,级联删除,支持国际化 + parent_comment = models.ForeignKey( + 'self', + verbose_name=_('parent comment'), + blank=True, + null=True, + on_delete=models.CASCADE + ) + # jyn: 评论启用状态,布尔值,默认禁用(需后台审核),非空约束 + is_enable = models.BooleanField( + _('enable'), + default=False, + blank=False, + null=False +>>>>>>> JYN_branch ) # 模型元数据:控制模型的整体行为(排序、显示名等) class Meta: +<<<<<<< HEAD ordering = ['-id'] # 数据查询时按ID倒序排列(最新评论在前) verbose_name = _('comment') # 模型单数显示名(Admin中“评论”) verbose_name_plural = verbose_name # 模型复数显示名(与单数一致,避免“评论s”) get_latest_by = 'id' # 使用Model.objects.latest()时,按id字段取最新数据 +======= + """jyn: 模型元数据配置,控制数据库表结构和 Admin 界面行为""" + ordering = ['-id'] # jyn: 数据查询默认排序:按评论 ID 倒序(最新评论在前) + verbose_name = _('comment') # jyn: 模型单数显示名称(支持国际化) + verbose_name_plural = verbose_name # jyn: 模型复数显示名称(与单数一致) + get_latest_by = 'id' # jyn: 使用 latest() 方法时,按 ID 字段获取最新记录 +>>>>>>> JYN_branch # 模型实例的字符串表示:打印评论对象时显示正文(便于调试和Admin显示) def __str__(self): <<<<<<< HEAD +<<<<<<< HEAD +======= + """jyn: 模型实例的字符串表示,Admin 界面及打印时显示评论正文""" +>>>>>>> JYN_branch return self.body ======= return self.body diff --git a/src/django-master/comments/tests.py b/src/django-master/comments/tests.py index bcb79c6..2bbb081 100644 --- a/src/django-master/comments/tests.py +++ b/src/django-master/comments/tests.py @@ -1,3 +1,4 @@ +<<<<<<< HEAD # 导入Django测试核心模块、URL工具及项目内模型/工具 from django.test import Client, RequestFactory, TransactionTestCase # Django测试类:Client模拟HTTP请求,RequestFactory构造请求对象,TransactionTestCase支持事务回滚 from django.urls import reverse # 生成URL(通过URL名称反向解析,避免硬编码) @@ -34,6 +35,34 @@ class CommentsTest(TransactionTestCase): value.save() # 保存到测试数据库 # 3. 创建测试超级用户:用于模拟登录状态下提交评论 +======= +# jyn: 评论功能测试模块,涵盖评论提交、审核状态、回复功能、模板标签及工具函数的完整性测试 +from django.test import Client, RequestFactory, TransactionTestCase # jyn: Django 测试核心类,Client模拟请求,RequestFactory构造请求,TransactionTestCase支持事务回滚 +from django.urls import reverse # jyn: 反向解析URL,用于生成测试请求地址 + +from accounts.models import BlogUser # jyn: 导入用户模型,用于创建测试用户 +from blog.models import Category, Article # jyn: 导入博客分类、文章模型,用于创建测试文章 +from comments.models import Comment # jyn: 导入评论模型,用于验证评论数据 +from comments.templatetags.comments_tags import * # jyn: 导入评论相关模板标签,测试模板渲染逻辑 +from djangoblog.utils import get_max_articleid_commentid # jyn: 导入工具函数,测试ID获取功能 + + +class CommentsTest(TransactionTestCase): + """jyn: 评论功能集成测试类,覆盖评论提交、状态更新、回复、模板标签及邮件通知等核心流程""" + + def setUp(self): + """jyn: 测试前置初始化方法,每次测试用例执行前自动调用,创建测试依赖数据""" + self.client = Client() # jyn: 初始化测试客户端,用于模拟前端HTTP请求 + self.factory = RequestFactory() # jyn: 初始化请求工厂,用于构造自定义请求对象 + + # jyn: 创建博客设置,开启评论需审核功能(模拟真实环境的评论审核机制) + from blog.models import BlogSettings + value = BlogSettings() + value.comment_need_review = True # jyn: 评论需后台审核才能显示 + value.save() + + # jyn: 创建超级用户,用于测试登录状态下的评论提交 +>>>>>>> JYN_branch self.user = BlogUser.objects.create_superuser( email="liangliangyy1@gmail.com", # 测试邮箱 username="liangliangyy1", # 测试用户名 @@ -42,6 +71,7 @@ class CommentsTest(TransactionTestCase): def update_article_comment_status(self, article): """ +<<<<<<< HEAD 辅助方法:批量更新某篇文章下所有评论的启用状态(设为“启用”) 模拟管理员审核通过评论的操作,用于测试审核后评论的显示逻辑 参数: @@ -50,11 +80,18 @@ class CommentsTest(TransactionTestCase): # 获取该文章下所有评论 comments = article.comment_set.all() # 遍历评论,将“是否启用”字段设为True并保存 +======= + jyn: 辅助方法,批量更新指定文章下所有评论的启用状态(模拟后台审核通过) + :param article: 目标文章实例 + """ + comments = article.comment_set.all() # jyn: 获取文章关联的所有评论 +>>>>>>> JYN_branch for comment in comments: - comment.is_enable = True - comment.save() + comment.is_enable = True # jyn: 设为启用状态 + comment.save() # jyn: 保存修改 def test_validate_comment(self): +<<<<<<< HEAD """ 核心测试用例:验证评论提交流程(登录→创建文章→提交评论→验证状态) 覆盖场景:登录用户提交评论、评论未审核时不显示、审核后正常显示 @@ -63,12 +100,20 @@ class CommentsTest(TransactionTestCase): self.client.login(username='liangliangyy1', password='liangliangyy1') # 2. 创建测试分类:文章需关联分类,先创建分类数据 +======= + """jyn: 核心测试用例,验证评论提交、审核、回复、模板标签及工具函数的正确性""" + # 1. 模拟用户登录 + self.client.login(username='liangliangyy1', password='liangliangyy1') + + # 2. 创建测试分类和文章(评论的关联对象) +>>>>>>> JYN_branch category = Category() category.name = "categoryccc" # 分类名称 category.save() # 保存到测试数据库 # 3. 创建测试文章:评论需关联文章,创建一篇已发布的文章 article = Article() +<<<<<<< HEAD article.title = "nicetitleccc" # 文章标题 article.body = "nicecontentccc" # 文章内容 article.author = self.user # 关联作者(测试用户) @@ -97,7 +142,82 @@ class CommentsTest(TransactionTestCase): self.assertEqual(len(article.comment_list()), 0) # comment_list()应为自定义方法,返回启用的评论 # 8. 模拟审核通过:调用辅助方法,将该文章下所有评论设为“启用” +======= + article.title = "nicetitleccc" + article.body = "nicecontentccc" + article.author = self.user + article.category = category + article.type = 'a' # jyn: 文章类型(假设'a'代表普通文章) + article.status = 'p' # jyn: 文章状态(假设'p'代表已发布) + article.save() + + # 3. 生成评论提交接口的URL + comment_url = reverse( + 'comments:postcomment', kwargs={ + 'article_id': article.id}) # jyn: 反向解析"提交评论"接口,传入文章ID + + # 4. 测试首次提交普通评论(未审核状态) + response = self.client.post(comment_url, + { + 'body': '123ffffffffff' # jyn: 评论正文 + }) + self.assertEqual(response.status_code, 302) # jyn: 验证提交成功后重定向(状态码302) + + # 未审核时,文章评论列表应为空 + article = Article.objects.get(pk=article.pk) + self.assertEqual(len(article.comment_list()), 0) # jyn: 假设comment_list()返回启用的评论 + + # 模拟审核通过,再次验证评论数量 +>>>>>>> JYN_branch self.update_article_comment_status(article) + self.assertEqual(len(article.comment_list()), 1) # jyn: 审核后应显示1条评论 +<<<<<<< HEAD # 9. 验证“审核后评论显示”:再次检查评论列表长度是否为1(审核通过后应显示) - self.assertEqual(len(article.comment_list()), 1) \ No newline at end of file + self.assertEqual(len(article.comment_list()), 1) +======= + # 5. 测试再次提交相同内容评论(验证重复提交允许性) + response = self.client.post(comment_url, + { + 'body': '123ffffffffff', + }) + self.assertEqual(response.status_code, 302) # jyn: 验证提交成功 + self.update_article_comment_status(article) + self.assertEqual(len(article.comment_list()), 2) # jyn: 评论总数应为2条 + + # 6. 测试回复功能(提交带父评论ID的评论) + parent_comment_id = article.comment_list()[0].id # jyn: 获取第一条评论ID作为父评论 + response = self.client.post(comment_url, + { + 'body': ''' + # Title1 + + ```python + import os + ``` + + [url](https://www.lylinux.net/) + + [ddd](http://www.baidu.com) + ''', # jyn: 带Markdown格式、链接、代码块的评论正文 + 'parent_comment_id': parent_comment_id # jyn: 指定父评论ID,实现回复 + }) + self.assertEqual(response.status_code, 302) # jyn: 验证回复提交成功 + self.update_article_comment_status(article) + article = Article.objects.get(pk=article.pk) + self.assertEqual(len(article.comment_list()), 3) # jyn: 评论总数应为3条(含1条回复) + + # 7. 测试评论树解析(验证回复层级关系) + comment = Comment.objects.get(id=parent_comment_id) + tree = parse_commenttree(article.comment_list(), comment) # jyn: 调用模板标签解析评论树 + self.assertEqual(len(tree), 1) # jyn: 验证父评论下有1条子回复 + # 8. 测试评论项渲染模板标签(验证模板标签功能正常) + data = show_comment_item(comment, True) # jyn: 调用模板标签生成评论HTML片段 + self.assertIsNotNone(data) # jyn: 验证渲染结果非空 + # 9. 测试工具函数(验证最大ID获取功能) + s = get_max_articleid_commentid() # jyn: 调用工具函数获取最大文章ID和评论ID + self.assertIsNotNone(s) # jyn: 验证返回结果非空 + # 10. 测试评论邮件通知(验证邮件发送功能) + from comments.utils import send_comment_email + send_comment_email(comment) # jyn: 调用工具函数发送评论通知邮件(无返回值,验证无报错) +>>>>>>> JYN_branch diff --git a/src/django-master/comments/urls.py b/src/django-master/comments/urls.py index 090779f..49db846 100644 --- a/src/django-master/comments/urls.py +++ b/src/django-master/comments/urls.py @@ -1,3 +1,4 @@ +<<<<<<< HEAD # 导入Django的URL路径配置模块 from django.urls import path @@ -25,4 +26,20 @@ urlpatterns = [ # URL名称:用于反向解析(如在模板或代码中通过name='postcomment'生成URL) name='postcomment' ), -] \ No newline at end of file +] +======= +# jyn: 评论应用(comments)的URL配置模块,定义评论相关接口的路由映射 +from django.urls import path # jyn: Django URL路径匹配核心函数,用于定义路由规则 +from . import views # jyn: 导入当前应用的视图模块,关联路由与视图逻辑 + +app_name = "comments" # jyn: 路由命名空间,避免不同应用间路由名称冲突 + +urlpatterns = [ + # jyn: 提交评论接口路由,接收文章ID参数,映射到CommentPostView视图类 + path( + 'article/</postcomment', # jyn: 路由路径,<接收整数型文章ID参数 + views.CommentPostView.as_view(), # jyn: 将请求分发到基于类的视图CommentPostView(需调用as_view()转换) + name='postcomment' # jyn: 路由名称,用于reverse反向解析URL + ), +] +>>>>>>> JYN_branch diff --git a/src/django-master/comments/utils.py b/src/django-master/comments/utils.py index 376c5be..7962cf7 100644 --- a/src/django-master/comments/utils.py +++ b/src/django-master/comments/utils.py @@ -1,3 +1,4 @@ +<<<<<<< HEAD # 导入日志模块和Django国际化工具,以及项目自定义工具 import logging # Python内置日志模块,用于记录邮件发送过程中的错误信息 @@ -8,10 +9,22 @@ from djangoblog.utils import send_email # 项目自定义工具:封装 # 初始化日志记录器:按当前模块名称创建logger,用于记录该模块的运行日志(如邮件发送失败) logger = logging.getLogger(__name__) +======= +# jyn: 评论功能邮件通知工具模块,用于评论提交后向评论者及被回复者发送邮件通知 +import logging # jyn: 日志模块,记录邮件发送过程中的异常信息 + +from django.utils.translation import gettext_lazy as _ # jyn: 国际化翻译支持,邮件内容适配多语言 + +from djangoblog.utils import get_current_site # jyn: 工具函数,获取当前网站域名 +from djangoblog.utils import send_email # jyn: 工具函数,封装邮件发送逻辑 + +logger = logging.getLogger(__name__) # jyn: 创建日志实例,用于记录模块内的日志信息 +>>>>>>> JYN_branch def send_comment_email(comment): """ +<<<<<<< HEAD 评论相关邮件发送函数:触发场景为用户提交评论后 1. 向评论作者发送「评论提交成功」的感谢邮件 2. 若该评论是回复(有父评论),向父评论作者发送「评论被回复」的通知邮件 @@ -44,14 +57,61 @@ def send_comment_email(comment): # 构建回复通知的HTML邮件内容:告知父评论作者“你的评论被回复了” html_content = _("""Your comment on %s
has received a reply.
%s +======= + jyn: 发送评论相关邮件通知的核心函数 + :param comment: 已保存的 Comment 模型实例(新提交的评论或回复) + :return: 无返回值,内部处理邮件发送逻辑及异常捕获 + """ + # jyn: 获取当前网站域名,用于拼接文章访问链接 + site = get_current_site().domain + # jyn: 邮件主题(支持国际化) + subject = _('Thanks for your comment') + # jyn: 拼接文章的完整访问链接(HTTPS协议) + article_url = f"https://{site}{comment.article.get_absolute_url()}" + + # 1. 向当前评论者发送「评论提交成功」通知邮件 + # jyn: 构建评论者邮件的HTML内容,包含文章链接和感谢语(支持国际化) + html_content = _("""

Thank you very much for your comments on this site

+ You can visit %(article_title)s + to review your comments, + Thank you again! +
+ If the link above cannot be opened, please copy this link to your browser. + %(article_url)s""") % { + 'article_url': article_url, + 'article_title': comment.article.title + } + tomail = comment.author.email # jyn: 获取当前评论者的邮箱地址 + send_email([tomail], subject, html_content) # jyn: 调用工具函数发送邮件 + + # 2. 若当前评论是回复(有父评论),向父评论者发送「收到回复」通知邮件 + try: + if comment.parent_comment: # jyn: 判断当前评论是否为对其他评论的回复 + # jyn: 构建父评论者邮件的HTML内容,告知其评论被回复(支持国际化) + html_content = _("""Your comment on %(article_title)s
has + received a reply.
%(comment_body)s +>>>>>>> JYN_branch
go check it out!
If the link above cannot be opened, please copy this link to your browser. +<<<<<<< HEAD %s """) % (article_url, comment.article.title, comment.parent_comment.body, article_url) tomail = comment.parent_comment.author.email # 收件人邮箱:父评论作者的邮箱 send_email([tomail], subject, html_content) # 发送回复通知邮件 # 捕获邮件发送过程中的所有异常(如邮箱格式错误、邮件服务器故障等) except Exception as e: - logger.error(e) # 将错误信息记录到日志(便于后续排查问题,不中断程序运行) \ No newline at end of file + logger.error(e) # 将错误信息记录到日志(便于后续排查问题,不中断程序运行) +======= + %(article_url)s + """) % { + 'article_url': article_url, + 'article_title': comment.article.title, + 'comment_body': comment.parent_comment.body # jyn: 包含父评论的正文内容 + } + tomail = comment.parent_comment.author.email # jyn: 获取父评论者的邮箱地址 + send_email([tomail], subject, html_content) # jyn: 发送回复通知邮件 + except Exception as e: + logger.error(e) # jyn: 捕获发送过程中的异常,记录错误日志(不中断程序执行) +>>>>>>> JYN_branch diff --git a/src/django-master/comments/views.py b/src/django-master/comments/views.py index 895e200..5ba7383 100644 --- a/src/django-master/comments/views.py +++ b/src/django-master/comments/views.py @@ -1,4 +1,5 @@ <<<<<<< HEAD +<<<<<<< HEAD # Create your views here. # 导入Django核心模块、异常类、视图工具及项目内模型/表单 from django.core.exceptions import ValidationError # Django内置验证异常类,用于抛出自定义验证错误 @@ -31,18 +32,43 @@ class CommentPostView(FormView): # 1. 类视图基础配置 form_class = CommentForm # 指定关联的表单类:使用CommentForm处理提交数据 template_name = 'blog/article_detail.html' # 指定表单渲染的模板:评论在文章详情页提交,故用文章详情模板 +======= +# jyn: 评论功能核心视图模块,处理评论提交、数据验证、权限判断及页面跳转逻辑 +from django.core.exceptions import ValidationError # jyn: Django 数据验证异常类,用于抛出评论相关错误 +from django.http import HttpResponseRedirect # jyn: 重定向响应类,评论提交后跳转至指定页面 +from django.shortcuts import get_object_or_404 # jyn: 快捷查询函数,查询不到数据时返回404 +from django.utils.decorators import method_decorator # jyn: 方法装饰器工具,用于给类视图方法添加装饰器 +from django.views.decorators.csrf import csrf_protect # jyn: CSRF防护装饰器,防止跨站请求伪造 +from django.views.generic.edit import FormView # jyn: 基于类的表单处理视图基类,简化表单验证逻辑 + +from accounts.models import BlogUser # jyn: 导入用户模型,关联评论作者 +from blog.models import Article # jyn: 导入文章模型,关联评论所属文章 +from .forms import CommentForm # jyn: 导入评论表单类,用于前端数据验证 +from .models import Comment # jyn: 导入评论模型,用于数据存储 + + +class CommentPostView(FormView): + """jyn: 评论提交处理视图类,继承FormView实现表单验证、数据保存及页面跳转""" + form_class = CommentForm # jyn: 指定关联的表单类,用于前端提交数据的验证 + template_name = 'blog/article_detail.html' # jyn: 表单验证失败时渲染的模板(文章详情页) +>>>>>>> JYN_branch # 2. 给dispatch方法添加CSRF保护:所有请求(GET/POST)都经过CSRF验证 @method_decorator(csrf_protect) def dispatch(self, *args, **kwargs): +<<<<<<< HEAD """ 类视图的请求入口方法:所有请求都会先经过此方法 作用:调用父类的dispatch逻辑,同时应用CSRF保护 """ +======= + """jyn: 重写dispatch方法,添加CSRF防护,所有请求先经过该方法分发""" +>>>>>>> JYN_branch return super(CommentPostView, self).dispatch(*args, **kwargs) # 3. 处理GET请求:当用户以GET方式访问该视图时触发 def get(self, request, *args, **kwargs): +<<<<<<< HEAD """ GET请求逻辑:不处理表单提交,直接重定向到对应的文章详情页的评论区 避免用户直接通过URL以GET方式访问该视图时出现异常 @@ -55,9 +81,17 @@ class CommentPostView(FormView): url = article.get_absolute_url() # 重定向到文章详情页的评论区 return HttpResponseRedirect(url + "#comments") +======= + """jyn: 处理GET请求,直接重定向到文章详情页的评论区""" + article_id = self.kwargs['article_id'] # jyn: 从URL参数中获取文章ID + article = get_object_or_404(Article, pk=article_id) # jyn: 查询文章,不存在则返回404 + url = article.get_absolute_url() # jyn: 获取文章的绝对URL + return HttpResponseRedirect(url + "#comments") # jyn: 重定向到文章评论区锚点 +>>>>>>> JYN_branch # 4. 处理表单验证失败的逻辑:当form.is_valid()为False时触发 def form_invalid(self, form): +<<<<<<< HEAD """ 表单数据验证失败(如评论内容为空、格式错误)时的处理 作用:重新渲染文章详情页,带上错误的表单对象(前端显示错误提示) @@ -67,6 +101,12 @@ class CommentPostView(FormView): article = get_object_or_404(Article, pk=article_id) # 渲染模板:传递错误的表单对象(form)和文章对象(article),前端可显示错误信息 +======= + """jyn: 表单数据验证失败时的处理逻辑,返回文章详情页并携带错误表单""" + article_id = self.kwargs['article_id'] # jyn: 从URL参数获取文章ID + article = get_object_or_404(Article, pk=article_id) # jyn: 查询目标文章 + # jyn: 渲染文章详情页,传递错误表单和文章实例(前端显示错误信息) +>>>>>>> JYN_branch return self.render_to_response({ 'form': form, # 带有错误信息的表单 'article': article # 当前文章对象(用于渲染文章详情) @@ -74,6 +114,7 @@ class CommentPostView(FormView): # 5. 处理表单验证成功的逻辑:当form.is_valid()为True时触发(核心业务逻辑) def form_valid(self, form): +<<<<<<< HEAD """提交的数据验证合法后的逻辑:保存评论数据到数据库,处理评论状态和回复关联""" # 1. 获取当前登录用户(评论作者) user = self.request.user # 从请求对象中获取登录用户 @@ -111,4 +152,40 @@ class CommentPostView(FormView): comment.parent_comment = parent_comment # (原代码缺失:最终需调用comment.save()将评论数据提交到数据库,否则评论不会保存) - # comment.save() \ No newline at end of file + # comment.save() +======= + """jyn: 表单数据验证合法后的核心逻辑,保存评论数据并跳转""" + user = self.request.user # jyn: 获取当前登录用户 + author = BlogUser.objects.get(pk=user.pk) # jyn: 通过用户ID查询BlogUser实例(评论作者) + article_id = self.kwargs['article_id'] # jyn: 从URL参数获取文章ID + article = get_object_or_404(Article, pk=article_id) # jyn: 查询目标文章,不存在返回404 + + # jyn: 校验文章评论状态和发布状态,禁止对关闭评论/草稿文章提交评论 + if article.comment_status == 'c' or article.status == 'c': + raise ValidationError("该文章评论已关闭.") # jyn: 抛出验证错误,终止评论提交 + + # jyn: 不立即保存评论(commit=False),先补充关联字段 + comment = form.save(False) + comment.article = article # jyn: 关联评论所属文章 + + # jyn: 根据博客设置决定评论是否需要审核(无需审核则直接启用) + from djangoblog.utils import get_blog_setting + settings = get_blog_setting() # jyn: 获取博客全局设置 + if not settings.comment_need_review: + comment.is_enable = True # jyn: 无需审核时,评论直接启用 + + comment.author = author # jyn: 关联评论作者 + + # jyn: 处理回复功能,若存在父评论ID则关联父评论 + if form.cleaned_data['parent_comment_id']: + parent_comment = Comment.objects.get( + pk=form.cleaned_data['parent_comment_id']) # jyn: 查询父评论 + comment.parent_comment = parent_comment # jyn: 关联父评论 + + comment.save(True) # jyn: 最终保存评论数据到数据库 + + # jyn: 重定向到文章详情页的当前评论锚点(精准定位到新提交的评论) + return HttpResponseRedirect( + "%s#div-comment-%d" % + (article.get_absolute_url(), comment.pk)) +>>>>>>> JYN_branch