为了避免对于树形结构查询时"递归"惭怍,基于Tree的谦虚遍历设计是一个无递归查询。来报错该树数据。
创建model:
class TreeMenu(models.Model):
name = models.CharField(max_length=32,verbose_name="名字")
left = models.IntegerField(verbose_name="左")
right = models.IntegerField(verbose_name="右")
class Meta:
db_table = "TreeMenu"
如下展示数据:
刚来时看这样表结构,大部分人不清楚left和right是什么鬼,如何得到的?这样表设计并没有保存父子节点继承关系,但是你去数如下的图,你会发现,你数的顺序就是这棵树进行前序遍历顺序。
举个例子:这样可以得到left大于2,并且right小于9的节点都是员工信息子节点。这样通过left,right就可以找到某个节点所拥有的子节点了,但这仅仅是不够的。那么怎样增删改查呢?
比如我们需要员工信息管理节点及其子节点们:
import os
import django
from django.db.models import Q
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "asyn_test.settings")
django.setup() # 启动django项目
# 引入models.py模型模块
from api.models import TreeMenu
#查询 left大于等于2,right小于等于9
tree = TreeMenu.objects.filter(Q(left__gte=2)&Q(right__lte=9)).values()
print(tree)
"""
"""
那么如何查询某个节点的子节点个数呢,通过左,右值可以得到,子节点总数=(右值-左值-1)/2, 以员工信息为例子,它的子节点总数为(9-2-1)/2=3,并且我们为了更直观展示树的结构,我们有时需要知道树种所处的层次,我们可以这么查询,这里还是以员工信息为例:
layer = TreeMenu.objects.filter(Q(left__lte=2)&Q(right__gte=9)).count()
print(layer)
"""
2
"""
# 可以知道,它是第二层的
通过上面方式我们可以得到每一个节点所在层数,这样我们通过ORM构造一个数据结构,如下:
tree_obj = TreeMenu.objects.all()
treemenu_list = []
for term in tree_obj:
layer = TreeMenu.objects.filter(Q(left__lte=term.left)&Q(right__gte=term.right)).count()
treemenu_list.append(
{"id":term.id,"name":term.name,"left":term.left,"right":term.right,"layer":layer}
)
print(treemenu_list)
# layer 为当前节点所在层数,你会发现获得所有节点所在的层数
"""
[{
'id': 1,
'name': '首页',
'left': 1,
'right': 40,
'layer': 1
}, {
'id': 2,
'name': '员工信息',
'left': 2,
'right': 9,
'layer': 2
}, ...]
"""
我们如何获得某个节点的父节点结构,也很简单,这里拿员工信息举例:
employ = TreeMenu.objects.filter(Q(left__lte=2)&Q(right__gte=9)).values()
print(employ)
"""
"""
如果说我们想在某个节点下添加一个新的子节点,比如我在更改信息下插入一个子节点绑定邮箱.如果田间玩,此时树形结构应该如下:
代码:
edit_info_obj = TreeMenu.objects.filter(pk=3).first()
# 获取"更改信息"当前对象right值
node_point = edit_info_obj.right
# 更新 所有节点left大于等于node_point的值
TreeMenu.objects.filter(left__gte=node_point).update(left=F('left') + 2)
# 更新 所有节点right大于等于node_point的值
TreeMenu.objects.filter(right__gte=node_point).update(right=F('right') + 2)
# 插入数据 为当前 node_point,node_point+1
TreeMenu.objects.create(name="绑定邮箱",left=node_point,right=node_point+1)
删除某个节点,如果想删除某个节点,会同时删除该节点的所有子节点,而被删除的节点个数应该为:(被删除节点右侧值-被删除节点的左侧值+1)/2,而剩下节点左,右值在大于被删除节点左、右值的情况下进行调整。
这样以删除更改信息为例,那么首先找到该节点左侧值和右侧值,并且大于等于左侧值小于等于右侧值的节点都是要删除节点,也就是left=3,right=10,left<=3 and right<=10,节点都要删除,其他节点left的值大于删除节点left值应该减去(被删除节点右侧值-被删除节点的左侧值+1),其他节点right值大于删除节点right值也应该减去(被删除节点右侧值-被删除节点的左侧值+1)。如下图:
代码:
edit_info_obj = TreeMenu.objects.filter(pk=3).first()
left_ponit = edit_info_obj.left#3
right_ponit = edit_info_obj.right#10
# 删除该节点下大于等于左侧值,小于等于右侧值的对象
TreeMenu.objects.filter(Q(left__gte=left_ponit)&Q(right__lte=right_ponit)).delete()
# 当其他节点左侧值大于删除节点左侧值,更新其他节点左侧值。
TreeMenu.objects.filter(left__gt=left_ponit).update(left=F('left')-(right_ponit-left_ponit+1))
# 当其他节点右侧值大于删除节点右侧值,更新其他节点右侧值。
TreeMenu.objects.filter(right__gt=right_ponit).update(right=F('right')-(right_ponit-left_ponit+1))
那么django中如何将数据封装json格式取出呢?上代码
class SchemaMenuSerializer(serializers.ModelSerializer):
layer = serializers.SerializerMethodField()
class Meta:
model = models.TreeMenu
fields = "__all__"
depth = 1
# 用于计算当前节点在第几层。
def get_layer(self,obj):
left = obj.left
right = obj.right
count_layer = models.TreeMenu.objects.filter(Q(left__lte=left) & Q(right__gte=right)).count()
return count_layer
def parse_data(layer_dict,tree_dict):
left = layer_dict.get("left")
right = layer_dict.get("right")
layer = layer_dict.get("layer")
id = layer_dict.get("id")
tree_dict["left"] = left
tree_dict["right"] = right
tree_dict["layer"] = layer
tree_dict["id"] = id
# 获取子节点数据
tree = models.TreeMenu.objects.filter(Q(left__gt=left) & Q(right__lt=right))
ser_tree = SchemaMenuSerializer(instance=tree, many=True).data
sub_ser_tree = [dict(i) for i in ser_tree if i.get("layer") == layer + 1]
# 递归创建子节点
for sub in sub_ser_tree:
tree_dict[sub.get("name")] = {}
parse_data(sub, tree_dict[sub.get("name")])
def make_node_list(ser_schemamenu):
# 构建首页字典
tree_dict = {
ser_schemamenu.get("name"):{},
"id":ser_schemamenu.get("id"),
"layer":ser_schemamenu.get("layer"),
"left":ser_schemamenu.get("left"),
"right":ser_schemamenu.get("right"),
}
# 交给parse_data,它会递归取出第二层,第三层。。。。数据
parse_data(ser_schemamenu,tree_dict[ser_schemamenu.get("name")])
return tree_dict
class SchemaMenu(APIView):
def get(self,request,*args,**kwargs):
# 这里第一层最高节点 也就是首页
schema_menu_obj = models.TreeMenu.objects.filter(pk=1).first()
# 首页字段序列化 获取layer层数
ser_schema_menu = SchemaMenuSerializer(instance=schema_menu_obj).data
# 创建节点
tree_dict = make_node_list(ser_schema_menu)
return Response({"code":200,"msg":"OK","data":tree_dict})
效果: