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

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

闭包表模型

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

场景

我们 基于 django orm实现一个文件树,文件夹直接可以实现无限嵌套
django 实现:闭包表—树状结构_第1张图片

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'数字人文件关系表'
id file_name
1 AAA
2 aaa.pdf
id ancestor_id descendant_id depth
1 1 1 0
2 2 2 0
3 1 2 1

增删改查

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

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

你可能感兴趣的:(django,数据库,sqlite)