From a261d69cd2ce925b186300ed13d65cd10a81c51b Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 18 May 2023 21:34:19 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=20m2m=20json=20field?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/asset/asset.py | 7 +++++-- apps/assets/models/asset/common.py | 28 +++++++++++++++++++++++++++- apps/assets/models/node.py | 13 +++++++++++++ apps/common/db/fields.py | 1 + apps/common/drf/filters.py | 16 ++++++++++++---- apps/users/api/user.py | 4 ++-- 6 files changed, 60 insertions(+), 9 deletions(-) diff --git a/apps/assets/api/asset/asset.py b/apps/assets/api/asset/asset.py index ba048e236..71d912188 100644 --- a/apps/assets/api/asset/asset.py +++ b/apps/assets/api/asset/asset.py @@ -15,7 +15,7 @@ from assets.filters import IpInFilterBackend, LabelFilterBackend, NodeFilterBack from assets.models import Asset, Gateway, Platform from assets.tasks import test_assets_connectivity_manual, update_assets_hardware_info_manual from common.api import SuggestionMixin -from common.drf.filters import BaseFilterSet +from common.drf.filters import BaseFilterSet, AttrRulesFilterBackend from common.utils import get_logger, is_uuid from orgs.mixins import generics from orgs.mixins.api import OrgBulkModelViewSet @@ -110,7 +110,10 @@ class AssetViewSet(SuggestionMixin, NodeFilterMixin, OrgBulkModelViewSet): ("spec_info", "assets.view_asset"), ("gathered_info", "assets.view_asset"), ) - extra_filter_backends = [LabelFilterBackend, IpInFilterBackend, NodeFilterBackend] + extra_filter_backends = [ + LabelFilterBackend, IpInFilterBackend, + NodeFilterBackend, AttrRulesFilterBackend + ] def get_serializer_class(self): cls = super().get_serializer_class() diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index b39119b81..6e6d6b836 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -6,6 +6,7 @@ import logging from collections import defaultdict from django.db import models +from django.db.models import Q from django.forms import model_to_dict from django.utils.translation import ugettext_lazy as _ @@ -116,7 +117,32 @@ class Protocol(models.Model): return self.asset_platform_protocol.get('public', True) -class Asset(NodesRelationMixin, AbsConnectivity, JMSOrgBaseModel): +class JSONFilterMixin: + @staticmethod + def get_json_filter_attr_q(name, value, match): + """ + :param name: 属性名称 + :param value: 定义的结果 + :param match: 匹配方式 + :return: + """ + from ..node import Node + if not isinstance(value, (list, tuple)): + value = [value] + if name == 'nodes': + nodes = Node.objects.filter(id__in=value) + children = Node.get_nodes_all_children(nodes, with_self=True).values_list('id', flat=True) + return Q(nodes__in=children) + elif name == 'category': + return Q(platform__category__in=value) + elif name == 'type': + return Q(platform__type__in=value) + elif name == 'protocols': + return Q(protocols__name__in=value) + return None + + +class Asset(NodesRelationMixin, AbsConnectivity, JSONFilterMixin, JMSOrgBaseModel): Category = const.Category Type = const.AllTypes diff --git a/apps/assets/models/node.py b/apps/assets/models/node.py index 32bfcaa09..3a729ba9f 100644 --- a/apps/assets/models/node.py +++ b/apps/assets/models/node.py @@ -63,6 +63,19 @@ class FamilyMixin: pattern += r'|^{0}$'.format(key) return pattern + @classmethod + def get_nodes_children_key_pattern(cls, nodes, with_self=True): + keys = [i.key for i in nodes] + keys = cls.clean_children_keys(keys) + patterns = [cls.get_node_all_children_key_pattern(key) for key in keys] + patterns = '|'.join(patterns) + return patterns + + @classmethod + def get_nodes_all_children(cls, nodes, with_self=True): + pattern = cls.get_nodes_children_key_pattern(nodes, with_self=with_self) + return Node.objects.filter(key__iregex=pattern) + @classmethod def get_node_children_key_pattern(cls, key, with_self=True): pattern = r'^{0}:[0-9]+$'.format(key) diff --git a/apps/common/db/fields.py b/apps/common/db/fields.py index 6039e943a..3897a8c5e 100644 --- a/apps/common/db/fields.py +++ b/apps/common/db/fields.py @@ -315,6 +315,7 @@ class RelatedManager: else: queryset = to_model.objects.all() q = cls.get_filter_q(value, to_model) + print("Q: ", q) return queryset.filter(q) @staticmethod diff --git a/apps/common/drf/filters.py b/apps/common/drf/filters.py index 6278efccf..cfdff0a6e 100644 --- a/apps/common/drf/filters.py +++ b/apps/common/drf/filters.py @@ -189,7 +189,7 @@ class NumberInFilter(drf_filters.BaseInFilter, drf_filters.NumberFilter): pass -class AttrRulesFilter(filters.BaseFilterBackend): +class AttrRulesFilterBackend(filters.BaseFilterBackend): def get_schema_fields(self, view): return [ coreapi.Field( @@ -204,7 +204,15 @@ class AttrRulesFilter(filters.BaseFilterBackend): if not attr_rules: return queryset - attr_rules = base64.b64decode(attr_rules.encode('utf-8')) - attr_rules = json.loads(attr_rules) + try: + attr_rules = base64.b64decode(attr_rules.encode('utf-8')) + except Exception: + raise ValidationError({'attr_rules': 'attr_rules should be base64'}) + try: + attr_rules = json.loads(attr_rules) + except Exception: + raise ValidationError({'attr_rules': 'attr_rules should be json'}) + + logging.debug('attr_rules: %s', attr_rules) q = RelatedManager.get_filter_q(attr_rules, queryset.model) - return queryset.filter(q) + return queryset.filter(q).distinct() diff --git a/apps/users/api/user.py b/apps/users/api/user.py index 26a8e9d05..c7882f6b2 100644 --- a/apps/users/api/user.py +++ b/apps/users/api/user.py @@ -8,7 +8,7 @@ from rest_framework.response import Response from rest_framework_bulk import BulkModelViewSet from common.api import CommonApiMixin, SuggestionMixin -from common.drf.filters import AttrRulesFilter +from common.drf.filters import AttrRulesFilterBackend from common.utils import get_logger from orgs.utils import current_org, tmp_to_root_org from rbac.models import Role, RoleBinding @@ -30,7 +30,7 @@ __all__ = [ class UserViewSet(CommonApiMixin, UserQuerysetMixin, SuggestionMixin, BulkModelViewSet): filterset_class = UserFilter - extra_filter_backends = [AttrRulesFilter] + extra_filter_backends = [AttrRulesFilterBackend] search_fields = ('username', 'email', 'name') serializer_classes = { 'default': UserSerializer,