feat: LazyLoadFilter懒加载过滤器扩展实现

pull/102/head
Angelo 2023-07-07 19:04:04 +08:00
parent d308d0ea7b
commit 509d14e723
3 changed files with 89 additions and 37 deletions

View File

@ -13,6 +13,7 @@ from dvadmin.utils.json_response import DetailResponse, SuccessResponse
from dvadmin.utils.permission import AnonymousUserPermission from dvadmin.utils.permission import AnonymousUserPermission
from dvadmin.utils.serializers import CustomModelSerializer from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet from dvadmin.utils.viewset import CustomModelViewSet
from dvadmin.utils.filters import LazyLoadFilter
class DeptSerializer(CustomModelSerializer): class DeptSerializer(CustomModelSerializer):
@ -120,6 +121,12 @@ class DeptCreateUpdateSerializer(CustomModelSerializer):
fields = '__all__' fields = '__all__'
class DeptLazyFilter(LazyLoadFilter):
class Meta:
model = Dept
fields = ['name', 'parent', 'status']
class DeptViewSet(CustomModelViewSet): class DeptViewSet(CustomModelViewSet):
""" """
部门管理接口 部门管理接口
@ -129,11 +136,13 @@ class DeptViewSet(CustomModelViewSet):
retrieve:单例 retrieve:单例
destroy:删除 destroy:删除
""" """
queryset = Dept.objects.all() queryset = Dept.objects.all()
serializer_class = DeptSerializer serializer_class = DeptSerializer
create_serializer_class = DeptCreateUpdateSerializer create_serializer_class = DeptCreateUpdateSerializer
update_serializer_class = DeptCreateUpdateSerializer update_serializer_class = DeptCreateUpdateSerializer
filter_fields = ['name', 'id', 'parent'] # filter_fields = ["name", "id", "parent"]
filter_class = DeptLazyFilter
search_fields = [] search_fields = []
# extra_filter_backends = [] # extra_filter_backends = []
import_serializer_class = DeptImportSerializer import_serializer_class = DeptImportSerializer

View File

