feat: 新增列权限配置功能

新增列权限配置,优化代码结构,更新初始化文件
This commit is contained in:
ahhui
2023-08-04 18:10:51 +08:00
committed by 李强
parent 85040c808f
commit 1b98fbc982
10 changed files with 254 additions and 71 deletions

View File

@@ -263,7 +263,50 @@
"cache": false, "cache": false,
"visible": true, "visible": true,
"children": [], "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": "用户管理", "name": "用户管理",

View File

@@ -5,12 +5,7 @@ from django.contrib.auth.models import AbstractUser, UserManager
from django.db import models from django.db import models
from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.exceptions import ObjectDoesNotExist, ValidationError
from application import dispatch from application import dispatch
from dvadmin.utils.models import CoreModel, table_prefix from dvadmin.utils.models import CoreModel, table_prefix, get_custom_app_models
STATUS_CHOICES = (
(0, "禁用"),
(1, "启用"),
)
class Role(CoreModel): class Role(CoreModel):
@@ -107,8 +102,7 @@ class Post(CoreModel):
class Dept(CoreModel): class Dept(CoreModel):
name = models.CharField(max_length=64, verbose_name="部门名称", help_text="部门名称") name = models.CharField(max_length=64, verbose_name="部门名称", help_text="部门名称")
key = models.CharField(max_length=64, unique=True, null=True, blank=True, verbose_name="关联字符", key = models.CharField(max_length=64, unique=True, null=True, blank=True, verbose_name="关联字符", help_text="关联字符")
help_text="关联字符")
sort = models.IntegerField(default=1, 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="负责人") 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="联系电话") phone = models.CharField(max_length=32, verbose_name="联系电话", null=True, blank=True, help_text="联系电话")
@@ -186,6 +180,23 @@ class Menu(CoreModel):
ordering = ("sort",) 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): class MenuButton(CoreModel):
menu = models.ForeignKey( menu = models.ForeignKey(
to="Menu", to="Menu",

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.role_menu_button_permission import RoleMenuButtonPermissionViewSet
from dvadmin.system.views.system_config import SystemConfigViewSet from dvadmin.system.views.system_config import SystemConfigViewSet
from dvadmin.system.views.user import UserViewSet from dvadmin.system.views.user import UserViewSet
from dvadmin.system.views.column import ColumnViewSet
system_url = routers.SimpleRouter() system_url = routers.SimpleRouter()
system_url.register(r'menu', MenuViewSet) 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'message_center', MessageCenterViewSet)
system_url.register(r'role_menu_button_permission', RoleMenuButtonPermissionViewSet) system_url.register(r'role_menu_button_permission', RoleMenuButtonPermissionViewSet)
system_url.register(r'role_menu_permission', RoleMenuPermissionViewSet) system_url.register(r'role_menu_permission', RoleMenuPermissionViewSet)
system_url.register(r'column', ColumnViewSet)
urlpatterns = [ 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.sort, dept.sort = dept.sort, previous_menu.sort
previous_menu.save() previous_menu.save()
dept.save() dept.save()
return SuccessResponse(data=[], msg="上移成功") return SuccessResponse(data=[], msg="上移成功")
@action(methods=['POST'], detail=False, permission_classes=[IsAuthenticated]) @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.sort, dept.sort = dept.sort, next_menu.sort
next_menu.save() next_menu.save()
dept.save() dept.save()
return SuccessResponse(data=[], msg="下移成功") return SuccessResponse(data=[], msg="下移成功")
@action(methods=['GET'], detail=False, permission_classes=[]) @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.sort, menu.sort = menu.sort, previous_menu.sort
previous_menu.save() previous_menu.save()
menu.save() menu.save()
return SuccessResponse(data=[], msg="上移成功") return SuccessResponse(data=[], msg="上移成功")
@action(methods=['POST'], detail=False, permission_classes=[]) @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.sort, menu.sort = menu.sort, next_menu.sort
next_menu.save() next_menu.save()
menu.save() menu.save()
return SuccessResponse(data=[], msg="下移成功") return SuccessResponse(data=[], msg="下移成功")

View File

@@ -12,6 +12,7 @@ from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import RoleMenuButtonPermission, Menu, MenuButton, Dept, RoleMenuPermission 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.json_response import DetailResponse, ErrorResponse
from dvadmin.utils.serializers import CustomModelSerializer from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet from dvadmin.utils.viewset import CustomModelViewSet
@@ -37,6 +38,7 @@ class RoleMenuButtonPermissionInitSerializer(CustomModelSerializer):
fields = "__all__" fields = "__all__"
read_only_fields = ["id"] read_only_fields = ["id"]
class RoleMenuButtonPermissionCreateUpdateSerializer(CustomModelSerializer): class RoleMenuButtonPermissionCreateUpdateSerializer(CustomModelSerializer):
""" """
初始化菜单按钮-序列化器 初始化菜单按钮-序列化器
@@ -74,16 +76,30 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
if is_superuser or True in is_admin: 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: for item in queryset:
btn_name = MenuButton.objects.filter(menu=item['menu_id']).values_list( btn_name = MenuButton.objects.filter(menu=item['menu_id']).values_list('name', flat=True)
'name', flat=True) data.append({
data.append({'menu_id': item['menu_id'], 'name': item['name'], 'parent': item['parent'], 'menu_id': item['menu_id'],
'permission': ','.join(btn_name), 'is_catalog': item['is_catalog']}) 'name': item['name'],
'parent': item['parent'],
'permission': btn_name,
'is_catalog': item['is_catalog']
})
else: else:
role_id = request.user.role.values_list('id', flat=True) 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() 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: for item in queryset:
btn_name = RoleMenuButtonPermission.objects.filter(menu_button__menu=item['menu_id']).values_list('menu_button__name',flat=True) btn_name = RoleMenuButtonPermission.objects.filter(
data.append({'menu_id':item['menu_id'], 'name':item['name'], 'parent':item['parent'],'permission':','.join(btn_name),'is_catalog':item['is_catalog']}) 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) return DetailResponse(data=data)
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated]) @action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
@@ -101,10 +117,9 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
queryset = MenuButton.objects.filter(menu=menu_id).values('id', 'name') queryset = MenuButton.objects.filter(menu=menu_id).values('id', 'name')
else: else:
role_list = request.user.role.values_list('id', flat=True) role_list = request.user.role.values_list('id', flat=True)
queryset = RoleMenuButtonPermission.objects.filter(role__in=role_list,menu_button__menu=menu_id).values( queryset = RoleMenuButtonPermission.objects.filter(
btn_id=F('menu_button__id'), role__in=role_list, menu_button__menu=menu_id
name=F('menu_button__name') ).values(btn_id=F('menu_button__id'), name=F('menu_button__name'))
)
return DetailResponse(data=queryset) return DetailResponse(data=queryset)
return ErrorResponse(msg="参数错误") return ErrorResponse(msg="参数错误")
@@ -145,7 +160,9 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
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 params := request.query_params:
if menu_button_id := params.get('menu_button', None): 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)) data_range_list = list(set(role_queryset))
for item in data_range_list: for item in data_range_list:
if item == 0: if item == 0:
@@ -223,8 +240,6 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
) )
return DetailResponse(data=queryset) return DetailResponse(data=queryset)
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated]) @action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
def menu_to_button(self, request): def menu_to_button(self, request):
""" """
@@ -277,4 +292,3 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
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) return DetailResponse(data=queryset)

