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

This commit is contained in:
猿小天
2025-06-01 19:55:05 +08:00
127 changed files with 7141 additions and 2915 deletions

View File

@@ -19,6 +19,20 @@ class UsersInitSerializer(CustomModelSerializer):
"""
初始化获取数信息(用于生成初始化json文件)
"""
role_key = serializers.SerializerMethodField()
dept_key = serializers.SerializerMethodField()
def get_dept_key(self, obj):
if obj.dept:
return obj.dept.key
else:
return None
def get_role_key(self, obj):
if obj.role.all():
return [role.key for role in obj.role.all()]
else:
return []
def save(self, **kwargs):
instance = super().save(**kwargs)
@@ -35,7 +49,7 @@ class UsersInitSerializer(CustomModelSerializer):
model = Users
fields = ["username", "email", 'mobile', 'avatar', "name", 'gender', 'user_type', "dept", 'user_type',
'first_name', 'last_name', 'email', 'is_staff', 'is_active', 'creator', 'dept_belong_id',
'password', 'last_login', 'is_superuser']
'password', 'last_login', 'is_superuser', 'role_key' ,'dept_key']
read_only_fields = ['id']
extra_kwargs = {
'creator': {'write_only': True},
@@ -175,15 +189,21 @@ class RoleMenuInitSerializer(CustomModelSerializer):
"""
初始化角色菜单(用于生成初始化json文件)
"""
role__key = serializers.CharField(max_length=100, required=True)
menu__web_path = serializers.CharField(max_length=100, required=True)
menu__component_name = serializers.CharField(max_length=100, required=True, allow_blank=True)
role__key = serializers.CharField(source='role.key')
menu__web_path = serializers.CharField(source='menu.web_path')
menu__component_name = serializers.CharField(source='menu.component_name', allow_blank=True)
def update(self, instance, validated_data):
init_data = self.initial_data
role_id = Role.objects.filter(key=init_data['role__key']).first()
menu_id = Menu.objects.filter(web_path=init_data['menu__web_path'], component_name=init_data['menu__component_name']).first()
validated_data['role'] = role_id
validated_data['menu'] = menu_id
return super().update(instance, validated_data)
def create(self, validated_data):
init_data = self.initial_data
validated_data.pop('menu__web_path')
validated_data.pop('menu__component_name')
validated_data.pop('role__key')
role_id = Role.objects.filter(key=init_data['role__key']).first()
menu_id = Menu.objects.filter(web_path=init_data['menu__web_path'], component_name=init_data['menu__component_name']).first()
validated_data['role'] = role_id
@@ -192,7 +212,7 @@ class RoleMenuInitSerializer(CustomModelSerializer):
class Meta:
model = RoleMenuPermission
fields = ['role__key', 'menu__web_path', 'menu__component_name', 'creator', 'dept_belong_id']
fields = ['role__key', 'menu__web_path', 'menu__component_name','creator', 'dept_belong_id']
read_only_fields = ["id"]
extra_kwargs = {
'role': {'required': False},
@@ -206,14 +226,22 @@ class RoleMenuButtonInitSerializer(CustomModelSerializer):
"""
初始化角色菜单按钮(用于生成初始化json文件)
"""
role__key = serializers.CharField(max_length=100, required=True)
menu_button__value = serializers.CharField(max_length=100, required=True)
role__key = serializers.CharField(source='role.key')
menu_button__value = serializers.CharField(source='menu_button.value')
data_range = serializers.CharField(max_length=100, required=False)
def update(self, instance, validated_data):
init_data = self.initial_data
role_id = Role.objects.filter(key=init_data['role__key']).first()
menu_button_id = MenuButton.objects.filter(value=init_data['menu_button__value']).first()
validated_data['role'] = role_id
validated_data['menu_button'] = menu_button_id
instance = super().create(validated_data)
instance.dept.set([])
return super().update(instance, validated_data)
def create(self, validated_data):
init_data = self.initial_data
validated_data.pop('menu_button__value')
validated_data.pop('role__key')
role_id = Role.objects.filter(key=init_data['role__key']).first()
menu_button_id = MenuButton.objects.filter(value=init_data['menu_button__value']).first()
validated_data['role'] = role_id
@@ -223,7 +251,7 @@ class RoleMenuButtonInitSerializer(CustomModelSerializer):
return instance
def save(self, **kwargs):
if self.instance and self.initial_data.get('reset'):
if not self.instance or self.initial_data.get('reset'):
return super().save(**kwargs)
return self.instance

View File

@@ -672,6 +672,53 @@
"model": "ApiWhiteList"
}
]
},
{
"name": "下载中心",
"icon": "ele-Download",
"sort": 9,
"is_link": false,
"is_catalog": false,
"web_path": "/downloadCenter",
"component": "system/downloadCenter/index",
"component_name": "downloadCenter",
"status": true,
"cache": false,
"visible": true,
"parent": 277,
"children": [],
"menu_button": [
{
"name": "查询",
"value": "Search",
"api": "/api/system/downloadCenter/",
"method": 0
},
{
"name": "详情",
"value": "Retrieve",
"api": "/api/system/downloadCenter/{id}/",
"method": 0
},
{
"name": "新增",
"value": "Create",
"api": "/api/system/downloadCenter/",
"method": 1
},
{
"name": "编辑",
"value": "Update",
"api": "/api/system/downloadCenter/{id}/",
"method": 2
},
{
"name": "删除",
"value": "Delete",
"api": "/api/system/downloadCenter/{id}/",
"method": 3
}
]
}
],
"menu_button": [],

View File