@ -12,6 +12,7 @@ from collections import OrderedDict
from functools import reduce from functools import reduce
import six import six
from django import forms
from django.db import models from django.db import models
from django.db.models import Q, F from django.db.models import Q, F
from django.db.models.constants import LOOKUP_SEP from django.db.models.constants import LOOKUP_SEP
@ -19,6 +20,7 @@ from django_filters import utils
from django_filters.conf import settings from django_filters.conf import settings
from django_filters.constants import ALL_FIELDS from django_filters.constants import ALL_FIELDS
from django_filters.filters import CharFilter from django_filters.filters import CharFilter
from django_filters.filterset import FilterSet, FilterSetMetaclass
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from django_filters.utils import get_model_field from django_filters.utils import get_model_field
from rest_framework.filters import BaseFilterBackend from rest_framework.filters import BaseFilterBackend
@ -71,9 +73,7 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
permission__api=F("url"), permission__method=F("method") permission__api=F("url"), permission__method=F("method")
) )
api_white_list = [ api_white_list = [
str(item.get("permission__api").replace("{id}", ".*?")) str(item.get("permission__api").replace("{id}", ".*?")) + ":" + str(item.get("permission__method"))
+ ":"
+ str(item.get("permission__method"))
for item in api_white_list for item in api_white_list
if item.get("permission__api") if item.get("permission__api")
] ]
@ -119,19 +119,13 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
# 4. 只为仅本人数据权限时只返回过滤本人数据,并且部门为自己本部门(考虑到用户会变部门,只能看当前用户所在的部门数据) # 4. 只为仅本人数据权限时只返回过滤本人数据,并且部门为自己本部门(考虑到用户会变部门,只能看当前用户所在的部门数据)
if 0 in dataScope_list: if 0 in dataScope_list:
return queryset.filter( return queryset.filter(creator=request.user, dept_belong_id=user_dept_id)
creator=request.user, dept_belong_id=user_dept_id
)
# 5. 自定数据权限 获取部门,根据部门过滤 # 5. 自定数据权限 获取部门,根据部门过滤
dept_list = [] dept_list = []
for ele in dataScope_list: for ele in dataScope_list:
if ele == 4: if ele == 4:
dept_list.extend( dept_list.extend(request.user.role.filter(status=1).values_list("dept__id", flat=True))
request.user.role.filter(status=1).values_list(
"dept__id", flat=True
)
)
elif ele == 2: elif ele == 2:
dept_list.append(user_dept_id) dept_list.append(user_dept_id)
elif ele == 1: elif ele == 1:
@ -141,7 +135,7 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
user_dept_id, user_dept_id,
) )
) )
if queryset.model._meta.model_name == 'dept': if queryset.model._meta.model_name == "dept":
return queryset.filter(id__in=list(set(dept_list))) return queryset.filter(id__in=list(set(dept_list)))
return queryset.filter(dept_belong_id__in=list(set(dept_list))) return queryset.filter(dept_belong_id__in=list(set(dept_list)))
else: else:
@ -186,16 +180,14 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
# TODO: remove assertion in 2.1 # TODO: remove assertion in 2.1
if filterset_class is None and hasattr(view, "filter_class"): if filterset_class is None and hasattr(view, "filter_class"):
utils.deprecate( utils.deprecate(
"`%s.filter_class` attribute should be renamed `filterset_class`." "`%s.filter_class` attribute should be renamed `filterset_class`." % view.__class__.__name__
% view.__class__.__name__
) )
filterset_class = getattr(view, "filter_class", None) filterset_class = getattr(view, "filter_class", None)
# TODO: remove assertion in 2.1 # TODO: remove assertion in 2.1
if filterset_fields is None and hasattr(view, "filter_fields"): if filterset_fields is None and hasattr(view, "filter_fields"):
utils.deprecate( utils.deprecate(
"`%s.filter_fields` attribute should be renamed `filterset_fields`." "`%s.filter_fields` attribute should be renamed `filterset_fields`." % view.__class__.__name__
% view.__class__.__name__
) )
filterset_fields = getattr(view, "filter_fields", None) filterset_fields = getattr(view, "filter_fields", None)
@ -224,8 +216,9 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
return [ return [
f.name f.name
for f in sorted(opts.fields + opts.many_to_many) for f in sorted(opts.fields + opts.many_to_many)
if (f.name == 'id') or not isinstance(f, models.AutoField) if (f.name == "id")
and not (getattr(f.remote_field, "parent_link", False)) or not isinstance(f, models.AutoField)
and not (getattr(f.remote_field, "parent_link", False))
] ]
@classmethod @classmethod
@ -239,9 +232,9 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
exclude = cls._meta.exclude exclude = cls._meta.exclude
assert not (fields is None and exclude is None), ( assert not (fields is None and exclude is None), (
"Setting 'Meta.model' without either 'Meta.fields' or 'Meta.exclude' " "Setting 'Meta.model' without either 'Meta.fields' or 'Meta.exclude' "
"has been deprecated since 0.15.0 and is now disallowed. Add an explicit " "has been deprecated since 0.15.0 and is now disallowed. Add an explicit "
"'Meta.fields' or 'Meta.exclude' to the %s class." % cls.__name__ "'Meta.fields' or 'Meta.exclude' to the %s class." % cls.__name__
) )
# Setting exclude with no fields implies all other fields. # Setting exclude with no fields implies all other fields.
@ -255,9 +248,7 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
# Remove excluded fields # Remove excluded fields
exclude = exclude or [] exclude = exclude or []
if not isinstance(fields, dict): if not isinstance(fields, dict):
fields = [ fields = [(f, [settings.DEFAULT_LOOKUP_EXPR]) for f in fields if f not in exclude]
(f, [settings.DEFAULT_LOOKUP_EXPR]) for f in fields if f not in exclude
]
else: else:
fields = [(f, lookups) for f, lookups in fields.items() if f not in exclude] fields = [(f, lookups) for f, lookups in fields.items() if f not in exclude]
@ -291,9 +282,12 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
if field is None: if field is None:
undefined.append(field_name) undefined.append(field_name)
# 更新默认字符串搜索为模糊搜索 # 更新默认字符串搜索为模糊搜索
if isinstance(field, (models.CharField)) and filterset_fields == '__all__' and lookups == [ if (
'exact']: isinstance(field, (models.CharField))
lookups = ['icontains'] and filterset_fields == "__all__"
and lookups == ["exact"]
):
lookups = ["icontains"]
for lookup_expr in lookups: for lookup_expr in lookups:
filter_name = cls.get_filter_name(field_name, lookup_expr) filter_name = cls.get_filter_name(field_name, lookup_expr)
@ -303,20 +297,15 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
continue continue
if field is not None: if field is not None:
filters[filter_name] = cls.filter_for_field( filters[filter_name] = cls.filter_for_field(field, field_name, lookup_expr)
field, field_name, lookup_expr
)
# Allow Meta.fields to contain declared filters *only* when a list/tuple # Allow Meta.fields to contain declared filters *only* when a list/tuple
if isinstance(cls._meta.fields, (list, tuple)): if isinstance(cls._meta.fields, (list, tuple)):
undefined = [ undefined = [f for f in undefined if f not in cls.declared_filters]
f for f in undefined if f not in cls.declared_filters
]
if undefined: if undefined:
raise TypeError( raise TypeError(
"'Meta.fields' must not contain non-model field names: %s" "'Meta.fields' must not contain non-model field names: %s" % ", ".join(undefined)
% ", ".join(undefined)
) )
# Add in declared filters. This is necessary since we don't enforce adding # Add in declared filters. This is necessary since we don't enforce adding
@ -364,3 +353,51 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
if not filterset.is_valid() and self.raise_exception: if not filterset.is_valid() and self.raise_exception:
raise utils.translate_validation(filterset.errors) raise utils.translate_validation(filterset.errors)
return filterset.qs return filterset.qs
# ####################### 懒加载FilterSet ####################### #
class FilterSetOptions:
def __init__(self, options=None):
self.model = getattr(options, "model", None)
self.fields = getattr(options, "fields", None)
self.exclude = getattr(options, "exclude", None)
# CharField默认模糊查询
self.filter_overrides = getattr(
options,
"filter_overrides",
{
models.CharField: {
"filter_class": CharFilter,
"extra": lambda f: {
"lookup_expr": "icontains",
},
}
},
)
self.form = getattr(options, "form", forms.Form)
class LazyLoadFilterSetMetaclass(FilterSetMetaclass):
def __new__(cls, name, bases, attrs):
attrs["declared_filters"] = cls.get_declared_filters(bases, attrs)
new_class = super().__new__(cls, name, bases, attrs)
new_class._meta = FilterSetOptions(getattr(new_class, "Meta", None))
new_class.base_filters = new_class.get_filters()
return new_class
class LazyLoadFilter(FilterSet, metaclass=LazyLoadFilterSetMetaclass):
@property
def qs(self):
is_node = self.queryset.filter(parent__isnull=False).exists()
if not is_node:
self.queryset = self.queryset.model.objects.filter(parent__in=self.queryset)
parent_ids = set(super().qs.values_list("parent_id", flat=True))
return self.queryset.model.objects.filter(id__in=parent_ids)
return super().qs

View File

@ -20,7 +20,13 @@ export const crudOptions = (vm) => {
hasChild: 'hasChild', hasChild: 'hasChild',
lazy: true, lazy: true,
loadMethod: ({ row }) => { loadMethod: ({ row }) => {
return api.GetList({ parent: row.id }).then(ret => { let query = JSON.parse(JSON.stringify(vm.getSearch().getForm()))
query = Object.fromEntries(
Object.entries(query).filter(([_, value]) => ![undefined, null, [], '[]', ''].includes(value))
)
query.parent = row.id
// console.log(query)
return api.GetList({ ...query }).then(ret => {
return ret.data.data return ret.data.data
}) })
}, },