Merge branch 'JYN_branch' into develop

develop
ymq 3 months ago
commit 673d7100f5

@ -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'<a href="%s">%s</a>' %
>>>>>>> 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”显示为“文章”
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'<a href="%s">%s</a>' % (link, obj.article.title)
)
# jyn: 为自定义列表字段设置后台显示名称(支持国际化)
link_to_userinfo.short_description = _('User')
link_to_article.short_description = _('Article')
>>>>>>> JYN_branch

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

@ -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无需前端用户输入
# 通常由后端自动填充如从登录态获取author无需前端用户输入
=======
model = Comment # jyn: 关联的核心模型,表单数据同步至该模型
fields = ['body'] # jyn: 前端需提交的字段(评论内容),自动生成对应表单控件
>>>>>>> JYN_branch

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

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

@ -1,3 +1,4 @@
<<<<<<< HEAD
# 导入Django的URL路径配置模块
from django.urls import path
@ -25,4 +26,20 @@ urlpatterns = [
# URL名称用于反向解析如在模板或代码中通过name='postcomment'生成URL
name='postcomment'
),
]
]
=======
# 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/<<int:article_id>/postcomment', # jyn: 路由路径,<<int:article_id>接收整数型文章ID参数
views.CommentPostView.as_view(), # jyn: 将请求分发到基于类的视图CommentPostView需调用as_view()转换)
name='postcomment' # jyn: 路由名称用于reverse反向解析URL
),
]
>>>>>>> JYN_branch

@ -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<br/> has
received a reply. <br/> %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 = _("""<p>Thank you very much for your comments on this site</p>
You can visit <a href="%(article_url)s" rel="bookmark">%(article_title)s</a>
to review your comments,
Thank you again!
<br />
If the link above cannot be opened, please copy this link to your browser.
%(article_url)s""") % {
'article_url': article_url,
'article_title': comment.article.title
}
tomail = comment.author.email # jyn: 获取当前评论者的邮箱地址
send_email([tomail], subject, html_content) # jyn: 调用工具函数发送邮件
# 2. 若当前评论是回复(有父评论),向父评论者发送「收到回复」通知邮件
try:
if comment.parent_comment: # jyn: 判断当前评论是否为对其他评论的回复
# jyn: 构建父评论者邮件的HTML内容告知其评论被回复支持国际化
html_content = _("""Your comment on <a href="%(article_url)s" rel="bookmark">%(article_title)s</a><br/> has
received a reply. <br/> %(comment_body)s
>>>>>>> JYN_branch
<br/>
go check it out!
<br/>
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) # 将错误信息记录到日志(便于后续排查问题,不中断程序运行)
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

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

Loading…
Cancel
Save