View File

@@ -110,24 +110,36 @@ def get_all_models_objects(model_name=None):
return settings.ALL_MODELS_OBJECTS or {} return settings.ALL_MODELS_OBJECTS or {}
def get_all_custom_app_models(): def get_model_from_app(app_name):
"""获取所有项目写的app里的models""" """获取模型里的字段"""
for app in settings.CUSTOM_APPS: model_module = import_module(app_name + '.models')
model_module = import_module(app + '.models')
filter_model = [ filter_model = [
getattr(model_module, item) for item in dir(model_module) getattr(model_module, item) for item in dir(model_module)
if item != 'CoreModel' and issubclass(getattr(model_module, item).__class__, models.base.ModelBase) if item != 'CoreModel' and issubclass(getattr(model_module, item).__class__, models.base.ModelBase)
] ]
model_list = [] model_list = []
for model in filter_model: for model in filter_model:
if model.__name__ == 'AbstractUser':
continue
fields = [ fields = [
{'title': field.verbose_name, 'name': field.name, 'object': field} {'title': field.verbose_name, 'name': field.name, 'object': field}
for field in model._meta.fields for field in model._meta.fields
] ]
model_list.append({ model_list.append({
'app': app_name,
'verbose': model._meta.verbose_name, 'verbose': model._meta.verbose_name,
'model': model.__name__, 'model': model.__name__,
'object': model, 'object': model,
'fields': fields 'fields': fields
}) })
yield model_list 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: try:
self.page = paginator.page(page_number) self.page = paginator.page(page_number)
except InvalidPage as exc: except InvalidPage as exc:
# msg = self.invalid_page_message.format( # msg = self.invalid_page_message.format(
# page_number=page_number, message=str(exc) # page_number=page_number, message=str(exc)
# ) # )
# raise NotFound(msg) # raise NotFound(msg)
empty = False empty = False
pass
if paginator.num_pages > 1 and self.template is not None: if paginator.num_pages > 1 and self.template is not None:
# The browsable API should display pagination controls. # The browsable API should display pagination controls.
@@ -58,15 +56,15 @@ class CustomPagination(PageNumberPagination):
self.page = [] self.page = []
return list(self.page) return list(self.page)
def get_paginated_response(self, data): def get_paginated_response(self, data):
code = 2000 code = 2000
msg = 'success' msg = 'success'
page = int(self.get_page_number(self.request, paginator)) or 1 page = int(self.get_page_number(self.request, paginator)) or 1
total = self.page.paginator.count if self.page else 0 total = self.page.paginator.count if self.page else 0
limit = int(self.get_page_size(self.request)) or 10 limit = int(self.get_page_size(self.request)) or 10
is_next= self.page.has_next() is_next = self.page.has_next() if self.page else False
is_previous= self.page.has_previous() is_previous = self.page.has_previous() if self.page else False
data=data
if not data: if not data:
code = 2000 code = 2000