django 实现:闭包表—树状结构

news2024/11/24 16:01:42

闭包表—树状结构数据的数据库表设计

闭包表模型

闭包表(Closure Table)是一种通过空间换时间的模型,它是用一个专门的关系表(其实这也是我们推荐的归一化方式)来记录树上节点之间的层级关系以及距离。

场景

我们 基于 django orm实现一个文件树,文件夹直接可以实现无限嵌套
在这里插入图片描述

models

#  文件详情表(主要用于记录文件名)
class DmFileDetail(models.Model):
    file_name = models.CharField("文件名(文件夹名)", max_length=50)
    is_file = models.BooleanField("是否是文件", default=False)
    user_id = models.IntegerField("用户id", default=0)
    create_time = models.IntegerField("创建时间", default=0)
    update_time = models.IntegerField("创建时间", default=0)
    is_del = models.BooleanField("是否删除", default=False)

    class Meta:
        db_table = 'inchat_dm_file_detail'
        verbose_name = verbose_name_plural = u'数字人文件详情表'

    def __str__(self):
        return self.file_name

#  文件关系表(主要用户记录文件之间的关联,即路径)
class DmFileRelation(models.Model):
    ancestor_id = models.IntegerField("祖先节点ID")
    descendant_id = models.IntegerField("子孙节点ID")
    depth = models.IntegerField("深度(层级)", db_index=True)
    user_id = models.IntegerField("用户id", default=0, db_index=True)
    is_del = models.BooleanField("是否删除", default=False)

    class Meta:
        db_table = 'inchat_dm_file_relation'
        index_together = ('ancestor_id', 'descendant_id')
        verbose_name = verbose_name_plural = u'数字人文件关系表'
idfile_name
1AAA
2aaa.pdf
idancestor_iddescendant_iddepth
1110
2220
3121

增删改查

class DmRelationNode:
    """
    关系节点
    """
    NAME = "DmRelationNode"
    RELATION_CLIENT = DmFileRelation

    @classmethod
    def insert_relation_node(cls, node_id, user_id, parent_id):
        """
        插入新的关系节点
        """
        # 自身
        insert_self = cls.RELATION_CLIENT(
            ancestor_id=parent_id,
            descendant_id=node_id,
            user_id=user_id,
            depth=1
        )
        insert_list = []
        # 获取父节点所有祖先
        parent_relation = cls.RELATION_CLIENT.objects.filter(descendant_id=parent_id) \
            .values_list('ancestor_id', 'depth')
        for ancestor_id, depth in parent_relation:
            insert_data = cls.RELATION_CLIENT(
                ancestor_id=ancestor_id,
                descendant_id=node_id,
                depth=depth + 1,
                user_id=user_id
            )
            insert_list.append(insert_data)
        # 插入自身
        insert_list.append(insert_self)
        logger.info('%s insert_relation_node.node_id:%s,parent_id:%s,insert_list:%s', cls.NAME, node_id, parent_id,
                    insert_list)
        ret = cls.RELATION_CLIENT.objects.bulk_create(insert_list)
        logger.info('%s insert_relation_node.node_id:%s,parent_id:%s,ret_list:%s', cls.NAME, node_id, parent_id, ret)
        return ret

    @classmethod
    def get_ancestor_relation(cls, node_id):
        """
        获取某个节点的所有祖先节点
        """
        arges = ['ancestor_id', 'descendant_id', 'depth']
        ancestor_relation_list = cls.RELATION_CLIENT.objects.filter(descendant_id=node_id, is_del=False).values(*arges)
        relation_map = dict()
        relation_dict = relation_map
        for ancestor in ancestor_relation_list:
            relation_dict['id'] = ancestor['ancestor_id']
            if ancestor['ancestor_id'] != node_id:
                relation_dict['children'] = {}
                relation_dict = relation_dict['children']
        return ancestor_relation_list

    @classmethod
    def get_descendant_relation(cls, node_id):
        """
        获取所有的子节点
        """
        arges = ['ancestor_id', 'descendant_id', 'depth']
        descendant_relation_list = cls.RELATION_CLIENT.objects.filter(ancestor_id=node_id, is_del=False).values(*arges)

        return descendant_relation_list

    @classmethod
    def get_direct_relation(cls, user_id):
        """
        获取所有直系
        """
        arges = ['ancestor_id', 'descendant_id', 'depth']
        direct_relation = cls.RELATION_CLIENT.objects.filter(depth=1, user_id=user_id, is_del=False).values(*arges)
        return direct_relation

    @classmethod
    def get_children_node(cls, node_id):
        """
        获取某节点的子节点
        """
        children_node = cls.RELATION_CLIENT.objects.filter(depth=1, ancestor_id=node_id, is_del=False) \
            .values_list('descendant_id', flat=True)
        return children_node

    @classmethod
    def remove_node(cls, node_id):
				"""
        删除节点
        """
        logger.info('%s remove_node. node_id:%s', cls.NAME, node_id)
        query = Q(ancestor_id=node_id, is_del=False) | Q(descendant_id=node_id, is_del=False)
        res = cls.RELATION_CLIENT.objects.filter(query).update(is_del=True)
        logger.info('%s remove_node. node_id:%s,count:%s', cls.NAME, node_id, res)
        return res

