Django3递归展示评论树和Ajax展示评论树

关于博客评论功能实现遇到的问题

  1. 评论功能

    这方面很容易实现,做一个comment的model。使用modelform进行表单验证,即可实现添加评论的功能。

    难点是利用ajax发送请求,实现动态添加评论。不刷新页面即可剪刀评论。在这方面对跟评论的添加很容易,使用ajax发请求向后端拿数据,找到评论的div,进行append即可。关键是子评论的添加。首先判断父评论的id值pid,在append的父评论中的div标签添加动态的评论的主键pk值为comment_d值,通过comment_d=pid找到这个标签,然后在这个标签进行append子评论,同时子评论也需要添加一个comment_d=pk,用于子子评论的动态添加。

  2. 评论树展示

    第一种方法是使用递归的方式实现。在views.py中拿到所有评论数据render给页面渲染。页面渲染处用自定义标签中的方法对渲染的comment_dict进行递归生成评论的HTML标签.

    第二种就是发送ajax请求,不绑定时间,直接写在body标签后(很重要,因为卸载body中由于文件过多可能会导致<script>标签不执行的问题),下边就和动态加载append的父评论标签和子评论标签一样啦。

JS代码

models.py

class Comment(models.Model):
    """
    文章评论
    """
    content = models.TextField(verbose_name='评论内容')
    username = models.CharField(max_length=30, verbose_name='用户名')
    add_time = models.DateTimeField(auto_now_add=True, verbose_name='发布时间')
    article = models.ForeignKey(Article, verbose_name='文章', on_delete=models.CASCADE)
    qq_email = models.CharField(max_length=100, verbose_name='qq邮箱')
    web_site = models.CharField(max_length=100, blank=True, null=True, verbose_name='网站')
    pid = models.ForeignKey('self', blank=True, null=True, verbose_name='父级评论', on_delete=models.CASCADE)

    class Meta:
        verbose_name = '评论'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.content[:20]

views.py

class CommentForm(forms.ModelForm):

    class Meta:
        model = models.Comment
        fields = "__all__"
        # exclude = ['pid', ]


class CommentView(View):

    def post(self, request):
        msg = {
    
    }
        error = {
    
    }
        data = {
    
    }
        form = CommentForm(request.POST)
        pid = request.POST.get('pid')
        if form.is_valid():
            msg['success'] = True
            # print(form.cleaned_data)
            comment_obj = form.save()
            data['pk'] = comment_obj.pk
            data['content'] = comment_obj.content
            data['username'] = comment_obj.username
            data['add_time'] = comment_obj.add_time.strftime('%Y-%m-%d %H:%M:%S')
            # print(comment_obj.pid)
            if comment_obj.pid != None:
                # pid = int(comment_obj.pid)

                fu = models.Comment.objects.get(pk=pid).username
                # print(fu)
                data['fu_username'] = fu
            else:
                data['fu_username'] = 0
            msg['data'] = data

        else:
            print(form.errors)
            msg['success'] = False
            for field in form.fields.keys():
                if form.has_error(field):
                    error[field] = 'valied'
                else:
                    error[field] = 0
            msg['error'] = error
        # print(msg) # 发给AjaxForm的数据
        return JsonResponse(msg)


class CommentTreeView(View):

    def get(self, request):
        msg = []
        article_id = request.GET.get('article_id')
        # 该文章的所有评论
        comment_obj = models.Comment.objects.filter(article_id=article_id)
        for comment in comment_obj:
            data = {
    
    }
            if comment.pid:
                data['pid'] = comment.pid.id
                data['fu_username'] = models.Comment.objects.get(pk=comment.pid.id).username
            else:
                data['pid'] = None
                data['fu_username'] = None
            data['pk'] = comment.pk
            data['content'] = comment.content
            data['username'] = comment.username
            data['add_time'] = comment.add_time.strftime('%Y-%m-%d %H:%M:%S')
            msg.append(data)
        # print(msg)  # print(msg) # 发给Ajax的数据

        return JsonResponse(msg, safe=False)

js代码

