From 6cda28c63d26ace1bfdd790c6eac17251da26f86 Mon Sep 17 00:00:00 2001 From: Bai Date: Wed, 7 Dec 2022 18:38:03 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E6=8E=88=E6=9D=83?= =?UTF-8?q?=E8=A7=84=E5=88=99=20user-permission=20=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E6=8E=88=E6=9D=83=E7=9B=B8=E5=85=B3=E7=9A=84=20API;=20?= =?UTF-8?q?=E5=8C=85=E6=8B=AC=20assets,=20nodes,=20tree-asset,=20tree-node?= =?UTF-8?q?,=20tree-node-with-asset;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset/asset.py | 7 +- apps/perms/api/user_group_permission.py | 8 +- apps/perms/api/user_permission/__init__.py | 2 +- apps/perms/api/user_permission/accounts.py | 3 +- apps/perms/api/user_permission/assets.py | 111 +++++------- apps/perms/api/user_permission/mixin.py | 51 +----- apps/perms/api/user_permission/nodes.py | 132 +++------------ .../api/user_permission/nodes_with_assets.py | 158 ------------------ .../api/user_permission/tree/__init__.py | 3 + apps/perms/api/user_permission/tree/asset.py | 48 ++++++ apps/perms/api/user_permission/tree/mixin.py | 17 ++ apps/perms/api/user_permission/tree/node.py | 39 +++++ .../user_permission/tree/node_with_asset.py | 125 ++++++++++++++ apps/perms/pagination.py | 4 +- apps/perms/serializers/user_permission.py | 4 +- apps/perms/urls/user_permission.py | 53 +++--- apps/perms/utils/permission.py | 2 + 17 files changed, 339 insertions(+), 428 deletions(-) delete mode 100644 apps/perms/api/user_permission/nodes_with_assets.py create mode 100644 apps/perms/api/user_permission/tree/__init__.py create mode 100644 apps/perms/api/user_permission/tree/asset.py create mode 100644 apps/perms/api/user_permission/tree/mixin.py create mode 100644 apps/perms/api/user_permission/tree/node.py create mode 100644 apps/perms/api/user_permission/tree/node_with_asset.py diff --git a/apps/assets/api/asset/asset.py b/apps/assets/api/asset/asset.py index df139a8c9..3b32ca748 100644 --- a/apps/assets/api/asset/asset.py +++ b/apps/assets/api/asset/asset.py @@ -30,16 +30,13 @@ __all__ = [ class AssetFilterSet(BaseFilterSet): type = django_filters.CharFilter(field_name="platform__type", lookup_expr="exact") - category = django_filters.CharFilter( - field_name="platform__category", lookup_expr="exact" - ) - hostname = django_filters.CharFilter(field_name="name", lookup_expr="exact") + category = django_filters.CharFilter(field_name="platform__category", lookup_expr="exact") class Meta: model = Asset fields = [ "id", "name", "address", "is_active", - "type", "category", "hostname" + "type", "category" ] diff --git a/apps/perms/api/user_group_permission.py b/apps/perms/api/user_group_permission.py index 48f94f6f7..c9abeee6c 100644 --- a/apps/perms/api/user_group_permission.py +++ b/apps/perms/api/user_group_permission.py @@ -23,8 +23,8 @@ __all__ = [ class UserGroupGrantedAssetsApi(ListAPIView): - serializer_class = serializers.AssetGrantedSerializer - only_fields = serializers.AssetGrantedSerializer.Meta.only_fields + serializer_class = serializers.AssetPermedSerializer + only_fields = serializers.AssetPermedSerializer.Meta.only_fields filterset_fields = ['name', 'address', 'id', 'comment'] search_fields = ['name', 'address', 'comment'] rbac_perms = { @@ -60,8 +60,8 @@ class UserGroupGrantedAssetsApi(ListAPIView): class UserGroupGrantedNodeAssetsApi(ListAPIView): - serializer_class = serializers.AssetGrantedSerializer - only_fields = serializers.AssetGrantedSerializer.Meta.only_fields + serializer_class = serializers.AssetPermedSerializer + only_fields = serializers.AssetPermedSerializer.Meta.only_fields filterset_fields = ['name', 'address', 'id', 'comment'] search_fields = ['name', 'address', 'comment'] rbac_perms = { diff --git a/apps/perms/api/user_permission/__init__.py b/apps/perms/api/user_permission/__init__.py index 55bc108b4..dc67ce7f2 100644 --- a/apps/perms/api/user_permission/__init__.py +++ b/apps/perms/api/user_permission/__init__.py @@ -2,5 +2,5 @@ # from .nodes import * from .assets import * -from .nodes_with_assets import * from .accounts import * +from .tree import * diff --git a/apps/perms/api/user_permission/accounts.py b/apps/perms/api/user_permission/accounts.py index 96c09667f..bb94d12aa 100644 --- a/apps/perms/api/user_permission/accounts.py +++ b/apps/perms/api/user_permission/accounts.py @@ -25,6 +25,5 @@ class UserPermedAssetAccountsApi(SelfOrPKUserMixin, ListAPIView): return asset def get_queryset(self): - util = PermAccountUtil() - accounts = util.get_permed_accounts_for_user(self.user, self.asset) + accounts = PermAccountUtil().get_permed_accounts_for_user(self.user, self.asset) return accounts diff --git a/apps/perms/api/user_permission/assets.py b/apps/perms/api/user_permission/assets.py index d3ee274aa..e494b7a4a 100644 --- a/apps/perms/api/user_permission/assets.py +++ b/apps/perms/api/user_permission/assets.py @@ -1,109 +1,76 @@ -from django.conf import settings +import abc from rest_framework.generics import ListAPIView from assets.models import Asset, Node -from common.utils import get_logger +from assets.api.asset.asset import AssetFilterSet from perms import serializers -from perms.pagination import AllGrantedAssetPagination -from perms.pagination import NodeGrantedAssetPagination +from perms.pagination import AllPermedAssetPagination +from perms.pagination import NodePermedAssetPagination from perms.utils.user_permission import UserGrantedAssetsQueryUtils +from common.utils import get_logger, lazyproperty + from .mixin import ( - SelfOrPKUserMixin, RebuildTreeMixin, - PermedAssetSerializerMixin, AssetsTreeFormatMixin + SelfOrPKUserMixin ) + __all__ = [ + 'UserAllPermedAssetsApi', 'UserDirectPermedAssetsApi', 'UserFavoriteAssetsApi', - 'UserDirectPermedAssetsAsTreeApi', - 'UserUngroupAssetsAsTreeApi', - 'UserAllPermedAssetsApi', 'UserPermedNodeAssetsApi', ] logger = get_logger(__name__) -class UserDirectPermedAssetsApi(SelfOrPKUserMixin, PermedAssetSerializerMixin, ListAPIView): - """ 直接授权给用户的资产 """ - only_fields = serializers.AssetGrantedSerializer.Meta.only_fields +class BaseUserPermedAssetsApi(SelfOrPKUserMixin, ListAPIView): + ordering = ('name',) + ordering_fields = ("name", "address") + search_fields = ('name', 'address', 'comment') + filterset_class = AssetFilterSet + serializer_class = serializers.AssetPermedSerializer + only_fields = serializers.AssetPermedSerializer.Meta.only_fields def get_queryset(self): if getattr(self, 'swagger_fake_view', False): return Asset.objects.none() - - assets = UserGrantedAssetsQueryUtils(self.user) \ - .get_direct_granted_assets() \ - .prefetch_related('platform') \ - .only(*self.only_fields) - return assets - - -class UserFavoriteAssetsApi(SelfOrPKUserMixin, PermedAssetSerializerMixin, ListAPIView): - only_fields = serializers.AssetGrantedSerializer.Meta.only_fields - """ 用户收藏的授权资产 """ - - def get_queryset(self): - if getattr(self, 'swagger_fake_view', False): - return Asset.objects.none() - - user = self.user - utils = UserGrantedAssetsQueryUtils(user) - assets = utils.get_favorite_assets() + assets = self.get_assets() assets = assets.prefetch_related('platform').only(*self.only_fields) return assets + @abc.abstractmethod + def get_assets(self): + return Asset.objects.none() -class UserDirectPermedAssetsAsTreeApi(RebuildTreeMixin, AssetsTreeFormatMixin, UserDirectPermedAssetsApi): - """ 用户直接授权的资产作为树 """ - only_fields = serializers.AssetGrantedSerializer.Meta.only_fields - - def get_queryset(self): - if getattr(self, 'swagger_fake_view', False): - return Asset.objects.none() - - assets = UserGrantedAssetsQueryUtils(self.user) \ - .get_direct_granted_assets() \ - .prefetch_related('platform') \ - .only(*self.only_fields) - return assets + @lazyproperty + def query_asset_util(self): + return UserGrantedAssetsQueryUtils(self.user) -class UserUngroupAssetsAsTreeApi(UserDirectPermedAssetsAsTreeApi): - """ 用户未分组节点下的资产作为树 """ +class UserAllPermedAssetsApi(BaseUserPermedAssetsApi): + pagination_class = AllPermedAssetPagination - def get_queryset(self): - queryset = super().get_queryset() - if not settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE: - queryset = queryset.none() - return queryset + def get_assets(self): + return self.query_asset_util.get_all_granted_assets() -class UserAllPermedAssetsApi(SelfOrPKUserMixin, PermedAssetSerializerMixin, ListAPIView): - only_fields = serializers.AssetGrantedSerializer.Meta.only_fields - pagination_class = AllGrantedAssetPagination - - def get_queryset(self): - if getattr(self, 'swagger_fake_view', False): - return Asset.objects.none() - queryset = UserGrantedAssetsQueryUtils(self.user).get_all_granted_assets() - only_fields = [i for i in self.only_fields if i not in ['protocols']] - queryset = queryset.prefetch_related('platform', 'protocols').only(*only_fields) - return queryset +class UserDirectPermedAssetsApi(BaseUserPermedAssetsApi): + def get_assets(self): + return self.query_asset_util.get_direct_granted_assets() -class UserPermedNodeAssetsApi(SelfOrPKUserMixin, PermedAssetSerializerMixin, ListAPIView): - only_fields = serializers.AssetGrantedSerializer.Meta.only_fields - pagination_class = NodeGrantedAssetPagination - kwargs: dict +class UserFavoriteAssetsApi(BaseUserPermedAssetsApi): + def get_assets(self): + return self.query_asset_util.get_favorite_assets() + + +class UserPermedNodeAssetsApi(BaseUserPermedAssetsApi): + pagination_class = NodePermedAssetPagination pagination_node: Node - def get_queryset(self): - if getattr(self, 'swagger_fake_view', False): - return Asset.objects.none() + def get_assets(self): node_id = self.kwargs.get("node_id") - - node, assets = UserGrantedAssetsQueryUtils(self.user).get_node_all_assets(node_id) - assets = assets.prefetch_related('platform').only(*self.only_fields) + node, assets = self.query_asset_util.get_node_all_assets(node_id) self.pagination_node = node return assets diff --git a/apps/perms/api/user_permission/mixin.py b/apps/perms/api/user_permission/mixin.py index 7be96caed..4f4d48181 100644 --- a/apps/perms/api/user_permission/mixin.py +++ b/apps/perms/api/user_permission/mixin.py @@ -3,27 +3,13 @@ from django.shortcuts import get_object_or_404 from django.utils.translation import ugettext_lazy as _ from rest_framework.request import Request -from rest_framework.response import Response -from assets.api.asset.asset import AssetFilterSet -from assets.api.mixin import SerializeToTreeNodeMixin -from common.exceptions import JMSObjectDoesNotExist -from common.http import is_true -from common.utils import is_uuid -from perms import serializers -from perms.utils.user_permission import UserGrantedTreeRefreshController -from rbac.permissions import RBACPermission from users.models import User +from rbac.permissions import RBACPermission +from common.utils import is_uuid +from common.exceptions import JMSObjectDoesNotExist - -class RebuildTreeMixin: - user: User - - def get(self, request: Request, *args, **kwargs): - force = is_true(request.query_params.get('rebuild_tree')) - controller = UserGrantedTreeRefreshController(self.user) - controller.refresh_if_need(force) - return super().get(request, *args, **kwargs) +__all__ = ['SelfOrPKUserMixin'] class SelfOrPKUserMixin: @@ -72,32 +58,3 @@ class SelfOrPKUserMixin: def request_user_is_self(self): return self.kwargs.get('user') in ['my', 'self'] - -class PermedAssetSerializerMixin: - serializer_class = serializers.AssetGrantedSerializer - filterset_class = AssetFilterSet - search_fields = ['name', 'address', 'comment'] - ordering_fields = ("name", "address") - ordering = ('name',) - - -class AssetsTreeFormatMixin(SerializeToTreeNodeMixin): - """ - 将 资产 序列化成树的结构返回 - """ - filter_queryset: callable - get_queryset: callable - - filterset_fields = ['name', 'address', 'id', 'comment'] - search_fields = ['name', 'address', 'comment'] - - def list(self, request: Request, *args, **kwargs): - queryset = self.filter_queryset(self.get_queryset()) - - if request.query_params.get('search'): - # 如果用户搜索的条件不精准,会导致返回大量的无意义数据。 - # 这里限制一下返回数据的最大条数 - queryset = queryset[:999] - queryset = sorted(queryset, key=lambda asset: asset.name) - data = self.serialize_assets(queryset, None) - return Response(data=data) diff --git a/apps/perms/api/user_permission/nodes.py b/apps/perms/api/user_permission/nodes.py index 6a57675f8..2fcfa5e2c 100644 --- a/apps/perms/api/user_permission/nodes.py +++ b/apps/perms/api/user_permission/nodes.py @@ -1,133 +1,49 @@ # -*- coding: utf-8 -*- # import abc -from rest_framework.request import Request -from rest_framework.response import Response from rest_framework.generics import ListAPIView -from common.utils import get_logger -from assets.api.mixin import SerializeToTreeNodeMixin +from assets.models import Node from perms import serializers -from perms.hands import User from perms.utils.user_permission import UserGrantedNodesQueryUtils +from common.utils import get_logger, lazyproperty -from .mixin import SelfOrPKUserMixin, RebuildTreeMixin +from .mixin import SelfOrPKUserMixin logger = get_logger(__name__) __all__ = [ - 'UserPermedNodesApi', - 'UserPermedNodesAsTreeApi', + 'UserAllPermedNodesApi', 'UserPermedNodeChildrenApi', - 'UserPermedNodeChildrenAsTreeApi', - 'BaseGrantedNodeAsTreeApi', - 'UserGrantedNodesMixin', ] -class _GrantedNodeStructApi(ListAPIView, metaclass=abc.ABCMeta): - @property - def user(self): - raise NotImplementedError - - def get_nodes(self): - # 不使用 `get_queryset` 单独定义 `get_nodes` 的原因是 - # `get_nodes` 返回的不一定是 `queryset` - raise NotImplementedError - - -class NodeChildrenMixin: - def get_children(self): - raise NotImplementedError - - def get_nodes(self): - nodes = self.get_children() - return nodes - - -class BaseGrantedNodeApi(_GrantedNodeStructApi, metaclass=abc.ABCMeta): +class BaseUserPermedNodesApi(SelfOrPKUserMixin, ListAPIView): serializer_class = serializers.NodeGrantedSerializer - def list(self, request, *args, **kwargs): - nodes = self.get_nodes() - serializer = self.get_serializer(nodes, many=True) - return Response(serializer.data) - - -class BaseNodeChildrenApi(NodeChildrenMixin, BaseGrantedNodeApi, metaclass=abc.ABCMeta): - pass - - -class BaseGrantedNodeAsTreeApi(SerializeToTreeNodeMixin, _GrantedNodeStructApi, metaclass=abc.ABCMeta): - def list(self, request: Request, *args, **kwargs): - nodes = self.get_nodes() - nodes = self.serialize_nodes(nodes, with_asset_amount=True) - return Response(data=nodes) - - -class BaseNodeChildrenAsTreeApi(NodeChildrenMixin, BaseGrantedNodeAsTreeApi, metaclass=abc.ABCMeta): - pass - - -class UserGrantedNodeChildrenMixin: - user: User - request: Request - - def get_children(self): - user = self.user - key = self.request.query_params.get('key') - nodes = UserGrantedNodesQueryUtils(user).get_node_children(key) - return nodes - - -class UserGrantedNodesMixin: - """ - 查询用户授权的所有节点 直接授权节点 + 授权资产关联的节点 - """ - user: User + def get_queryset(self): + if getattr(self, 'swagger_fake_view', False): + return Node.objects.none() + return self.get_nodes() + @abc.abstractmethod def get_nodes(self): - utils = UserGrantedNodesQueryUtils(self.user) - nodes = utils.get_whole_tree_nodes() - return nodes + return [] + + @lazyproperty + def query_node_util(self): + return UserGrantedNodesQueryUtils(self.user) -# API - - -class UserPermedNodeChildrenApi( - SelfOrPKUserMixin, - UserGrantedNodeChildrenMixin, - BaseNodeChildrenApi -): - """ 用户授权的节点下的子节点""" - pass - - -class UserPermedNodeChildrenAsTreeApi( - SelfOrPKUserMixin, - RebuildTreeMixin, - UserGrantedNodeChildrenMixin, - BaseNodeChildrenAsTreeApi -): - """ 用户授权的节点下的子节点树""" - pass - - -class UserPermedNodesApi( - SelfOrPKUserMixin, - UserGrantedNodesMixin, - BaseGrantedNodeApi -): +class UserAllPermedNodesApi(BaseUserPermedNodesApi): """ 用户授权的节点 """ - pass + def get_nodes(self): + return self.query_node_util.get_whole_tree_nodes() -class UserPermedNodesAsTreeApi( - SelfOrPKUserMixin, - RebuildTreeMixin, - UserGrantedNodesMixin, - BaseGrantedNodeAsTreeApi -): - """ 用户授权的节点树 """ - pass +class UserPermedNodeChildrenApi(BaseUserPermedNodesApi): + """ 用户授权的节点下的子节点 """ + def get_nodes(self): + key = self.request.query_params.get('key') + nodes = self.query_node_util.get_node_children(key) + return nodes diff --git a/apps/perms/api/user_permission/nodes_with_assets.py b/apps/perms/api/user_permission/nodes_with_assets.py deleted file mode 100644 index 20cda7e00..000000000 --- a/apps/perms/api/user_permission/nodes_with_assets.py +++ /dev/null @@ -1,158 +0,0 @@ -# -*- coding: utf-8 -*- -# -from django.conf import settings -from django.db.models import F, Value, CharField -from rest_framework.generics import ListAPIView -from rest_framework.request import Request -from rest_framework.response import Response - -from common.utils import get_logger, get_object_or_none -from common.utils.common import timeit -from common.permissions import IsValidUser - -from assets.models import Asset -from assets.api import SerializeToTreeNodeMixin -from perms.hands import Node -from perms.models import AssetPermission, PermNode -from perms.utils.user_permission import ( - UserGrantedTreeBuildUtils, get_user_all_asset_perm_ids, - UserGrantedNodesQueryUtils, UserGrantedAssetsQueryUtils, -) - -from .mixin import SelfOrPKUserMixin, RebuildTreeMixin - -logger = get_logger(__name__) - - -class MyGrantedNodesWithAssetsAsTreeApi(SerializeToTreeNodeMixin, ListAPIView): - permission_classes = (IsValidUser,) - - @timeit - def add_ungrouped_resource(self, data: list, nodes_query_utils, assets_query_utils): - if not settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE: - return - ungrouped_node = nodes_query_utils.get_ungrouped_node() - - direct_granted_assets = assets_query_utils.get_direct_granted_assets().annotate( - parent_key=Value(ungrouped_node.key, output_field=CharField()) - ).prefetch_related('platform') - - data.extend(self.serialize_nodes([ungrouped_node], with_asset_amount=True)) - data.extend(self.serialize_assets(direct_granted_assets)) - - @timeit - def add_favorite_resource(self, data: list, nodes_query_utils, assets_query_utils): - favorite_node = nodes_query_utils.get_favorite_node() - - favorite_assets = assets_query_utils.get_favorite_assets() - favorite_assets = favorite_assets.annotate( - parent_key=Value(favorite_node.key, output_field=CharField()) - ).prefetch_related('platform') - - data.extend(self.serialize_nodes([favorite_node], with_asset_amount=True)) - data.extend(self.serialize_assets(favorite_assets)) - - @timeit - def add_node_filtered_by_system_user(self, data: list, user, asset_perm_ids): - utils = UserGrantedTreeBuildUtils(user, asset_perm_ids) - nodes = utils.get_whole_tree_nodes() - data.extend(self.serialize_nodes(nodes, with_asset_amount=True)) - - def add_assets(self, data: list, assets_query_utils: UserGrantedAssetsQueryUtils): - if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE: - all_assets = assets_query_utils.get_direct_granted_nodes_assets() - else: - all_assets = assets_query_utils.get_all_granted_assets() - all_assets = all_assets.annotate(parent_key=F('nodes__key')).prefetch_related('platform') - data.extend(self.serialize_assets(all_assets)) - - def list(self, request: Request, *args, **kwargs): - """ - 此算法依赖 UserGrantedMappingNode - 获取所有授权的节点和资产 - - Node = UserGrantedMappingNode + 授权节点的子节点 - Asset = 授权节点的资产 + 直接授权的资产 - """ - - user = request.user - data = [] - asset_perm_ids = get_user_all_asset_perm_ids(user) - - system_user_id = request.query_params.get('system_user') - if system_user_id: - asset_perm_ids = list(AssetPermission.objects.valid().filter( - id__in=asset_perm_ids, system_users__id=system_user_id, actions__gt=0 - ).values_list('id', flat=True).distinct()) - - nodes_query_utils = UserGrantedNodesQueryUtils(user, asset_perm_ids) - assets_query_utils = UserGrantedAssetsQueryUtils(user, asset_perm_ids) - - self.add_ungrouped_resource(data, nodes_query_utils, assets_query_utils) - self.add_favorite_resource(data, nodes_query_utils, assets_query_utils) - - if system_user_id: - # 有系统用户筛选的需要重新计算树结构 - self.add_node_filtered_by_system_user(data, user, asset_perm_ids) - else: - all_nodes = nodes_query_utils.get_whole_tree_nodes(with_special=False) - data.extend(self.serialize_nodes(all_nodes, with_asset_amount=True)) - - self.add_assets(data, assets_query_utils) - return Response(data=data) - - -class GrantedNodeChildrenWithAssetsAsTreeApiMixin(SerializeToTreeNodeMixin, - ListAPIView): - """ - 带资产的授权树 - """ - user: None - - def ensure_key(self): - key = self.request.query_params.get('key', None) - id = self.request.query_params.get('id', None) - - if key is not None: - return key - - node = get_object_or_none(Node, id=id) - if node: - return node.key - - def list(self, request: Request, *args, **kwargs): - user = self.user - key = self.ensure_key() - - nodes_query_utils = UserGrantedNodesQueryUtils(user) - assets_query_utils = UserGrantedAssetsQueryUtils(user) - - nodes = PermNode.objects.none() - assets = Asset.objects.none() - all_tree_nodes = [] - - if not key: - nodes = nodes_query_utils.get_top_level_nodes() - elif key == PermNode.UNGROUPED_NODE_KEY: - assets = assets_query_utils.get_ungroup_assets() - elif key == PermNode.FAVORITE_NODE_KEY: - assets = assets_query_utils.get_favorite_assets() - else: - nodes = nodes_query_utils.get_node_children(key) - assets = assets_query_utils.get_node_assets(key) - assets = assets.prefetch_related('platform') - - tree_nodes = self.serialize_nodes(nodes, with_asset_amount=True) - tree_assets = self.serialize_assets(assets, key) - all_tree_nodes.extend(tree_nodes) - all_tree_nodes.extend(tree_assets) - return Response(data=all_tree_nodes) - - -class UserGrantedNodeChildrenWithAssetsAsTreeApi( - SelfOrPKUserMixin, - RebuildTreeMixin, - GrantedNodeChildrenWithAssetsAsTreeApiMixin -): - """ 用户授权的节点的子节点与资产树 """ - pass diff --git a/apps/perms/api/user_permission/tree/__init__.py b/apps/perms/api/user_permission/tree/__init__.py new file mode 100644 index 000000000..d0e88bc45 --- /dev/null +++ b/apps/perms/api/user_permission/tree/__init__.py @@ -0,0 +1,3 @@ +from .asset import * +from .node import * +from .node_with_asset import * diff --git a/apps/perms/api/user_permission/tree/asset.py b/apps/perms/api/user_permission/tree/asset.py new file mode 100644 index 000000000..081dbc756 --- /dev/null +++ b/apps/perms/api/user_permission/tree/asset.py @@ -0,0 +1,48 @@ +from django.conf import settings +from rest_framework.response import Response + +from assets.models import Asset +from assets.api import SerializeToTreeNodeMixin +from common.utils import get_logger + +from ..assets import UserDirectPermedAssetsApi +from .mixin import RebuildTreeMixin + +logger = get_logger(__name__) + + +__all__ = [ + 'UserDirectPermedAssetsAsTreeApi', + 'UserUngroupAssetsAsTreeApi', +] + + +class AssetTreeMixin(RebuildTreeMixin, SerializeToTreeNodeMixin): + """ 将资产序列化成树节点的结构返回 """ + filter_queryset: callable + get_queryset: callable + + ordering = ('name',) + filterset_fields = ('id', 'name', 'address', 'comment') + search_fields = ('name', 'address', 'comment') + + def list(self, request, *args, **kwargs): + assets = self.filter_queryset(self.get_queryset()) + if request.query_params.get('search'): + """ 限制返回数量, 搜索的条件不精准时,会返回大量的无意义数据 """ + assets = assets[:999] + data = self.serialize_assets(assets, None) + return Response(data=data) + + +class UserDirectPermedAssetsAsTreeApi(AssetTreeMixin, UserDirectPermedAssetsApi): + """ 用户 '直接授权的资产' 作为树 """ + pass + + +class UserUngroupAssetsAsTreeApi(UserDirectPermedAssetsAsTreeApi): + """ 用户 '未分组节点的资产(直接授权的资产)' 作为树 """ + def get_assets(self): + if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE: + return super().get_assets() + return Asset.objects.none() diff --git a/apps/perms/api/user_permission/tree/mixin.py b/apps/perms/api/user_permission/tree/mixin.py new file mode 100644 index 000000000..cbb405994 --- /dev/null +++ b/apps/perms/api/user_permission/tree/mixin.py @@ -0,0 +1,17 @@ +from rest_framework.request import Request + +from users.models import User +from perms.utils.user_permission import UserGrantedTreeRefreshController +from common.http import is_true + + +__all__ = ['RebuildTreeMixin'] + + +class RebuildTreeMixin: + user: User + + def get(self, request: Request, *args, **kwargs): + force = is_true(request.query_params.get('rebuild_tree')) + UserGrantedTreeRefreshController(self.user).refresh_if_need(force) + return super().get(request, *args, **kwargs) diff --git a/apps/perms/api/user_permission/tree/node.py b/apps/perms/api/user_permission/tree/node.py new file mode 100644 index 000000000..5872753c5 --- /dev/null +++ b/apps/perms/api/user_permission/tree/node.py @@ -0,0 +1,39 @@ +from rest_framework.response import Response + +from assets.api import SerializeToTreeNodeMixin +from common.utils import get_logger + +from .mixin import RebuildTreeMixin +from ..nodes import ( + UserAllPermedNodesApi, + UserPermedNodeChildrenApi, +) + +logger = get_logger(__name__) + +__all__ = [ + 'UserAllPermedNodesAsTreeApi', + 'UserPermedNodeChildrenAsTreeApi', +] + + +class NodeTreeMixin(RebuildTreeMixin, SerializeToTreeNodeMixin): + filter_queryset: callable + get_queryset: callable + + def list(self, request, *args, **kwargs): + nodes = self.filter_queryset(self.get_queryset()) + data = self.serialize_nodes(nodes, with_asset_amount=True) + return Response(data) + + +class UserAllPermedNodesAsTreeApi(NodeTreeMixin, UserAllPermedNodesApi): + """ 用户 '授权的节点' 作为树 """ + pass + + +class UserPermedNodeChildrenAsTreeApi(NodeTreeMixin, UserPermedNodeChildrenApi): + """ 用户授权的节点下的子节点树 """ + pass + + diff --git a/apps/perms/api/user_permission/tree/node_with_asset.py b/apps/perms/api/user_permission/tree/node_with_asset.py new file mode 100644 index 000000000..d5df7ba19 --- /dev/null +++ b/apps/perms/api/user_permission/tree/node_with_asset.py @@ -0,0 +1,125 @@ +import abc + +from django.conf import settings +from django.db.models import F, Value, CharField +from rest_framework.generics import ListAPIView +from rest_framework.response import Response + +from assets.models import Asset +from assets.api import SerializeToTreeNodeMixin +from perms.hands import Node +from perms.models import PermNode +from perms.utils.user_permission import ( + UserGrantedNodesQueryUtils, UserGrantedAssetsQueryUtils, +) +from perms.utils.permission import AssetPermissionUtil +from common.utils import get_object_or_none, lazyproperty +from common.utils.common import timeit + + +from ..mixin import SelfOrPKUserMixin +from .mixin import RebuildTreeMixin + +__all__ = [ + 'UserPermedNodesWithAssetsAsTreeApi', + 'UserPermedNodeChildrenWithAssetsAsTreeApi' +] + + +class BaseUserNodeWithAssetAsTreeApi(SelfOrPKUserMixin, RebuildTreeMixin, SerializeToTreeNodeMixin, + ListAPIView): + + def list(self, request, *args, **kwargs): + nodes, assets = self.get_nodes_assets() + tree_nodes = self.serialize_nodes(nodes, with_asset_amount=True) + tree_assets = self.serialize_assets(assets, node_key=self.node_key_for_serializer_assets) + data = list(tree_nodes) + list(tree_assets) + return Response(data=data) + + @abc.abstractmethod + def get_nodes_assets(self): + return [], [] + + @lazyproperty + def node_key_for_serializer_assets(self): + return None + + +class UserPermedNodesWithAssetsAsTreeApi(BaseUserNodeWithAssetAsTreeApi): + query_node_util: UserGrantedNodesQueryUtils + query_asset_util: UserGrantedAssetsQueryUtils + + def get_nodes_assets(self): + perm_ids = AssetPermissionUtil().get_permissions_for_user(self.request.user, flat=True) + self.query_node_util = UserGrantedNodesQueryUtils(self.request.user, perm_ids) + self.query_asset_util = UserGrantedAssetsQueryUtils(self.request.user, perm_ids) + ung_nodes, ung_assets = self._get_nodes_assets_for_ungrouped() + fav_nodes, fav_assets = self._get_nodes_assets_for_favorite() + all_nodes, all_assets = self._get_nodes_assets_for_all() + nodes = list(ung_nodes) + list(fav_nodes) + list(all_nodes) + assets = list(ung_assets) + list(fav_assets) + list(all_assets) + return nodes, assets + + @timeit + def _get_nodes_assets_for_ungrouped(self): + if not settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE: + return [], [] + node = self.query_node_util.get_ungrouped_node() + assets = self.query_asset_util.get_ungroup_assets() + assets = assets.annotate(parent_key=Value(node.key, output_field=CharField())) \ + .prefetch_related('platform') + return [node], assets + + @timeit + def _get_nodes_assets_for_favorite(self): + node = self.query_node_util.get_favorite_node() + assets = self.query_asset_util.get_favorite_assets() + assets = assets.annotate(parent_key=Value(node.key, output_field=CharField())) \ + .prefetch_related('platform') + return [node], assets + + def _get_nodes_assets_for_all(self): + nodes = self.query_node_util.get_whole_tree_nodes(with_special=False) + if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE: + assets = self.query_asset_util.get_direct_granted_nodes_assets() + else: + assets = self.query_asset_util.get_all_granted_assets() + assets = assets.annotate(parent_key=F('nodes__key')).prefetch_related('platform') + return nodes, assets + + +class UserPermedNodeChildrenWithAssetsAsTreeApi(BaseUserNodeWithAssetAsTreeApi): + """ 用户授权的节点的子节点与资产树 """ + + def get_nodes_assets(self): + nodes = PermNode.objects.none() + assets = Asset.objects.none() + query_node_util = UserGrantedNodesQueryUtils(self.user) + query_asset_util = UserGrantedAssetsQueryUtils(self.user) + node_key = self.query_node_key + if not node_key: + nodes = query_node_util.get_top_level_nodes() + elif node_key == PermNode.UNGROUPED_NODE_KEY: + assets = query_asset_util.get_ungroup_assets() + elif node_key == PermNode.FAVORITE_NODE_KEY: + assets = query_asset_util.get_favorite_assets() + else: + nodes = query_node_util.get_node_children(node_key) + assets = query_asset_util.get_node_assets(node_key) + assets = assets.prefetch_related('platform') + return nodes, assets + + @lazyproperty + def query_node_key(self): + node_key = self.request.query_params.get('key', None) + if node_key is not None: + return node_key + node_id = self.request.query_params.get('id', None) + node = get_object_or_none(Node, id=node_id) + node_key = getattr(node, 'key', None) + return node_key + + @lazyproperty + def node_key_for_serializer_assets(self): + return self.query_node_key + diff --git a/apps/perms/pagination.py b/apps/perms/pagination.py index 622306924..ca20b68d4 100644 --- a/apps/perms/pagination.py +++ b/apps/perms/pagination.py @@ -16,7 +16,7 @@ class GrantedAssetPaginationBase(AssetPaginationBase): self._user = view.user -class NodeGrantedAssetPagination(GrantedAssetPaginationBase): +class NodePermedAssetPagination(GrantedAssetPaginationBase): def get_count_from_nodes(self, queryset): node = getattr(self._view, 'pagination_node', None) if node: @@ -29,7 +29,7 @@ class NodeGrantedAssetPagination(GrantedAssetPaginationBase): return None -class AllGrantedAssetPagination(GrantedAssetPaginationBase): +class AllPermedAssetPagination(GrantedAssetPaginationBase): def get_count_from_nodes(self, queryset): if settings.PERM_SINGLE_ASSET_TO_UNGROUP_NODE: return None diff --git a/apps/perms/serializers/user_permission.py b/apps/perms/serializers/user_permission.py index a85770a50..ba0619edc 100644 --- a/apps/perms/serializers/user_permission.py +++ b/apps/perms/serializers/user_permission.py @@ -11,12 +11,12 @@ from common.drf.fields import ObjectRelatedField, LabeledChoiceField from perms.serializers.permission import ActionChoicesField __all__ = [ - 'NodeGrantedSerializer', 'AssetGrantedSerializer', + 'NodeGrantedSerializer', 'AssetPermedSerializer', 'AccountsPermedSerializer' ] -class AssetGrantedSerializer(serializers.ModelSerializer): +class AssetPermedSerializer(serializers.ModelSerializer): """ 被授权资产的数据结构 """ platform = ObjectRelatedField(required=False, queryset=Platform.objects, label=_('Platform')) protocols = AssetProtocolsSerializer(many=True, required=False, label=_('Protocols')) diff --git a/apps/perms/urls/user_permission.py b/apps/perms/urls/user_permission.py index 1ff44eeee..90cb3177b 100644 --- a/apps/perms/urls/user_permission.py +++ b/apps/perms/urls/user_permission.py @@ -4,39 +4,38 @@ from .. import api user_permission_urlpatterns = [ # such as: my | self | user.id - # assets path('/assets/', api.UserAllPermedAssetsApi.as_view(), - name='user-assets'), - path('/assets/tree/', api.UserDirectPermedAssetsAsTreeApi.as_view(), - name='user-assets-as-tree'), - path('/ungroup/assets/tree/', api.UserUngroupAssetsAsTreeApi.as_view(), - name='user-ungroup-assets-as-tree'), - - # nodes - path('/nodes/', api.UserPermedNodesApi.as_view(), - name='user-nodes'), - path('/nodes/tree/', api.UserPermedNodesAsTreeApi.as_view(), - name='user-nodes-as-tree'), - path('/nodes/children/', api.UserPermedNodeChildrenApi.as_view(), - name='user-nodes-children'), - path('/nodes/children/tree/', api.UserPermedNodeChildrenAsTreeApi.as_view(), - name='user-nodes-children-as-tree'), - - # node-assets + name='user-all-assets'), + path('/nodes/ungrouped/assets/', api.UserDirectPermedAssetsApi.as_view(), + name='user-direct-assets'), + path('/nodes/favorite/assets/', api.UserFavoriteAssetsApi.as_view(), + name='user-favorite-assets'), path('/nodes//assets/', api.UserPermedNodeAssetsApi.as_view(), name='user-node-assets'), - path('/nodes/ungrouped/assets/', api.UserDirectPermedAssetsApi.as_view(), - name='user-ungrouped-assets'), - path('/nodes/favorite/assets/', api.UserFavoriteAssetsApi.as_view(), - name='user-ungrouped-assets'), + # nodes + path('/nodes/', api.UserAllPermedNodesApi.as_view(), + name='user-all-nodes'), + path('/nodes/children/', api.UserPermedNodeChildrenApi.as_view(), + name='user-node-children'), + + # tree-asset + path('/assets/tree/', api.UserDirectPermedAssetsAsTreeApi.as_view(), + name='user-direct-assets-as-tree'), + path('/ungroup/assets/tree/', api.UserUngroupAssetsAsTreeApi.as_view(), + name='user-ungroup-assets-as-tree'), + # tree-node + path('/nodes/tree/', api.UserAllPermedNodesAsTreeApi.as_view(), + name='user-all-nodes-as-tree'), + path('/nodes/children/tree/', api.UserPermedNodeChildrenAsTreeApi.as_view(), + name='user-node-children-as-tree'), + # tree-node-with-asset path('/nodes/children-with-assets/tree/', - api.UserGrantedNodeChildrenWithAssetsAsTreeApi.as_view(), - name='user-nodes-children-with-assets-as-tree'), - - path('nodes-with-assets/tree/', api.MyGrantedNodesWithAssetsAsTreeApi.as_view(), - name='my-nodes-with-assets-as-tree'), + api.UserPermedNodeChildrenWithAssetsAsTreeApi.as_view(), + name='user-node-children-with-assets-as-tree'), + path('/nodes-with-assets/tree/', api.UserPermedNodesWithAssetsAsTreeApi.as_view(), + name='user-nodes-with-assets-as-tree'), # accounts path('/assets//accounts/', api.UserPermedAssetAccountsApi.as_view(), diff --git a/apps/perms/utils/permission.py b/apps/perms/utils/permission.py index 8e4cd9199..4358e2075 100644 --- a/apps/perms/utils/permission.py +++ b/apps/perms/utils/permission.py @@ -4,6 +4,8 @@ from perms.models import AssetPermission logger = get_logger(__file__) +__all__ = ['AssetPermissionUtil'] + class AssetPermissionUtil(object): """ 资产授权相关的方法工具 """