Merge remote-tracking branch 'origin/dev' into dev
commit
1180ab205e
|
@ -131,6 +131,7 @@ class Dept(CoreModel):
|
|||
null=True,
|
||||
blank=True,
|
||||
help_text="上级部门",
|
||||
db_index=True
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -13,6 +13,7 @@ from dvadmin.utils.json_response import DetailResponse, SuccessResponse
|
|||
from dvadmin.utils.permission import AnonymousUserPermission
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
from dvadmin.utils.filters import LazyLoadFilter
|
||||
|
||||
|
||||
class DeptSerializer(CustomModelSerializer):
|
||||
|
@ -120,6 +121,12 @@ class DeptCreateUpdateSerializer(CustomModelSerializer):
|
|||
fields = '__all__'
|
||||
|
||||
|
||||
class DeptLazyFilter(LazyLoadFilter):
|
||||
class Meta:
|
||||
model = Dept
|
||||
fields = ['name', 'parent', 'status']
|
||||
|
||||
|
||||
class DeptViewSet(CustomModelViewSet):
|
||||
"""
|
||||
部门管理接口
|
||||
|
@ -129,11 +136,13 @@ class DeptViewSet(CustomModelViewSet):
|
|||
retrieve:单例
|
||||
destroy:删除
|
||||
"""
|
||||
|
||||
queryset = Dept.objects.all()
|
||||
serializer_class = DeptSerializer
|
||||
create_serializer_class = DeptCreateUpdateSerializer
|
||||
update_serializer_class = DeptCreateUpdateSerializer
|
||||
filter_fields = ['name', 'id', 'parent']
|
||||
# filter_fields = ["name", "id", "parent"]
|
||||
filter_class = DeptLazyFilter
|
||||
search_fields = []
|
||||
# extra_filter_backends = []
|
||||
import_serializer_class = DeptImportSerializer
|
||||
|
|
|
@ -23,7 +23,7 @@ class MenuButtonSerializer(CustomModelSerializer):
|
|||
|
||||
class Meta:
|
||||
model = MenuButton
|
||||
fields = ['id', 'name', 'value', 'api', 'method', 'menu']
|
||||
fields = ["id", "name", "value", "api", "method", "menu"]
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
|
@ -34,7 +34,7 @@ class MenuButtonInitSerializer(CustomModelSerializer):
|
|||
|
||||
class Meta:
|
||||
model = MenuButton
|
||||
fields = ['id', 'name', 'value', 'api', 'method', 'menu']
|
||||
fields = ["id", "name", "value", "api", "method", "menu"]
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
|
@ -58,21 +58,26 @@ class MenuButtonViewSet(CustomModelViewSet):
|
|||
retrieve:单例
|
||||
destroy:删除
|
||||
"""
|
||||
|
||||
queryset = MenuButton.objects.all()
|
||||
serializer_class = MenuButtonSerializer
|
||||
create_serializer_class = MenuButtonCreateUpdateSerializer
|
||||
update_serializer_class = MenuButtonCreateUpdateSerializer
|
||||
extra_filter_backends = []
|
||||
|
||||
@action(methods=['get'], detail=False)
|
||||
def get_btn_permission(self,request):
|
||||
@action(methods=["GET"], detail=False, permission_classes=[])
|
||||
def get_btn_permission(self, request):
|
||||
"""
|
||||
获取当前用户的按钮权限
|
||||
"""
|
||||
user = request.user
|
||||
if not user.is_superuser:
|
||||
menuIds = user.role.values_list('menu__id', flat=True)
|
||||
menuIds = user.role.values_list("menu__id", flat=True)
|
||||
else:
|
||||
menuIds = Menu.objects.filter(status=1)
|
||||
queryset = MenuButton.objects.filter(menu__in=menuIds).annotate(permission=Concat('menu__web_path',Value(':'),'value',output_field=CharField())).values_list('permission',flat=True)
|
||||
queryset = (
|
||||
MenuButton.objects.filter(menu__in=menuIds)
|
||||
.annotate(permission=Concat("menu__web_path", Value(":"), "value", output_field=CharField()))
|
||||
.values_list("permission", flat=True)
|
||||
)
|
||||
return DetailResponse(data=queryset)
|
||||
|
|
|
@ -11,7 +11,7 @@ import traceback
|
|||
|
||||
from django.db.models import ProtectedError, RestrictedError
|
||||
from django.http import Http404
|
||||
from rest_framework.exceptions import APIException as DRFAPIException, AuthenticationFailed
|
||||
from rest_framework.exceptions import APIException as DRFAPIException, AuthenticationFailed, PermissionDenied
|
||||
from rest_framework.status import HTTP_407_PROXY_AUTHENTICATION_REQUIRED, HTTP_401_UNAUTHORIZED
|
||||
from rest_framework.views import set_rollback, exception_handler
|
||||
|
||||
|
@ -50,6 +50,8 @@ def CustomExceptionHandler(ex, context):
|
|||
elif isinstance(ex, DRFAPIException):
|
||||
set_rollback()
|
||||
msg = ex.detail
|
||||
if isinstance(ex, PermissionDenied):
|
||||
msg = f'{msg} ({context["request"].method}: {context["request"].path})'
|
||||
if isinstance(msg, dict):
|
||||
for k, v in msg.items():
|
||||
for i in v:
|
||||
|
|
|
@ -12,6 +12,7 @@ from collections import OrderedDict
|
|||
from functools import reduce
|
||||
|
||||
import six
|
||||
from django import forms
|
||||
from django.db import models
|
||||
from django.db.models import Q, F
|
||||
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.constants import ALL_FIELDS
|
||||
from django_filters.filters import CharFilter
|
||||
from django_filters.filterset import FilterSet, FilterSetMetaclass
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from django_filters.utils import get_model_field
|
||||
from rest_framework.filters import BaseFilterBackend
|
||||
|
@ -71,9 +73,7 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
|
|||
permission__api=F("url"), permission__method=F("method")
|
||||
)
|
||||
api_white_list = [
|
||||
str(item.get("permission__api").replace("{id}", ".*?"))
|
||||
+ ":"
|
||||
+ str(item.get("permission__method"))
|
||||
str(item.get("permission__api").replace("{id}", ".*?")) + ":" + str(item.get("permission__method"))
|
||||
for item in api_white_list
|
||||
if item.get("permission__api")
|
||||
]
|
||||
|
@ -119,19 +119,13 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
|
|||
|
||||
# 4. 只为仅本人数据权限时只返回过滤本人数据,并且部门为自己本部门(考虑到用户会变部门,只能看当前用户所在的部门数据)
|
||||
if 0 in dataScope_list:
|
||||
return queryset.filter(
|
||||
creator=request.user, dept_belong_id=user_dept_id
|
||||
)
|
||||
return queryset.filter(creator=request.user, dept_belong_id=user_dept_id)
|
||||
|
||||
# 5. 自定数据权限 获取部门,根据部门过滤
|
||||
dept_list = []
|
||||
for ele in dataScope_list:
|
||||
if ele == 4:
|
||||
dept_list.extend(
|
||||
request.user.role.filter(status=1).values_list(
|
||||
"dept__id", flat=True
|
||||
)
|
||||
)
|
||||
dept_list.extend(request.user.role.filter(status=1).values_list("dept__id", flat=True))
|
||||
elif ele == 2:
|
||||
dept_list.append(user_dept_id)
|
||||
elif ele == 1:
|
||||
|
@ -141,7 +135,7 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
|
|||
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(dept_belong_id__in=list(set(dept_list)))
|
||||
else:
|
||||
|
@ -186,16 +180,14 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
|
|||
# TODO: remove assertion in 2.1
|
||||
if filterset_class is None and hasattr(view, "filter_class"):
|
||||
utils.deprecate(
|
||||
"`%s.filter_class` attribute should be renamed `filterset_class`."
|
||||
% view.__class__.__name__
|
||||
"`%s.filter_class` attribute should be renamed `filterset_class`." % view.__class__.__name__
|
||||
)
|
||||
filterset_class = getattr(view, "filter_class", None)
|
||||
|
||||
# TODO: remove assertion in 2.1
|
||||
if filterset_fields is None and hasattr(view, "filter_fields"):
|
||||
utils.deprecate(
|
||||
"`%s.filter_fields` attribute should be renamed `filterset_fields`."
|
||||
% view.__class__.__name__
|
||||
"`%s.filter_fields` attribute should be renamed `filterset_fields`." % view.__class__.__name__
|
||||
)
|
||||
filterset_fields = getattr(view, "filter_fields", None)
|
||||
|
||||
|
@ -224,8 +216,9 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
|
|||
return [
|
||||
f.name
|
||||
for f in sorted(opts.fields + opts.many_to_many)
|
||||
if (f.name == 'id') or not isinstance(f, models.AutoField)
|
||||
and not (getattr(f.remote_field, "parent_link", False))
|
||||
if (f.name == "id")
|
||||
or not isinstance(f, models.AutoField)
|
||||
and not (getattr(f.remote_field, "parent_link", False))
|
||||
]
|
||||
|
||||
@classmethod
|
||||
|
@ -239,9 +232,9 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
|
|||
exclude = cls._meta.exclude
|
||||
|
||||
assert not (fields is None and exclude is None), (
|
||||
"Setting 'Meta.model' without either 'Meta.fields' or 'Meta.exclude' "
|
||||
"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__
|
||||
"Setting 'Meta.model' without either 'Meta.fields' or 'Meta.exclude' "
|
||||
"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__
|
||||
)
|
||||
|
||||
# Setting exclude with no fields implies all other fields.
|
||||
|
@ -255,9 +248,7 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
|
|||
# Remove excluded fields
|
||||
exclude = exclude or []
|
||||
if not isinstance(fields, dict):
|
||||
fields = [
|
||||
(f, [settings.DEFAULT_LOOKUP_EXPR]) for f in fields if f not in exclude
|
||||
]
|
||||
fields = [(f, [settings.DEFAULT_LOOKUP_EXPR]) for f in fields if f not in exclude]
|
||||
else:
|
||||
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:
|
||||
undefined.append(field_name)
|
||||
# 更新默认字符串搜索为模糊搜索
|
||||
if isinstance(field, (models.CharField)) and filterset_fields == '__all__' and lookups == [
|
||||
'exact']:
|
||||
lookups = ['icontains']
|
||||
if (
|
||||
isinstance(field, (models.CharField))
|
||||
and filterset_fields == "__all__"
|
||||
and lookups == ["exact"]
|
||||
):
|
||||
lookups = ["icontains"]
|
||||
for lookup_expr in lookups:
|
||||
filter_name = cls.get_filter_name(field_name, lookup_expr)
|
||||
|
||||
|
@ -303,20 +297,15 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
|
|||
continue
|
||||
|
||||
if field is not None:
|
||||
filters[filter_name] = cls.filter_for_field(
|
||||
field, field_name, lookup_expr
|
||||
)
|
||||
filters[filter_name] = cls.filter_for_field(field, field_name, lookup_expr)
|
||||
|
||||
# Allow Meta.fields to contain declared filters *only* when a list/tuple
|
||||
if isinstance(cls._meta.fields, (list, tuple)):
|
||||
undefined = [
|
||||
f for f in undefined if f not in cls.declared_filters
|
||||
]
|
||||
undefined = [f for f in undefined if f not in cls.declared_filters]
|
||||
|
||||
if undefined:
|
||||
raise TypeError(
|
||||
"'Meta.fields' must not contain non-model field names: %s"
|
||||
% ", ".join(undefined)
|
||||
"'Meta.fields' must not contain non-model field names: %s" % ", ".join(undefined)
|
||||
)
|
||||
|
||||
# Add in declared filters. This is necessary since we don't enforce adding
|
||||
|
@ -364,3 +353,113 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
|
|||
if not filterset.is_valid() and self.raise_exception:
|
||||
raise utils.translate_validation(filterset.errors)
|
||||
return filterset.qs
|
||||
|
||||
|
||||
# ####################### 懒加载FilterSet ####################### #
|
||||
|
||||
import time
|
||||
|
||||
|
||||
def calculate_execution_time(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
start_time = time.time()
|
||||
result = func(*args, **kwargs)
|
||||
end_time = time.time()
|
||||
execution_time = end_time - start_time
|
||||
print(f"Function {func.__name__} took {execution_time:.6f} seconds to execute.", flush=True)
|
||||
return result
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
# def get_children(model: models, obj_id: int, all_qs=None, rec_list=None):
|
||||
# if not all_qs:
|
||||
# all_qs = model.objects.all().values("id", "parent")
|
||||
# if rec_list is None:
|
||||
# rec_list = [obj_id]
|
||||
# for ele in all_qs:
|
||||
# if ele.get("parent") == obj_id:
|
||||
# rec_list.append(ele.get("id"))
|
||||
# get_dept(ele.get("id"), all_qs, rec_list)
|
||||
# return list(set(rec_list))
|
||||
|
||||
|
||||
# @calculate_execution_time
|
||||
# def get_qs_children(model, qs):
|
||||
# dept_ids = []
|
||||
# for d in qs:
|
||||
# r = get_children(model, d.id)
|
||||
# dept_ids.extend(r)
|
||||
# return list(set(dept_ids))
|
||||
|
||||
|
||||
def next_layer_data(qs_filter, qs_node):
|
||||
parent_nodes = set(qs_node.values_list("id", flat=True))
|
||||
# print(f"过滤查询集 ==> {qs_filter}", flush=True)
|
||||
# print(f"待渲染节点的id ==> {parent_nodes=}", flush=True)
|
||||
if set(qs_filter) == set(qs_node):
|
||||
return parent_nodes
|
||||
# qs_filter内所有父级id 去重
|
||||
parent_ids = set()
|
||||
for node in qs_filter:
|
||||
while node.parent:
|
||||
if node.id in parent_nodes:
|
||||
parent_ids.add(node.id)
|
||||
break
|
||||
if node.parent.id in parent_nodes:
|
||||
parent_ids.add(node.parent.id)
|
||||
break
|
||||
node = node.parent
|
||||
# print(f"过滤查询集的父节点id ==> {parent_ids=}", flush=True)
|
||||
return parent_ids
|
||||
|
||||
|
||||
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):
|
||||
# @calculate_execution_time
|
||||
@property
|
||||
def qs(self):
|
||||
queryset = self.queryset
|
||||
filter_params = [k for k, v in self.form.cleaned_data.items() if not v]
|
||||
for field in filter_params:
|
||||
self.form.cleaned_data.pop(field)
|
||||
self.form.cleaned_data.pop("parent", None)
|
||||
# print(queryset, flush=True)
|
||||
if self.form.cleaned_data:
|
||||
self.queryset = queryset.model.objects.all()
|
||||
node_ids = next_layer_data(super().qs, queryset)
|
||||
return queryset.model.objects.filter(id__in=node_ids)
|
||||
return super().qs
|
||||
|
|
|
@ -13,6 +13,7 @@ export const crudOptions = (vm) => {
|
|||
height: '100%', // 表格高度100%, 使用toolbar必须设置
|
||||
highlightCurrentRow: false,
|
||||
defaultExpandAll: true,
|
||||
resizable: true,
|
||||
treeConfig: {
|
||||
transform: true,
|
||||
rowField: 'id',
|
||||
|
@ -20,7 +21,13 @@ export const crudOptions = (vm) => {
|
|||
hasChild: 'hasChild',
|
||||
lazy: true,
|
||||
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
|
||||
})
|
||||
},
|
||||
|
@ -28,6 +35,7 @@ export const crudOptions = (vm) => {
|
|||
}
|
||||
},
|
||||
rowHandle: {
|
||||
fixed: 'right',
|
||||
width: 140,
|
||||
view: {
|
||||
thin: true,
|
||||
|
@ -55,7 +63,7 @@ export const crudOptions = (vm) => {
|
|||
// 或者直接传true,不显示title,不居中
|
||||
title: '序号',
|
||||
align: 'center',
|
||||
width: 100
|
||||
width: 70
|
||||
},
|
||||
|
||||
viewOptions: {
|
||||
|
@ -71,7 +79,7 @@ export const crudOptions = (vm) => {
|
|||
show: false,
|
||||
disabled: true,
|
||||
search: {
|
||||
disabled: false
|
||||
disabled: true
|
||||
},
|
||||
form: {
|
||||
disabled: true,
|
||||
|
@ -136,8 +144,8 @@ export const crudOptions = (vm) => {
|
|||
}
|
||||
}
|
||||
},
|
||||
width: 180,
|
||||
type: 'input',
|
||||
showOverflow: 'tooltip',
|
||||
form: {
|
||||
rules: [
|
||||
// 表单校验规则
|
||||
|
|
|
@ -275,7 +275,7 @@ export const crudOptions = (vm) => {
|
|||
return request({
|
||||
url: url
|
||||
}).then(ret => {
|
||||
return ret.data.data
|
||||
return ret.data
|
||||
})
|
||||
}
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue