django(19)多级评论树结构

Posted fqh202

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了django(19)多级评论树结构相关的知识,希望对你有一定的参考价值。

评论列表转换

列表转换前

comment_list = [
    {'id': 1, 'content': '1111', 'parent_id': None, 'children_contents':[]},
    {'id': 2, 'content': '222', 'parent_id': 1, 'children_contents':[]},
    {'id': 3, 'content': '33333', 'parent_id': 1, 'children_contents':[]},
    {'id': 4, 'content': '4444', 'parent_id': 3, 'children_contents':[]},
    {'id': 5, 'content': '555555', 'parent_id': None, 'children_contents':[]},
    {'id': 6, 'content': '666', 'parent_id': 5, 'children_contents':[]},
    {'id': 7, 'content': '777777', 'parent_id': None, 'children_contents':[]},
    {'id': 8, 'content': '777777', 'parent_id': 7, 'children_contents':[]},
]

转换后

comment_list=[
{
    'id': 1,
    'content': '1111',
    'parent_id': None,
    'children_contents': [
        {'id': 2,
        'content': '222',
        'parent_id': 1,
        'children_contents': []}, 
        
        {'id': 3,
        'content': '33333',
        'parent_id': 1,
        'children_contents': [
            
            {'id': 4,
            'content': '4444',
            'parent_id': 3,
            'children_contents': []
        }]
    }]
}, {
    'id': 5,
    'content': '555555',
    'parent_id': None,
    'children_contents': [
        
        {'id': 6,
        'content': '666',
        'parent_id': 5,
        'children_contents': []
    }]
}, {
    'id': 7,
    'content': '777777',
    'parent_id': None,
    'children_contents': [
        
        {'id': 8,
        'content': '777777',
        'parent_id': 7,
        'children_contents': []
    }]
}]

功能函数

def transform_list(comment_list):
    comment_dict = {}
    for d in comment_list:
        id = d.get('id')
        comment_dict[id] = d

    for k in comment_dict:
        parent_id = comment_dict[k]['parent_id']
        if parent_id:
            comment_dict[parent_id]['children_contents'].append(comment_dict[k])

    res_list = []
    for i in comment_dict:
        if not comment_dict[i]['parent_id']:
            res_list.append(comment_dict[i])
    return res_list


new_lst = transform_list(comment_list)
print(new_lst)
推倒步骤

1、模拟取出某文章的所有评论的部分信息如下:

comment_list = [
    {'id': 1, 'content': '1111', 'parent_id': None, 'children_contents': []},
    {'id': 2, 'content': '2222', 'parent_id': None, 'children_contents': []},
    {'id': 3, 'content': '3333', 'parent_id': 1, 'children_contents': []},
    {'id': 4, 'content': '4444', 'parent_id': 2, 'children_contents': []},
    {'id': 5, 'content': '5555', 'parent_id': 4, 'children_contents': []},
    {'id': 6, 'content': '6666', 'parent_id': 3, 'children_contents': []},
    {'id': 7, 'content': '7777', 'parent_id': 6, 'children_contents': []},
    {'id': 8, 'content': '8888', 'parent_id': None, 'children_contents': []},
]

2、进一步构建数据结构, {1: {‘id‘:1, ...}, 2: {‘id‘:2, ...},}

comment_dict = {}

for d in comment_list:
    id = d.get('id')
    comment_dict[id] = d