@@ -10,7 +10,7 @@ django.setup()
from django.core.management.base import BaseCommand
from application.settings import BASE_DIR
from dvadmin.system.models import Menu, Users, Dept, Role, ApiWhiteList, Dictionary, SystemConfig
from dvadmin.system.models import Menu, Users, Dept, Role, ApiWhiteList, Dictionary, SystemConfig, RoleMenuButtonPermission, RoleMenuPermission
from dvadmin.system.fixtures.initSerializer import UsersInitSerializer, DeptInitSerializer, RoleInitSerializer, \
MenuInitSerializer, ApiWhiteListInitSerializer, DictionaryInitSerializer, SystemConfigInitSerializer, \
RoleMenuInitSerializer, RoleMenuButtonInitSerializer
@@ -57,6 +57,12 @@ class Command(BaseCommand):
def generate_system_config(self):
self.serializer_data(SystemConfigInitSerializer, SystemConfig.objects.filter(parent_id__isnull=True))
def generate_role_menu(self):
self.serializer_data(RoleMenuInitSerializer, RoleMenuPermission.objects.all())
def generate_role_menu_button(self):
self.serializer_data(RoleMenuButtonInitSerializer, RoleMenuButtonPermission.objects.all())
def handle(self, *args, **options):
generate_name = options.get('generate_name')
generate_name_dict = {
@@ -67,6 +73,8 @@ class Command(BaseCommand):
"api_white_list": self.generate_api_white_list,
"dictionary": self.generate_dictionary,
"system_config": self.generate_system_config,
"role_menu": self.generate_role_menu,
"role_menu_button": self.generate_role_menu_button,
}
if not generate_name:
for ele in generate_name_dict.keys():

View File

@@ -1,5 +1,7 @@
import hashlib
import os
from time import time
from pathlib import PurePosixPath
from django.contrib.auth.models import AbstractUser, UserManager
from django.db import models
@@ -7,8 +9,8 @@ from django.core.exceptions import ObjectDoesNotExist, ValidationError
from application import dispatch
from dvadmin.utils.models import CoreModel, table_prefix, get_custom_app_models
class Role(CoreModel):
from dvadmin3_flow.base_model import FlowBaseModel
class Role(CoreModel,FlowBaseModel):
name = models.CharField(max_length=64, verbose_name="角色名称", help_text="角色名称")
key = models.CharField(max_length=64, unique=True, verbose_name="权限字符", help_text="权限字符")
sort = models.IntegerField(default=1, verbose_name="角色顺序", help_text="角色顺序")
@@ -71,6 +73,7 @@ class Users(CoreModel, AbstractUser):
help_text="关联部门",
)
login_error_count = models.IntegerField(default=0, verbose_name="登录错误次数", help_text="登录错误次数")
pwd_change_count = models.IntegerField(default=0,blank=True, verbose_name="密码修改次数", help_text="密码修改次数")
objects = CustomUserManager()
def set_password(self, raw_password):
@@ -119,6 +122,27 @@ class Dept(CoreModel):
help_text="上级部门",
)
@classmethod
def _recursion(cls, instance, parent, result):
new_instance = getattr(instance, parent, None)
res = []
data = getattr(instance, result, None)
if data:
res.append(data)
if new_instance:
array = cls._recursion(new_instance, parent, result)
res += array
return res
@classmethod
def get_region_name(cls, obj):
"""
获取某个用户的递归所有部门名称
"""
dept_name_all = cls._recursion(obj, "parent", "name")
dept_name_all.reverse()
return "/".join(dept_name_all)
@classmethod
def recursion_all_dept(cls, dept_id: int, dept_all_list=None, dept_list=None):
"""
@@ -405,6 +429,18 @@ class FileList(CoreModel):
mime_type = models.CharField(max_length=100, blank=True, verbose_name="Mime类型", help_text="Mime类型")
size = models.CharField(max_length=36, blank=True, verbose_name="文件大小", help_text="文件大小")
md5sum = models.CharField(max_length=36, blank=True, verbose_name="文件md5", help_text="文件md5")
UPLOAD_METHOD_CHOIDES = (
(0, '默认上传'),
(1, '文件选择器上传'),
)
upload_method = models.SmallIntegerField(default=0, blank=True, null=True, choices=UPLOAD_METHOD_CHOIDES, verbose_name='上传方式', help_text='上传方式')
FILE_TYPE_CHOIDES = (
(0, '图片'),
(1, '视频'),
(2, '音频'),
(3, '其他'),
)
file_type = models.SmallIntegerField(default=3, choices=FILE_TYPE_CHOIDES, blank=True, null=True, verbose_name='文件类型', help_text='文件类型')
def save(self, *args, **kwargs):
if not self.md5sum: # file is new
@@ -595,3 +631,41 @@ class MessageCenterTargetUser(CoreModel):
db_table = table_prefix + "message_center_target_user"
verbose_name = "消息中心目标用户表"
verbose_name_plural = verbose_name
def media_file_name_downloadcenter(instance:'DownloadCenter', filename):
h = instance.md5sum
basename, ext = os.path.splitext(filename)
return PurePosixPath("files", "dlct", h[:1], h[1:2], basename + '-' + str(time()).replace('.', '') + ext.lower())
class DownloadCenter(CoreModel):
TASK_STATUS_CHOICES = [
(0, '任务已创建'),
(1, '任务进行中'),
(2, '任务完成'),
(3, '任务失败'),
]
task_name = models.CharField(max_length=255, verbose_name="任务名称", help_text="任务名称")
task_status = models.SmallIntegerField(default=0, choices=TASK_STATUS_CHOICES, verbose_name='是否可下载', help_text='是否可下载')
file_name = models.CharField(max_length=255, null=True, blank=True, verbose_name="文件名", help_text="文件名")
url = models.FileField(upload_to=media_file_name_downloadcenter, null=True, blank=True)
size = models.BigIntegerField(default=0, verbose_name="文件大小", help_text="文件大小")
md5sum = models.CharField(max_length=36, null=True, blank=True, verbose_name="文件md5", help_text="文件md5")
def save(self, *args, **kwargs):
if self.url:
if not self.md5sum: # file is new
md5 = hashlib.md5()
for chunk in self.url.chunks():
md5.update(chunk)
self.md5sum = md5.hexdigest()
if not self.size:
self.size = self.url.size
super(DownloadCenter, self).save(*args, **kwargs)
class Meta:
db_table = table_prefix + "download_center"
verbose_name = "下载中心"
verbose_name_plural = verbose_name
ordering = ("-create_datetime",)

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

@@ -0,0 +1,107 @@
from hashlib import md5
from io import BytesIO
from datetime import datetime
from time import sleep
from openpyxl import Workbook
from openpyxl.worksheet.table import Table, TableStyleInfo
from openpyxl.utils import get_column_letter
from django.core.files.base import ContentFile
from application.celery import app
from dvadmin.system.models import DownloadCenter
def is_number(num):
try:
float(num)
return True
except ValueError:
pass
try:
import unicodedata
unicodedata.numeric(num)
return True
except (TypeError, ValueError):
pass
return False
def get_string_len(string):
"""
获取字符串最大长度
:param string:
:return:
"""
length = 4
if string is None:
return length
if is_number(string):
return length
for char in string:
length += 2.1 if ord(char) > 256 else 1
return round(length, 1) if length <= 50 else 50
@app.task
def async_export_data(data: list, filename: str, dcid: int, export_field_label: dict):
instance = DownloadCenter.objects.get(pk=dcid)
instance.task_status = 1
instance.save()
sleep(2)
try:
wb = Workbook()
ws = wb.active
header_data = ["序号", *export_field_label.values()]
hidden_header = ["#", *export_field_label.keys()]
df_len_max = [get_string_len(ele) for ele in header_data]
row = get_column_letter(len(export_field_label) + 1)
column = 1
ws.append(header_data)
for index, results in enumerate(data):
results_list = []
for h_index, h_item in enumerate(hidden_header):
for key, val in results.items():
if key == h_item:
if val is None or val == "":
results_list.append("")
elif isinstance(val, datetime):
val = val.strftime("%Y-%m-%d %H:%M:%S")
results_list.append(val)
else:
results_list.append(val)
# 计算最大列宽度
result_column_width = get_string_len(val)
if h_index != 0 and result_column_width > df_len_max[h_index]:
df_len_max[h_index] = result_column_width
ws.append([index + 1, *results_list])
column += 1
#  更新列宽
for index, width in enumerate(df_len_max):
ws.column_dimensions[get_column_letter(index + 1)].width = width
tab = Table(displayName="Table", ref=f"A1:{row}{column}") # 名称管理器
style = TableStyleInfo(
name="TableStyleLight11",
showFirstColumn=True,
showLastColumn=True,
showRowStripes=True,
showColumnStripes=True,
)
tab.tableStyleInfo = style
ws.add_table(tab)
stream = BytesIO()
wb.save(stream)
stream.seek(0)
s = md5()
while True:
chunk = stream.read(1024)
if not chunk:
break
s.update(chunk)
stream.seek(0)
instance.md5sum = s.hexdigest()
instance.file_name = filename
instance.url.save(filename, ContentFile(stream.read()))
instance.task_status = 2
except Exception as e:
instance.task_status = 3
instance.description = str(e)[:250]
instance.save()

View File

@@ -18,6 +18,7 @@ from dvadmin.system.views.role_menu_button_permission import RoleMenuButtonPermi
from dvadmin.system.views.system_config import SystemConfigViewSet
from dvadmin.system.views.user import UserViewSet
from dvadmin.system.views.menu_field import MenuFieldViewSet
from dvadmin.system.views.download_center import DownloadCenterViewSet
system_url = routers.SimpleRouter()
system_url.register(r'menu', MenuViewSet)
@@ -36,6 +37,7 @@ system_url.register(r'role_menu_button_permission', RoleMenuButtonPermissionView
system_url.register(r'role_menu_permission', RoleMenuPermissionViewSet)
system_url.register(r'column', MenuFieldViewSet)
system_url.register(r'login_log', LoginLogViewSet)
system_url.register(r'download_center', DownloadCenterViewSet)
urlpatterns = [
@@ -47,7 +49,7 @@ urlpatterns = [
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('dept_lazy_tree/', DeptViewSet.as_view({'get': 'dept_lazy_tree'})),
# 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,4 +1,5 @@
# -*- coding: utf-8 -*-
import pypinyin
from django.db.models import Q
from rest_framework import serializers
@@ -15,6 +16,11 @@ 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()
@@ -36,6 +42,18 @@ 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__'
@@ -52,20 +70,28 @@ class AreaViewSet(CustomModelViewSet, FieldPermissionMixin):
"""
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

@@ -0,0 +1,49 @@
from rest_framework import serializers
from django.conf import settings
from django_filters.rest_framework import FilterSet, CharFilter
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
from dvadmin.system.models import DownloadCenter
class DownloadCenterSerializer(CustomModelSerializer):
url = serializers.SerializerMethodField(read_only=True)
def get_url(self, instance):
if self.request.query_params.get('prefix'):
if settings.ENVIRONMENT in ['local']:
prefix = 'http://127.0.0.1:8000'
elif settings.ENVIRONMENT in ['test']:
prefix = 'http://{host}/api'.format(host=self.request.get_host())
else:
prefix = 'https://{host}/api'.format(host=self.request.get_host())
return (f'{prefix}/media/{str(instance.url)}')
return f'media/{str(instance.url)}'
class Meta:
model = DownloadCenter
fields = "__all__"
read_only_fields = ["id"]
class DownloadCenterFilterSet(FilterSet):
task_name = CharFilter(field_name='task_name', lookup_expr='icontains')
file_name = CharFilter(field_name='file_name', lookup_expr='icontains')
class Meta:
model = DownloadCenter
fields = ['task_status', 'task_name', 'file_name']
class DownloadCenterViewSet(CustomModelViewSet):
queryset = DownloadCenter.objects.all()
serializer_class = DownloadCenterSerializer
filter_class = DownloadCenterFilterSet
permission_classes = []
extra_filter_class = []
def get_queryset(self):
if self.request.user.is_superuser:
return super().get_queryset()
return super().get_queryset().filter(creator=self.request.user)

