Merge remote-tracking branch 'origin/develop' into develop

This commit is contained in:
1638245306
2024-09-25 23:32:42 +08:00
69 changed files with 1927 additions and 1478 deletions

View File

@@ -0,0 +1 @@
# -*- coding: utf-8 -*-

View File

@@ -167,19 +167,13 @@
"method": 0
},
{
"name": "查询所有",
"name": "获取所有部门",
"value": "dept:SearchAll",
"api": "/api/system/dept/all_dept/",
"method": 0
},
{
"name": "懒加载查询所有",
"value": "dept:LazySearchAll",
"api": "/api/system/dept/dept_lazy_tree/",
"method": 0
},
{
"name": "头信息",
"name": "部门顶部信息",
"value": "dept:HeaderInfo",
"api": "/api/system/dept/dept_info/",
"method": 0

View File

@@ -0,0 +1,12 @@
from django.dispatch import Signal
# 初始化信号
pre_init_complete = Signal()
detail_init_complete = Signal()
post_init_complete = Signal()
# 租户初始化信号
pre_tenants_init_complete = Signal()
detail_tenants_init_complete = Signal()
post_tenants_init_complete = Signal()
post_tenants_all_init_complete = Signal()
# 租户创建完成信号
tenants_create_complete = Signal()

View File

@@ -35,6 +35,7 @@ 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', MenuFieldViewSet)
system_url.register(r'login_log', LoginLogViewSet)
urlpatterns = [
@@ -44,8 +45,8 @@ urlpatterns = [
path('system_config/get_association_table/', SystemConfigViewSet.as_view({'get': 'get_association_table'})),
path('system_config/get_table_data/<int:pk>/', SystemConfigViewSet.as_view({'get': 'get_table_data'})),
path('system_config/get_relation_info/', SystemConfigViewSet.as_view({'get': 'get_relation_info'})),
path('login_log/', LoginLogViewSet.as_view({'get': 'list'})),
path('login_log/<int:pk>/', LoginLogViewSet.as_view({'get': 'retrieve'})),
# path('login_log/', LoginLogViewSet.as_view({'get': 'list'})),
# path('login_log/<int:pk>/', LoginLogViewSet.as_view({'get': 'retrieve'})),
path('dept_lazy_tree/', DeptViewSet.as_view({'get': 'dept_lazy_tree'})),
path('clause/privacy.html', PrivacyView.as_view()),
path('clause/terms_service.html', TermsServiceView.as_view()),

View File

@@ -1,8 +1,10 @@
# -*- coding: utf-8 -*-
import pypinyin
from django.db.models import Q
from rest_framework import serializers
from dvadmin.system.models import Area
from dvadmin.utils.field_permission import FieldPermissionMixin
from dvadmin.utils.json_response import SuccessResponse
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
@@ -14,13 +16,21 @@ class AreaSerializer(CustomModelSerializer):
"""
pcode_count = serializers.SerializerMethodField(read_only=True)
hasChild = serializers.SerializerMethodField()
pcode_info = serializers.SerializerMethodField()
def get_pcode_info(self, instance):
pcode = Area.objects.filter(code=instance.pcode_id).values("name", "code")
return pcode
def get_pcode_count(self, instance: Area):
return Area.objects.filter(pcode=instance).count()
def get_hasChild(self, instance):
hasChild = Area.objects.filter(pcode=instance.code)
if hasChild:
return True
return False
class Meta:
model = Area
fields = "__all__"
@@ -32,12 +42,24 @@ class AreaCreateUpdateSerializer(CustomModelSerializer):
地区管理 创建/更新时的列化器
"""
def to_internal_value(self, data):
pinyin = ''.join([''.join(i) for i in pypinyin.pinyin(data["name"], style=pypinyin.NORMAL)])
data["level"] = 1
data["pinyin"] = pinyin
data["initials"] = pinyin[0].upper() if pinyin else "#"
pcode = data["pcode"] if 'pcode' in data else None
if pcode:
pcode = Area.objects.get(pk=pcode)
data["pcode"] = pcode.code
data["level"] = pcode.level + 1
return super().to_internal_value(data)
class Meta:
model = Area
fields = '__all__'
class AreaViewSet(CustomModelViewSet):
class AreaViewSet(CustomModelViewSet, FieldPermissionMixin):
"""
地区管理接口
list:查询
@@ -48,21 +70,28 @@ class AreaViewSet(CustomModelViewSet):
"""
queryset = Area.objects.all()
serializer_class = AreaSerializer
create_serializer_class = AreaCreateUpdateSerializer
update_serializer_class = AreaCreateUpdateSerializer
extra_filter_class = []
def get_queryset(self):
def list(self, request, *args, **kwargs):
self.request.query_params._mutable = True
params = self.request.query_params
pcode = params.get('pcode', None)
page = params.get('page', None)
limit = params.get('limit', None)
if page:
del params['page']
if limit:
del params['limit']
if params and pcode:
queryset = self.queryset.filter(enable=True, pcode=pcode)
else:
known_params = {'page', 'limit', 'pcode'}
# 使用集合操作检查是否有未知参数
other_params_exist = any(param not in known_params for param in params)
if other_params_exist:
queryset = self.queryset.filter(enable=True)
return queryset
else:
pcode = params.get('pcode', None)
params['limit'] = 999
if params and pcode:
queryset = self.queryset.filter(enable=True, pcode=pcode)
else:
queryset = self.queryset.filter(enable=True, level=1)
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="获取成功")

View File

@@ -10,6 +10,7 @@ from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import Dept, RoleMenuButtonPermission, Users
from dvadmin.utils.filters import DataLevelPermissionsFilter
from dvadmin.utils.json_response import DetailResponse, SuccessResponse, ErrorResponse
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
@@ -125,17 +126,6 @@ class DeptViewSet(CustomModelViewSet):
return SuccessResponse(data=data)
@action(methods=["GET"], detail=False, permission_classes=[IsAuthenticated])
def dept_lazy_tree(self, request, *args, **kwargs):
parent = self.request.query_params.get('parent')
is_superuser = request.user.is_superuser
if is_superuser:
queryset = Dept.objects.values('id', 'name', 'parent')
else:
queryset = Dept.objects.values('id', 'name', 'parent')
queryset = self.filter_queryset(queryset)
return DetailResponse(data=queryset, msg="获取成功")
@action(methods=["GET"], detail=False, permission_classes=[IsAuthenticated], extra_filter_class=[])
def all_dept(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
data = queryset.filter(status=True).order_by('sort').values('name', 'id', 'parent')

View File

@@ -15,8 +15,8 @@ class FileSerializer(CustomModelSerializer):
url = serializers.SerializerMethodField(read_only=True)
def get_url(self, instance):
# return 'media/' + str(instance.url)
return instance.file_url or (f'media/{str(instance.url)}')
base_url = f"{self.request.scheme}://{self.request.get_host()}/"
return base_url + (instance.file_url or (f'media/{str(instance.url)}'))
class Meta:
model = FileList

View File

@@ -4,6 +4,7 @@ from datetime import datetime, timedelta
from captcha.views import CaptchaStore, captcha_image
from django.contrib import auth
from django.contrib.auth import login
from django.db.models import Q
from django.shortcuts import redirect
from django.utils.translation import gettext_lazy as _
from drf_yasg import openapi
@@ -83,11 +84,18 @@ class LoginSerializer(TokenObtainPairSerializer):
else:
self.image_code and self.image_code.delete()
raise CustomValidationError("图片验证码错误")
user = Users.objects.get(username=attrs['username'])
try:
user = Users.objects.get(
Q(username=attrs['username']) | Q(email=attrs['username']) | Q(mobile=attrs['username']))
except Users.DoesNotExist:
raise CustomValidationError("您登录的账号不存在")
except Users.MultipleObjectsReturned:
raise CustomValidationError("您登录的账号存在多个,请联系管理员检查登录账号唯一性")
if not user.is_active:
raise CustomValidationError("账号已被锁定,联系管理员解锁")
try:
# 必须重置用户名为username,否则使用邮箱手机号登录会提示密码错误
attrs['username'] = user.username
data = super().validate(attrs)
data["name"] = self.user.name
data["userId"] = self.user.id
@@ -114,6 +122,7 @@ class LoginSerializer(TokenObtainPairSerializer):
user.login_error_count += 1
if user.login_error_count >= 5:
user.is_active = False
user.save()
raise CustomValidationError("账号已被锁定,联系管理员解锁")
user.save()
count = 5 - user.login_error_count

View File

@@ -7,6 +7,7 @@
@Remark: 按钮权限管理
"""
from dvadmin.system.models import LoginLog
from dvadmin.utils.field_permission import FieldPermissionMixin
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
@@ -22,7 +23,7 @@ class LoginLogSerializer(CustomModelSerializer):
read_only_fields = ["id"]
class LoginLogViewSet(CustomModelViewSet):
class LoginLogViewSet(CustomModelViewSet, FieldPermissionMixin):
"""
登录日志接口
list:查询
@@ -33,4 +34,4 @@ class LoginLogViewSet(CustomModelViewSet):
"""
queryset = LoginLog.objects.all()
serializer_class = LoginLogSerializer
extra_filter_class = []
# extra_filter_class = []

View File

@@ -16,6 +16,8 @@ from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
class MenuButtonSerializer(CustomModelSerializer):
"""
菜单按钮-序列化器
@@ -92,17 +94,15 @@ class MenuButtonViewSet(CustomModelViewSet):
"""
menu_obj = Menu.objects.filter(id=request.data['menu']).first()
result_list = [
{'menu': menu_obj.id, 'name': '新增', 'value': f'{menu_obj.component_name}:Create', 'api': f'/api{menu_obj.web_path}/',
'method': 1},
{'menu': menu_obj.id, 'name': '删除', 'value': f'{menu_obj.component_name}:Delete', 'api': f'/api{menu_obj.web_path}/{{id}}/',
'method': 3},
{'menu': menu_obj.id, 'name': '修改', 'value': f'{menu_obj.component_name}:Update', 'api': f'/api{menu_obj.web_path}/{{id}}/',
'method': 2},
{'menu': menu_obj.id, 'name': '查询', 'value': f'{menu_obj.component_name}:Search', 'api': f'/api{menu_obj.web_path}/',
'method': 0},
{'menu': menu_obj.id, 'name': '详情', 'value': f'{menu_obj.component_name}:Retrieve', 'api': f'/api{menu_obj.web_path}/{{id}}/',
'method': 0}]
{'menu': menu_obj.id, 'name': '新增', 'value': f'{menu_obj.component_name}:Create', 'api': f'/api/{menu_obj.component_name}/', 'method': 1},
{'menu': menu_obj.id, 'name': '删除', 'value': f'{menu_obj.component_name}:Delete', 'api': f'/api/{menu_obj.component_name}/{{id}}/', 'method': 3},
{'menu': menu_obj.id, 'name': '编辑', 'value': f'{menu_obj.component_name}:Update', 'api': f'/api/{menu_obj.component_name}/{{id}}/', 'method': 2},
{'menu': menu_obj.id, 'name': '查询', 'value': f'{menu_obj.component_name}:Search', 'api': f'/api/{menu_obj.component_name}/', 'method': 0},
{'menu': menu_obj.id, 'name': '详情', 'value': f'{menu_obj.component_name}:Retrieve', 'api': f'/api/{menu_obj.component_name}/{{id}}/', 'method': 0},
{'menu': menu_obj.id, 'name': '复制', 'value': f'{menu_obj.component_name}:Copy', 'api': f'/api/{menu_obj.component_name}/', 'method': 1},
{'menu': menu_obj.id, 'name': '导入', 'value': f'{menu_obj.component_name}:Import', 'api': f'/api/{menu_obj.component_name}/import_data/', 'method': 1},
{'menu': menu_obj.id, 'name': '导出', 'value': f'{menu_obj.component_name}:Export', 'api': f'/api{menu_obj.component_name}/export_data/', 'method': 1},]
serializer = self.get_serializer(data=result_list, many=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return SuccessResponse(serializer.data, msg="批量创建成功")
return SuccessResponse(serializer.data, msg="批量创建成功")

View File

@@ -36,6 +36,8 @@ class MessageCenterSerializer(CustomModelSerializer):
return serializer.data
def get_user_info(self, instance, parsed_query):
if instance.target_type in (1,2,3):
return []
users = instance.target_user.all()
# You can do what ever you want in here
# `parsed_query` param is passed to BookSerializer to allow further querying
@@ -80,6 +82,9 @@ class MessageCenterTargetUserListSerializer(CustomModelSerializer):
"""
目标用户序列化器-序列化器
"""
role_info = DynamicSerializerMethodField()
user_info = DynamicSerializerMethodField()
dept_info = DynamicSerializerMethodField()
is_read = serializers.SerializerMethodField()
def get_is_read(self, instance):
@@ -90,6 +95,44 @@ class MessageCenterTargetUserListSerializer(CustomModelSerializer):
return queryset.is_read
return False
def get_role_info(self, instance, parsed_query):
roles = instance.target_role.all()
# You can do what ever you want in here
# `parsed_query` param is passed to BookSerializer to allow further querying
from dvadmin.system.views.role import RoleSerializer
serializer = RoleSerializer(
roles,
many=True,
parsed_query=parsed_query
)
return serializer.data
def get_user_info(self, instance, parsed_query):
if instance.target_type in (1,2,3):
return []
users = instance.target_user.all()
# You can do what ever you want in here
# `parsed_query` param is passed to BookSerializer to allow further querying
from dvadmin.system.views.user import UserSerializer
serializer = UserSerializer(
users,
many=True,
parsed_query=parsed_query
)
return serializer.data
def get_dept_info(self, instance, parsed_query):
dept = instance.target_dept.all()
# You can do what ever you want in here
# `parsed_query` param is passed to BookSerializer to allow further querying
from dvadmin.system.views.dept import DeptSerializer
serializer = DeptSerializer(
dept,
many=True,
parsed_query=parsed_query
)
return serializer.data
class Meta:
model = MessageCenter
fields = "__all__"

View File

@@ -6,9 +6,11 @@
@Created on: 2021/6/3 003 0:30
@Remark: 菜单按钮管理
"""
from django.db.models import F, Subquery, OuterRef, Exists
from django.db.models import F, Subquery, OuterRef, Exists, BooleanField, Q, Case, Value, When
from django.db.models.functions import Coalesce
from rest_framework import serializers
from rest_framework.decorators import action
from rest_framework.fields import ListField
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import RoleMenuButtonPermission, Menu, MenuButton, Dept, RoleMenuPermission, FieldPermission, \
@@ -172,59 +174,103 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
update_serializer_class = RoleMenuButtonPermissionCreateUpdateSerializer
extra_filter_class = []
# @action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
# def get_role_premission(self, request):
# """
# 角色授权获取:
# :param request: role
# :return: menu,btns,columns
# """
# params = request.query_params
# is_superuser = request.user.is_superuser
# if is_superuser:
# queryset = Menu.objects.filter(status=1, is_catalog=True).values('name', 'id').all()
# else:
# role_id = request.user.role.values_list('id', flat=True)
# menu_list = RoleMenuPermission.objects.filter(role__in=role_id).values_list('menu__id', flat=True)
# queryset = Menu.objects.filter(status=1, is_catalog=True, id__in=menu_list).values('name', 'id').all()
# serializer = RoleMenuSerializer(queryset, many=True, request=request)
# data = serializer.data
# return DetailResponse(data=data)
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
def get_role_premission(self, request):
"""
角色授权获取:
:param request: role
:return: menu,btns,columns
"""
def get_role_permission(self, request):
params = request.query_params
role = params.get('role', None)
if role is None:
return ErrorResponse(msg="未获取到角色信息")
# 需要授权的角色信息
current_role = params.get('role', None)
# 当前登录用户的角色
role_list = request.user.role.values_list('id', flat=True)
if current_role is None:
return ErrorResponse(msg='参数错误')
is_superuser = request.user.is_superuser
if is_superuser:
queryset = Menu.objects.filter(status=1, is_catalog=True).values('name', 'id').all()
menu_queryset = Menu.objects.prefetch_related('menuPermission').prefetch_related(
'menufield_set')
else:
role_id = request.user.role.values_list('id', flat=True)
menu_list = RoleMenuPermission.objects.filter(role__in=role_id).values_list('id', flat=True)
queryset = Menu.objects.filter(status=1, is_catalog=True, id__in=menu_list).values('name', 'id').all()
serializer = RoleMenuSerializer(queryset, many=True, request=request)
data = serializer.data
return DetailResponse(data=data)
# data = []
# if is_superuser:
# queryset = Menu.objects.filter(status=1, is_catalog=False).values('name', 'id').all()
# else:
# role_id = request.user.role.values_list('id', flat=True)
# menu_list = RoleMenuPermission.objects.filter(role__in=role_id).values_list('id', flat=True)
# queryset = Menu.objects.filter(status=1, is_catalog=False, id__in=menu_list).values('name', 'id')
# for item in queryset:
# parent_list = Menu.get_all_parent(item['id'])
# names = [d["name"] for d in parent_list]
# completeName = "/".join(names)
# isCheck = RoleMenuPermission.objects.filter(
# menu__id=item['id'],
# role__id=role,
# ).exists()
# mbCheck = RoleMenuButtonPermission.objects.filter(
# menu_button=OuterRef("pk"),
# role__id=role,
# )
# btns = MenuButton.objects.filter(
# menu__id=item['id'],
# ).annotate(isCheck=Exists(mbCheck)).values('id', 'name', 'value', 'isCheck',
# data_range=F('menu_button_permission__data_range'))
# dicts = {
# 'name': completeName,
# 'id': item['id'],
# 'isCheck': isCheck,
# 'btns': btns,
#
# }
# data.append(dicts)
# return DetailResponse(data=data)
role_id_list = request.user.role.values_list('id', flat=True)
menu_list = RoleMenuPermission.objects.filter(role__in=role_id_list).values_list('menu__id', flat=True)
# 当前角色已授权的菜单
menu_queryset = Menu.objects.filter(id__in=menu_list).prefetch_related('menuPermission').prefetch_related(
'menufield_set')
result = []
for menu_item in menu_queryset:
isCheck = RoleMenuPermission.objects.filter(
menu_id=menu_item.id,
role_id=current_role
).exists()
dicts = {
'name': menu_item.name,
'id': menu_item.id,
'parent': menu_item.parent.id if menu_item.parent else None,
'isCheck': isCheck,
'btns': [],
'columns': []
}
for mb_item in menu_item.menuPermission.all():
rolemenubuttonpermission_queryset = RoleMenuButtonPermission.objects.filter(
menu_button_id=mb_item.id,
role_id=current_role
).first()
dicts['btns'].append(
{
'id': mb_item.id,
'name': mb_item.name,
'value': mb_item.value,
'data_range': rolemenubuttonpermission_queryset.data_range
if rolemenubuttonpermission_queryset
else None,
'isCheck': bool(rolemenubuttonpermission_queryset),
'dept': rolemenubuttonpermission_queryset.dept.all().values_list('id', flat=True)
if rolemenubuttonpermission_queryset
else [],
}
)
for column_item in menu_item.menufield_set.all():
# 需要授权角色已拥有的列权限
fieldpermission_queryset = column_item.menu_field.filter(role_id=current_role).first()
is_query = fieldpermission_queryset.is_query if fieldpermission_queryset else False
is_create = fieldpermission_queryset.is_create if fieldpermission_queryset else False
is_update = fieldpermission_queryset.is_update if fieldpermission_queryset else False
# 当前登录用户角色可分配的列权限
fieldpermission_queryset_disabled = column_item.menu_field.filter(role_id__in=role_list).first()
disabled_query = fieldpermission_queryset_disabled.is_query if fieldpermission_queryset_disabled else True
disabled_create = fieldpermission_queryset_disabled.is_create if fieldpermission_queryset_disabled else True
disabled_update = fieldpermission_queryset_disabled.is_update if fieldpermission_queryset_disabled else True
dicts['columns'].append({
'id': column_item.id,
'field_name': column_item.field_name,
'title': column_item.title,
'is_query': is_query,
'is_create': is_create,
'is_update': is_update,
'disabled_query': False if is_superuser else not disabled_query,
'disabled_create': False if is_superuser else not disabled_create,
'disabled_update': False if is_superuser else not disabled_update,
})
result.append(dicts)
return DetailResponse(data=result)
@action(methods=['PUT'], detail=True, permission_classes=[IsAuthenticated])
def set_role_premission(self, request, pk):
@@ -238,27 +284,21 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
RoleMenuPermission.objects.filter(role=pk).delete()
RoleMenuButtonPermission.objects.filter(role=pk).delete()
for item in body:
for menu in item["menus"]:
if menu.get('isCheck'):
menu_parent = Menu.get_all_parent(menu.get('id'))
role_menu_permission_list = []
for d in menu_parent:
role_menu_permission_list.append(RoleMenuPermission(role_id=pk, menu_id=d["id"]))
RoleMenuPermission.objects.bulk_create(role_menu_permission_list)
# RoleMenuPermission.objects.create(role_id=pk, menu_id=menu.get('id'))
for btn in menu.get('btns'):
if btn.get('isCheck'):
data_range = btn.get('data_range', 0) or 0
instance = RoleMenuButtonPermission.objects.create(role_id=pk, menu_button_id=btn.get('id'),
data_range=data_range)
instance.dept.set(btn.get('dept', []))
for col in menu.get('columns'):
FieldPermission.objects.update_or_create(role_id=pk, field_id=col.get('id'),
defaults={
'is_query': col.get('is_query'),
'is_create': col.get('is_create'),
'is_update': col.get('is_update')
})
if item.get('isCheck'):
RoleMenuPermission.objects.create(role_id=pk, menu_id=item["id"])
for btn in item.get('btns'):
if btn.get('isCheck'):
data_range = btn.get('data_range', 0) or 0
instance = RoleMenuButtonPermission.objects.create(role_id=pk, menu_button_id=btn.get('id'),
data_range=data_range)
instance.dept.set(btn.get('dept', []))
for col in item.get('columns'):
FieldPermission.objects.update_or_create(role_id=pk, field_id=col.get('id'),
defaults={
'is_query': col.get('is_query'),
'is_create': col.get('is_create'),
'is_update': col.get('is_update')
})
return DetailResponse(msg="授权成功")
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
@@ -291,86 +331,45 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
is_superuser = request.user.is_superuser
if is_superuser:
data = [
{
"value": 0,
"label": '仅本人数据权限'
},
{
"value": 1,
"label": '本部门及以下数据权限'
},
{
"value": 2,
"label": '本部门数据权限'
},
{
"value": 3,
"label": '全部数据权限'
},
{
"value": 4,
"label": '自定义数据权限'
}
{"value": 0, "label": '仅本人数据权限'},
{"value": 1, "label": '本部门及以下数据权限'},
{"value": 2, "label": '本部门数据权限'},
{"value": 3, "label": '全部数据权限'},
{"value": 4, "label": '自定义数据权限'}
]
return DetailResponse(data=data)
else:
data = []
params = request.query_params
data = [{"value": 0, "label": '仅本人数据权限'}]
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)
data_range_list = list(set(role_queryset))
for item in data_range_list:
if item == 0:
data = [{
"value": 0,
"label": '仅本人数据权限'
}]
elif item == 1:
data = [{
"value": 0,
"label": '仅本人数据权限'
}, {
"value": 1,
"label": '本部门及以下数据权限'
},
{
"value": 2,
"label": '本部门数据权限'
}]
elif item == 2:
data = [{
"value": 0,
"label": '仅本人数据权限'
},
{
"value": 2,
"label": '本部门数据权限'
}]
elif item == 3:
data = [{
"value": 0,
"label": '仅本人数据权限'
},
{
"value": 3,
"label": '全部数据权限'
}, ]
elif item == 4:
data = [{
"value": 0,
"label": '仅本人数据权限'
},
{
"value": 4,
"label": '自定义数据权限'
}]
else:
data = []
return DetailResponse(data=data)
return ErrorResponse(msg="参数错误")
# 权限页面进入初始化获取所有的数据权限范围
role_queryset = RoleMenuButtonPermission.objects.filter(
role__in=role_list
).values_list('data_range', flat=True)
# 通过按钮小齿轮获取指定按钮的权限
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)
data_range_list = list(set(role_queryset))
for item in data_range_list:
if item == 0:
data = data
elif item == 1:
data.extend([
{"value": 1, "label": '本部门及以下数据权限'},
{"value": 2, "label": '本部门数据权限'}
])
elif item == 2:
data.extend([{"value": 2, "label": '本部门数据权限'}])
elif item == 3:
data.extend([{"value": 3, "label": '全部数据权限'}])
elif item == 4:
data.extend([{"value": 4, "label": '自定义数据权限'}])
else:
data = []
return DetailResponse(data=data)
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
def role_to_dept_all(self, request):
@@ -379,23 +378,23 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
:param request:
:return:
"""
params = request.query_params
is_superuser = request.user.is_superuser
if is_superuser:
queryset = Dept.objects.values('id', 'name', 'parent')
else:
if not params:
return ErrorResponse(msg="参数错误")
menu_button = params.get('menu_button')
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(
dept_id=F('dept__id'),
name=F('dept__name'),
parent=F('dept__parent')
)
return DetailResponse(data=queryset)
params = request.query_params
# 当前登录用户的角色
role_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_disabled
data.append(dept)
return DetailResponse(data=data)
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
def menu_to_button(self, request):

View File

@@ -119,7 +119,6 @@ class UserUpdateSerializer(CustomModelSerializer):
"""
更改激活状态
"""
print(111, value)
if value:
self.initial_data["login_error_count"] = 0
return value
@@ -331,6 +330,10 @@ class UserViewSet(CustomModelViewSet):
if not verify_password:
old_pwd_md5 = hashlib.md5(old_pwd.encode(encoding='UTF-8')).hexdigest()
verify_password = check_password(str(old_pwd_md5), request.user.password)
# 创建用户时、自定义密码无法修改问题
if not verify_password:
old_pwd_md5 = hashlib.md5(old_pwd_md5.encode(encoding='UTF-8')).hexdigest()
verify_password = check_password(str(old_pwd_md5), request.user.password)
if verify_password:
request.user.password = make_password(hashlib.md5(new_pwd.encode(encoding='UTF-8')).hexdigest())
request.user.save()
@@ -407,11 +410,11 @@ class UserViewSet(CustomModelViewSet):
queryset = self.filter_queryset(self.get_queryset())
else:
queryset = self.filter_queryset(self.get_queryset())
print(queryset.values('id','name','dept__id'))
# print(queryset.values('id','name','dept__id'))
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True, request=request)
print(serializer.data)
# print(serializer.data)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True, request=request)

View File

@@ -1,4 +1,7 @@
# -*- coding: utf-8 -*-
from itertools import groupby
from django.db.models import F
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
@@ -35,4 +38,34 @@ class FieldPermissionMixin:
data= FieldPermission.objects.filter(
field__model=model['model'],role__in=roles
).values( 'is_create', 'is_query', 'is_update',field_name=F('field__field_name'))
"""
合并权限
这段代码首先根据 field_name 对列表进行排序,
然后使用 groupby 按 field_name 进行分组。
对于每个组,它创建一个新的字典 merged
并遍历组中的每个字典将布尔值字段使用逻辑或or操作符进行合并如果 merged 中还没有该字段,则默认为 False
其他字段(如 field_name则直接取组的关键字即 key
"""
# 使用field_name对列表进行分组, # groupby 需要先对列表进行排序,因为它只能对连续相同的元素进行分组。
grouped = groupby(sorted(list(data), key=lambda x: x['field_name']), key=lambda x: x['field_name'])
data = []
# 遍历分组,合并权限
for key, group in grouped:
# 初始化一个空字典来存储合并后的结果
merged = {}
for item in group:
# 合并权限, True值优先
merged['is_create'] = merged.get('is_create', False) or item['is_create']
merged['is_query'] = merged.get('is_query', False) or item['is_query']
merged['is_update'] = merged.get('is_update', False) or item['is_update']
merged['field_name'] = key
data.append(merged)
return DetailResponse(data=data)

View File

@@ -37,11 +37,11 @@ class CoreModelFilterBankend(BaseFilterBackend):
if any([create_datetime_after, create_datetime_before, update_datetime_after, update_datetime_before]):
create_filter = Q()
if create_datetime_after and create_datetime_before:
create_filter &= Q(create_datetime__gte=create_datetime_after) & Q(create_datetime__lte=create_datetime_before)
create_filter &= Q(create_datetime__gte=create_datetime_after) & Q(create_datetime__lte=f'{create_datetime_before} 23:59:59')
elif create_datetime_after:
create_filter &= Q(create_datetime__gte=create_datetime_after)
elif create_datetime_before:
create_filter &= Q(create_datetime__lte=create_datetime_before)
create_filter &= Q(create_datetime__lte=f'{create_datetime_before} 23:59:59')
# 更新时间范围过滤条件
update_filter = Q()

View File

@@ -32,6 +32,14 @@ class ApiLoggingMiddleware(MiddlewareMixin):
request.request_path = get_request_path(request)
def __handle_response(self, request, response):
# 判断有无log_id属性使用All记录时会出现此情况
if request.request_data.get('log_id', None) is None:
return
# 移除log_id不记录此ID
log_id = request.request_data.pop('log_id')
# request_data,request_ip由PermissionInterfaceMiddleware中间件中添加的属性
body = getattr(request, 'request_data', {})
# 请求含有password则用*替换掉(暂时先用于所有接口的password请求参数)
@@ -60,7 +68,7 @@ class ApiLoggingMiddleware(MiddlewareMixin):
'status': True if response.data.get('code') in [2000, ] else False,
'json_result': {"code": response.data.get('code'), "msg": response.data.get('msg')},
}
operation_log, creat = OperationLog.objects.update_or_create(defaults=info, id=self.operation_log_id)
operation_log, creat = OperationLog.objects.update_or_create(defaults=info, id=log_id)
if not operation_log.request_modular and settings.API_MODEL_MAP.get(request.request_path, None):
operation_log.request_modular = settings.API_MODEL_MAP[request.request_path]
operation_log.save()
@@ -71,7 +79,8 @@ class ApiLoggingMiddleware(MiddlewareMixin):
if self.methods == 'ALL' or request.method in self.methods:
log = OperationLog(request_modular=get_verbose_name(view_func.cls.queryset))
log.save()
self.operation_log_id = log.id
# self.operation_log_id = log.id
request.request_data['log_id'] = log.id
return

View File

@@ -6,13 +6,14 @@
@Created on: 2021/5/31 031 22:08
@Remark: 公共基础model类
"""
from datetime import datetime
from importlib import import_module
from django.apps import apps
from django.db import models
from django.conf import settings
from application import settings
from django.apps import apps
from django.conf import settings
from django.db import models
from rest_framework.request import Request
table_prefix = settings.TABLE_PREFIX # 数据库表名前缀
@@ -60,8 +61,24 @@ class SoftDeleteModel(models.Model):
"""
重写删除方法,直接开启软删除
"""
self.is_deleted = True
self.save(using=using)
if soft_delete:
self.is_deleted = True
self.save(using=using)
# 级联软删除关联对象
for related_object in self._meta.related_objects:
related_model = getattr(self, related_object.get_accessor_name())
# 处理一对多和多对多的关联对象
if related_object.one_to_many or related_object.many_to_many:
related_objects = related_model.all()
elif related_object.one_to_one:
related_objects = [related_model]
else:
continue
for obj in related_objects:
obj.delete(soft_delete=True)
else:
super().delete(using=using, *args, **kwargs)
class CoreModel(models.Model):
@@ -87,6 +104,111 @@ class CoreModel(models.Model):
verbose_name = '核心模型'
verbose_name_plural = verbose_name
def get_request_user(self, request: Request):
if getattr(request, "user", None):
return request.user
return None
def get_request_user_id(self, request: Request):
if getattr(request, "user", None):
return getattr(request.user, "id", None)
return None
def get_request_user_name(self, request: Request):
if getattr(request, "user", None):
return getattr(request.user, "name", None)
return None
def get_request_user_username(self, request: Request):
if getattr(request, "user", None):
return getattr(request.user, "username", None)
return None
def common_insert_data(self, request: Request):
data = {
'create_datetime': datetime.now(),
'creator': self.get_request_user(request)
}
return {**data, **self.common_update_data(request)}
def common_update_data(self, request: Request):
return {
'update_datetime': datetime.now(),
'modifier': self.get_request_user_username(request)
}
exclude_fields = [
'_state',
'pk',
'id',
'create_datetime',
'update_datetime',
'creator',
'creator_id',
'creator_pk',
'creator_name',
'modifier',
'modifier_id',
'modifier_pk',
'modifier_name',
'dept_belong_id',
]
def get_exclude_fields(self):
return self.exclude_fields
def get_all_fields(self):
return self._meta.fields
def get_all_fields_names(self):
return [field.name for field in self.get_all_fields()]
def get_need_fields_names(self):
return [field.name for field in self.get_all_fields() if field.name not in self.exclude_fields]
def to_data(self):
"""将模型转化为字典(去除不包含字段)(注意与to_dict_data区分)。
"""
res = {}
for field in self.get_need_fields_names():
field_value = getattr(self, field)
res[field] = field_value.id if (issubclass(field_value.__class__, CoreModel)) else field_value
return res
@property
def DATA(self):
return self.to_data()
def to_dict_data(self):
"""需要导出的字段去除不包含字段注意与to_data区分
"""
return {field: getattr(self, field) for field in self.get_need_fields_names()}
@property
def DICT_DATA(self):
return self.to_dict_data()
def insert(self, request):
"""插入模型
"""
assert self.pk is None, f'模型{self.__class__.__name__}还没有保存到数据中不能手动指定ID'
validated_data = {**self.common_insert_data(request), **self.DICT_DATA}
return self.__class__._default_manager.create(**validated_data)
def update(self, request, update_data: dict[str, any] = None):
"""更新模型
"""
assert isinstance(update_data, dict), 'update_data必须为字典'
validated_data = {**self.common_insert_data(request), **update_data}
for key, value in validated_data.items():
# 不允许修改id,pk,uuid字段
if key in ['id', 'pk', 'uuid']:
continue
if hasattr(self, key):
setattr(self, key, value)
self.save()
return self
def get_all_models_objects(model_name=None):
"""
@@ -97,16 +219,9 @@ def get_all_models_objects(model_name=None):
if not settings.ALL_MODELS_OBJECTS:
all_models = apps.get_models()
for item in list(all_models):
table = {
"tableName": item._meta.verbose_name,
"table": item.__name__,
"tableFields": []
}
table = {"tableName": item._meta.verbose_name, "table": item.__name__, "tableFields": []}
for field in item._meta.fields:
fields = {
"title": field.verbose_name,
"field": field.name
}
fields = {"title": field.verbose_name, "field": field.name}
table['tableFields'].append(fields)
settings.ALL_MODELS_OBJECTS.setdefault(item.__name__, {"table": table, "object": item})
if model_name:
@@ -117,25 +232,20 @@ def get_all_models_objects(model_name=None):
def get_model_from_app(app_name):
"""获取模型里的字段"""
model_module = import_module(app_name + '.models')
exclude_models = getattr(model_module, 'exclude_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)
value for key, value in model_module.__dict__.items()
if key != 'CoreModel'
and isinstance(value, type)
and issubclass(value, models.Model)
and key not in exclude_models
]
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.append({
'app': app_name,
'verbose': model._meta.verbose_name,
'model': model.__name__,
'object': model,
'fields': fields
})
fields = [{'title': field.verbose_name, 'name': field.name, 'object': field} for field in model._meta.fields]
model_list.append({'app': app_name, 'verbose': model._meta.verbose_name, 'model': model.__name__, 'object': model, 'fields': fields})
return model_list

View File

@@ -44,6 +44,35 @@ class AnonymousUserPermission(BasePermission):
return True
class SuperuserPermission(BasePermission):
"""
超级管理员权限类
"""
def has_permission(self, request, view):
if isinstance(request.user, AnonymousUser):
return False
# 判断是否是超级管理员
if request.user.is_superuser:
return True
class AdminPermission(BasePermission):
"""
普通管理员权限类
"""
def has_permission(self, request, view):
if isinstance(request.user, AnonymousUser):
return False
# 判断是否是超级管理员
is_superuser = request.user.is_superuser
# 判断是否是管理员角色
is_admin = request.user.role.values_list('admin', flat=True)
if is_superuser or True in is_admin:
return True
def ReUUID(api):
"""
将接口的uuid替换掉
@@ -81,8 +110,9 @@ class CustomPermission(BasePermission):
# ********#
if not hasattr(request.user, "role"):
return False
role_id_list = request.user.role.values_list('id',flat=True)
userApiList = RoleMenuButtonPermission.objects.filter(role__in=role_id_list).values(permission__api=F('menu_button__api'), permission__method=F('menu_button__method')) # 获取当前用户的角色拥有的所有接口
role_id_list = request.user.role.values_list('id', flat=True)
userApiList = RoleMenuButtonPermission.objects.filter(role__in=role_id_list).values(
permission__api=F('menu_button__api'), permission__method=F('menu_button__method')) # 获取当前用户的角色拥有的所有接口
ApiList = [
str(item.get('permission__api').replace('{id}', '([a-zA-Z0-9-]+)')) + ":" + str(
item.get('permission__method')) + '$' for item in userApiList if item.get('permission__api')]

View File

@@ -26,7 +26,6 @@ class CustomModelSerializer(DynamicFieldsMixin, ModelSerializer):
# 修改人的审计字段名称, 默认modifier, 继承使用时可自定义覆盖
modifier_field_id = "modifier"
modifier_name = serializers.SerializerMethodField(read_only=True)
dept_belong_id = serializers.IntegerField(required=False, allow_null=True)
def get_modifier_name(self, instance):
if not hasattr(instance, "modifier"):
@@ -52,7 +51,7 @@ class CustomModelSerializer(DynamicFieldsMixin, ModelSerializer):
format="%Y-%m-%d %H:%M:%S", required=False, read_only=True
)
update_datetime = serializers.DateTimeField(
format="%Y-%m-%d %H:%M:%S", required=False
format="%Y-%m-%d %H:%M:%S", required=False, read_only=True
)
def __init__(self, instance=None, data=empty, request=None, **kwargs):
@@ -71,11 +70,11 @@ class CustomModelSerializer(DynamicFieldsMixin, ModelSerializer):
validated_data[self.creator_field_id] = self.request.user
if (
self.dept_belong_id_field_name in self.fields.fields
and validated_data.get(self.dept_belong_id_field_name, None) is None
self.dept_belong_id_field_name in self.fields.fields
and validated_data.get(self.dept_belong_id_field_name, None) is None
):
validated_data[self.dept_belong_id_field_name] = getattr(
self.request.user, "dept_id", None
self.request.user, "dept_id", validated_data.get(self.dept_belong_id_field_name, None)
)
return super().create(validated_data)