以下 是一些常规的操作

class DmFileTree:
    """
    DM文件树
    """
    NAME = "DmFileTree"
    DETAIL_CLIENT = DmFileDetail
    RELATION_NODE_CLIENT = DmRelationNode
    FILE_SAVE_DIR = 'media/dm/'

    @classmethod
    def get_file_map(cls, user_id):
        """
        获取用户所有文件文件名
        """
        file_detail = cls.DETAIL_CLIENT.objects.filter(user_id=user_id).values('id', 'file_name', 'path', 'is_file')
        file_map = dict()
        for file in file_detail:
            file_dict = dict(
                id=file['id'],
                name=file['file_name'],
                is_file=file['is_file'],
                filePath=cls.FILE_SAVE_DIR + file['path'] + file['file_name']
            )
            file_map[file['id']] = file_dict
        return file_map

    @classmethod
    def add_file(cls, user_id, file_name, parent_id, path='', is_file=False):
        """
         新建文件(夹)
        """
        kwargs = dict(
            file_name=file_name,
            path=path,
            is_file=is_file,
            user_id=user_id,
            create_time=get_cur_timestamp()
        )
        file_obj = cls.DETAIL_CLIENT.objects.create(**kwargs)
        if not file_obj:
            logger.error('%s add_file failed. kwargs:%s', cls.NAME, kwargs)
            return False

        res = cls.RELATION_NODE_CLIENT.insert_relation_node(node_id=file_obj.id, user_id=user_id, parent_id=parent_id)
        if not res:
            return False

        return dict(id=file_obj.id, name=file_name)

    @classmethod
    def get_file_path(cls, file_id):
        """
        获取文件路径
        """
        ancestor_query = cls.RELATION_NODE_CLIENT.get_ancestor_relation(file_id)
        ancestor = map(lambda x: x['ancestor_id'], ancestor_query)
        # 过滤0
        ancestor = list(filter(lambda x: x > 0, ancestor))
        # 排序
        ancestor.sort()
        path = '/'.join(map(str, ancestor))
        return '/' + path + '/' if path else '/'

    @classmethod
    def get_all_files(cls, user_id):
        # 获取所有文件名字典
        file_map = cls.get_file_map(user_id)
        # 查询所有子目录及文件
        files_relation_list = cls.RELATION_NODE_CLIENT.get_direct_relation(user_id)
        file_info = {a['descendant_id']: file_map.get(a['descendant_id']) or {} for a in files_relation_list}
        tree = cls.list_to_tree(files_relation_list, file_info)
        return tree

    @classmethod
    def get_child_files(cls, user_id, parent_id):
        """
        获取下级文件
        """
        # 获取所有文件名字典
        file_map = cls.get_file_map(user_id)
        file_list = cls.RELATION_NODE_CLIENT.get_children_node(node_id=parent_id)

        files = map(lambda x: dict(id=x, name=file_map.get(x) or ''), file_list)
        return files

    @staticmethod
    def list_to_tree(data, node_dict):
        """
        将节点列表转换成树形结构字典
        :param data: 带有 id 和 parent_id 属性的节点列表
        :param node_dict: 单节点的数据结构字典
        :return: 树形结构字典
        """

        tree = []

        # 遍历每一个节点,将其添加到父节点的字典或根节点列表中
        for item in data:
            id = item['descendant_id']
            parent_id = item['ancestor_id']

            # 如果父节点为 None,则将当前节点添加到根节点列表中
            if not parent_id:
                tree.append(node_dict[id])
            # 如果父节点存在,则将当前节点添加到父节点的 children 属性中
            else:
                parent = node_dict[parent_id]
                if 'children' not in parent:
                    parent['children'] = []
                parent['children'].append(node_dict[id])

        return tree

    @classmethod
    def delete_file(cls, file_id):
        """
         文件删除
        """
        res1 = cls.DETAIL_CLIENT.objects.filter(id=file_id).update(is_del=True)
        logger.info('%s delete_file. file_id:%s, count:%s', cls.NAME, file_id, res1)
        res2 = cls.RELATION_NODE_CLIENT.remove_node(file_id)

        return res1, res2

    @classmethod
    def search_file(cls, file_name):
        """
        搜索文件
        """
        query_set = cls.DETAIL_CLIENT.objects.filter(file_name__icontains=file_name) \
            .values('id', 'file_name', 'path', 'is_file')
        file_list = []
        for file in query_set:
            file_dict = dict(
                id=file['id'],
                name=file['file_name'],
                is_file=file['is_file'],
                filePath='media/dm_upload' + file['path']
            )
            file_list.append(file_dict)
        return file_list

    @classmethod
    def get_file_url(cls, file_id, file_obj=None):
        """
        获取文件下载链接
        """
        file_url = ''
        if not file_obj:
            file_obj = cls.DETAIL_CLIENT.objects.filter(id=file_id).first()
        if file_obj:
            file_url = 'http://127.0.0.1:8000/' + cls.FILE_SAVE_DIR + file_obj.path + file_obj.file_name
        return file_url

