diff --git a/apps/assets/api/asset.py b/apps/assets/api/asset.py index 92a1775d0..986829def 100644 --- a/apps/assets/api/asset.py +++ b/apps/assets/api/asset.py @@ -41,40 +41,46 @@ class AssetViewSet(IDInFilterMixin, LabelFilter, BulkModelViewSet): pagination_class = LimitOffsetPagination permission_classes = (IsOrgAdminOrAppUser,) - def filter_node(self): + def filter_node(self, queryset): node_id = self.request.query_params.get("node_id") if not node_id: - return + return queryset node = get_object_or_404(Node, id=node_id) show_current_asset = self.request.query_params.get("show_current_asset") in ('1', 'true') - if node.is_root(): - if show_current_asset: - self.queryset = self.queryset.filter( - Q(nodes=node_id) | Q(nodes__isnull=True) - ) - return - if show_current_asset: - self.queryset = self.queryset.filter(nodes=node) + if node.is_root() and show_current_asset: + queryset = queryset.filter( + Q(nodes=node_id) | Q(nodes__isnull=True) + ) + elif node.is_root() and not show_current_asset: + pass + elif not node.is_root() and show_current_asset: + queryset = queryset.filter(nodes=node) else: - self.queryset = self.queryset.filter( + queryset = queryset.filter( nodes__key__regex='^{}(:[0-9]+)*$'.format(node.key), ) + return queryset - def filter_admin_user_id(self): + def filter_admin_user_id(self, queryset): admin_user_id = self.request.query_params.get('admin_user_id') - if admin_user_id: - admin_user = get_object_or_404(AdminUser, id=admin_user_id) - self.queryset = self.queryset.filter(admin_user=admin_user) + if not admin_user_id: + return queryset + admin_user = get_object_or_404(AdminUser, id=admin_user_id) + queryset = queryset.filter(admin_user=admin_user) + return queryset + + def filter_queryset(self, queryset): + queryset = super().filter_queryset(queryset) + queryset = self.filter_node(queryset) + queryset = self.filter_admin_user_id(queryset) + return queryset def get_queryset(self): - self.queryset = super().get_queryset()\ - .prefetch_related('labels', 'nodes')\ - .select_related('admin_user') - self.filter_admin_user_id() - self.filter_node() - return self.queryset.distinct() + queryset = super().get_queryset().distinct() + queryset = self.get_serializer_class().setup_eager_loading(queryset) + return queryset class AssetListUpdateApi(IDInFilterMixin, ListBulkCreateUpdateDestroyAPIView): diff --git a/apps/assets/api/node.py b/apps/assets/api/node.py index 73313f222..84ba4c69f 100644 --- a/apps/assets/api/node.py +++ b/apps/assets/api/node.py @@ -17,13 +17,13 @@ from rest_framework import generics, mixins, viewsets from rest_framework.serializers import ValidationError from rest_framework.views import APIView from rest_framework.response import Response -from rest_framework_bulk import BulkModelViewSet from django.utils.translation import ugettext_lazy as _ from django.shortcuts import get_object_or_404 from common.utils import get_logger, get_object_or_none +from common.tree import TreeNodeSerializer from ..hands import IsOrgAdmin -from ..models import Node, Asset +from ..models import Node from ..tasks import update_assets_hardware_info_util, test_asset_connectability_util from .. import serializers @@ -33,7 +33,8 @@ __all__ = [ 'NodeViewSet', 'NodeChildrenApi', 'NodeAssetsApi', 'NodeAddAssetsApi', 'NodeRemoveAssetsApi', 'NodeReplaceAssetsApi', 'NodeAddChildrenApi', 'RefreshNodeHardwareInfoApi', - 'TestNodeConnectiveApi' + 'TestNodeConnectiveApi', 'NodeListAsTreeApi', + 'NodeChildrenAsTreeApi', ] @@ -42,22 +43,89 @@ class NodeViewSet(viewsets.ModelViewSet): permission_classes = (IsOrgAdmin,) serializer_class = serializers.NodeSerializer - def perform_create(self, serializer): - child_key = Node.root().get_next_child_key() - serializer.validated_data["key"] = child_key - serializer.save() - def update(self, request, *args, **kwargs): - node = self.get_object() - if node.is_root(): - node_value = node.value - post_value = request.data.get('value') - if node_value != post_value: - return Response( - {"msg": _("You can't update the root node name")}, - status=400 - ) - return super().update(request, *args, **kwargs) +class NodeListAsTreeApi(generics.ListAPIView): + """ + 获取节点列表树 + [ + { + "id": "", + "name": "", + "pId": "", + "meta": "" + } + ] + """ + permission_classes = (IsOrgAdmin,) + serializer_class = TreeNodeSerializer + + def get_queryset(self): + queryset = [node.as_tree_node() for node in Node.objects.all()] + return queryset + + def filter_queryset(self, queryset): + if self.request.query_params.get('refresh', '0') == '1': + queryset = self.refresh_nodes(queryset) + return queryset + + @staticmethod + def refresh_nodes(queryset): + Node.expire_nodes_assets_amount() + Node.expire_nodes_full_value() + return queryset + + +class NodeChildrenAsTreeApi(generics.ListAPIView): + """ + 节点子节点作为树返回, + [ + { + "id": "", + "name": "", + "pId": "", + "meta": "" + } + ] + + """ + permission_classes = (IsOrgAdmin,) + serializer_class = TreeNodeSerializer + node = None + is_root = False + + def get_queryset(self): + node_key = self.request.query_params.get('key') + if node_key: + self.node = Node.objects.get(key=node_key) + queryset = self.node.get_children(with_self=False) + else: + self.is_root = True + self.node = Node.root() + queryset = list(self.node.get_children(with_self=True)) + nodes_invalid = Node.objects.exclude(key__startswith=self.node.key) + queryset.extend(list(nodes_invalid)) + queryset = [node.as_tree_node() for node in queryset] + return queryset + + def filter_assets(self, queryset): + include_assets = self.request.query_params.get('assets', '0') == '1' + if not include_assets: + return queryset + assets = self.node.get_assets() + for asset in assets: + queryset.append(asset.as_tree_node(self.node)) + return queryset + + def filter_queryset(self, queryset): + queryset = self.filter_assets(queryset) + queryset = self.filter_refresh_nodes(queryset) + return queryset + + def filter_refresh_nodes(self, queryset): + if self.request.query_params.get('refresh', '0') == '1': + Node.expire_nodes_assets_amount() + Node.expire_nodes_full_value() + return queryset class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView): @@ -66,19 +134,10 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView): serializer_class = serializers.NodeSerializer instance = None - def counter(self): - values = [ - child.value[child.value.rfind(' '):] - for child in self.get_object().get_children() - if child.value.startswith("新节点 ") - ] - values = [int(value) for value in values if value.strip().isdigit()] - count = max(values)+1 if values else 1 - return count - def post(self, request, *args, **kwargs): + instance = self.get_object() if not request.data.get("value"): - request.data["value"] = _("New node {}").format(self.counter()) + request.data["value"] = instance.get_next_child_preset_name() return super().post(request, *args, **kwargs) def create(self, request, *args, **kwargs): @@ -90,10 +149,7 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView): 'The same level node name cannot be the same' ) node = instance.create_child(value=value) - return Response( - {"id": node.id, "key": node.key, "value": node.value}, - status=201, - ) + return Response(self.serializer_class(instance=node).data, status=201) def get_object(self): pk = self.kwargs.get('pk') or self.request.query_params.get('id') @@ -106,7 +162,6 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView): def get_queryset(self): queryset = [] query_all = self.request.query_params.get("all") - query_assets = self.request.query_params.get('assets') node = self.get_object() if node is None: @@ -119,23 +174,8 @@ class NodeChildrenApi(mixins.ListModelMixin, generics.CreateAPIView): else: children = node.get_children() queryset.extend(list(children)) - - if query_assets: - assets = node.get_assets() - for asset in assets: - node_fake = Node() - node_fake.assets__count = 0 - node_fake.id = asset.id - node_fake.is_node = False - node_fake.key = node.key + ':0' - node_fake.value = asset.hostname - queryset.append(node_fake) - queryset = sorted(queryset, key=lambda x: x.is_node, reverse=True) return queryset - def get(self, request, *args, **kwargs): - return super().list(request, *args, **kwargs) - class NodeAssetsApi(generics.ListAPIView): permission_classes = (IsOrgAdmin,) diff --git a/apps/assets/models/asset.py b/apps/assets/models/asset.py index cde9cde2e..06fb29a51 100644 --- a/apps/assets/models/asset.py +++ b/apps/assets/models/asset.py @@ -255,6 +255,36 @@ class Asset(OrgModelMixin): }) return data + def as_tree_node(self, parent_node): + from common.tree import TreeNode + icon_skin = 'file' + if self.platform.lower() == 'windows': + icon_skin = 'windows' + elif self.platform.lower() == 'linux': + icon_skin = 'linux' + data = { + 'id': str(self.id), + 'name': self.hostname, + 'title': self.ip, + 'pId': parent_node.key, + 'isParent': False, + 'open': False, + 'iconSkin': icon_skin, + 'meta': { + 'type': 'asset', + 'asset': { + 'id': self.id, + 'hostname': self.hostname, + 'ip': self.ip, + 'port': self.port, + 'platform': self.platform, + 'protocol': self.protocol, + } + } + } + tree_node = TreeNode(**data) + return tree_node + class Meta: unique_together = [('org_id', 'hostname')] verbose_name = _("Asset") diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py index 47a835861..881b041d7 100644 --- a/apps/assets/models/node.py +++ b/apps/assets/models/node.py @@ -5,6 +5,7 @@ import uuid from django.db import models, transaction from django.db.models import Q from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext from django.core.cache import cache from orgs.mixins import OrgModelMixin @@ -103,6 +104,15 @@ class Node(OrgModelMixin): key = self._full_value_cache_key.format(self.key) cache.delete_pattern(key+'*') + @classmethod + def expire_nodes_full_value(cls, nodes=None): + if nodes: + for node in nodes: + node.expire_full_value() + return + key = cls._full_value_cache_key.format('*') + cache.delete_pattern(key+'*') + @property def level(self): return len(self.key.split(':')) @@ -113,6 +123,17 @@ class Node(OrgModelMixin): self.save() return "{}:{}".format(self.key, mark) + def get_next_child_preset_name(self): + name = ugettext("New node") + values = [ + child.value[child.value.rfind(' '):] + for child in self.get_children() + if child.value.startswith(name) + ] + values = [int(value) for value in values if value.strip().isdigit()] + count = max(values) + 1 if values else 1 + return '{} {}'.format(name, count) + def create_child(self, value): with transaction.atomic(): child_key = self.get_next_child_key() @@ -162,7 +183,7 @@ class Node(OrgModelMixin): pattern = r'^{0}$|^{0}:'.format(self.key) args = [] kwargs = {} - if self.is_default_node(): + if self.is_root(): args.append(Q(nodes__key__regex=pattern) | Q(nodes=None)) else: kwargs['nodes__key__regex'] = pattern @@ -256,6 +277,26 @@ class Node(OrgModelMixin): defaults = {'value': 'Default'} return cls.objects.get_or_create(defaults=defaults, key='1') + def as_tree_node(self): + from common.tree import TreeNode + from ..serializers import NodeSerializer + name = '{} ({})'.format(self.value, self.assets_amount) + node_serializer = NodeSerializer(instance=self) + data = { + 'id': self.key, + 'name': name, + 'title': name, + 'pId': self.parent_key, + 'isParent': True, + 'open': self.is_root(), + 'meta': { + 'node': node_serializer.data, + 'type': 'node' + } + } + tree_node = TreeNode(**data) + return tree_node + @classmethod def generate_fake(cls, count=100): import random diff --git a/apps/assets/serializers/asset.py b/apps/assets/serializers/asset.py index dae9ab9af..1066ae0b7 100644 --- a/apps/assets/serializers/asset.py +++ b/apps/assets/serializers/asset.py @@ -9,6 +9,7 @@ from .system_user import AssetSystemUserSerializer __all__ = [ 'AssetSerializer', 'AssetGrantedSerializer', 'MyAssetGrantedSerializer', + 'AssetAsNodeSerializer', ] @@ -22,6 +23,13 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer): fields = '__all__' validators = [] + @classmethod + def setup_eager_loading(cls, queryset): + """ Perform necessary eager loading of data. """ + queryset = queryset.prefetch_related('labels', 'nodes')\ + .select_related('admin_user') + return queryset + def get_field_names(self, declared_fields, info): fields = super().get_field_names(declared_fields, info) fields.extend([ @@ -30,6 +38,12 @@ class AssetSerializer(BulkSerializerMixin, serializers.ModelSerializer): return fields +class AssetAsNodeSerializer(serializers.ModelSerializer): + class Meta: + model = Asset + fields = ['id', 'hostname', 'ip', 'port', 'platform', 'protocol'] + + class AssetGrantedSerializer(serializers.ModelSerializer): """ 被授权资产的数据结构 diff --git a/apps/assets/serializers/node.py b/apps/assets/serializers/node.py index f44ff44d6..79c573c60 100644 --- a/apps/assets/serializers/node.py +++ b/apps/assets/serializers/node.py @@ -8,76 +8,33 @@ from .asset import AssetGrantedSerializer __all__ = [ - 'NodeSerializer', "NodeGrantedSerializer", "NodeAddChildrenSerializer", + 'NodeSerializer', "NodeAddChildrenSerializer", "NodeAssetsSerializer", ] -class NodeGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer): - """ - 授权资产组 - """ - assets_granted = AssetGrantedSerializer(many=True, read_only=True) - assets_amount = serializers.SerializerMethodField() - parent = serializers.SerializerMethodField() - name = serializers.SerializerMethodField() - - class Meta: - model = Node - fields = [ - 'id', 'key', 'name', 'value', 'parent', - 'assets_granted', 'assets_amount', 'org_id', - ] - - @staticmethod - def get_assets_amount(obj): - return len(obj.assets_granted) - - @staticmethod - def get_name(obj): - return obj.name - - @staticmethod - def get_parent(obj): - return obj.parent.id - - class NodeSerializer(serializers.ModelSerializer): - assets_amount = serializers.IntegerField() - tree_id = serializers.SerializerMethodField() - tree_parent = serializers.SerializerMethodField() + assets_amount = serializers.IntegerField(read_only=True) class Meta: model = Node fields = [ - 'id', 'key', 'value', 'assets_amount', - 'is_node', 'org_id', 'tree_id', 'tree_parent', + 'id', 'key', 'value', 'assets_amount', 'org_id', ] read_only_fields = [ - 'id', 'key', 'assets_amount', 'is_node', - 'org_id', + 'id', 'key', 'assets_amount', 'org_id', ] - list_serializer_class = BulkListSerializer - def validate(self, data): - value = data.get('value') + def validate_value(self, data): instance = self.instance if self.instance else Node.root() children = instance.parent.get_children().exclude(key=instance.key) values = [child.value for child in children] - if value in values: + if data in values: raise serializers.ValidationError( 'The same level node name cannot be the same' ) return data - @staticmethod - def get_tree_id(obj): - return obj.key - - @staticmethod - def get_tree_parent(obj): - return obj.parent_key - class NodeAssetsSerializer(serializers.ModelSerializer): assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all()) @@ -89,3 +46,4 @@ class NodeAssetsSerializer(serializers.ModelSerializer): class NodeAddChildrenSerializer(serializers.Serializer): nodes = serializers.ListField() + diff --git a/apps/assets/templates/assets/asset_list.html b/apps/assets/templates/assets/asset_list.html index bd3a776b3..4a77f1de5 100644 --- a/apps/assets/templates/assets/asset_list.html +++ b/apps/assets/templates/assets/asset_list.html @@ -136,6 +136,7 @@
+