feat: LazyLoadFilter懒加载过滤器扩展实现
parent
d308d0ea7b
commit
509d14e723
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue