From 31985976dcebcae0c9e20f612f77a83e92de2d4e 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, 1 Nov 2025 16:34:31 +0800 Subject: [PATCH] =?UTF-8?q?comments=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- comments/urls.py | 14 ++++++- comments/utils.py | 40 ++++++++++++++++++-- comments/views.py | 93 ++++++++++++++++++++++++++++++++--------------- 3 files changed, 111 insertions(+), 36 deletions(-) diff --git a/comments/urls.py b/comments/urls.py index 7df3fab..35e9999 100644 --- a/comments/urls.py +++ b/comments/urls.py @@ -1,11 +1,21 @@ +# 导入Django的path函数,用于定义URL路径 from django.urls import path +# 导入当前应用下的views模块,用于关联视图函数/类 from . import views +# 定义应用命名空间为"comments",用于在模板中通过命名空间引用URL,避免多应用URL名称冲突 app_name = "comments" + +# URL模式列表,定义该应用的URL路由规则 urlpatterns = [ + # 定义一个评论提交的URL路径 path( + # URL路径字符串,包含一个整数类型的文章ID参数(article_id),用于指定评论所属的文章 'article//postcomment', + # 关联的视图类,使用as_view()方法将类视图转换为可调用的视图函数 views.CommentPostView.as_view(), - name='postcomment'), -] + # 为该URL指定名称"postcomment",结合应用命名空间可通过"comments:postcomment"引用 + name='postcomment' + ), +] \ No newline at end of file diff --git a/comments/utils.py b/comments/utils.py index f01dba7..2becf36 100644 --- a/comments/utils.py +++ b/comments/utils.py @@ -1,28 +1,54 @@ +# 导入日志模块,用于记录程序运行中的信息和错误 import logging +# 导入Django的翻译函数,用于实现多语言支持,_为常用别名 from django.utils.translation import gettext_lazy as _ +# 从项目工具模块导入获取当前站点信息和发送邮件的工具函数 from djangoblog.utils import get_current_site from djangoblog.utils import send_email +# 创建当前模块的日志记录器,用于记录该模块相关的日志信息 logger = logging.getLogger(__name__) def send_comment_email(comment): + """ + 发送评论相关的邮件通知 + + 功能说明: + 1. 向评论者发送感谢邮件 + 2. 若该评论是回复其他评论的(即有父评论),则向被回复的评论者发送回复通知邮件 + """ + # 获取当前站点的域名(如example.com) site = get_current_site().domain + # 邮件主题:感谢评论(支持多语言) subject = _('Thanks for your comment') + # 构建评论所属文章的完整URL(包含协议和域名) article_url = f"https://{site}{comment.article.get_absolute_url()}" + + # 构建给评论者的邮件内容(HTML格式,支持多语言) + # 使用占位符替换文章URL和标题 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} + %(article_url)s""") % { + 'article_url': article_url, + 'article_title': comment.article.title + } + + # 获取评论者的邮箱地址 tomail = comment.author.email + # 发送感谢邮件给评论者 send_email([tomail], subject, html_content) + try: + # 检查当前评论是否有父评论(即是否是回复其他评论) if comment.parent_comment: + # 构建给被回复者的邮件内容(HTML格式,支持多语言) html_content = _("""Your comment on %(article_title)s
has received a reply.
%(comment_body)s
@@ -30,9 +56,15 @@ 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(e) \ No newline at end of file diff --git a/comments/views.py b/comments/views.py index ad9b2b9..9b99bb0 100644 --- a/comments/views.py +++ b/comments/views.py @@ -1,63 +1,96 @@ # Create your views here. -from django.core.exceptions import ValidationError -from django.http import HttpResponseRedirect -from django.shortcuts import get_object_or_404 -from django.utils.decorators import method_decorator -from django.views.decorators.csrf import csrf_protect -from django.views.generic.edit import FormView +# 导入Django相关异常、响应和工具类 +from django.core.exceptions import ValidationError # 用于抛出数据验证异常 +from django.http import HttpResponseRedirect # 用于重定向HTTP响应 +from django.shortcuts import get_object_or_404 # 根据主键获取对象,不存在则返回404 +from django.utils.decorators import method_decorator # 用于为类视图方法添加装饰器 +from django.views.decorators.csrf import csrf_protect # CSRF保护装饰器 +from django.views.generic.edit import FormView # 表单处理的通用类视图 -from accounts.models import BlogUser -from blog.models import Article -from .forms import CommentForm -from .models import Comment +# 导入相关模型和表单 +from accounts.models import BlogUser # 用户模型 +from blog.models import Article # 文章模型 +from .forms import CommentForm # 评论表单 +from .models import Comment # 评论模型 class CommentPostView(FormView): - form_class = CommentForm - template_name = 'blog/article_detail.html' + """评论提交处理的类视图""" + form_class = CommentForm # 指定使用的表单类 + template_name = 'blog/article_detail.html' # 表单渲染和错误显示的模板 @method_decorator(csrf_protect) def dispatch(self, *args, **kwargs): + """ + 重写dispatch方法,添加CSRF保护 + + 使用method_decorator将csrf_protect装饰器应用到dispatch方法, + 确保表单提交经过CSRF验证,防止跨站请求伪造攻击 + """ return super(CommentPostView, self).dispatch(*args, **kwargs) 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") + """ + 处理GET请求 + + 当通过GET访问评论提交URL时,重定向到对应的文章详情页的评论区 + """ + article_id = self.kwargs['article_id'] # 从URL参数中获取文章ID + article = get_object_or_404(Article, pk=article_id) # 获取对应的文章对象 + url = article.get_absolute_url() # 获取文章的绝对URL + return HttpResponseRedirect(url + "#comments") # 重定向到文章详情页的评论锚点 def form_invalid(self, form): - article_id = self.kwargs['article_id'] - article = get_object_or_404(Article, pk=article_id) + """ + 表单数据验证失败时的处理 + + 当表单提交的数据验证不通过(如必填项为空、格式错误等), + 重新渲染文章详情页,并携带错误表单对象和文章对象,方便前端显示错误信息 + """ + article_id = self.kwargs['article_id'] # 获取文章ID + article = get_object_or_404(Article, pk=article_id) # 获取文章对象 return self.render_to_response({ - 'form': form, - 'article': article + 'form': form, # 包含错误信息的表单对象 + 'article': article # 文章对象,用于页面渲染 }) def form_valid(self, form): - """提交的数据验证合法后的逻辑""" - user = self.request.user - author = BlogUser.objects.get(pk=user.pk) - article_id = self.kwargs['article_id'] - article = get_object_or_404(Article, pk=article_id) + """ + 表单数据验证合法后的处理逻辑 + + 当表单数据验证通过后,执行评论保存等业务逻辑 + """ + user = self.request.user # 获取当前登录用户 + author = BlogUser.objects.get(pk=user.pk) # 根据用户ID获取对应的用户对象 + article_id = self.kwargs['article_id'] # 获取文章ID + article = get_object_or_404(Article, pk=article_id) # 获取文章对象 + # 检查文章评论状态:若文章评论关闭或文章状态为关闭,则抛出验证异常 if article.comment_status == 'c' or article.status == 'c': raise ValidationError("该文章评论已关闭.") + + # 保存表单数据但不提交到数据库(commit=False),以便后续补充字段 comment = form.save(False) - comment.article = article + comment.article = article # 关联评论对应的文章 + + # 获取博客设置,判断评论是否需要审核 from djangoblog.utils import get_blog_setting settings = get_blog_setting() if not settings.comment_need_review: - comment.is_enable = True - comment.author = author + comment.is_enable = True # 若无需审核,直接设置评论为启用状态 + comment.author = author # 设置评论的作者 + + # 处理回复功能:若存在父评论ID,则关联父评论 if form.cleaned_data['parent_comment_id']: parent_comment = Comment.objects.get( pk=form.cleaned_data['parent_comment_id']) comment.parent_comment = parent_comment - comment.save(True) + comment.save(True) # 最终保存评论到数据库 + + # 重定向到文章详情页中当前评论的锚点位置 return HttpResponseRedirect( "%s#div-comment-%d" % - (article.get_absolute_url(), comment.pk)) + (article.get_absolute_url(), comment.pk)) \ No newline at end of file