之前我的网站只有一级评论功能,后面发现对于用户的评论无法准确送达,因此决定优化为二级评论,相比于一级评论,多了个回复的功能,我们先从前端开始优化,给每一条评论添加一个回复的功能,关键代码如下所示
<span class="reply" style="margin-left: 10px">
<a href="javascript:void(0);" style="text-decoration: none" onclick="NewReply('{{subMark.users}}','{{subMark.UUid}}')">回复</a>
</span>
<script>
function NewReply(reply_toUser,commentUUid)
{
$("#newComment").text("回复:"+reply_toUser);
var showDisplay = document.getElementById("showReply");
showDisplay.style["display"] = "block";
$('iframe').contents().find(".cke_editable").html("<p>@"+reply_toUser+":</p>");
// $('iframe').contents().find(".cke_editable").focus();
$("body,html").animate({scrollTop: $("#commentSubmit").offset().top - 90}, 100);
//发送ajax
$.ajax({
type:"post",
dataType: "json",
url: "{% url 'SendUUid' %}",
data: {
comment_UUid:commentUUid,
},
success:function (data) {
if(data['success'])
{return "success";
}
if(data['error']){
return '请登录';
}
if(data['warings']){
return '非法调试,永久封号';
}
}
});
}
</script>
先看下我的评论Comment模型表,如下所示
class Comment(models.Model):
UUid = models.CharField(null=True,verbose_name='评论id',max_length=100)
article = models.ForeignKey('blog_table.Article', on_delete=models.CASCADE, verbose_name='博客文章')
comment_time = models.DateTimeField(auto_now_add=True,verbose_name='评论时间')
user = models.ForeignKey('users.UserProfile',on_delete=models.SET_NULL,null=True,verbose_name='评论用户名')
content = RichTextField(null=True, verbose_name='评论内容',max_length=10000)
user_image = models.CharField(null=True,verbose_name='评论者头像',max_length=10000)
#新增
reply_user = models.CharField(max_length=1000,verbose_name='楼主',default='0')
from_uuid = models.CharField(max_length=100,verbose_name='楼主的uuid',default='0')
objects = models.Manager
class Meta:
verbose_name = '评论信息'
verbose_name_plural = verbose_name
def __str__(self):
return self.content
UUid是评论的唯一标识符,相比于一级评论,这里二级评论表中增加了reply_user和from__uuid字段,第一个是二级评论回复一级评论的用户,第二个是二级评论回复一级评论的uuid,这个很好理解。上面的前端代码点击回复即立马传递要回复的用户一级其评论的uuid,由于我的富文本编辑框是contenteditable="true"样式的,无法设置placeholder,不然我直接将@xxx字样写成placeholder属性。这里我直接将@用户名写入content内容了,具体样式如下所示点击回复后生成display=block属性使得取消回复的按钮正常显示出来,然后重新将光标聚焦到富文本编辑器这里。最后通过ajax将uuid传递给后端来接收。后端代码如下所示
class SendUUid(View):
def post(self,request):
if not request.user.is_authenticated:
return JsonResponse({'error': '请登录'})
else:
user = request.user.username
request_data = request.body
msgs = bytes.decode(request_data)
UUid = msgs.replace('comment_UUid=', '')
print('uuid:',UUid)
try:
userId = Comment.objects.filter(UUid=UUid).values()[0].get('user_id')
except:
qq_email_back = UserProfile.objects.filter(username=user).values('qq_email')[0].get('qq_email')
print(qq_email_back)
Backperson.objects.create(qq_email=qq_email_back)
return JsonResponse({'warings':'非法调试,永久封号'})
request.session['reply_comment_uuid'] = UUid
return JsonResponse({'success':'ok'})
先判断用户是否登录,在判断传入的uuid是否存在,这些措施是为了防止爬虫的,如果检测到直接加入黑名单,正常时将uuid写入session,方面后面参数传递。该视图对应的路由如下所示
path('SendUUid/',csrf_exempt(views.SendUUid.as_view()),name='SendUUid'),
接下来就是写取消回复的js代码了,
<button type="button" class="btn btn-warning" style="float: right;margin-right: 5%;display: none" id="showReply" onclick="CancelReply()">取消回复</button>
<script>
function CancelReply()
{
$("#newComment").text("新的评论");
var showDisplay = document.getElementById("showReply");
showDisplay.style["display"] = "none";
$('iframe').contents().find(".cke_editable").html("");
$('iframe').contents().find(".cke_editable").focus();
$("body,html").animate({scrollTop: $("#commentSubmit").offset().top - 90}, 100);
}
</script>
逻辑很简单,直接将按钮设置为none,然后清空内容,光标聚焦到编辑器即可。前端差不多了,现在来写回复的后端逻辑了,部分代码如下所示
def post(self,request,categery,username,article_id):
print('request.COOKIES:',request.COOKIES.get('is_login'))
if not request.user.is_authenticated:
return redirect(reverse('login'))
cookie = request.COOKIES
limit_list = []
commentform = CommentForm(request.POST)
user = request.user.username
if commentform.is_valid():
try:
UUid = create_uuid()
if 'a_' not in commentform.cleaned_data.get('content'):
comments = BeautifulSoup(commentform.cleaned_data.get('content'),'html.parser').get_text()
reply_user = ''.join(re.findall('@(.*?)[:|:]',comments))
#如果不是新评论
if reply_user:
#判断username是否存在
exist_user = UserProfile.objects.filter(username=reply_user)
if not exist_user:
request.session['noExist_user'] = "艾特的用户不存在"
return redirect(reverse('detail_article',args=(categery,username,article_id)))
from_uuid = request.session['reply_comment_uuid']
#如果是新评论
else:
reply_user = "0"
from_uuid = '0'
# 去除掉@标志
# comments = re.sub('@(.*?)[:|:]','',comments)
else:
comments = commentform.cleaned_data.get('content')
reply_user = ''.join(re.findall('@(.*?)[:|:]', comments))
if reply_user:
# 判断username是否存在
exist_user = UserProfile.objects.filter(username=reply_user)
if not exist_user:
request.session['noExist_user'] = "艾特的用户不存在"
return redirect(reverse('detail_article', args=(categery, username, article_id)))
from_uuid = request.session['reply_comment_uuid']
else:
reply_user = '0'
from_uuid = '0'
#去除掉@标志
# comments = re.sub('@(.*?)[:|:]','',comments)
print(77777,comments)
user_id = UserProfile.objects.filter(username=user).values('id')[0].get('id')
pd_limit_comments = Comment.objects.filter(user_id=user_id).values('comment_time')
for lim in pd_limit_comments:
# print(int(time.mktime(lim.get('comment_time').timetuple()))+3600*8)
limit_list.append(int(time.mktime(lim.get('comment_time').timetuple()))+3600*8)
now_time = int(time.time())
pd_time = [ ii for ii in limit_list if fabs(now_time-ii)<3600*24]
super_men = UserProfile.objects.filter(username=user).values('is_superuser')[0].get('is_superuser')
print(super_men)
if (len(pd_time) >= 5 or 'script' in comments) and not super_men:
limit_delete_comment = Comment.objects.filter(user_id=user_id)
limit_delete_comment.delete()
qq_email_back = UserProfile.objects.filter(username=user).values('qq_email')[0].get('qq_email')
print(qq_email_back)
Backperson.objects.create(qq_email=qq_email_back)
return redirect(reverse('logout'))
print(len(pd_time))
print(pd_time)
print(pd_limit_comments)
print(limit_list)
################
if from_uuid != '0':
# 判断是否为三级评论,先在二级评论中找是否存在
last_uuid_comment = Comment.objects.filter(UUid=from_uuid).values()[0].get('from_uuid')
# 如果为三级评论,则将它重置为二级评论
if last_uuid_comment != '0':
from_uuid = last_uuid_comment
id = Article.objects.filter(article_id=article_id).values('id')[0].get('id')
user_image = UserProfile.objects.filter(username=user).values('image')[0].get('image')
Comment.objects.create(UUid=UUid,content=comments,article_id=id,user_image=user_image,user_id=user_id,reply_user=reply_user,from_uuid=from_uuid)
request.session['comment_success'] = "评论成功"
return redirect(reverse('detail_article',args=(categery,username,article_id)))
except:
return HttpResponse('请求错误!')
else:
errors = commentform.errors.get_json_data()
request.session['errors_comment'] = '评论不能为空!'
return redirect(reverse('detail_article',args=[categery,username,article_id]))
get部分的代码就不发了,主要展示post请求的代码。主要先判断艾特的用户是否存在,然后就是对于二级评论中的回复问题(三级评论),我们需要将他转为真正的二级评论,然后就是前端的2个for循环显示评论了,这个很简单,如下所示
<div class="commentList">
<div style="text-align: center;padding-top:30px">
<span style="color: rgb(125,139,141)">评论列表</span>
</div>
{% if comment_msg.0 %}
{% for commentUser in comment_msg.values %}
<div style="margin-left: 10%">
<a href="{% url 'blog' commentUser.users %}">
<img class="img-circle image-responsive " src="{{commentUser.user_image}}" style="height: 30px;width: 30px" >
</a>
<span style="margin-left: 10px">{{commentUser.comment_time}}</span>
<span style="margin-left: 10px">{{commentUser.users}}:</span>
{% if commentUser.users == detail_msg.username %}
<a href="javascript:void(0);">
<img src="{% static 'bloger.png' %}" height="20" width="20">
</a>
{% endif %}
{% if commentUser.is_superuser %}
<a href="javascript:void(0);">
<img src="{% static 'admin.png' %}" height="20" width="20">
</a>
{% endif %}
{% if request.user.is_authenticated %}
{% if request.user.username == commentUser.users %}
<a href="javascript:void(0);" style="margin-left: 10px;text-decoration: none" onclick="CommentDelete('{{commentUser.UUid}}')">删除</a>
{% endif %}
{% endif %}
<span class="reply" style="margin-left: 10px">
<a href="javascript:void(0);" style="text-decoration: none" onclick="NewReply('{{commentUser.users}}','{{commentUser.UUid}}')">回复</a>
</span>
</div>
<div style="margin-left: 10%;margin-top: 5px" id="commentsAll">
<span>{% autoescape off %}{{commentUser.content}}{% endautoescape %}</span>
</div>
<!-- <div style="margin-top: 15px">-->
<!-- <hr>-->
<!-- </div>-->
<!-- 二级评论-->
{% if commentUser.subComments %}
<!-- 循环二级评论-->
{% for subMark in commentUser.subComments %}
<div id="subComment" style="margin-left: 15%;margin-top: 20px">
<a href="{% url 'blog' subMark.users %}">
<img class="img-circle image-responsive " src="{{subMark.user_image}}" style="height: 30px;width: 30px" >
</a>
<span style="margin-left: 10px">{{subMark.comment_time}}</span>
<span style="margin-left: 10px">{{subMark.users}}:</span>
{% if subMark.users == detail_msg.username %}
<a href="javascript:void(0);">
<img src="{% static 'bloger.png' %}" height="20" width="20">
</a>
{% endif %}
{% if subMark.is_superuser %}
<a href="javascript:void(0);">
<img src="{% static 'admin.png' %}" height="20" width="20">
</a>
{% endif %}
{% if request.user.is_authenticated %}
{% if request.user.username == subMark.users %}
<a href="javascript:void(0);" style="margin-left: 10px;text-decoration: none" onclick="CommentDelete('{{subMark.UUid}}')">删除</a>
{% endif %}
{% endif %}
<span class="reply" style="margin-left: 10px">
<a href="javascript:void(0);" style="text-decoration: none" onclick="NewReply('{{subMark.users}}','{{subMark.UUid}}')">回复</a>
</span>
</div>
<div style="margin-left: 15%;margin-top: 5px" id="subcommentsAll">
<span>{% autoescape off %}{{subMark.content}}{% endautoescape %}</span>
</div>
{% endfor %}
{% endif %}
<div style="margin-top: 15px">
<hr>
</div>
{% endfor %}
{% else %}
<div style="text-align: center;margin-top: 40px;color: rgb(125,139,141)">
<span>暂时还没有任何评论哦...</span>
</div>
{% endif %}
<div style="margin-bottom: 30px">
<hr>
</div>
</div>
最后的效果如下所示
点击此处登录后即可评论