refactor: UserPermTreeAPI finished - Luna Page

This commit is contained in:
Bai
2025-12-26 14:20:25 +08:00
parent eb9337e45b
commit 414e547a67
9 changed files with 194 additions and 48 deletions

View File

@@ -146,7 +146,7 @@ class NodeChildrenAsTreeApi(SerializeToTreeNodeMixin, NodeChildrenApi):
if current_org.is_root():
orgs = Organization.objects.all()
node_levels = [1]
with_assets_node_levels = []
with_assets_node_levels = None
expand_level = 0
else:
orgs = [current_org]
@@ -211,7 +211,7 @@ class NodeChildrenAsTreeApi(SerializeToTreeNodeMixin, NodeChildrenApi):
assets_q_object = Q(name__icontains=search) | Q(address__icontains=search)
if current_org.is_root():
orgs = list(Organization.objects.all())
with_assets_limit = with_assets_limit / len(orgs)
with_assets_limit = max(100, with_assets_limit // max(1, orgs.count()))
else:
orgs = [current_org]

View File

@@ -0,0 +1,128 @@
from django.db.models import Q
from rest_framework.generics import ListAPIView
from rest_framework.response import Response
from rest_framework.generics import get_object_or_404
from common.utils import get_logger, timeit
from orgs.utils import current_org
from assets.api import SerializeToTreeNodeMixin
from assets.models import Node
from perms.tree import UserPermTree, PermTreeNode
from .mixin import SelfOrPKUserMixin
logger = get_logger(__name__)
__all__ = [
'UserPermNodeChildrenAsTreeApi'
]
class UserPermNodeChildrenAsTreeApi(SelfOrPKUserMixin, SerializeToTreeNodeMixin, ListAPIView):
@timeit
def list(self, request, *args, **kwargs):
search = request.query_params.get('search')
key = request.query_params.get('key')
if search:
return self.search_user_perm_tree_with_assets(search)
if key:
return self.expand_tree_node_with_assets(key)
return self.init_user_perm_tree_with_assets()
def init_user_perm_tree_with_assets(self):
''' 初始化用户权限资产树 - 包含资产
全局组织: 返回第1级节点不返回资产不展开节点
实体组织返回第1级和第2级节点返回第1级节点的资产展开第1级节点
返回收藏节点和资产
返回未分组节点和资产 (如果需要)
'''
if current_org.is_root():
orgs = self.user.orgs.all()
nodes_level = [1]
with_assets_node_levels = None
expand_level = 0
else:
orgs = [current_org]
nodes_level = [1, 2]
with_assets_node_levels = [1]
expand_level = 1
nodes = []
assets = []
for org in orgs:
tree = UserPermTree(
user=self.user, org=org, with_assets_node_levels=with_assets_node_levels
)
_nodes = tree.get_nodes(levels=nodes_level)
nodes.extend(_nodes)
_assets = tree.get_assets()
assets.extend(_assets)
nodes = self.serialize_nodes(
nodes, with_asset_amount=True, expand_level=expand_level, with_assets=True
)
assets = self.serialize_assets(assets)
data = [*nodes, *assets]
return Response(data)
def expand_tree_node_with_assets(self, key):
''' 展开用户权限资产树节点 - 包含资产
全局组织: 返回展开节点的直接孩子节点,返回展开节点的资产,不展开其他节点
实体组织: 同上
'''
expand_level = 0
node = get_object_or_404(Node, key=key)
tree = UserPermTree(
user=self.user, org=node.org, with_assets_node_id=node.id
)
tree_node = tree.get_node(node.key)
if not tree_node:
return Response(data=[])
_nodes = tree_node.children
nodes = self.serialize_nodes(
_nodes, with_asset_amount=True, expand_level=expand_level, with_assets=True
)
_assets = tree.get_assets()
assets = self.serialize_assets(_assets)
data = [*nodes, *assets]
return Response(data=data)
def search_user_perm_tree_with_assets(self, search):
''' 初始化用户权限资产搜索树 - 包含资产
全局组织: 返回所有节点,返回所有资产,展开所有节点,搜索资产 (最大 1000 n 个组织每个组织分配1000/n个资产)
实体组织: 同上,最大资产数 1000
'''
expand_level = 10000
with_assets_all = True
with_assets_limit = 1000
if current_org.is_root():
orgs = self.user.orgs.all()
with_assets_limit = max(100, with_assets_limit // max(1, orgs.count()))
else:
orgs = [current_org]
assets_q_object = Q(name__icontains=search) | Q(address__icontains=search)
nodes = []
assets = []
for org in orgs:
tree = UserPermTree(
user=self.user, assets_q_object=assets_q_object, org=org,
with_assets_all=with_assets_all,
with_assets_limit=with_assets_limit
)
_nodes = tree.get_nodes()
nodes.extend(_nodes)
_assets = tree.get_assets()
assets.extend(_assets)
nodes = self.serialize_nodes(
nodes, with_asset_amount=True, expand_level=expand_level, with_assets=True
)
assets = self.serialize_assets(assets)
data = [*nodes, *assets]
return Response(data=data)

View File

@@ -1,9 +1,12 @@
from django.utils.translation import gettext_lazy as _
from collections import defaultdict
from django.db.models import Q, Count
from django.db.models import Q, Count, TextChoices
from django.core.cache import cache
from common.utils import get_logger
from users.models import User
from assets.tree.asset_tree import AssetTree, AssetTreeNode
from assets.models import FavoriteAsset, Asset
from assets.tree.asset_tree import AssetTree, AssetTreeNode, AssetTreeNodeAsset
from perms.utils.utils import UserPermUtil
@@ -23,9 +26,9 @@ class PermTreeNode(AssetTreeNode):
# Node with only direct permission assets
DA = 'da'
def __init__(self, tp, _id, key, value, assets_count=0, assets=None):
super().__init__(_id, key, value, assets_count)
def __init__(self, tp=None, **kwargs):
self.type = tp or self.Type.BRIDGE
super().__init__(**kwargs)
def as_dict(self, simple=True):
data = super().as_dict(simple=simple)
@@ -33,27 +36,26 @@ class PermTreeNode(AssetTreeNode):
'type': self.type,
})
return data
class UserPermTree(AssetTree):
TreeNode = PermTreeNode
def __init__(self, user=None, assets_q_object=None, category=None, org=None, with_assets=False):
super().__init__(
assets_q_object=assets_q_object,
category=category,
org=org,
with_assets=with_assets,
full_tree=False
)
def __init__(self, user, **kwargs):
self._user: User = user
self._util = UserPermUtil(user, org=self._org)
self._util = UserPermUtil(user, org=kwargs.get('org'))
kwargs.update({
# 用户授权树只返回有资产的节点
'full_tree': False,
})
super().__init__(**kwargs)
def _make_assets_q_object(self):
q = super()._make_assets_q_object()
q_perm_assets = Q(id__in=self._util._user_direct_asset_ids)
q_perm_nodes = Q(node_id__in=self._util._user_direct_node_all_children_ids)
q_perm_assets = Q(id__in=list(self._util._user_direct_asset_ids))
q_perm_nodes = Q(node_id__in=list(self._util._user_direct_node_all_children_ids))
q = q & (q_perm_assets | q_perm_nodes)
return q
@@ -61,13 +63,13 @@ class UserPermTree(AssetTree):
data = super()._get_tree_node_data(node_id)
if node_id in self._util._user_direct_node_all_children_ids:
tp = PermTreeNode.Type.DN
elif self._nodes_assets_count_mapper.get(node_id, 0) > 0:
elif self._nodes_assets_amount_mapper.get(node_id, 0) > 0:
tp = PermTreeNode.Type.DA
else:
tp = PermTreeNode.Type.BRIDGE
data.update({ 'tp': tp })
return data
def print(self, simple=True, count=10):
self._util.print()
super().print(simple=simple, count=count)

