【Dv3Admin】系统视图角色菜单按钮权限API文件解析
按钮级权限控制是后台权限系统进阶的重要标志,传统以菜单为单位的授权粒度过粗,容易造成越权或权限冗余。通过对按钮进行细粒度管理,能实现更精准、安全的操作权限划分。
本文聚焦 role_menu_button_permission.py
源码,解析角色与菜单按钮权限关联的实现机制,包括权限查询、授权操作、数据范围控制等,揭示其在系统权限架构中的定位与作用。
文章目录
- role_menu_button_permission.py
- 项目源码解析
- 应用案例
- 总结
role_menu_button_permission.py
本系统基于 Django 框架构建后台权限管理平台,dvadmin/system/views/role_menu_button_permission.py
文件负责管理角色与菜单按钮权限的关联配置。系统通过按钮级权限控制,实现了更细粒度的访问授权,避免传统角色权限粒度过粗的问题。用户在系统中拥有的操作能力不仅取决于所绑定的菜单,还与具体按钮操作密切关联,例如新增、编辑、删除等操作的可见性和使用权都受此模块控制。
项目特点 | 描述 |
---|---|
技术栈 | Django + DRF(Django Rest Framework) |
功能定位 | 角色与菜单按钮权限关联管理 |
设计原则 | 按钮级别细分权限,支持数据范围控制 |
配置管理方式 | 后台动态增删改角色按钮权限配置,实时生效 |
dvadmin/system/views/role_menu_button_permission.py
定义了角色按钮权限管理模块的视图逻辑。该模块允许为角色配置可操作的具体菜单按钮,控制前端按钮级别的展示与操作权限。通过继承 CustomModelViewSet
实现了标准的增删改查接口,结合序列化器自动完成数据校验与转换。文件中视图类 RoleMenuButtonPermissionViewSet
绑定了角色与按钮的对应关系,为系统实现精确授权提供基础支持。
模块职责 | 说明 |
---|---|
定义视图控制器 | 继承自定义基础 ViewSet,规范增删改查接口逻辑 |
管理角色与按钮权限关联 | 允许后台灵活配置角色对应的菜单按钮操作权限 |
数据权限范围管理 | 角色在按钮操作同时绑定数据权限范围,控制数据可见性 |
提供序列化器支持 | 通过序列化器校验和序列化数据,保持接口数据一致性 |
预留扩展点 | 可在实际应用中结合前端动态渲染按钮权限 |
在后台系统中根据不同岗位或部门,分配对应角色并细化到具体按钮操作,例如某些角色只拥有“查询”权限,某些角色拥有“编辑”“删除”权限。通过 dvadmin/system/views/role_menu_button_permission.py
管理角色与菜单按钮之间的映射关系,实现前端根据权限动态渲染按钮,保证权限隔离和安全性。例如 CRM 系统中,普通销售人员只能查看客户资料,销售主管可以编辑和删除客户信息。
使用场景 | 说明 |
---|---|
后台动态分配按钮操作权限 | 例如控制某角色是否能点击“新增”“删除”按钮 |
根据角色动态渲染页面按钮 | 登录后根据权限展示或隐藏不同操作按钮 |
精细化控制不同数据权限范围 | 结合按钮操作绑定数据权限,如仅能修改自己部门数据 |
降低开发硬编码权限逻辑 | 所有权限管理可通过后台配置完成,无需频繁改动代码 |
提高系统安全性与灵活性 | 避免低权限用户通过前端绕过进行敏感操作 |
项目源码解析
按钮权限查询序列化器
用于将角色菜单按钮权限表数据序列化输出,字段定义简单,直接映射数据库字段,支持接口查询展示。依赖 RoleMenuButtonPermission
模型,作为基础数据载体,利于接口返回统一化。
class RoleMenuButtonPermissionSerializer(CustomModelSerializer):class Meta:model = RoleMenuButtonPermissionfields = "__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)class Meta:model = RoleMenuButtonPermissionfields = "__all__"read_only_fields = ["id"]
菜单关系序列化器
用于展示角色对应的菜单列表,动态标记菜单是否已授权,简化前端授权界面的处理逻辑。通过 isCheck
字段动态判断角色与菜单的绑定关系,依赖 RoleMenuPermission
表进行计算。
class RoleMenuSerializer(CustomModelSerializer):isCheck = serializers.SerializerMethodField()def get_isCheck(self, instance):params = self.request.query_paramsdata = self.request.datareturn RoleMenuPermission.objects.filter(menu_id=instance.id,role_id=params.get('roleId', data.get('roleId')),).exists()class Meta:model = Menufields = ["id", "name", "parent", "is_catalog", "isCheck"]
角色-菜单-按钮关系序列化器
用于展示角色所拥有的按钮权限,支持按钮是否授权、数据权限范围、自定义授权部门等信息。动态封装 isCheck
、data_range
、role_menu_btn_perm_id
、dept
等关键字段,功能完整,灵活性高。
class RoleMenuButtonSerializer(CustomModelSerializer):isCheck = serializers.SerializerMethodField()data_range = serializers.SerializerMethodField()role_menu_btn_perm_id = serializers.SerializerMethodField()dept = serializers.SerializerMethodField()def get_isCheck(self, instance):params = self.request.query_paramsdata = self.request.datareturn RoleMenuButtonPermission.objects.filter(menu_button_id=instance.id,role_id=params.get('roleId', data.get('roleId')),).exists()def get_data_range(self, instance):obj = self.get_role_menu_btn_prem(instance)if obj is None:return Nonereturn obj.data_rangedef get_role_menu_btn_perm_id(self, instance):obj = self.get_role_menu_btn_prem(instance)if obj is None:return Nonereturn obj.iddef get_dept(self, instance):obj = self.get_role_menu_btn_prem(instance)if obj is None:return Nonereturn obj.dept.all().values_list('id', flat=True)def get_role_menu_btn_prem(self, instance):params = self.request.query_paramsdata = self.request.datareturn RoleMenuButtonPermission.objects.filter(menu_button_id=instance.id,role_id=params.get('roleId', data.get('roleId')),).first()class Meta:model = MenuButtonfields = ['id', 'menu', 'name', 'isCheck', 'data_range', 'role_menu_btn_perm_id', 'dept']
字段权限序列化器
专用于处理角色对菜单字段权限的管理,如是否可查询、创建、更新,字段粒度的权限控制细化到了字段级别。通过 FieldPermission
模型数据实现字段操作权限的动态绑定。
class RoleMenuFieldSerializer(CustomModelSerializer):is_query = serializers.SerializerMethodField()is_create = serializers.SerializerMethodField()is_update = serializers.SerializerMethodField()def get_is_query(self, instance):params = self.request.query_paramsqueryset = instance.menu_field.filter(role=params.get('roleId')).first()return queryset.is_query if queryset else Falsedef get_is_create(self, instance):params = self.request.query_paramsqueryset = instance.menu_field.filter(role=params.get('roleId')).first()return queryset.is_create if queryset else Falsedef get_is_update(self, instance):params = self.request.query_paramsqueryset = instance.menu_field.filter(role=params.get('roleId')).first()return queryset.is_update if queryset else Falseclass Meta:model = MenuFieldfields = ['id', 'field_name', 'title', 'is_query', 'is_create', 'is_update']
按钮权限接口管理类
统一管理角色菜单按钮权限的增删改查,扩展了角色菜单授权、按钮授权、字段授权的特定功能接口。结合 RoleMenuPermission
、RoleMenuButtonPermission
、FieldPermission
等模型,支撑整个系统权限模块的细粒度控制。
class RoleMenuButtonPermissionViewSet(CustomModelViewSet):queryset = RoleMenuButtonPermission.objects.all()serializer_class = RoleMenuButtonPermissionSerializercreate_serializer_class = RoleMenuButtonPermissionCreateUpdateSerializerupdate_serializer_class = RoleMenuButtonPermissionCreateUpdateSerializerextra_filter_class = []
查询当前角色可以管理的菜单信息,并返回是否勾选状态,支撑前端角色菜单授权界面初始化数据。
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])def get_role_menu(self, request):menu_queryset = Menu.objects.all()serializer = RoleMenuSerializer(menu_queryset, many=True, request=request)return DetailResponse(data=serializer.data)
根据是否勾选操作,新增或取消角色与菜单之间的绑定关系。支持动态授权与回收,保证权限配置的实时性和准确性。
@action(methods=['PUT'], detail=False, permission_classes=[IsAuthenticated])def set_role_menu(self, request):data = request.dataroleId = data.get('roleId')menuId = data.get('menuId')isCheck = data.get('isCheck')if isCheck:instance = RoleMenuPermission.objects.create(role_id=roleId, menu_id=menuId)else:RoleMenuPermission.objects.filter(role_id=roleId, menu_id=menuId).delete()menu_instance = Menu.objects.get(id=menuId)serializer = RoleMenuSerializer(menu_instance, request=request)return DetailResponse(data=serializer.data, msg="更新成功")
同时返回角色菜单下所有按钮及字段权限列表,便于前端统一渲染按钮权限和字段权限界面。
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])def get_role_menu_btn_field(self, request):params = request.query_paramsmenuId = params.get('menuId', None)menu_btn_queryset = MenuButton.objects.filter(menu_id=menuId)menu_btn_serializer = RoleMenuButtonSerializer(menu_btn_queryset, many=True, request=request)menu_field_queryset = MenuField.objects.filter(menu_id=menuId)menu_field_serializer = RoleMenuFieldSerializer(menu_field_queryset, many=True, request=request)return DetailResponse(data={'menu_btn': menu_btn_serializer.data, 'menu_field': menu_field_serializer.data})
为指定角色批量更新菜单字段的增删改查权限,支持一次性配置多个字段权限,简化操作。
@action(methods=['PUT'], detail=True, permission_classes=[IsAuthenticated])def set_role_menu_field(self, request, pk):data = request.datafor col in data:FieldPermission.objects.update_or_create(role_id=pk, field_id=col.get('id'),defaults={'is_create': col.get('is_create'),'is_update': col.get('is_update'),'is_query': col.get('is_query'),})return DetailResponse(data=[], msg="更新成功")
处理角色按钮权限的授权或取消操作,同时支持设置数据范围和关联部门,是角色数据权限管理的关键接口之一。
@action(methods=['PUT'], detail=False, permission_classes=[IsAuthenticated])def set_role_menu_btn(self, request):data = request.dataisCheck = data.get('isCheck', None)roleId = data.get('roleId', None)btnId = data.get('btnId', None)data_range = data.get('data_range', None) or 0dept = data.get('dept', None) or []if isCheck:instance = RoleMenuButtonPermission.objects.create(role_id=roleId,menu_button_id=btnId,data_range=data_range)if data_range == 4 and dept:instance.dept.set(dept)else:RoleMenuButtonPermission.objects.filter(role_id=roleId, menu_button_id=btnId).delete()menu_btn_instance = MenuButton.objects.get(id=btnId)serializer = RoleMenuButtonSerializer(menu_btn_instance, request=request)return DetailResponse(data=serializer.data, msg="更新成功")
动态调整角色按钮的可操作数据范围,同时可配置自定义部门权限,强化数据隔离控制。
@action(methods=['PUT'], detail=False, permission_classes=[IsAuthenticated])def set_role_menu_btn_data_range(self, request):data = request.datainstance = RoleMenuButtonPermission.objects.get(id=data.get('role_menu_btn_perm_id'))instance.data_range = data.get('data_range')instance.dept.add(*data.get('dept'))if not data.get('dept'):instance.dept.clear()instance.save()serializer = RoleMenuButtonPermissionSerializer(instance, request=request)return DetailResponse(data=serializer.data, msg="更新成功")
返回当前用户角色下能够进行部门授权的数据集合,区分超级管理员与普通角色权限,增强安全控制能力。
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])def role_to_dept_all(self, request):is_superuser = request.user.is_superuserparams = request.query_paramsrole_list = request.user.role.values_list('id', flat=True)menu_button_id = params.get('menu_button')dept_checked_disabled = RoleMenuButtonPermission.objects.filter(role_id__in=role_list, menu_button_id=menu_button_id).values_list('dept', flat=True)dept_list = Dept.objects.values('id', 'name', 'parent')data = []for dept in dept_list:dept["disabled"] = False if is_superuser else dept["id"] not in dept_checked_disableddata.append(dept)return DetailResponse(data=data)
应用案例
按钮级权限控制在后台权限系统中的实际应用
后台管理系统通常会面临过粗的权限粒度管理问题,传统的基于菜单的权限控制往往无法覆盖到更具体的操作场景,例如某些角色是否可以点击“新增”“删除”按钮,或者是否能够查看某些敏感字段等。为了实现更精细的权限控制,项目通过 dvadmin/system/views/role_menu_button_permission.py
模块,结合 Django REST Framework (DRF),在角色与菜单按钮之间实现了精细化权限授权机制。
功能点 | 内容描述 |
---|---|
场景需求 | 解决传统菜单级权限控制过于粗放的问题,细化到按钮级别,控制角色是否可以执行新增、删除、编辑、查看等具体操作。 |
核心模块 | dvadmin/system/views/role_menu_button_permission.py :实现角色与菜单按钮精细化权限授权机制。 |
支持功能 | - 按钮级权限管理:管理员可为每个角色配置具体菜单按钮操作权限,如新增、编辑、删除、查看等。 |
- 动态授权:支持角色与菜单按钮的权限动态绑定,权限变更实时生效。 | |
实现机制 | - RoleMenuButtonPermissionViewSet:基于 Django REST Framework (DRF) 实现的视图集,用于管理按钮级别权限。 |
- 接口支持:提供增删改查接口,管理员可通过接口配置或调整角色与按钮的权限绑定。 | |
前端动态渲染 | - 根据当前角色的按钮权限动态渲染前端可操作按钮,避免无权限按钮的展示与操作。 |
应用场景 | - 操作权限控制:如控制某角色是否可以点击“新增”“删除”按钮。 - 敏感操作限制:如限制某角色查看或编辑敏感字段。 |
优势 | - 提供细粒度权限控制,确保角色操作权限的精确管理。 - 避免角色过度授权或权限冗余,提高系统安全性与灵活性。 |
扩展能力 | - 可与字段级权限控制模块结合,构建完整的精细化权限体系。 - 支持权限模板配置,用于快速为多个角色分配常用权限。 |
该模块允许管理员为每个角色配置具体的菜单按钮操作权限,包括新增、编辑、删除、查看等具体权限,并可针对角色与菜单按钮的关系动态授权。通过 RoleMenuButtonPermissionViewSet
视图集,系统实现了按钮级别的权限管理,确保角色操作权限的精确控制,避免了角色过度授权或权限冗余的问题。管理员可以通过接口配置或调整角色与按钮的权限绑定,前端则根据当前角色的权限动态渲染可操作按钮。
实际使用场景中的代码操作逻辑
在实际业务中,当管理员需要为“销售主管”角色分配“客户信息编辑”权限时,系统通过以下接口操作实现按钮授权:
PUT /api/system/role_menu_button_permission/set_role_menu_btn/{"roleId": 3,"btnId": 5,"isCheck": true,"data_range": 1
}
此请求将“销售主管”角色(roleId = 3
)的“编辑按钮”(btnId = 5
)权限激活,并为该按钮分配数据权限范围 data_range = 1
(即限制为当前部门内的数据)。前端在渲染时根据 data_range
判断该按钮是否可用,仅在数据权限匹配时显示编辑按钮。
角色与菜单按钮权限的增删改查
系统通过以下代码将角色与按钮权限进行绑定,实现按钮权限的增删改查操作:
class RoleMenuButtonPermissionViewSet(CustomModelViewSet):queryset = RoleMenuButtonPermission.objects.all()serializer_class = RoleMenuButtonPermissionSerializer@action(methods=['PUT'], detail=False, permission_classes=[IsAuthenticated])def set_role_menu_btn(self, request):data = request.dataroleId = data.get('roleId')btnId = data.get('btnId')isCheck = data.get('isCheck')data_range = data.get('data_range', 0)dept = data.get('dept', None) or []if isCheck:instance = RoleMenuButtonPermission.objects.create(role_id=roleId, menu_button_id=btnId, data_range=data_range)if data_range == 4 and dept:instance.dept.set(dept)else:RoleMenuButtonPermission.objects.filter(role_id=roleId, menu_button_id=btnId).delete()return DetailResponse(data={}, msg="权限更新成功")
在此操作中,当角色权限更新时,系统会将角色与按钮的权限关系进行插入或删除,保持角色权限数据的实时更新与同步。这使得角色权限管理更加灵活,权限变更可以即时生效,而无需重启服务或修改其他代码。
按钮权限查询与动态渲染
前端页面会根据角色权限信息动态渲染按钮,避免低权限用户看到并操作本不该允许的按钮。通过以下接口,前端可以获取当前角色在某一菜单下可操作的按钮权限列表:
GET /api/system/role_menu_button_permission/get_role_menu_btn_field/?roleId=3&menuId=12
接口返回的按钮权限信息包括每个按钮是否已授权、数据权限范围等,前端根据这些数据来决定是否显示按钮。例如,当用户登录后,系统根据角色查询出角色在某一菜单下是否有权限进行“删除”操作,若没有权限,则不展示删除按钮。
按钮权限的字段级控制
在一些场景中,系统不仅需要控制按钮级权限,还需要控制字段级权限。例如,销售人员仅能查看客户信息,而销售主管则可以编辑和删除客户数据。通过以下代码,系统实现了字段级权限的控制:
class RoleMenuFieldSerializer(CustomModelSerializer):is_query = serializers.SerializerMethodField()is_create = serializers.SerializerMethodField()is_update = serializers.SerializerMethodField()def get_is_query(self, instance):params = self.request.query_paramsqueryset = instance.menu_field.filter(role=params.get('roleId')).first()return queryset.is_query if queryset else Falsedef get_is_create(self, instance):params = self.request.query_paramsqueryset = instance.menu_field.filter(role=params.get('roleId')).first()return queryset.is_create if queryset else Falsedef get_is_update(self, instance):params = self.request.query_paramsqueryset = instance.menu_field.filter(role=params.get('roleId')).first()return queryset.is_update if queryset else Falseclass Meta:model = MenuFieldfields = ['id', 'field_name', 'title', 'is_query', 'is_create', 'is_update']
这种细粒度的字段权限控制可以保证不同角色对相同数据项具有不同的操作权限,例如,销售人员只能查看客户信息,而销售主管既可以查看,也可以修改或删除客户数据。
通过 role_menu_button_permission.py
模块,系统实现了角色与按钮之间的精确权限控制,结合视图集和序列化器完成了接口权限的增删改查。按钮权限的动态渲染与字段级权限的结合使得系统能够满足多场景、细粒度的安全需求,有效避免了权限冗余和越权操作,提高了系统的安全性和灵活性。
总结
模块以视图集方式封装按钮权限操作,结合序列化器实现数据标准化处理,支持角色到按钮的动态授权与取消,并扩展数据权限范围绑定。接口功能独立清晰,支持实时权限更新,便于前端动态渲染,实现了菜单、按钮、字段三级权限体系的协同控制。
模块逻辑偏重接口直连模型,业务抽象层较薄,存在一定扩展困难。部分接口数据校验与异常处理缺失,容易因参数异常导致程序中断。如果重构,建议提取统一的权限服务层,细化错误处理与日志记录机制,提高整体健壮性与可维护性。