feat: 新增列权限配置功能

新增列权限配置,优化代码结构,更新初始化文件
This commit is contained in:
ahhui
2023-08-04 18:10:51 +08:00
parent 6597826fdd
commit 97c76b6afe
10 changed files with 254 additions and 71 deletions

View File

@@ -188,7 +188,7 @@ class RoleMenuButtonInitSerializer(CustomModelSerializer):
class Meta:
model = RoleMenuButtonPermission
fields = ['role_key', 'menu_button_value','data_range','dept','creator', 'dept_belong_id']
fields = ['role_key', 'menu_button_value', 'data_range', 'dept', 'creator', 'dept_belong_id']
read_only_fields = ["id"]
extra_kwargs = {
'role': {'required': False},

View File

@@ -263,7 +263,50 @@
"cache": false,
"visible": true,
"children": [],
"menu_button": []
"menu_button": [
{
"name": "查询",
"value": "column:Search",
"api": "/api/system/column/",
"method": 0
},
{
"name": "详情",
"value": "column:Retrieve",
"api": "/api/system/column/{id}/",
"method": 0
},
{
"name": "新增",
"value": "column:Create",
"api": "/api/system/column/",
"method": 1
},
{
"name": "编辑",
"value": "column:Update",
"api": "/api/system/column/{id}/",
"method": 2
},
{
"name": "删除",
"value": "column:Delete",
"api": "/api/system/column/{id}/",
"method": 3
},
{
"name": "所有模型表",
"value": "column:AllModel",
"api": "/api/system/column/get_models/",
"method": 0
},
{
"name": "自动匹配所有字段",
"value": "column:AutoMatch",
"api": "/api/system/column/auto_match_fields/",
"method": 1
}
]
},
{
"name": "用户管理",

View File

@@ -5,12 +5,7 @@ from django.contrib.auth.models import AbstractUser, UserManager
from django.db import models
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from application import dispatch
from dvadmin.utils.models import CoreModel, table_prefix
STATUS_CHOICES = (
(0, "禁用"),
(1, "启用"),
)
from dvadmin.utils.models import CoreModel, table_prefix, get_custom_app_models
class Role(CoreModel):
@@ -107,8 +102,7 @@ class Post(CoreModel):
class Dept(CoreModel):
name = models.CharField(max_length=64, verbose_name="部门名称", help_text="部门名称")
key = models.CharField(max_length=64, unique=True, null=True, blank=True, verbose_name="关联字符",
help_text="关联字符")
key = models.CharField(max_length=64, unique=True, null=True, blank=True, verbose_name="关联字符", help_text="关联字符")
sort = models.IntegerField(default=1, verbose_name="显示排序", help_text="显示排序")
owner = models.CharField(max_length=32, verbose_name="负责人", null=True, blank=True, help_text="负责人")
phone = models.CharField(max_length=32, verbose_name="联系电话", null=True, blank=True, help_text="联系电话")
@@ -186,6 +180,23 @@ class Menu(CoreModel):
ordering = ("sort",)
class Columns(CoreModel):
role = models.ForeignKey(to='Role', on_delete=models.CASCADE, verbose_name='角色', db_constraint=False)
app = models.CharField(max_length=64, verbose_name='应用名')
model = models.CharField(max_length=64, verbose_name='表名')
field_name = models.CharField(max_length=64, verbose_name='模型表字段名')
title = models.CharField(max_length=64, verbose_name='字段显示名')
is_query = models.BooleanField(default=1, verbose_name='是否可查询')
is_create = models.BooleanField(default=1, verbose_name='是否可创建')
is_update = models.BooleanField(default=1, verbose_name='是否可更新')
class Meta:
db_table = table_prefix + "system_columns"
verbose_name = "列权限表"
verbose_name_plural = verbose_name
ordering = ("id",)
class MenuButton(CoreModel):
menu = models.ForeignKey(
to="Menu",
@@ -289,7 +300,7 @@ class Dictionary(CoreModel):
(7, "images"),
)
label = models.CharField(max_length=100, blank=True, null=True, verbose_name="字典名称", help_text="字典名称")
value = models.CharField(max_length=200, blank=True, null=True, verbose_name="字典编号",help_text="字典编号/实际值")
value = models.CharField(max_length=200, blank=True, null=True, verbose_name="字典编号", help_text="字典编号/实际值")
parent = models.ForeignKey(
to="self",
related_name="sublist",
@@ -374,7 +385,7 @@ class FileList(CoreModel):
if not self.size:
self.size = self.url.size
if not self.file_url:
url = media_file_name(self,self.name)
url = media_file_name(self, self.name)
self.file_url = f'media/{url}'
super(FileList, self).save(*args, **kwargs)

View File

@@ -16,6 +16,7 @@ from dvadmin.system.views.role_menu import RoleMenuPermissionViewSet
from dvadmin.system.views.role_menu_button_permission import RoleMenuButtonPermissionViewSet
from dvadmin.system.views.system_config import SystemConfigViewSet
from dvadmin.system.views.user import UserViewSet
from dvadmin.system.views.column import ColumnViewSet
system_url = routers.SimpleRouter()
system_url.register(r'menu', MenuViewSet)
@@ -32,6 +33,7 @@ system_url.register(r'system_config', SystemConfigViewSet)
system_url.register(r'message_center', MessageCenterViewSet)
system_url.register(r'role_menu_button_permission', RoleMenuButtonPermissionViewSet)
system_url.register(r'role_menu_permission', RoleMenuPermissionViewSet)
system_url.register(r'column', ColumnViewSet)
urlpatterns = [

View File

@@ -0,0 +1,107 @@
# -*- coding: utf-8 -*-
from rest_framework import serializers
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import Columns, Role
from dvadmin.utils.models import get_custom_app_models
from dvadmin.utils.viewset import CustomModelViewSet
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.json_response import DetailResponse, ErrorResponse, SuccessResponse
class ColumnSerializer(CustomModelSerializer):
"""
列权限序列化器
"""
class Meta:
model = Columns
fields = '__all__'
read_only_fields = ['id']
class ColumnViewSet(CustomModelViewSet):
"""
列权限视图集
"""
queryset = Columns.objects.all()
serializer_class = ColumnSerializer
def list(self, request, *args, **kwargs):
role_id = request.query_params.get('role')
app_name = request.query_params.get('app')
model_name = request.query_params.get('model')
if not role_id or not model_name or not app_name:
return SuccessResponse([])
queryset = self.filter_queryset(self.get_queryset().filter(role_id=role_id, model=model_name, app=app_name))
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True, request=request)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True, request=request)
return SuccessResponse(data=serializer.data, msg="获取成功")
def create(self, request, *args, **kwargs):
payload = request.data
if not Role.objects.filter(pk=payload.get('role')).exists():
return ErrorResponse(msg='角色不存在')
model = None
for app in get_custom_app_models():
equal = False
for model in app:
if payload.get('model') == model['model']:
equal = True
model = model
break
if equal:
break
else:
return ErrorResponse(msg='模型表不存在')
if Columns.objects.filter(app=model['app'], model=model['model'], field_name=payload.get('field_name')).exists():
return ErrorResponse(msg='%s 字段权限已有,不可重复创建' % payload.get('title'))
serializer = self.get_serializer(data=request.data, request=request)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
return DetailResponse(data=serializer.data, msg="新增成功")
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
def get_models(self, request):
"""获取所有项目app下的model"""
res = []
for app in get_custom_app_models():
for model in app:
res.append({
'app': model['app'],
'title': model['verbose'],
'key': model['model']
})
return DetailResponse(res)
@action(methods=['POST'], detail=False, permission_classes=[IsAuthenticated])
def auto_match_fields(self, request):
"""自动匹配已有的字段"""
role_id = request.data.get('role')
app_name = request.data.get('app')
model_name = request.data.get('model')
if not role_id or not model_name or not app_name:
return DetailResponse([], msg='无操作')
for model in get_custom_app_models(app_name):
if model['model'] != model_name:
continue
for field in model['fields']:
if Columns.objects.filter(
role_id=role_id, app=app_name, model=model_name, field_name=field['name']
).exists():
continue
data = {
'role': role_id,
'app': app_name,
'model': model_name,
'field_name': field['name'],
'title': str(field['title']),
}
serializer = self.get_serializer(data=data, request=request)
serializer.is_valid(raise_exception=True)
serializer.save()
return SuccessResponse(msg='匹配成功')

View File

@@ -171,7 +171,6 @@ class DeptViewSet(CustomModelViewSet):
previous_menu.sort, dept.sort = dept.sort, previous_menu.sort
previous_menu.save()
dept.save()
return SuccessResponse(data=[], msg="上移成功")
@action(methods=['POST'], detail=False, permission_classes=[IsAuthenticated])
@@ -187,7 +186,6 @@ class DeptViewSet(CustomModelViewSet):
next_menu.sort, dept.sort = dept.sort, next_menu.sort
next_menu.save()
dept.save()
return SuccessResponse(data=[], msg="下移成功")
@action(methods=['GET'], detail=False, permission_classes=[])

View File

@@ -155,7 +155,6 @@ class MenuViewSet(CustomModelViewSet):
previous_menu.sort, menu.sort = menu.sort, previous_menu.sort
previous_menu.save()
menu.save()
return SuccessResponse(data=[], msg="上移成功")
@action(methods=['POST'], detail=False, permission_classes=[])
@@ -171,5 +170,4 @@ class MenuViewSet(CustomModelViewSet):
next_menu.sort, menu.sort = menu.sort, next_menu.sort
next_menu.save()
menu.save()
return SuccessResponse(data=[], msg="下移成功")

View File

@@ -12,6 +12,7 @@ from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import RoleMenuButtonPermission, Menu, MenuButton, Dept, RoleMenuPermission
from dvadmin.system.views.menu import MenuSerializer
from dvadmin.utils.json_response import DetailResponse, ErrorResponse
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
@@ -37,12 +38,13 @@ class RoleMenuButtonPermissionInitSerializer(CustomModelSerializer):
fields = "__all__"
read_only_fields = ["id"]
class RoleMenuButtonPermissionCreateUpdateSerializer(CustomModelSerializer):
"""
初始化菜单按钮-序列化器
"""
menu_button__name = serializers.CharField(source='menu_button.name', read_only=True)
menu_button__value= serializers.CharField(source='menu_button.value', read_only=True)
menu_button__value = serializers.CharField(source='menu_button.value', read_only=True)
class Meta:
model = RoleMenuButtonPermission
@@ -72,22 +74,36 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
is_superuser = request.user.is_superuser
is_admin = request.user.role.values_list('admin', flat=True)
if is_superuser or True in is_admin:
queryset = Menu.objects.filter(status=1).values('name','parent','is_catalog',menu_id=F('id'))
queryset = Menu.objects.filter(status=1).values('name', 'parent', 'is_catalog', menu_id=F('id'))
for item in queryset:
btn_name = MenuButton.objects.filter(menu=item['menu_id']).values_list(
'name', flat=True)
data.append({'menu_id': item['menu_id'], 'name': item['name'], 'parent': item['parent'],
'permission': ','.join(btn_name), 'is_catalog': item['is_catalog']})
btn_name = MenuButton.objects.filter(menu=item['menu_id']).values_list('name', flat=True)
data.append({
'menu_id': item['menu_id'],
'name': item['name'],
'parent': item['parent'],
'permission': btn_name,
'is_catalog': item['is_catalog']
})
else:
role_id = request.user.role.values_list('id',flat=True)
queryset = RoleMenuPermission.objects.filter(role__in=role_id).values('menu_id',name=F('menu__name'),parent=F('menu__parent'),is_catalog=F('menu__is_catalog')).distinct()
role_id = request.user.role.values_list('id', flat=True)
queryset = RoleMenuPermission.objects.filter(role__in=role_id).values(
'menu_id', name=F('menu__name'), parent=F('menu__parent'), is_catalog=F('menu__is_catalog')
).distinct()
for item in queryset:
btn_name = RoleMenuButtonPermission.objects.filter(menu_button__menu=item['menu_id']).values_list('menu_button__name',flat=True)
data.append({'menu_id':item['menu_id'], 'name':item['name'], 'parent':item['parent'],'permission':','.join(btn_name),'is_catalog':item['is_catalog']})
btn_name = RoleMenuButtonPermission.objects.filter(
menu_button__menu=item['menu_id']
).values_list('menu_button__name', flat=True)
data.append({
'menu_id': item['menu_id'],
'name': item['name'],
'parent': item['parent'],
'permission': btn_name,
'is_catalog': item['is_catalog']
})
return DetailResponse(data=data)
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
def role_menu_get_button(self,request):
def role_menu_get_button(self, request):
"""
当前用户角色和菜单获取可下拉选项的按钮:角色授权页面使用
:param request:
@@ -100,11 +116,10 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
if is_superuser or True in is_admin:
queryset = MenuButton.objects.filter(menu=menu_id).values('id', 'name')
else:
role_list = request.user.role.values_list('id',flat=True)
queryset = RoleMenuButtonPermission.objects.filter(role__in=role_list,menu_button__menu=menu_id).values(
btn_id=F('menu_button__id'),
name=F('menu_button__name')
)
role_list = request.user.role.values_list('id', flat=True)
queryset = RoleMenuButtonPermission.objects.filter(
role__in=role_list, menu_button__menu=menu_id
).values(btn_id=F('menu_button__id'), name=F('menu_button__name'))
return DetailResponse(data=queryset)
return ErrorResponse(msg="参数错误")
@@ -142,10 +157,12 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
return DetailResponse(data=data)
else:
data = []
role_list = request.user.role.values_list('id',flat=True)
role_list = request.user.role.values_list('id', flat=True)
if params := request.query_params:
if menu_button_id := params.get('menu_button', None):
role_queryset = RoleMenuButtonPermission.objects.filter(role__in=role_list,menu_button__id=menu_button_id).values_list('data_range',flat=True)
role_queryset = RoleMenuButtonPermission.objects.filter(
role__in=role_list, menu_button__id=menu_button_id
).values_list('data_range', flat=True)
data_range_list = list(set(role_queryset))
for item in data_range_list:
if item == 0:
@@ -208,7 +225,7 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
is_superuser = request.user.is_superuser
is_admin = request.user.role.values_list('admin', flat=True)
if is_superuser or True in is_admin:
queryset = Dept.objects.values('id','name','parent')
queryset = Dept.objects.values('id', 'name', 'parent')
else:
if not params:
return ErrorResponse(msg="参数错误")
@@ -216,17 +233,15 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
if menu_button is None:
return ErrorResponse(msg="参数错误")
role_list = request.user.role.values_list('id', flat=True)
queryset = RoleMenuButtonPermission.objects.filter(role__in=role_list,menu_button=None).values(
queryset = RoleMenuButtonPermission.objects.filter(role__in=role_list, menu_button=None).values(
dept_id=F('dept__id'),
name=F('dept__name'),
parent=F('dept__parent')
)
return DetailResponse(data=queryset)
@action(methods=['get'],detail=False,permission_classes=[IsAuthenticated])
def menu_to_button(self,request):
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
def menu_to_button(self, request):
"""
根据所选择菜单获取已配置的按钮/接口权限:角色授权页面使用
:param request:
@@ -253,7 +268,7 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
role_id = params.get('role', None)
if role_id is None:
return ErrorResponse(msg="未获取到参数")
queryset = RoleMenuButtonPermission.objects.filter(role=role_id,menu_button__menu=menu_id).values(
queryset = RoleMenuButtonPermission.objects.filter(role=role_id, menu_button__menu=menu_id).values(
'id',
'data_range',
'menu_button',
@@ -274,7 +289,6 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
role_id = params.get('role', None)
if role_id is None:
return ErrorResponse(msg="未获取到参数")
queryset = RoleMenuPermission.objects.filter(role_id=role_id).values_list('menu_id',flat=True).distinct()
queryset = RoleMenuPermission.objects.filter(role_id=role_id).values_list('menu_id', flat=True).distinct()
return DetailResponse(data=queryset)

View File

@@ -110,24 +110,36 @@ def get_all_models_objects(model_name=None):
return settings.ALL_MODELS_OBJECTS or {}
def get_all_custom_app_models():
"""获取所有项目写的app里的models"""
for app in settings.CUSTOM_APPS:
model_module = import_module(app + '.models')
filter_model = [
getattr(model_module, item) for item in dir(model_module)
if item != 'CoreModel' and issubclass(getattr(model_module, item).__class__, models.base.ModelBase)
def get_model_from_app(app_name):
"""获取模型里的字段"""
model_module = import_module(app_name + '.models')
filter_model = [
getattr(model_module, item) for item in dir(model_module)
if item != 'CoreModel' and issubclass(getattr(model_module, item).__class__, models.base.ModelBase)
]
model_list = []
for model in filter_model:
if model.__name__ == 'AbstractUser':
continue
fields = [
{'title': field.verbose_name, 'name': field.name, 'object': field}
for field in model._meta.fields
]
model_list = []
for model in filter_model:
fields = [
{'title': field.verbose_name, 'name': field.name, 'object': field}
for field in model._meta.fields
]
model_list.append({
'verbose': model._meta.verbose_name,
'model': model.__name__,
'object': model,
'fields': fields
})
yield model_list
model_list.append({
'app': app_name,
'verbose': model._meta.verbose_name,
'model': model.__name__,
'object': model,
'fields': fields
})
return model_list
def get_custom_app_models(app_name=None):
"""获取所有项目写的app里的models"""
if app_name:
return get_model_from_app(app_name)
res = []
for app in settings.CUSTOM_APPS:
res.append(get_model_from_app(app))
return res

View File

@@ -40,13 +40,11 @@ class CustomPagination(PageNumberPagination):
try:
self.page = paginator.page(page_number)
except InvalidPage as exc:
# msg = self.invalid_page_message.format(
# page_number=page_number, message=str(exc)
# )
# raise NotFound(msg)
empty = False
pass
if paginator.num_pages > 1 and self.template is not None:
# The browsable API should display pagination controls.
@@ -58,15 +56,15 @@ class CustomPagination(PageNumberPagination):
self.page = []
return list(self.page)
def get_paginated_response(self, data):
code = 2000
msg = 'success'
page =int(self.get_page_number(self.request, paginator)) or 1
total=self.page.paginator.count if self.page else 0
limit= int(self.get_page_size(self.request)) or 10
is_next= self.page.has_next()
is_previous= self.page.has_previous()
data=data
page = int(self.get_page_number(self.request, paginator)) or 1
total = self.page.paginator.count if self.page else 0
limit = int(self.get_page_size(self.request)) or 10
is_next = self.page.has_next() if self.page else False
is_previous = self.page.has_previous() if self.page else False
if not data:
code = 2000