View File

@@ -23,30 +23,34 @@ user_permission_urlpatterns = [
path('<str:user>/nodes/children/', api.UserPermedNodeChildrenApi.as_view(),
name='user-node-children'),
# 授权树只通过这一个 API 获取, 类似资产树
path('<str:user>/nodes/children/tree/', api.UserPermNodeChildrenAsTreeApi.as_view(),
name='user-perm-node-children-tree'),
# tree-asset
path('<str:user>/assets/tree/', api.UserAllPermedAssetsAsTreeApi.as_view(),
name='user-direct-assets-as-tree'),
path('<str:user>/ungroup/assets/tree/', api.UserUngroupAssetsAsTreeApi.as_view(),
name='user-ungroup-assets-as-tree'),
# path('<str:user>/assets/tree/', api.UserAllPermedAssetsAsTreeApi.as_view(),
# name='user-direct-assets-as-tree'),
# path('<str:user>/ungroup/assets/tree/', api.UserUngroupAssetsAsTreeApi.as_view(),
# name='user-ungroup-assets-as-tree'),
# tree-node不包含资产
path('<str:user>/nodes/tree/', api.UserAllPermedNodesAsTreeApi.as_view(),
name='user-all-nodes-as-tree'),
path('<str:user>/nodes/children/tree/', api.UserPermedNodeChildrenAsTreeApi.as_view(),
name='user-node-children-as-tree'),
# path('<str:user>/nodes/tree/', api.UserAllPermedNodesAsTreeApi.as_view(),
# name='user-all-nodes-as-tree'),
# path('<str:user>/nodes/children/tree/', api.UserPermedNodeChildrenAsTreeApi.as_view(),
# name='user-node-children-as-tree'),
# tree-node-with-asset
# 异步树
path('<str:user>/nodes/children-with-assets/tree/',
api.UserPermedNodeChildrenWithAssetsAsTreeApi.as_view(),
name='user-node-children-with-assets-as-tree'),
path('<str:user>/nodes/children-with-assets/category/tree/',
api.UserPermedNodeChildrenWithAssetsAsCategoryTreeApi.as_view(),
name='user-node-children-with-assets-as-category-tree'),
# path('<str:user>/nodes/children-with-assets/tree/',
# api.UserPermedNodeChildrenWithAssetsAsTreeApi.as_view(),
# name='user-node-children-with-assets-as-tree'),
# path('<str:user>/nodes/children-with-assets/category/tree/',
# api.UserPermedNodeChildrenWithAssetsAsCategoryTreeApi.as_view(),
# name='user-node-children-with-assets-as-category-tree'),
# 同步树
path('<str:user>/nodes/all-with-assets/tree/',
api.UserPermedNodesWithAssetsAsTreeApi.as_view(),
name='user-nodes-with-assets-as-tree'),
# path('<str:user>/nodes/all-with-assets/tree/',
# api.UserPermedNodesWithAssetsAsTreeApi.as_view(),
# name='user-nodes-with-assets-as-tree'),
]
user_group_permission_urlpatterns = [

View File

@@ -34,6 +34,7 @@ class UserPermUtil(object):
self._user_direct_node_all_children_ids = set()
self._init()
@timeit
def _init(self):
self._load_user_permission_ids()
self._load_user_group_ids()
@@ -45,7 +46,8 @@ class UserPermUtil(object):
@timeit
def _load_user_permission_ids(self):
perm_ids = self.PermUserThrough.objects.filter(
user_id=self._user.id
user_id=self._user.id,
assetpermission__org_id=self._org.id
).distinct('assetpermission_id').values_list('assetpermission_id', flat=True)
perm_ids = self._uuids_to_string(perm_ids)
self._user_permission_ids.update(perm_ids)
@@ -54,7 +56,8 @@ class UserPermUtil(object):
@timeit
def _load_user_group_ids(self):
group_ids = self.UserGroupThrough.objects.filter(
user_id=self._user.id
user_id=self._user.id,
usergroup__org_id=self._org.id
).distinct('usergroup_id').values_list('usergroup_id', flat=True)
group_ids = self._uuids_to_string(group_ids)
self._user_group_ids.update(group_ids)
@@ -62,7 +65,8 @@ class UserPermUtil(object):
@timeit
def _load_user_group_permission_ids(self):
perm_ids = self.PermUserGroupThrough.objects.filter(
usergroup_id__in=self._user_group_ids
usergroup_id__in=self._user_group_ids,
assetpermission__org_id=self._org.id
).distinct('assetpermission_id').values_list('assetpermission_id', flat=True)
perm_ids = self._uuids_to_string(perm_ids)
self._user_group_permission_ids.update(perm_ids)
@@ -89,27 +93,35 @@ class UserPermUtil(object):
nid_key_pairs = Node.objects.filter(org_id=self._org.id).values_list('id', 'key')
nid_key_mapper = { str(nid): key for nid, key in nid_key_pairs }
dn_keys = [ nid_key_mapper[nid] for nid in self._user_direct_node_ids ]
dn_keys = [
nid_key_mapper[nid] for nid in self._user_direct_node_ids
]
def has_ancestor_in_direct_nodes(key: str) -> bool:
ancestor_keys = [ ':'.join(key.split(':')[:i]) for i in range(1, key.count(':') + 1) ]
ancestor_keys = [
':'.join(key.split(':')[:i])
for i in range(1, key.count(':') + 1)
]
return bool(set(ancestor_keys) & set(dn_keys))
dn_children_ids = [ nid for nid, key in nid_key_mapper.items() if has_ancestor_in_direct_nodes(key) ]
dn_children_ids = [
nid for nid, key in nid_key_mapper.items()
if has_ancestor_in_direct_nodes(key)
]
self._user_direct_node_all_children_ids.update(self._user_direct_node_ids)
self._user_direct_node_all_children_ids.update(dn_children_ids)
def get_node_assets(self, node: Node):
''' 获取节点下授权的直接资产, Luna 页面展开时需要 '''
''' 获取节点下授权的直接资产 '''
q = Q(node_id=node.id)
if str(node.id) not in self._user_direct_node_all_children_ids:
q &= Q(id__in=self._user_direct_asset_ids)
assets = Asset.objects.filter(q)
assets = Asset.objects.filter(org_id=self._org.id).filter(q)
return assets
def get_node_all_assets(self, node: Node):
''' 获取节点及其子节点下所有授权资产, 测试时需要 '''
''' 获取节点及其子节点下所有授权资产 '''
if str(node.id) in self._user_direct_node_all_children_ids:
assets = node.get_all_assets()
return assets
@@ -121,7 +133,7 @@ class UserPermUtil(object):
q = Q(node_id__in=dn_all_nodes_ids)
q |= Q(node_id__in=other_nodes_ids) & Q(id__in=self._user_direct_asset_ids)
assets = Asset.objects.filter(q)
assets = Asset.objects.filter(org_id=self._org.id).filter(q)
return assets
def _uuids_to_string(self, uuids):