除此之外,还有移动、复制文件(夹)。移动就是先删除再新增

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1049969.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

百度交易中台之内容分润结算系统架构浅析

作者 | 交易中台团队 导读 随着公司内容生态的蓬勃发展,内容产出方和流量提供方最关注的“收益结算”的工作,也就成为重中之重。本文基于内容分润结算业务为入口,介绍了实现过程中的重难点,比如千万级和百万级数据量下的技术选型和…

使用 AI 编程助手 CodeWhisperer,开发如有神助

前段时间体验了chatGPT,听说它可以写代码,结果发现更多的只是一个对答写小作文的百度助手,虽然也能写代码,但不是我想要的,可以在 idea 中可以快速生成代码块的。一个偶然的机会,从微信群里了解到&#xff…

科技云报道:大模型的阴面:无法忽视的安全隐忧

科技云报道原创。 在AI大模型的身上,竟也出现了“to be or not to be”问题。 争议是伴随着大模型的能力惊艳四座而来的,争议的核心问题在于安全。安全有两个方面,一个是大模型带来的对人类伦理的思考,一个是大模型本身带来的隐…

Unity实现设计模式——解释器模式

Unity实现设计模式——解释器模式 解释器模式(Interpreter Pattern)是一种按照规定语法进行解析的模式,现实项目中用得较少。 给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来…

在windows的ubuntu LTS中安装及使用EZ-InSAR进行InSAR数据处理

EZ-InSAR(曾被称为MIESAR,即Matlab界面用于易于使用的合成孔径雷达干涉测量)是一个用MATLAB编写的工具箱,用于通过易于使用的图形用户界面(GUI)进行干涉合成孔径雷达(InSAR)数据处理…

网络基础(了解网络知识的前提)

前言 在正式学习网络之前,我们需要了解的一些关于计算机网络的基本知识,本文主要阐述这些基本知识,带着大家一步一步迈进互联网网络的世界; 一、局域网与广域网的概念 在正式了解这些概念的前提是我们要搞懂网络出现的意义&#x…

Linux高级应用——web网站服务

作者简介:一名云计算网络运维人员、每天分享网络与运维的技术与干货。 公众号:网络豆云计算学堂 座右铭:低头赶路,敬事如仪 个人主页: 网络豆的主页​​​​​ 目录 前言 一.Apache 1.Apache介绍 2.Apache的特…

【AI绘画】Stable Diffusion WebUI

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…

gitee 远程仓库操作基础(一)

git remote add <远程仓库名> <仓库远程地址> :给远程仓库取个别名,简化一大堆字符串操作 git remote add origin xxx.git :取个Origin名字 git remote -v :查看本地存在的远程仓库 git pull <远程仓库名><远程分支名>:<本地分支名> 相同可取消…

【SpringBoot实践】事务和事务传播机制失效原因正确使用事务的建议

文章目录 1.概述2.事务与事务传播2.1 声明式事务说明2.2.声明式事务失效原因2.3.事务的传播机制2.4.事务传播失效原因 3.事务使用建议4.总结 1.概述 我们在开发工作中经常会使用到事务&#xff0c;来保证数据库做增、删、改操作时的数据一致性&#xff0c;在使用Spring来处理事…

【c语言】通讯录【动态版本:有排序和文件操作】

目录 一、通讯录定义 二、通讯录的实现 1、test.c中菜单的实现 2、通讯录的创建逻辑 3、初始化 4、检查容量和添加 5、查找 6、删除功能 7、修改功能 8、打印 9、查找并打印 10、qsort排序 11、摧毁 12、保存数据到文件 13、从文件中读数据 完整代码&#xff1a; 一、通讯录定…

Windows上安装 Go 环境

一、下载go环境 下载go环境&#xff1a;Go下载官网链接找到自己想下载的版本&#xff0c;点击下载&#xff0c;比如我这是windows64位的&#xff0c;我就直接点击最新的。 二、安装go环境 双击下载的.msi文件 next next 他默认的是c盘&#xff0c;你自己可以改&#xff0c;然…

Android 导入ncnn-android-yolov8-seg : 实现人体识别和人像分割

1. 前言 上篇文章我们在Android中使用OpenCV实现了人脸识别&#xff0c;这篇文章我们使用OpenCVYOLOv8NCNN 来实现人像分割的功能。 首先来看下效果&#xff0c;这里会识别出人体&#xff0c;并会用蓝色的框框出来&#xff0c;并会有标签标注识别出的物体是什么&#xff0c;概…

Python爬虫实战案例——第六例

文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff01;严禁将文中内容用于任何商业与非法用途&#xff0c;由此产生的一切后果与作者无关。若有侵权&#xff0c;请联系删除。 目标&#xff1a;去哪儿网指定城市人气值最高的15个景点评论数据采集 地址&a…

趣解设计模式之《小王的披萨店》

〇、小故事 小王看到最近越来越多的人喜欢吃披萨了&#xff0c;所以&#xff0c;他决定自己也开一个披萨店。最初开的时候&#xff0c;他只提供了一种口味的披萨&#xff0c;因为这样先试试水&#xff0c;看看生意如何&#xff0c;如果生意不好&#xff0c;也可以快速止损。 没…

一道签到题目 签到.zip

一道签到题目 https://www.xuenixiang.com/ctfexercise-competition-589.html 下载附件&#xff1a;签到.zip双击打开zip包。 进行base64转换 在线 Unicode 编码转换 | 菜鸟工具 (runoob.com) 获得压缩包密码&#xff1a;haishi 文字倒序工具,在线文字倒序 (qqxiuzi.cn)

一招根治Windows自带杀毒软件 Microsoft Defender

以毒攻毒&#xff1a;用腾讯电脑管家的文件粉碎机将Microsoft Defender 文件粉碎&#xff0c;再卸载腾讯电脑管家。 整个世界都安静了。 开机任务管理器就能看到 Microsoft Defender 又瞎忙起来了 打开文件位置&#xff1a; 记录下此时该文件的路径&#xff08;保存在记事本里…

使用SPY++查看窗口信息去排查客户端UI软件问题

目录 1、使用SPY查看窗口的信息 2、使用SPY查看某些软件UI窗口用什么UI组件实现的 2.1、查看海康视频监控客户端安装包程序 2.2、查看华为协同办公软件WeLink 2.3、查看字节协同办公软件飞书 2.4、查看最新版本的Chrome浏览器 2.5、查看小鱼易连视频会议客户端软件 2.6…

cad图纸如何防止盗图(一个的制造设计型企业如何保护设计图纸文件)

在现代企业中&#xff0c;设计图纸是公司的重要知识产权&#xff0c;关系到公司的核心竞争力。然而&#xff0c;随着技术的发展&#xff0c;员工获取和传播设计图纸的途径越来越多样化&#xff0c;如何有效地防止员工复制设计图纸成为了企业管理的一大挑战。本文将从技术、管理…

如何用WiFi实现无线定位

一、WiFi主从模块设置 1. 实验器材 2. 实验步骤 ① 给控制板刷一套空的程序。 ② 将Esp8266模块连接到Bigfish扩展板上&#xff0c;并将扩展板插到控制板上。 ③ 在arduino的Seiral Monitor中&#xff0c;输入AT指令集&#xff0c;观察模块的相应应答。 3. 常用指令 ① 基础A…