$("#comment-submit").click(function () {
    
    

        $("#comment-submit").html("正在提交评论...");
        $("#comment-form").ajaxSubmit(function (data) {
    
    
            console.log(data);
            // console.log(data.error.qq_email);
            // console.log(data.error.article);
            if (data.success == false) {
    
    
                if (data.error.username) {
    
    
                    $("#username").addClass("is-invalid");
                    $("#username-feedback").html(data.error.username);
                }
                if (data.error.qq_email) {
    
    
                    $("#qq_email").addClass("is-invalid");
                    $("#qq_email-feedback").html(data.error.qq_email);
                }
                if (data.error.web_site) {
    
    
                    $("#web_site").addClass("is-invalid");
                }
                if (data.error.content) {
    
    
                    $("#content").addClass("is-invalid");
                    $("#content-feedback").html(data.error.content);
                }
                if (data.error.article) {
    
    
                    Swal.fire({
    
    text: data.error.article, type: 'error'});
                    //layer.msg(data.error.article_id.join(","),{icon:2,time:3000});
                }
                $("#comment-submit").html("提交评论");
                // if (data.error.category_id) {
    
    
                //     Swal.fire({text: data.error.category_id.join(","), type: 'error'});
                //     //layer.msg(data.error.category_id.join(","),{icon:2,time:3000});
                // }
                // if (data.error.p_id) {
    
    
                //     Swal.fire({text: data.error.parent_id, type: 'error'});
                // }
                // if (data.error.msg) {  join(",")
                //     Swal.fire({text: data.error.msg, type: 'error'});
                // }
            } else {
    
    

                var content = data.data.content
                var add_time = data.data.add_time
                var username = data.data.username
                var fu_username = data.data.fu_username
                var pk = data.data.pk

                // ajax动态加载评论
                var gen_comment = `           
                <li class="list-group-item comment-${
      
      pk} mt-3 px-2 pt-3 pb-2 depth-0" comment_id=${
      
      pk}>
                    <div class="clearfix" id="div-comment-${
      
      pk}">
                        <div class="media">
                            <img src="/static/picture/g-sdk_cFeAJq3pic4ekYTaQMJSx4Q_10.jpg"
                                 class="mr-3 rounded-circle" width="50" height="50"
                                 οnerrοr="javascript:this.src='/static/image/unknow.png';">
                            <div class="media-body">
                                <div class="comment-info">
                                    <cite class="c3">

                                        ${
      
      username}

                                    </cite>

                                </div>
                                <div class="comment-meta"><span
                                        class="font-weight-light text-muted">${
      
      add_time}</span>
                                </div>
                            </div>
                        </div>
                        <p class="text-break mt-2">${
      
      content}</p>
                        <a class="btn btn-sm btn-secondary float-right"
                           οnclick="reply('div-comment-${
      
      pk}','${
      
      pk}')">回复</a>
                    </div>                </li>
                        `
                var zi_comment = `
                <li class="list-group-item comment-${
      
      pk} mt-3 px-2 pt-3 pb-2 depth-0" comment_id=${
      
      pk}>
                    <div class="clearfix" id="div-comment-${
      
      pk}">
                        <div class="media">
                            <img src="/static/picture/g-sdk_cFeAJq3pic4ekYTaQMJSx4Q_10.jpg"
                                 class="mr-3 rounded-circle" width="50" height="50"
                                 οnerrοr="javascript:this.src='/static/image/unknow.png';">
                            <div class="media-body">
                                <div class="comment-info">
                                    <cite class="c3">

                                        ${
      
      username}

                                    </cite>
                       <i class="fa fa-share fa-fw fa-1x mr-2 c1" aria-hidden="true"></i>
                       <cite class="c3"><a href="#div-comment-48" class="text-reset">${
      
      fu_username}</a></cite>
                                                       </div>
                                <div class="comment-meta"><span class="font-weight-light text-muted">${
      
      add_time}</span>
                                </div>
                            </div>
                        </div>
                        <p class="text-break mt-2">${
      
      content}</p>
                        <a class="btn btn-sm btn-secondary float-right"
                           οnclick="reply('div-comment-${
      
      pk}','${
      
      pk}')">回复</a>
                    </div>                </li>
                `
                if ($("input[name='pid']").val()) {
    
    
                    // console.log($("input[name='pid']").val())
                    $('#respond').before('<ol class="children">' + zi_comment + '</ol>');
                } else {
    
    
                    $(".comment-list").append(gen_comment);
                }
                // 清空pid的值,不然下次的就不是根评论啦
                $("input[name='pid']").val('');
                // js改变了输入框的值但是页面不显示,用prop()
                // $("#content").text(" ");//清空评论内容
                $("#content").prop('value','');//清空评论内容

                //触发取消评论按钮点击事件即恢复评论输入框位置,同时隐藏取消评论按钮
                $("#cancel-reply").trigger("click").addClass("d-none");
                //页面反馈信息
                Swal.fire("提交成功");
            }
            //恢复提交按钮内容
            $("#comment-submit").html("提交评论");
        })
    })

})

