日常开发与内容相关的Web系统时,不管是 Blog 还是 CMS,如果需要增加与用户互动的环节那肯定需要评论的功能,接下来基于Python的 MPTT框架 在 Django 中实现评论回复功能。
注意:由于用户评论功能会涉及到一些问题,很多内容管理平台都没有开放这个功能,主要考虑人工成本,因此本方法收藏起来用的时候再拿出来吧。
基于框架自动生成的评论恢复模板。实现起来虽然说很简单,但是要静下心来慢慢看,否则你懂得。

安装与配置
CMD使用命令行安装

创建应用
python manage.py startapp Comment
配置应用模块
...
# 添加文章应用
apps.Comment,
.......
MPTT_COMMENTS_ALLOW_ANONYMOUS = True # True 为允许匿名评论,否则不允许
COMMENTS_APP = django_mptt_comments
SITE_ID = 1
MIDDLEWARE = [
......
crequest.middleware.CrequestMiddleware,
]
X_FRAME_OPTIONS = SAMEORIGIN # 设置弹窗显示
不需要修改该的文件
admin.py 这个我们使用xadmin代替的原有版本的admin,因此这个文件不需要修改该apps.py 应用的配置文件,不需要动test.py 测试执行脚本,不需要动
应用 models.py
from django.db import models
from mptt.models import MPTTModel, TreeForeignKey
from apps.User.models import *
from ckeditor.fields import RichTextField
from apps.Blog.models import *
# 替换 models.Model 为 MPTTModel
class Comment(MPTTModel):
article_slug = models.SlugField(
default="",
verbose_name=文章slug, help_text=填写英文、字母、下划线、数字,不可重复,用于标签快速查找,
)
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name=comments
)
# mptt树形结构
parent = TreeForeignKey(
self,
on_delete=models.CASCADE,
null=True,
blank=True,
related_name=children
)
# 记录二级评论回复给谁, str
reply_to = models.ForeignKey(
UserProfile,
null=True,
blank=True,
on_delete=models.CASCADE,
related_name=replyers
)
comment_message = RichTextField()
created = models.DateTimeField(auto_now_add=True)
class MPTTMeta:
order_insertion_by = [created]
def __str__(self):
return self.comment_message[:20]
管理 adminx.py
import xadmin
from .models import *
# 评论管理
class CommentAdmin(object):
list_display = [id,article_slug, user, reply_to,comment_message]
show_bookmarks = False
xadmin.site.register(Comment, CommentAdmin)
视图 Views.py
from .models import *
from apps.Blog.models import *
from django.contrib.auth.decorators import *
from django.shortcuts import *
from .Forms import *
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, JsonResponse
from .models import Comment
from MyHome.settings import *
from notifications.signals import notify
from django.contrib.auth.models import User
# 文章评论
@login_required(login_url=/userprofile/login/)
def post_comment(request):
# 处理 POST 请求
if request.method == POST:
article_slug = request.GET.get(article_slug)
comment_message = request.POST.get(comment_message)
parent_comment_id = request.GET.get(parent_comment_id)
comment_form = CommentForm(request.POST)
# print("=" * 50)
# print(article_slug)
# print("=" * 50)
if comment_form.is_valid():
new_comment = Comment()
new_comment.comment_message = comment_message
new_comment.user = request.user
new_comment.article_slug = article_slug
# 返回文章当前页
redirect_url = WebBaseUrl + "MyBlogDetail?article_slug=" + article_slug
# 二级回复
if parent_comment_id:
parent_comment = Comment.objects.get(id=parent_comment_id)
# 若回复层级超过二级,则转换为二级
new_comment.parent_id = parent_comment.get_root().id
# 被回复人
new_comment.reply_to = parent_comment.user
new_comment.save()
context = {
"message": "回复成功,请关闭窗口"
}
return render(request, Comment/reply.html, context)
new_comment.save()
return redirect(redirect_url)
else:
return HttpResponse("表单内容有误,请重新填写。")
# 处理 GET 请求
elif request.method == GET:
article_slug = request.GET.get(article_slug)
parent_comment_id = request.GET.get(parent_comment_id)
comment_form = CommentForm()
context = {
comment_form: comment_form,
article_slug: article_slug,
parent_comment_id: parent_comment_id,
}
return render(request, Comment/reply.html, context)
# 处理其他请求
else:
return HttpResponse("仅接受GET/POST请求。")
配置 url.py
from apps.Comment.views import *
app_name = Comment
urlpatterns = [
# 已有代码,处理一级回复
path(post-comment, post_comment, name=post_comment),
# 新增代码,处理二级回复
path(comment_reply, post_comment, name=comment_reply)
]
表单验证 Forms.py
from captcha.fields import CaptchaField
from django import forms
from .models import *
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = [comment_message]
前端渲染模板
内容回复窗口(弹窗) / reply.html
<html lang="zh-cn">
{% load static %}
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="{% static Blog/bootstrap.min.css %}">
<link rel="stylesheet" href="{% static Blog/prism.css %}">
<script src="{% static Blog/prism_patched.min.js %}"></script>
<script src="{% static ckeditor/ckeditor-init.js %}"></script>
<script src="{% static ckeditor/ckeditor/ckeditor.js %}"></script>
<script src="{% static Blog/js/jquery/jquery-3.3.1.js %}"></script>
<script src="{% static Blog/js/popper/popper-1.14.4.js %}"></script>
<script src="{% static Blog/js/bootstrap/js/bootstrap.min.js %}"></script>
<script src="{% static Blog/js/csrf.js %}"></script>
<style>
.class_form{
width: 100%;
}
</style>
</head>
<body>
{% if message %}
{{ message }}
点击窗口外任意位置返回。
{% else %}
<form action="{% url comment:comment_reply %}?article_slug={{ article_slug }}&parent_comment_id={{ parent_comment_id }}"
method="POST" class="class_form">
{% csrf_token %}
<div class="col-md-12 col-sm-12">
{{ comment_form.comment_message }}
</div>
<button class="btn btn-primary">提交评论</button>
</form>
{% endif %}
</body>
</html>
发表评论部分 / article.html
{% load mptt_tags %}
{% if comments.count != 0 %}
<h4 class="title-t text-uppercase text-bold d-black mb-40">{{ comments.count }}
Comments</h4>
{% recursetree comments %}
{% with comment=node %}
<div class="comment mt-30">
<div class="{% if comment.parent %} comment mt-30 pull-in {% else %} comment mt-30 {% endif %}">
<img class="pull-left"
src="{{ MEDIA_URL }}{{ comment.user_image }}"
style="width: 63px"
alt="">
<div class="comment-content bubble">
<h5 class="fz-13 text-bold text-uppercase d-black">{{ comment.user }}</h5>
<h6 class="orange-light text-uppercase mt-10">
{{ comment.created|date:"Y-m-d H:i"}}</h6>
<p class="fz-13 lh-28 mt-10">{{ comment.comment_message|safe }}</p>
<div class="text-right mt-30">
<!-- 加载 modal 的按钮 -->
{% if user.is_authenticated %}
<button type="button"
class="btn btn-trans text-uppercase ls-2"
onclick="new_comment({{ blog_info.article_slug }}, {{ comment.id }})">
回复
</button>
{% else %}
<a class="btn btn-light btn-sm text-muted"
href="{% url User:UserLogin %}">
登录后方可回复
</a>
{% endif %}
</div>
<div class="modal fade"
id="comment_{{ comment.id }}"
tabindex="-1"
role="dialog"
aria-labelledby="CommentModalCenter"
aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg"
role="document">
<div class="modal-content" style="height: 400px">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalCenterTitle">
回复 {{ comment.user }}:</h5>
</div>
<div class="modal-body"
id="modal_body_{{ comment.id }}"></div>
</div>
</div>
</div>
{% if not comment.is_leaf_node %}
<div class="children">
{{ children }}
</div>
{% endif %}
</div>
</div>
</div>
<div class="clearfix"></div>
{% endwith %}
{% endrecursetree %}
{% else %}
<h3>暂无评论,快来发表你的观点吧</h3>
{% endif %}
</div>
显示评论部分 / article.html
<h4 class="title-t text-bold text-uppercase d-black">留言</h4>
<div class="clearfix"></div>
<hr>
{% if user.is_authenticated %}
<form action="{% url comment:post_comment %}?article_slug={{ blog_info.article_slug }}"
method="POST">
{% csrf_token %}
{{ comment_form.comment_message }}
<div class="mt-30">
<input name="submit" type="submit" class="btn btn-orange-light text-uppercase"
id="submit" value="提交评论">
</div>
</form>
{% else %}
<br><h5 class="row justify-content-center">
请<a href="{% url User:UserLogin %}">登录</a>后回复
</h5><br>
{% endif %}
</div>
</div>
ckeditor 和 js调用部分
<script src="{% static ckeditor/ckeditor/ckeditor.js %}"></script>
<script>
// 加载 modal
function new_comment(article_slug, comment_id) {
let modal_body = #modal_body_ + comment_id;
let modal_id = #comment_ + comment_id;
// 加载编辑器
if ($(modal_body).children().length === 0) {
let content = <iframe src="{% url comment:post_comment %}?article_slug= +
article_slug +
&parent_comment_id= +
comment_id +
" frameborder="0" style="width: 100%; height: 100%;"></iframe>;
$(modal_body).append(content);
}
;
$(modal_id).modal(show);
}
</script>