View File

@@ -1,12 +1,15 @@
import hashlib
import mimetypes
import django_filters
from django.conf import settings
from django.db import connection
from rest_framework import serializers
from rest_framework.decorators import action
from application import dispatch
from dvadmin.system.models import FileList
from dvadmin.utils.json_response import DetailResponse
from dvadmin.utils.json_response import DetailResponse, SuccessResponse
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
@@ -15,8 +18,17 @@ class FileSerializer(CustomModelSerializer):
url = serializers.SerializerMethodField(read_only=True)
def get_url(self, instance):
base_url = f"{self.request.scheme}://{self.request.get_host()}/"
return base_url + (instance.file_url or (f'media/{str(instance.url)}'))
if self.request.query_params.get('prefix'):
if settings.ENVIRONMENT in ['local']:
prefix = 'http://127.0.0.1:8000'
elif settings.ENVIRONMENT in ['test']:
prefix = 'http://{host}/api'.format(host=self.request.get_host())
else:
prefix = 'https://{host}/api'.format(host=self.request.get_host())
if instance.file_url:
return instance.file_url if instance.file_url.startswith('http') else f"{prefix}/{instance.file_url}"
return (f'{prefix}/media/{str(instance.url)}')
return instance.file_url or (f'media/{str(instance.url)}')
class Meta:
model = FileList
@@ -35,6 +47,8 @@ class FileSerializer(CustomModelSerializer):
validated_data['md5sum'] = md5.hexdigest()
validated_data['engine'] = file_engine
validated_data['mime_type'] = file.content_type
ft = {'image':0,'video':1,'audio':2}.get(file.content_type.split('/')[0], None)
validated_data['file_type'] = 3 if ft is None else ft
if file_backup:
validated_data['url'] = file
if file_engine == 'oss':
@@ -64,6 +78,22 @@ class FileSerializer(CustomModelSerializer):
return super().create(validated_data)
class FileAllSerializer(CustomModelSerializer):
class Meta:
model = FileList
fields = ['id', 'name']
class FileFilter(django_filters.FilterSet):
name = django_filters.CharFilter(field_name="name", lookup_expr="icontains", help_text="文件名")
mime_type = django_filters.CharFilter(field_name="mime_type", lookup_expr="icontains", help_text="文件类型")
class Meta:
model = FileList
fields = ['name', 'mime_type', 'upload_method', 'file_type']
class FileViewSet(CustomModelViewSet):
"""
文件管理接口
@@ -75,5 +105,22 @@ class FileViewSet(CustomModelViewSet):
"""
queryset = FileList.objects.all()
serializer_class = FileSerializer
filter_fields = ['name', ]
permission_classes = []
filter_class = FileFilter
permission_classes = []
@action(methods=['GET'], detail=False)
def get_all(self, request):
data1 = self.get_serializer(self.get_queryset(), many=True).data
data2 = []
if dispatch.is_tenants_mode():
from django_tenants.utils import schema_context
with schema_context('public'):
data2 = self.get_serializer(FileList.objects.all(), many=True).data
return DetailResponse(data=data2+data1)
def list(self, request, *args, **kwargs):
if self.request.query_params.get('system', 'False') == 'True' and dispatch.is_tenants_mode():
from django_tenants.utils import schema_context
with schema_context('public'):
return super().list(request, *args, **kwargs)
return super().list(request, *args, **kwargs)

View File

@@ -4,12 +4,15 @@ 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.contrib.auth.hashers import check_password, make_password
from django.db.models import Q
from django.shortcuts import redirect
from django.utils.translation import gettext_lazy as _
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework import serializers
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenObtainPairView
@@ -97,16 +100,17 @@ class LoginSerializer(TokenObtainPairSerializer):
# 必须重置用户名为username,否则使用邮箱手机号登录会提示密码错误
attrs['username'] = user.username
data = super().validate(attrs)
data["username"] = self.user.username
data["name"] = self.user.name
data["userId"] = self.user.id
data["avatar"] = self.user.avatar
data['user_type'] = self.user.user_type
data['pwd_change_count'] = self.user.pwd_change_count
dept = getattr(self.user, 'dept', None)
if dept:
data['dept_info'] = {
'dept_id': dept.id,
'dept_name': dept.name,
}
role = getattr(self.user, 'role', None)
if role:
@@ -124,6 +128,7 @@ class LoginSerializer(TokenObtainPairSerializer):
user.is_active = False
user.save()
raise CustomValidationError("账号已被锁定,联系管理员解锁")
user.save()
count = 5 - user.login_error_count
raise CustomValidationError(f"账号/密码错误;重试{count}次后将被锁定~")

View File

@@ -120,11 +120,11 @@ class MenuViewSet(CustomModelViewSet):
"""用于前端获取当前角色的路由"""
user = request.user
if user.is_superuser:
queryset = self.queryset.filter(status=1)
queryset = self.queryset.filter(status=1).order_by("sort")
else:
role_list = user.role.values_list('id', flat=True)
menu_list = RoleMenuPermission.objects.filter(role__in=role_list).values_list('menu_id', flat=True)
queryset = Menu.objects.filter(id__in=menu_list)
queryset = Menu.objects.filter(id__in=menu_list).order_by("sort")
serializer = WebRouterSerializer(queryset, many=True, request=request)
data = serializer.data
return SuccessResponse(data=data, total=len(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
@@ -106,6 +108,8 @@ class MessageCenterTargetUserListSerializer(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

View File

@@ -10,22 +10,29 @@ from rest_framework import serializers
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import Role, Menu, MenuButton, Dept
from dvadmin.system.models import Role, Menu, MenuButton, Dept, Users
from dvadmin.system.views.dept import DeptSerializer
from dvadmin.system.views.menu import MenuSerializer
from dvadmin.system.views.menu_button import MenuButtonSerializer
from dvadmin.utils.crud_mixin import FastCrudMixin
from dvadmin.utils.field_permission import FieldPermissionMixin
from dvadmin.utils.json_response import SuccessResponse, DetailResponse
from dvadmin.utils.json_response import SuccessResponse, DetailResponse, ErrorResponse
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.validator import CustomUniqueValidator
from dvadmin.utils.viewset import CustomModelViewSet
from dvadmin.utils.permission import CustomPermission
class RoleSerializer(CustomModelSerializer):
"""
角色-序列化器
"""
users = serializers.SerializerMethodField()
@staticmethod
def get_users(instance):
users = instance.users_set.exclude(id=1).values('id', 'name', 'dept__name')
return users
class Meta:
model = Role
@@ -101,7 +108,6 @@ class MenuButtonPermissionSerializer(CustomModelSerializer):
fields = '__all__'
class RoleViewSet(CustomModelViewSet, FastCrudMixin,FieldPermissionMixin):
"""
角色管理接口
@@ -116,3 +122,82 @@ class RoleViewSet(CustomModelViewSet, FastCrudMixin,FieldPermissionMixin):
create_serializer_class = RoleCreateUpdateSerializer
update_serializer_class = RoleCreateUpdateSerializer
search_fields = ['name', 'key']
@action(methods=['PUT'], detail=True, permission_classes=[IsAuthenticated])
def set_role_users(self, request, pk):
"""
设置 角色-用户
:param request:
:return:
"""
data = request.data
direction = data.get('direction')
movedKeys = data.get('movedKeys')
role = Role.objects.get(pk=pk)
if direction == "left":
# left : 移除用户权限
role.users_set.remove(*movedKeys)
else:
# right : 添加用户权限
role.users_set.add(*movedKeys)
serializer = RoleSerializer(role)
return DetailResponse(data=serializer.data, msg="更新成功")
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated, CustomPermission])
def get_role_users(self, request):
"""
获取角色已授权、未授权的用户
已授权的用户:1
未授权的用户:0
"""
role_id = request.query_params.get('role_id', None)
if not role_id:
return ErrorResponse(msg="请选择角色")
if request.query_params.get('authorized', 0) == "1":
queryset = Users.objects.filter(role__id=role_id).exclude(is_superuser=True)
else:
queryset = Users.objects.exclude(role__id=role_id).exclude(is_superuser=True)
if name := request.query_params.get('name', None):
queryset = queryset.filter(name__icontains=name)
if dept := request.query_params.get('dept', None):
queryset = queryset.filter(dept=dept)
page = self.paginate_queryset(queryset.values('id', 'name', 'dept__name'))
if page is not None:
return self.get_paginated_response(page)
return SuccessResponse(data=page)
@action(methods=['DELETE'], detail=True, permission_classes=[IsAuthenticated, CustomPermission])
def remove_role_user(self, request, pk):
"""
角色-删除用户
"""
user_id = request.data.get('user_id', None)
if not user_id:
return ErrorResponse(msg="请选择用户")
role = self.get_object()
role.users_set.remove(*user_id)
return SuccessResponse(msg="删除成功")
@action(methods=['POST'], detail=True, permission_classes=[IsAuthenticated, CustomPermission])
def add_role_users(self, request, pk):
"""
角色-添加用户
"""
users_id = request.data.get('users_id', None)
if not users_id:
return ErrorResponse(msg="请选择用户")
role = self.get_object()
role.users_set.add(*users_id)
return DetailResponse(msg="添加成功")

View File

@@ -6,24 +6,20 @@
@Created on: 2021/6/3 003 0:30
@Remark: 菜单按钮管理
"""
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, \
MenuField
from dvadmin.system.views.menu import MenuSerializer
from dvadmin.utils.json_response import DetailResponse, ErrorResponse
from dvadmin.system.models import RoleMenuButtonPermission, Menu, Dept, MenuButton, RoleMenuPermission, \
MenuField, FieldPermission
from dvadmin.utils.json_response import DetailResponse
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
class RoleMenuButtonPermissionSerializer(CustomModelSerializer):
"""
菜单按钮-序列化
角色-菜单-按钮-权限 查询序列化
"""
class Meta:
@@ -34,7 +30,7 @@ class RoleMenuButtonPermissionSerializer(CustomModelSerializer):
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)
@@ -45,63 +41,99 @@ class RoleMenuButtonPermissionCreateUpdateSerializer(CustomModelSerializer):
read_only_fields = ["id"]
class RoleButtonPermissionSerializer(CustomModelSerializer):
class RoleMenuSerializer(CustomModelSerializer):
"""
角色按钮权限
角色-菜单 序列化
"""
isCheck = serializers.SerializerMethodField()
data_range = serializers.SerializerMethodField()
def get_isCheck(self, instance):
params = self.request.query_params
data = self.request.data
return RoleMenuPermission.objects.filter(
menu_id=instance.id,
role_id=params.get('roleId', data.get('roleId')),
).exists()
class Meta:
model = Menu
fields = ["id", "name", "parent", "is_catalog", "isCheck"]
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_params
data = self.request.data
return RoleMenuButtonPermission.objects.filter(
menu_button__id=instance['id'],
role__id=params.get('role'),
menu_button_id=instance.id,
role_id=params.get('roleId', data.get('roleId')),
).exists()
def get_data_range(self, instance):
params = self.request.query_params
obj = RoleMenuButtonPermission.objects.filter(
menu_button__id=instance['id'],
role__id=params.get('role'),
).first()
obj = self.get_role_menu_btn_prem(instance)
if obj is None:
return None
return obj.data_range
def get_role_menu_btn_perm_id(self, instance):
obj = self.get_role_menu_btn_prem(instance)
if obj is None:
return None
return obj.id
def get_dept(self, instance):
obj = self.get_role_menu_btn_prem(instance)
if obj is None:
return None
return obj.dept.all().values_list('id', flat=True)
def get_role_menu_btn_prem(self, instance):
params = self.request.query_params
data = self.request.data
obj = RoleMenuButtonPermission.objects.filter(
menu_button_id=instance.id,
role_id=params.get('roleId', data.get('roleId')),
).first()
return obj
class Meta:
model = MenuButton
fields = ['id', 'name', 'value', 'isCheck', 'data_range']
class RoleFieldPermissionSerializer(CustomModelSerializer):
class Meta:
model = FieldPermission
fields = "__all__"
fields = ['id', 'menu', 'name', 'isCheck', 'data_range', 'role_menu_btn_perm_id', 'dept']
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_params
queryset = instance.menu_field.filter(role=params.get('role')).first()
queryset = instance.menu_field.filter(role=params.get('roleId')).first()
if queryset:
return queryset.is_query
return False
def get_is_create(self, instance):
params = self.request.query_params
queryset = instance.menu_field.filter(role=params.get('role')).first()
queryset = instance.menu_field.filter(role=params.get('roleId')).first()
if queryset:
return queryset.is_create
return False
def get_is_update(self, instance):
params = self.request.query_params
queryset = instance.menu_field.filter(role=params.get('role')).first()
queryset = instance.menu_field.filter(role=params.get('roleId')).first()
if queryset:
return queryset.is_update
return False
@@ -111,54 +143,6 @@ class RoleMenuFieldSerializer(CustomModelSerializer):
fields = ['id', 'field_name', 'title', 'is_query', 'is_create', 'is_update']
class RoleMenuSerializer(CustomModelSerializer):
menus = serializers.SerializerMethodField()
def get_menus(self, instance):
menu_list = Menu.objects.filter(parent=instance['id']).values('id', 'name')
serializer = RoleMenuPermissionSerializer(menu_list, many=True, request=self.request)
return serializer.data
class Meta:
model = Menu
fields = ['id', 'name', 'menus']
class RoleMenuPermissionSerializer(CustomModelSerializer):
"""
菜单和按钮权限
"""
# name = serializers.SerializerMethodField()
isCheck = serializers.SerializerMethodField()
btns = serializers.SerializerMethodField()
columns = serializers.SerializerMethodField()
# def get_name(self, instance):
# parent_list = Menu.get_all_parent(instance['id'])
# names = [d["name"] for d in parent_list]
# return "/".join(names)
def get_isCheck(self, instance):
params = self.request.query_params
return RoleMenuPermission.objects.filter(
menu__id=instance['id'],
role__id=params.get('role'),
).exists()
def get_btns(self, instance):
btn_list = MenuButton.objects.filter(menu__id=instance['id']).values('id', 'name', 'value')
serializer = RoleButtonPermissionSerializer(btn_list, many=True, request=self.request)
return serializer.data
def get_columns(self, instance):
col_list = MenuField.objects.filter(menu=instance['id'])
serializer = RoleMenuFieldSerializer(col_list, many=True, request=self.request)
return serializer.data
class Meta:
model = Menu
fields = ['id', 'name', 'isCheck', 'btns', 'columns']
class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
"""
菜单按钮接口
@@ -174,202 +158,111 @@ 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_menu(self, request):
"""
获取 角色-菜单
:param request:
:return:
"""
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):
"""
设置 角色-菜单
:param request:
:return:
"""
data = request.data
roleId = 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_permission(self, request):
def get_role_menu_btn_field(self, request):
"""
获取 角色-菜单-按钮-列字段
:param request:
:return:
"""
params = request.query_params
# 需要授权的角色信息
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:
menu_queryset = Menu.objects.prefetch_related('menuPermission').prefetch_related(
'menufield_set')
else:
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)
menuId = 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_premission(self, request, pk):
def set_role_menu_field(self, request, pk):
"""
角色菜单和按钮及按钮范围授权:
:param request:
:param pk: role
:return:
设置 角色-菜单-列字段
"""
body = request.data
RoleMenuPermission.objects.filter(role=pk).delete()
RoleMenuButtonPermission.objects.filter(role=pk).delete()
for item in body:
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="授权成功")
data = request.data
for 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'),
})
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
def role_menu_get_button(self, request):
"""
当前用户角色和菜单获取可下拉选项的按钮:角色授权页面使用
:param request:
:return:
"""
if params := request.query_params:
if menu_id := params.get('menu', None):
is_superuser = request.user.is_superuser
if is_superuser:
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'))
return DetailResponse(data=queryset)
return ErrorResponse(msg="参数错误")
return DetailResponse(data=[], msg="更新成功")
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
def data_scope(self, request):
@action(methods=['PUT'], detail=False, permission_classes=[IsAuthenticated])
def set_role_menu_btn(self, request):
"""
获取数据权限范围:角色授权页面使用
:param request:
:return:
设置 角色-菜单-按钮
"""
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": '自定义数据权限'}
]
return DetailResponse(data=data)
data = request.data
isCheck = data.get('isCheck', None)
roleId = data.get('roleId', None)
btnId = data.get('btnId', None)
data_range = data.get('data_range', None) or 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:
params = request.query_params
data = [{"value": 0, "label": '仅本人数据权限'}]
role_list = request.user.role.values_list('id', flat=True)
# 权限页面进入初始化获取所有的数据权限范围
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)
# 删除权限:移除关联记录
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="更新成功")
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=['PUT'], detail=False, permission_classes=[IsAuthenticated])
def set_role_menu_btn_data_range(self, request):
"""
设置 角色-菜单-按钮-权限
"""
data = request.data
instance = 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):
@@ -395,55 +288,3 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
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):
"""
根据所选择菜单获取已配置的按钮/接口权限:角色授权页面使用
:param request:
:return:
"""
params = request.query_params
menu_id = params.get('menu', None)
if menu_id is None:
return ErrorResponse(msg="未获取到参数")
is_superuser = request.user.is_superuser
if is_superuser:
queryset = RoleMenuButtonPermission.objects.filter(menu_button__menu=menu_id).values(
'id',
'data_range',
'menu_button',
'menu_button__name',
'menu_button__value'
)
return DetailResponse(data=queryset)
else:
if params:
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(
'id',
'data_range',
'menu_button',
'menu_button__name',
'menu_button__value'
)
return DetailResponse(data=queryset)
return ErrorResponse(msg="未获取到参数")
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
def role_to_menu(self, request):
"""
获取角色对应的按钮权限
:param request:
:return:
"""
params = request.query_params
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()
return DetailResponse(data=queryset)

View File

@@ -286,6 +286,7 @@ class UserViewSet(CustomModelViewSet):
"dept": user.dept_id,
"is_superuser": user.is_superuser,
"role": user.role.values_list('id', flat=True),
"pwd_change_count":user.pwd_change_count
}
if hasattr(connection, 'tenant'):
result['tenant_id'] = connection.tenant and connection.tenant.id
@@ -319,7 +320,6 @@ class UserViewSet(CustomModelViewSet):
"""密码修改"""
data = request.data
old_pwd = data.get("oldPassword")
print(old_pwd)
new_pwd = data.get("newPassword")
new_pwd2 = data.get("newPassword2")
if old_pwd is None or new_pwd is None or new_pwd2 is None:
@@ -330,13 +330,33 @@ 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.password = make_password(hashlib.md5(new_pwd.encode(encoding='UTF-8')).hexdigest())
request.user.pwd_change_count += 1
request.user.save()
return DetailResponse(data=None, msg="修改成功")
else:
return ErrorResponse(msg="旧密码不正确")
@action(methods=["post"], detail=False, permission_classes=[IsAuthenticated])
def login_change_password(self, request, *args, **kwargs):
"""初次登录进行密码修改"""
data = request.data
new_pwd = data.get("password")
new_pwd2 = data.get("password_regain")
if new_pwd != new_pwd2:
return ErrorResponse(msg="两次密码不匹配")
else:
request.user.password = make_password(new_pwd)
request.user.pwd_change_count += 1
request.user.save()
return DetailResponse(data=None, msg="修改成功")
@action(methods=["PUT"], detail=True, permission_classes=[IsAuthenticated])
def reset_to_default_password(self, request,pk):
"""恢复默认密码"""

View File

@@ -5,34 +5,39 @@ from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import FieldPermission, MenuField
from dvadmin.utils.json_response import DetailResponse
from dvadmin.utils.models import get_custom_app_models
def merge_permission(data):
"""
合并权限
"""
result = {}
for item in data:
field_name = item.pop('field_name')
if field_name not in result:
result[field_name] = item
else:
for key, value in item.items():
result[field_name][key] = result[field_name][key] or value
return result
class FieldPermissionMixin:
@action(methods=['get'], detail=False,permission_classes=[IsAuthenticated])
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
def field_permission(self, request):
"""
获取字段权限
"""
finded = False
for model in get_custom_app_models():
if model['object'] is self.serializer_class.Meta.model:
finded = True
break
if finded:
break
if finded is False:
return []
model = self.serializer_class.Meta.model.__name__
user = request.user
if user.is_superuser==1:
data = MenuField.objects.filter( model=model['model']).values('field_name')
for item in data:
item['is_create'] = True
item['is_query'] = True
item['is_update'] = True
# 创建一个默认字典来存储最终的结果
if user.is_superuser == 1:
data = MenuField.objects.filter(model=model).values('field_name')
result = {item['field_name']: {"is_create": True, "is_query": True, "is_update": True} for item in data}
else:
roles = request.user.role.values_list('id', flat=True)
data= FieldPermission.objects.filter(
field__model=model['model'],role__in=roles
).values( 'is_create', 'is_query', 'is_update',field_name=F('field__field_name'))
return DetailResponse(data=data)
data = FieldPermission.objects.filter(
field__model=model, role__in=roles
).values('is_create', 'is_query', 'is_update', field_name=F('field__field_name'))
result = merge_permission(data)
return DetailResponse(data=result)

View File

@@ -22,7 +22,7 @@ from django_filters.rest_framework import DjangoFilterBackend
from django_filters.utils import get_model_field
from rest_framework.filters import BaseFilterBackend
from django_filters.conf import settings
from dvadmin.system.models import Dept, ApiWhiteList, RoleMenuButtonPermission
from dvadmin.system.models import Dept, ApiWhiteList, RoleMenuButtonPermission, MenuButton
from dvadmin.utils.models import CoreModel
class CoreModelFilterBankend(BaseFilterBackend):
@@ -33,15 +33,15 @@ class CoreModelFilterBankend(BaseFilterBackend):
create_datetime_after = request.query_params.get('create_datetime_after', None)
create_datetime_before = request.query_params.get('create_datetime_before', None)
update_datetime_after = request.query_params.get('update_datetime_after', None)
update_datetime_before = request.query_params.get('update_datetime_after', None)
update_datetime_before = request.query_params.get('update_datetime_before', None)
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()
@@ -149,13 +149,16 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
if _pk: # 判断是否是单例查询
re_api = re.sub(_pk,'{id}', api)
role_id_list = request.user.role.values_list('id', flat=True)
role_permission_list=RoleMenuButtonPermission.objects.filter(
role__in=role_id_list,
role__status=1,
menu_button__api=re_api,
menu_button__method=method).values(
'data_range'
)
# 修复权限获取bug
menu_button_ids = MenuButton.objects.filter(api=re_api,method=method).values_list('id', flat=True)
role_permission_list = []
if menu_button_ids:
role_permission_list=RoleMenuButtonPermission.objects.filter(
role__in=role_id_list,
role__status=1,
menu_button_id__in=menu_button_ids).values(
'data_range'
)
dataScope_list = [] # 权限范围列表
for ele in role_permission_list:
# 判断用户是否为超级管理员角色/如果拥有[全部数据权限]则返回所有数据
@@ -340,7 +343,7 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
from timezone_field import TimeZoneField
# 不进行 过滤的model 类
if isinstance(field, (models.JSONField, TimeZoneField)):
if isinstance(field, (models.JSONField, TimeZoneField, models.FileField)):
continue
# warn if the field doesn't exist.
if field is None:

View File

@@ -86,4 +86,5 @@ def import_to_data(file_url, field_data, m2m_fields=None):
else:
array[key] = cell_value
tables.append(array)
return tables
data = [i for i in tables if len(i) != 0]
return data

View File

@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import datetime
from urllib.parse import quote
from django.db import transaction
@@ -11,8 +12,10 @@ from rest_framework.decorators import action
from rest_framework.request import Request
from dvadmin.utils.import_export import import_to_data
from dvadmin.utils.json_response import DetailResponse
from dvadmin.utils.json_response import DetailResponse, SuccessResponse
from dvadmin.utils.request_util import get_verbose_name
from dvadmin.system.tasks import async_export_data
from dvadmin.system.models import DownloadCenter
class ImportSerializerMixin:
@@ -301,6 +304,16 @@ class ExportSerializerMixin:
assert self.export_field_label, "'%s' 请配置对应的导出模板字段。" % self.__class__.__name__
assert self.export_serializer_class, "'%s' 请配置对应的导出序列化器。" % self.__class__.__name__
data = self.export_serializer_class(queryset, many=True, request=request).data
try:
async_export_data.delay(
data,
str(f"导出{get_verbose_name(queryset)}-{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}.xlsx"),
DownloadCenter.objects.create(creator=request.user, task_name=f'{get_verbose_name(queryset)}数据导出任务', dept_belong_id=request.user.dept_id).pk,
self.export_field_label
)
return SuccessResponse(msg="导入任务已创建,请前往‘下载中心’等待下载")
except:
pass
# 导出excel 表
response = HttpResponse(content_type="application/msexcel")
response["Access-Control-Expose-Headers"] = f"Content-Disposition"

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

@@ -61,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):
@@ -216,9 +232,13 @@ 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:

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)

View File

@@ -6,6 +6,8 @@
@Created on: 2021/6/1 001 22:57
@Remark: 自定义视图集
"""
import copy
from django.db import transaction
from django_filters import DateTimeFromToRangeFilter
from django_filters.rest_framework import FilterSet
@@ -67,12 +69,14 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi
kwargs.setdefault('context', self.get_serializer_context())
# 全部以可见字段为准
can_see = self.get_menu_field(serializer_class)
# 排除掉序列化器级的字段
# sub_set = set(serializer_class._declared_fields.keys()) - set(can_see)
# for field in sub_set:
# serializer_class._declared_fields.pop(field)
# 排除掉序列化器级的字段(排除字段权限中未授权的字段)
# if not self.request.user.is_superuser:
# serializer_class.Meta.fields = can_see
# exclude_set = set(serializer_class._declared_fields.keys()) - set(can_see)
# for field in exclude_set:
# serializer_class._declared_fields.pop(field)
# meta = copy.deepcopy(serializer_class.Meta)
# meta.fields = list(can_see)
# serializer_class.Meta = meta
# 在分页器中使用
self.request.permission_fields = can_see
if isinstance(self.request.data, list):
@@ -83,15 +87,17 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi
def get_menu_field(self, serializer_class):
"""获取字段权限"""
finded = False
for model in get_custom_app_models():
if model['object'] is serializer_class.Meta.model:
finded = True
break
if finded is False:
if not any(model['object'] is serializer_class.Meta.model for model in get_custom_app_models()):
return []
return MenuField.objects.filter(model=model['model']
).values('field_name', 'title')
# 匿名用户没有角色
ret = FieldPermission.objects.filter(field__model=serializer_class.Meta.model.__name__)
if hasattr(self.request.user, 'role'):
roles = self.request.user.role.values_list('id', flat=True)
ret = ret.filter(is_query=True, role__in=roles)
return ret.values_list('field__field_name', flat=True)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data, request=request)
@@ -131,8 +137,7 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi
instance.delete()
return DetailResponse(data=[], msg="删除成功")
keys = openapi.Schema(description='主键列表', type=openapi.TYPE_ARRAY, items=openapi.TYPE_STRING)
keys = openapi.Schema(description='主键列表', type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_STRING))
@swagger_auto_schema(request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
required=['keys'],