Django实现二级评论功能

2023年1月1日 14:59 ry 500

之前我的网站只有一级评论功能,后面发现对于用户的评论无法准确送达,因此决定优化为二级评论,相比于一级评论,多了个回复的功能,我们先从前端开始优化,给每一条评论添加一个回复的功能,关键代码如下所示

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

最后的效果如下所示

如果上述代码帮助您很多,可以打赏下以减少服务器的开支吗,万分感谢!

欢迎发表评论~

点击此处登录后即可评论


评论列表
暂时还没有任何评论哦...

赣ICP备2021001574号-1

赣公网安备 36092402000079号