perf: 优化 labels 在 json field 中的筛选 (#12577)

* perf: 优化 labels 在 json field 中的筛选

* perf: 修改 labels 搜索

---------

Co-authored-by: ibuler <ibuler@qq.com>
pull/12583/head
fit2bot 2024-01-22 11:36:18 +08:00 committed by GitHub
parent 3853d0bcc6
commit 0c74e92bfb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 69 additions and 33 deletions

View File

@ -362,7 +362,11 @@ class RelatedManager:
if name is None or val is None:
continue
if custom_attr_filter:
custom_filter_q = None
spec_attr_filter = getattr(to_model, "get_{}_filter_attr_q".format(name), None)
if spec_attr_filter:
custom_filter_q = spec_attr_filter(val, match)
elif custom_attr_filter:
custom_filter_q = custom_attr_filter(name, val, match)
if custom_filter_q:
filters.append(custom_filter_q)
@ -464,10 +468,14 @@ class JSONManyToManyDescriptor:
rule_value = rule.get('value', '')
rule_match = rule.get('match', 'exact')
if custom_attr_filter:
q = custom_attr_filter(rule['name'], rule_value, rule_match)
if q:
custom_q &= q
custom_filter_q = None
spec_attr_filter = getattr(to_model, "get_filter_{}_attr_q".format(rule['name']), None)
if spec_attr_filter:
custom_filter_q = spec_attr_filter(rule_value, rule_match)
elif custom_attr_filter:
custom_filter_q = custom_attr_filter(rule['name'], rule_value, rule_match)
if custom_filter_q:
custom_q &= custom_filter_q
continue
if rule_match == 'in':
@ -517,7 +525,6 @@ class JSONManyToManyDescriptor:
res &= rule_value.issubset(value)
else:
res &= bool(value & rule_value)
else:
logging.error("unknown match: {}".format(rule['match']))
res &= False

View File

@ -6,7 +6,7 @@ import logging
from django.core.cache import cache
from django.core.exceptions import ImproperlyConfigured
from django.db.models import Q, Count
from django.db.models import Q
from django_filters import rest_framework as drf_filters
from rest_framework import filters
from rest_framework.compat import coreapi, coreschema
@ -180,36 +180,30 @@ class LabelFilterBackend(filters.BaseFilterBackend):
]
@staticmethod
def filter_resources(resources, labels_id):
def parse_label_ids(labels_id):
from labels.models import Label
label_ids = [i.strip() for i in labels_id.split(',')]
cleaned = []
args = []
for label_id in label_ids:
kwargs = {}
if ':' in label_id:
k, v = label_id.split(':', 1)
kwargs['label__name'] = k.strip()
kwargs['name'] = k.strip()
if v != '*':
kwargs['label__value'] = v.strip()
else:
kwargs['label_id'] = label_id
kwargs['value'] = v.strip()
args.append(kwargs)
else:
cleaned.append(label_id)
if len(args) == 1:
resources = resources.filter(**args[0])
return resources
if len(args) != 0:
q = Q()
for kwarg in args:
q |= Q(**kwarg)
resources = resources.filter(q) \
.values('res_id') \
.order_by('res_id') \
.annotate(count=Count('res_id', distinct=True)) \
.values('res_id', 'count') \
.filter(count=len(args))
return resources
ids = Label.objects.filter(q).values_list('id', flat=True)
cleaned.extend(list(ids))
return cleaned
def filter_queryset(self, request, queryset, view):
labels_id = request.query_params.get('labels')
@ -230,7 +224,8 @@ class LabelFilterBackend(filters.BaseFilterBackend):
resources = labeled_resource_cls.objects.filter(
res_type__app_label=app_label, res_type__model=model_name,
)
resources = self.filter_resources(resources, labels_id)
label_ids = self.parse_label_ids(labels_id)
resources = model.filter_resources_by_labels(resources, label_ids)
res_ids = resources.values_list('res_id', flat=True)
queryset = queryset.filter(id__in=set(res_ids))
return queryset

View File

@ -1,6 +1,6 @@
from django.contrib.contenttypes.fields import GenericRelation
from django.db import models
from django.db.models import OneToOneField
from django.db.models import OneToOneField, Count
from common.utils import lazyproperty
from .models import LabeledResource
@ -36,3 +36,37 @@ class LabeledMixin(models.Model):
@res_labels.setter
def res_labels(self, value):
self.real.labels.set(value, bulk=False)
@classmethod
def filter_resources_by_labels(cls, resources, label_ids):
return cls._get_filter_res_by_labels_m2m_all(resources, label_ids)
@classmethod
def _get_filter_res_by_labels_m2m_in(cls, resources, label_ids):
return resources.filter(label_id__in=label_ids)
@classmethod
def _get_filter_res_by_labels_m2m_all(cls, resources, label_ids):
if len(label_ids) == 1:
return cls._get_filter_res_by_labels_m2m_in(resources, label_ids)
resources = resources.filter(label_id__in=label_ids) \
.values('res_id') \
.order_by('res_id') \
.annotate(count=Count('res_id', distinct=True)) \
.values('res_id', 'count') \
.filter(count=len(label_ids))
return resources
@classmethod
def get_labels_filter_attr_q(cls, value, match):
resources = LabeledResource.objects.all()
if not value:
return None
if match != 'm2m_all':
resources = cls._get_filter_res_by_labels_m2m_in(resources, value)
else:
resources = cls._get_filter_res_by_labels_m2m_all(resources, value)
res_ids = set(resources.values_list('res_id', flat=True))
return models.Q(id__in=res_ids)