'''
{1: {'id': 1, 'content': '...', 'parent_id': None, 'children_contents': []},
2: {'id': 2, 'content': '...', 'parent_id': None, 'children_contents': []},
3: {'id': 3, 'content': '...', 'parent_id': 1, 'children_contents': []},
4: {'id': 4, 'content': '...', 'parent_id': 1, 'children_contents': []},
5: {'id': 5, 'content': '...', 'parent_id': 4, 'children_contents': []},
6: {'id': 6, 'content': '...', 'parent_id': 3, 'children_contents': []},
7: {'id': 7, 'content': '...', 'parent_id': 6, 'children_contents': []},
8: {'id': 8, 'content': '...', 'parent_id': None, 'children_contents': []}}

3、若存在父评论将每个评论放进其parent_id对应的children_contents列表中:

for k in comment_dict:
    parent_id = comment_dict[k]['parent_id']
    if parent_id:
        comment_dict[parent_id]['children_contents'].append(comment_dict[k])

'''
{1: {'id': 1, 'content': '...', 'parent_id': None, 'children_contents': [
    {'id': 3, 'content': '...', 'parent_id': 1, 'children_contents': [],
    {'id': 4, 'content': '...', 'parent_id': 1, 'children_contents': []}
    ]},
    
2: {'id': 2, 'content': '...', 'parent_id': None, 'children_contents': []},
3: {'id': 3, 'content': '...', 'parent_id': 1, 'children_contents': [
    {'id': 6, 'content': '...', 'parent_id': 3, 'children_contents': []},
]},

4: {'id': 4, 'content': '...', 'parent_id': 1, 'children_contents': [
    {'id': 5, 'content': '...', 'parent_id': 4, 'children_contents': []},
    ]},
    
5: {'id': 5, 'content': '...', 'parent_id': 4, 'children_contents': []},
6: {'id': 6, 'content': '...', 'parent_id': 3, 'children_contents': [
    {'id': 7, 'content': '...', 'parent_id': 6, 'children_contents': []},
]},

7: {'id': 7, 'content': '...', 'parent_id': 6, 'children_contents': []},
8: {'id': 8, 'content': '...', 'parent_id': None, 'children_contents': []},
}

4、筛选出所有的根评论, 整理成列表形式

res_list = []
for i in comment_dict:
    if not comment_dict[i]['parent_id']:
        res_list.append(comment_dict[i])

遍历根评论(最关键)

需求展示

根评论1 
    子评论1
    子评论1

根评论2
    子评论3
    子评论4

跟评论3
    子评论5        
    子评论6

实现函数

def get_content(list):
    for d in list:
        print(d['content'])
        if d['children_contents']:
            # 递归,调用自身
            get_content(d['children_contents'])



# 测试列表
res_list = [
{'id': 1, 'content': '1111', 'parent_id': None, 'children_contents': [
    {'id': 2, 'content': '222', 'parent_id': 1, 'children_contents': [
        {'id': 8, 'content': '8888888', 'parent_id': 2, 'children_contents': []}]},
    {'id': 3, 'content': '33333', 'parent_id': 1, 'children_contents': [
        {'id': 4, 'content': '4444', 'parent_id': 3, 'children_contents': []}]}]}, 
    {'id': 5, 'content': '555555', 'parent_id': None, 'children_contents': [
        {'id': 6, 'content': '666', 'parent_id': 5, 'children_contents': []}]}, {
    'id': 7, 'content': '777777', 'parent_id': None, 'children_contents': []}]


# 调用函数
get_content(res_list)


'''
#  结构展示
1111
222
8888888
33333
4444
555555
666
777777
'''

方法1:自定义标签渲染评论

urls.py

urlpatterns = [
    # 方式3、运用自定义标签在前端渲染评论
    url(r'^p/(d{1,5})/', views.article_detail_page, name='article'),
]

views.py

def article_detail_page(request, pk):
    article = Article.objects.filter(nid=int(pk)).first()
    if not article:
        return HttpResponse('<h1>资源页面不存在</h1>')

    return render(request, 'article_detail_2.html', {
        'pk': pk,
        'article': article,
    })

article_detail_2.html

# 评论处
<div class="blog_comment">
    <div class="comment_title">评论列表</div>
    <hr>
    {#   引用自定义标签渲染html字符串  #}
    {% user_comment pk %}
</div>

自定义标签

from django import template
from my_blog.models import *

register = template.Library() 


from my_blog.models import Article
from django.shortcuts import HttpResponse
from ..utils import transform_list

def process_menu_data(pk):
    """取出数据列表"""
    article = Article.objects.filter(nid=int(pk)).first()
    if not article:
        return HttpResponse("<h1>资源不存在!</h1>")
    comment_list = list(article.comment_set.all().values('nid', 'content', 'user__username', 'parent_id_id', 'create_time'))
    comment_list = transform_list(comment_list)
    print(comment_list)
    return comment_list

'''
[{'nid': 14, 'content': '写得不错', 'user__username': 'alex', 'parent_id_id': None, 'create_time': '2018-08-10 19:47:52', 'children_contents': [
    {'nid': 15, 'content': '我也这个认为', 'user__username': 'alex', 'parent_id_id': 14, 'create_time': '2018-08-10 19:47:58', 'children_contents': [
        {'nid': 16, 'content': '认同', 'user__username': 'bob', 'parent_id_id': 15, 'create_time': '2018-08-10 19:48:38', 'children_contents': []}]}]}, 

{'nid': 17, 'content': '有个问题', 'user__username': 'bob', 'parent_id_id': None, 'create_time': '2018-08-10 19:48:54', 'children_contents': [
    {'nid': 18, 'content': '啥问题', 'user__username': 'alex', 'parent_id_id': 17, 'create_time': '2018-08-10 19:49:06', 'children_contents': [
        {'nid': 19, 'content': '不告诉你', 'user__username': 'bob', 'parent_id_id': 18, 'create_time': '2018-08-10 19:49:16', 'children_contents': [
            {'nid': 20, 'content': '为什么不告诉我', 'user__username': 'alex', 'parent_id_id': 19, 'create_time': '2018-08-10 19:49:25', 'children_contents': [
                {'nid': 21, 'content': '不喜欢你', 'user__username': 'bob', 'parent_id_id': 20, 'create_time': '2018-08-10 19:49:31', 'children_contents': []}]}]}]}]}]
        
'''


def produce_html(comment_list):
    """拼接评论列表html字符串"""
    html = ''
    tpl1 = """        
         <div class="comment_item">
           <div class="comment-header">
               <a href="#"><span class="user_name">{0}</span></a>
                <span class="comment_time">{1}</span>
                <span class="comment_content">{2}</span>
                <span id="{3}" hidden></span>
                <a class="reply">回复</a>
            </div>
           <div class="comment-body">{4}</div>
        </div>
           """
    for item in comment_list:
        # 因为children_contents中内容结构是相同的
        if item['children_contents']:
            html += tpl1.format(item['user__username'], str(item['create_time'])[:19], item['content'].strip(), item['nid'],
                                produce_html(item["children_contents"]))
        else:
            html += tpl1.format(item['user__username'], str(item['create_time'])[:19], item['content'].strip(),  item['nid'], '')
    return html


from django.utils import safestring


# 引入菜单标签到模板中
@register.simple_tag
def user_comment(pk):
    # 1. 判断用户是否登录,即查看有没有权限列表
    # 2. 若为登录用户,则调动自定义函数从数据库取到相关的评论数据
    data = process_menu_data(pk)
    # 3. 生成html, 注意转换成可以渲染的html
    html = safestring.mark_safe(produce_html(data))
    return html

评论回复 & 普通评论 思路

ajax代码

<script>
    // 扩展js的功能,可以使用字符串拼接功能
    String.prototype.format= function(){
                   var args = arguments;
                   return this.replace(/{(d+)}/g,function(s,i){
                     return args[i];
                   });
            };

    var parent_comment_id = "";  // 设置全局变量,当点击回复按钮则赋值为点击的评论的id;
    var reply_ele = '';

    // 回复评论事件
    $('.blog_comment').on('click', '.reply', function () {
        // 1、取出该条评论的id值,传到后端
        parent_comment_id = $(this).prev().attr('id');

        // 2、定位到当前items的body区域,以添加子评论
        reply_ele = $(this).parent().next();

        // 3、聚焦到评论框
        $('#comment_input').focus()
    });
    

    // 增加文章评论
      $("#comment_submit_btn").click(function () {

          // 4、取出输入的评论内容
          var comment_content=$("#comment_input").val().trim();

          // 5、查看当前用户是否登录,因为没有登录就不能取到user
          {% if request.user.is_authenticated %}
            $.ajax({
               url:"/blog/comment/",
               type:"post",
               data:{"csrfmiddlewaretoken":$("[name='csrfmiddlewaretoken']").val(),
                     "article_id":{{ article.nid }},
                     "comment_content":comment_content,
                     "parent_comment_id":parent_comment_id
               },

               success:function (data) {
                   // 评论内容模板
                   s = ' <div class="comment_item"> <div class="comment-header"> <a href="#"><span class="user_name">{0}</span></a> <span class="comment_time">{1}</span> <span class="comment_content">{2}</span> <span id="{3}" hidden></span> <a class="reply">回复</a> </div> <div class="comment-body">{4}</div> </div>';

                   // 10、拼接html字符串
                   s=s.format('{{ request.user.username }}',
                            data.comment_createTime,
                            comment_content,
                            data.comment_id, '');

                   // 11、动态添加新评论
                   if (reply_ele){
                       // 若存在,则说明是子评论,且已定位到子评论的body区域
                        reply_ele.append(s);
                   }
                   else
                   {
                       // 不通过回复按钮添加
                       $('.blog_comment').append(s)
                   }

                   // 12、清空评论区域
                   $("#comment_input").val("");

                   // 13、关键点, 每次走完此处必须对全局变量清零
                   father_comment_username="";
                   parent_comment_id=0;
                   reply_ele = '';
            }
       });

          {% else %}
            // 6、没有登录则直接发送登录请求并携带当期页面的path
            location.href="/login?path={{ request.path }}";
          {% endif %}
      });

</script>
关于登录后跳转回原页面

1、原页面:

<script>

{% if request.user.is_authenticated %}
    ...
{% else %}
    // 6、没有登录则直接发送登录请求并携带当期页面的path
    location.href="/login?path={{ request.path }}";
{% endif %}

</script>

2、始终保证在流程中保存path参数即可最终若是ajax请求

  • 若最终跳转是ajax请求,则使用:window.location.href= data.path;
  • 若是view逻辑,则使用redirect(path)
def my_login(request):
    if request.method == "POST":
        path = request.POST.get('path', '')
        pass
        
    elif request.method == "GET":
    # 获取path路径
    path = request.GET.get('path', '')
    if not path:
        path = '/index/'
    print('path', path)
    return render(request, 'login.html', {
        'path': path
    })

views.py

def user_comment(request):
    """方法1, 直接在前端操作评论内容"""

    # 6、取出传入的评论的相关信息
    user_id = request.user.nid
    article_id = request.POST.get("article_id")
    comment_content = request.POST.get("comment_content").strip()

    if not comment_content:
        return HttpResponse('noy ok')

    # 7、创建新评论
    if request.POST.get("parent_comment_id"):
        # 7、 获取该评论的父级评论id并保存记录
        c = int(request.POST.get("parent_comment_id"))
        comment_obj = Comment.objects.create(article_id=article_id, content=comment_content, user_id=user_id,
                                             parent_id_id=c)
    else:
        comment_obj = Comment.objects.create(article_id=article_id, content=comment_content, user_id=user_id)

    from django.db.models import F
    # 8、对评论数量自加操作
    Article.objects.filter(nid=article_id).update(comment_num=F("comment_num") + 1)

    # 9、只需要传输创建时间过去
    response_ajax = {"comment_createTime": str(comment_obj.create_time)[:16], 'comment_id':comment_obj.nid}  # 很关键,不取毫秒!
    return HttpResponse(json.dumps(response_ajax), content_type='application/json')

具体参考,见实战项目

  • 地址:https://github.com/Fangqihan/blog_sys
  • mysql数据库还原:用户名root,密码abc123

以上是关于django(19)多级评论树结构的主要内容,如果未能解决你的问题,请参考以下文章

Django多级评论

django-jsonp 瀑布流 组合搜索 多级评论

JAVA代码实现多级树结构封装对象

Java多级菜单结构

Django小项目简单BBS论坛

简单几步,快速实现封装多级树结构对象(Java版)