{
    
    #    js代码要写在body后,否则容易不执行  评论树展示#}
                <script>
                {
    
    #$(".dianji").click(function (){
    
    #}
                    $.ajax({
    
    
                        url: "/comment/tree/",
                        type: "get",
                        data: {
    
    
                            article_id: "{
    
    { article.id }}",
                        },
                        success:function (data){
    
    
                            {
    
    #console.log(data)#}
                            $.each(data, function (index, coment_obj){
    
    
                                var pid = coment_obj.pid
                                var add_time = coment_obj.add_time
                                var content = coment_obj.content
                                var pk = coment_obj.pk
                                var username = coment_obj.username
                                var fu_username = coment_obj.fu_username
                                if(!pid){
    
    
                                     var gen_comment =
                                `
                <li class="list-group-item comment-${
      
      pk} mt-3 px-2 pt-3 pb-2 depth-0" comment_id=${
      
      pk}>
                    <div class="clearfix" id="div-comment-${
      
      pk}">
                        <div class="media">
                            <img src="/static/picture/g-sdk_cFeAJq3pic4ekYTaQMJSx4Q_10.jpg"
                                 class="mr-3 rounded-circle" width="50" height="50"
                                 οnerrοr="javascript:this.src='/static/image/unknow.png';">
                            <div class="media-body">
                                <div class="comment-info">
                                    <cite class="c3">

                                        ${
      
      username}

                                    </cite>

                                </div>
                                <div class="comment-meta"><span
                                        class="font-weight-light text-muted">${
      
      add_time}</span>
                                </div>
                            </div>
                        </div>
                        <p class="text-break mt-2">${
      
      content}</p>
                        <a class="btn btn-sm btn-secondary float-right"
                           οnclick="reply('div-comment-${
      
      pk}','${
      
      pk}')">回复</a>
                    </div>                </li>`
                                    $(".comment-list").append(gen_comment);
                                }else {
    
    
                                                                var zi_comment = `
                <li class="list-group-item comment-${
      
      pk} mt-3 px-2 pt-3 pb-2 depth-0" comment_id=${
      
      pk}>
                    <div class="clearfix" id="div-comment-${
      
      pk}">
                        <div class="media">
                            <img src="/static/picture/g-sdk_cFeAJq3pic4ekYTaQMJSx4Q_10.jpg"
                                 class="mr-3 rounded-circle" width="50" height="50"
                                 οnerrοr="javascript:this.src='/static/image/unknow.png';">
                            <div class="media-body">
                                <div class="comment-info">
                                    <cite class="c3">

                                        ${
      
      username}

                                    </cite>
                       <i class="fa fa-share fa-fw fa-1x mr-2 c1" aria-hidden="true"></i>
                       <cite class="c3"><a href="#div-comment-48" class="text-reset">${
      
      fu_username}</a></cite>
                                                       </div>
                                <div class="comment-meta"><span
                                        class="font-weight-light text-muted">${
      
      add_time}</span>
                                </div>
                            </div>
                        </div>
                        <p class="text-break mt-2">${
      
      content}</p>
                        <a class="btn btn-sm btn-secondary float-right"
                           οnclick="reply('div-comment-${
      
      pk}','${
      
      pk}')">回复</a>
                    </div>                </li>`
                                    {
    
    #console.log(pid)#}
                                    {
    
    #console.log(fu_username)#}
                                    $("[comment_id="+pid+"]").append('<ol class="children">' + zi_comment + '</ol>');
                                }

                            })
                        }
                      })

                {
    
    # })#}

</script>

html

   <div id="comments" class="" style="opacity:0.85;">

            <!--评论列表-start-->

        <ol class="list-group comment-list" id="comments"></ol>

            <!--评论表单-start-->
            <div id="respond" class="bg-white mt-3 px-3 pt-3 pb-2">
                <a name="comment"></a>
                <h4>发表评论
                    <a class="btn btn-sm btn-info d-none text-white" id="cancel-reply"
                       onclick="cancelReply();">取消回复</a>
                </h4>
                <p class="text-muted">电子邮件地址不会被公开。</p>

                <form id="comment-form" onsubmit="return false;" method="post" action="{% url 'comment' %}" novalidate>
                    {% csrf_token %}
                    <div class="form-group" id="article_content">
                                        <textarea class="form-control OwO-textarea" id="content" name="content" rows="5"
                                                  placeholder="居然什么都不说,哼!" autocomplete="off" aria-label="content"
                                                  aria-describedby="addon-wrapping" required=""></textarea>
                        <div class="invalid-feedback" id="content-feedback">

                        </div>
                        <div class="OwO">
                            <a href="javascript: void(0);" class="btn btn-sm btn-warning OwO-logo"
                           rel="external nofollow"><i class="fa fa-smile-o"
                                                      aria-hidden="true"></i>表情</a>
                        </div>

                    </div>

                    <!--评论人信息--->

                    <div class="input-group mb-3">
                        <div class="input-group-prepend">
                            <span class="input-group-text text-center"><i class="fa fa-user"></i></span>
                        </div>
                        <input name="username" id="username" type="text" class="form-control"
                               placeholder="昵称(必填)" aria-label="username"
                               aria-describedby="addon-wrapping" required="">
                        <div class="invalid-feedback" id="username-feedback">
                        </div>
                    </div>

                    <div class="input-group mb-3">
                        <div class="input-group-prepend">
                            <span class="input-group-text text-center"><i class="fa fa-envelope-o"></i></span>
                        </div>
                        <input name="qq_email" type="email" id="qq_email" class="form-control"
                               placeholder="QQ邮箱(必填)" aria-label="email"
                               aria-describedby="addon-wrapping" required="">
                        <div class="invalid-feedback" id="qq_email-feedback">
                            请输入正确格式的qq邮箱
                        </div>
                    </div>
                    <div class="input-group mb-3">
                        <div class="input-group-prepend">
                            <span class="input-group-text text-center"><i class="fa fa-link"></i></span>
                        </div>
                        <input name="link" id="web_site" type="text" class="form-control"
                               placeholder="网站(选填)" aria-label="link" aria-describedby="addon-wrapping">
                        <div class="invalid-feedback">
                            请输入以http或https开头的URL,格式如:https://libo_sober.top
                        </div>
                    </div>

                    <button type="submit" class="btn btn-info btn-block" id="comment-submit"
                            data-placement="top" data-toggle="tooltip" title="评论需博主审核才会显示">提交评论
                    </button>
                                        <input type="hidden" name="article" value="{
     
     { article.id }}" id="comment_post_ID">
                    {#                    <input type="hidden" name="category_id" value="7" id="comment_post_type">#}
{#                                        <input type="hidden" name="pid" id="pid" value="{
     
     {  }}"><!--为0代表新评论-->#}
                    {#                    <input type="hidden" name="csrfmiddlewaretoken"#}
                    {#                           value="13fuPwPJplNcsC9wTDaYPs3KK5qS6W6u9HTA2NaMmjdDkHlqCcSdMO92BzDF0BIs">#}
                </form>
            </div>
            <!--评论表单-end-->

        </div>

递归部分

把数据造成这样传给html页面渲染

ret = [{
    
    
   'pid': None,
   'fu_username': None,
   'pk': 21,
   'content': '哈哈哈',
   'username': 'libo',
   'add_time': '2021-03-11 12:53:14',
   'children': [{
    
    
      'pid': 21,
      'fu_username': 'libo',
      'pk': 29,
      'content': '笑你妈',
      'username': 'h8fanc6o',
      'add_time': '2021-03-11 13:34:41',
      'children': [{
    
    
         'pid': 29,
         'fu_username': 'h8fanc6o',
         'pk': 30,
         'content': '你管人家',
         'username': 'gzjuq2rh',
         'add_time': '2021-03-11 13:35:29',
         'children': []
      }]
   }, {
    
    
      'pid': 21,
      'fu_username': 'libo',
      'pk': 32,
      'content': '开心吗',
      'username': 'taibai666',
      'add_time': '2021-03-11 13:36:20',
      'children': []
   }, {
    
    
      'pid': 21,
      'fu_username': 'libo',
      'pk': 34,
      'content': '<img src="/static/picture/aini_org.png">',
      'username': 'libo',
      'add_time': '2021-03-13 10:52:43',
      'children': []
   }]
}, {
    
    
   'pid': None,
   'fu_username': None,
   'pk': 31,
   'content': '我来了',
   'username': 'taibai',
   'add_time': '2021-03-11 13:35:49',
   'children': []
}]

造数据方法

class ArticleView(View):

    def get(self, request, article_id=None):
        article = models.Article.objects.get(pk=article_id)
        article.viewed()  # 增加阅读数P
        # 为甚么刷新页面会产生两次访问ArticleView
        # 已经解决,因为文章中的请求js或者csss图片等路径为空或出错的,就会自动请求当前路径
        mk = mistune.Markdown()
        output = mk(article.content)

        # 文章分类
        categories = models.Category.objects.all()
        # 该文章的所有评论
        comment_obj = models.Comment.objects.filter(article_id=article_id)
        comment_list = self.build_msg(comment_obj)
        ret = self.get_comment_list(comment_list)

        return render(request, 'datail.html', {
    
    'article': article, 'detail_html': output, 'categories': categories, 'ret': ret})

    def get_comment_list(self, comment_list):
        # 把msg增加一个chirld键值对,存放它的儿子们
        ret = []
        comment_dic = {
    
    }
        for comment_obj in comment_list:
            comment_obj['children'] = []
            comment_dic[comment_obj['pk']] = comment_obj

        for comment in comment_list:
            p_obj = comment_dic.get(comment['pid'])
            if not p_obj:
                ret.append(comment)
            else:
                p_obj['children'].append(comment)
        return ret

    def build_msg(self, comment_obj):
        # 把数据造成列表里边套字典的形式
        msg = []
        for comment in comment_obj:
            data = {
    
    }
            if comment.pid:
                data['pid'] = comment.pid.id
                data['fu_username'] = models.Comment.objects.get(pk=comment.pid.id).username
            else:
                data['pid'] = None
                data['fu_username'] = None
            data['pk'] = comment.pk
            data['content'] = comment.content
            data['username'] = comment.username
            data['add_time'] = comment.add_time.strftime('%Y-%m-%d %H:%M:%S')
            msg.append(data)
        return msg

render给html页面后,页面使用自定义的过滤器

{% load custom_tag %} 
			<!--文章内容区域-start-->
            <div id="article-content" class="article-content">

                {
   
   { detail_html | custom_markdown | safe }}}
            </div>
            <!--文章内容区域-end-->

自定义过滤器进行递归处理

def tree_son(comment):
    zi_com = ''
    for com in comment:
        pk = com['pk']
        pid = com['pid']
        username = com['username']
        add_time = com['add_time']
        content = com['content']
        fu_username = com['fu_username']

        zi_com += f"""
        <li class="list-group-item comment-{pk} mt-3 px-2 pt-3 pb-2 depth-0" comment_id={pk}>
                    <div class="clearfix" id="div-comment-{pk}">
                        <div class="media">
                            <img src="/static/picture/g-sdk_cFeAJq3pic4ekYTaQMJSx4Q_10.jpg"
                                 class="mr-3 rounded-circle" width="50" height="50"
                                 οnerrοr="javascript:this.src='/static/image/unknow.png';">
                            <div class="media-body">
                                <div class="comment-info">
                                    <cite class="c3">

                                        {username}

                                    </cite>
                       <i class="fa fa-share fa-fw fa-1x mr-2 c1" aria-hidden="true"></i>
                       <cite class="c3"><a href="#div-comment-{pid}" class="text-reset">{fu_username}</a></cite>
                                                       </div>
                                <div class="comment-meta"><span
                                        class="font-weight-light text-muted">{add_time}</span>
                                </div>
                            </div>
                        </div>
                        <p class="text-break mt-2">{content}</p>
                        <a class="btn btn-sm btn-secondary float-right"
                           οnclick="reply('div-comment-{pk}','{pk}')">回复</a>
                    </div>                </li>
        """
        if com['children'] != []:
            zi_com += tree_son(com['children'])
    return zi_com


@register.filter(is_safe=True)
def build_coment_tree(ret):
    comment = ''
    for comment_dicts in ret:
        pk = comment_dicts['pk']
        username = comment_dicts['username']
        add_time = comment_dicts['add_time']
        content = comment_dicts['content']

        comment += f"""
                    <li class="list-group-item comment-{pk} mt-3 px-2 pt-3 pb-2 depth-0" comment_id={pk}>
                        <div class="clearfix" id="div-comment-{pk}">
                            <div class="media">
                                <img src="/static/picture/g-sdk_cFeAJq3pic4ekYTaQMJSx4Q_10.jpg"
                                     class="mr-3 rounded-circle" width="50" height="50"
                                     οnerrοr="javascript:this.src='/static/image/unknow.png';">
                                <div class="media-body">
                                    <div class="comment-info">
                                        <cite class="c3">

                                            {username}

                                        </cite>

                                    </div>
                                    <div class="comment-meta"><span
                                            class="font-weight-light text-muted">{add_time}</span>
                                    </div>
                                </div>
                            </div>
                            <p class="text-break mt-2">{content}</p>
                            <a class="btn btn-sm btn-secondary float-right"
                               οnclick="reply('div-comment-{pk}','{pk}')">回复</a>
                        </div>                </li>
                    """
        if comment_dicts['children'] != []:
            comment += tree_son(comment_dicts['children'])

    return comment

over

猜你喜欢

转载自blog.csdn.net/qq_31910669/article/details/114682184