init
This commit is contained in:
0
backend/dvadmin/system/__init__.py
Normal file
0
backend/dvadmin/system/__init__.py
Normal file
3
backend/dvadmin/system/admin.py
Normal file
3
backend/dvadmin/system/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
backend/dvadmin/system/apps.py
Normal file
6
backend/dvadmin/system/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class SystemConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'dvadmin.system'
|
||||
0
backend/dvadmin/system/fixtures/__init__.py
Normal file
0
backend/dvadmin/system/fixtures/__init__.py
Normal file
345
backend/dvadmin/system/fixtures/initSerializer.py
Normal file
345
backend/dvadmin/system/fixtures/initSerializer.py
Normal file
@@ -0,0 +1,345 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
|
||||
import django
|
||||
django.setup()
|
||||
from dvadmin.system.models import Role, Dept, Users, Menu, MenuButton, ApiWhiteList, Dictionary, SystemConfig, \
|
||||
RoleMenuPermission, RoleMenuButtonPermission
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
|
||||
|
||||
class UsersInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化获取数信息(用于生成初始化json文件)
|
||||
"""
|
||||
|
||||
def save(self, **kwargs):
|
||||
instance = super().save(**kwargs)
|
||||
role_key = self.initial_data.get('role_key', [])
|
||||
role_ids = Role.objects.filter(key__in=role_key).values_list('id', flat=True)
|
||||
instance.role.set(role_ids)
|
||||
dept_key = self.initial_data.get('dept_key', None)
|
||||
dept_id = Dept.objects.filter(key=dept_key).first()
|
||||
instance.dept = dept_id
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
class Meta:
|
||||
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']
|
||||
read_only_fields = ['id']
|
||||
extra_kwargs = {
|
||||
'creator': {'write_only': True},
|
||||
'dept_belong_id': {'write_only': True}
|
||||
}
|
||||
|
||||
|
||||
class MenuButtonInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化菜单按钮-序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = MenuButton
|
||||
fields = ['id', 'name', 'value', 'api', 'method', 'menu']
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
|
||||
class MenuInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
递归深度获取数信息(用于生成初始化json文件)
|
||||
"""
|
||||
name = serializers.CharField(required=False)
|
||||
children = serializers.SerializerMethodField()
|
||||
menu_button = serializers.SerializerMethodField()
|
||||
|
||||
def get_children(self, obj: Menu):
|
||||
data = []
|
||||
instance = Menu.objects.filter(parent_id=obj.id)
|
||||
if instance:
|
||||
serializer = MenuInitSerializer(instance=instance, many=True)
|
||||
data = serializer.data
|
||||
return data
|
||||
|
||||
def get_menu_button(self, obj: Menu):
|
||||
data = []
|
||||
instance = obj.menuPermission.order_by('method')
|
||||
if instance:
|
||||
data = list(instance.values('name', 'value', 'api', 'method'))
|
||||
return data
|
||||
|
||||
def save(self, **kwargs):
|
||||
instance = super().save(**kwargs)
|
||||
children = self.initial_data.get('children')
|
||||
menu_button = self.initial_data.get('menu_button')
|
||||
# 菜单表
|
||||
if children:
|
||||
for menu_data in children:
|
||||
menu_data['parent'] = instance.id
|
||||
filter_data = {
|
||||
"name": menu_data['name'],
|
||||
"web_path": menu_data['web_path'],
|
||||
"component": menu_data['component'],
|
||||
"component_name": menu_data['component_name'],
|
||||
}
|
||||
instance_obj = Menu.objects.filter(**filter_data).first()
|
||||
if instance_obj and not self.initial_data.get('reset'):
|
||||
continue
|
||||
serializer = MenuInitSerializer(instance_obj, data=menu_data, request=self.request)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
# 菜单按钮
|
||||
if menu_button:
|
||||
for menu_button_data in menu_button:
|
||||
menu_button_data['menu'] = instance.id
|
||||
filter_data = {
|
||||
"menu": menu_button_data['menu'],
|
||||
"value": menu_button_data['value']
|
||||
}
|
||||
instance_obj = MenuButton.objects.filter(**filter_data).first()
|
||||
serializer = MenuButtonInitSerializer(instance_obj, data=menu_button_data, request=self.request)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
return instance
|
||||
|
||||
class Meta:
|
||||
model = Menu
|
||||
fields = ['name', 'icon', 'sort', 'is_link', 'is_catalog', 'web_path', 'component', 'component_name', 'status',
|
||||
'cache', 'visible', 'parent', 'children', 'menu_button', 'creator', 'dept_belong_id']
|
||||
extra_kwargs = {
|
||||
'creator': {'write_only': True},
|
||||
'dept_belong_id': {'write_only': True}
|
||||
}
|
||||
read_only_fields = ['id', 'children']
|
||||
|
||||
|
||||
class RoleInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化获取数信息(用于生成初始化json文件)
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = Role
|
||||
fields = ['name', 'key', 'sort', 'status', 'admin',
|
||||
'creator', 'dept_belong_id']
|
||||
read_only_fields = ["id"]
|
||||
extra_kwargs = {
|
||||
'creator': {'write_only': True},
|
||||
'dept_belong_id': {'write_only': True}
|
||||
}
|
||||
|
||||
|
||||
class RoleMenuInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化角色菜单(用于生成初始化json文件)
|
||||
"""
|
||||
role_key = serializers.CharField(max_length=100,required=True)
|
||||
menu_component_name = serializers.CharField(max_length=100,required=True)
|
||||
|
||||
def create(self, validated_data):
|
||||
init_data = self.initial_data
|
||||
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(component_name=init_data['menu_component_name']).first()
|
||||
validated_data['role'] = role_id
|
||||
validated_data['menu'] = menu_id
|
||||
return super().create(validated_data)
|
||||
|
||||
class Meta:
|
||||
model = RoleMenuPermission
|
||||
fields = ['role_key','menu_component_name','creator', 'dept_belong_id']
|
||||
read_only_fields = ["id"]
|
||||
extra_kwargs = {
|
||||
'role': {'required': False},
|
||||
'menu': {'required': False},
|
||||
'creator': {'write_only': True},
|
||||
'dept_belong_id': {'write_only': True}
|
||||
}
|
||||
|
||||
|
||||
class RoleMenuButtonInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化角色菜单按钮(用于生成初始化json文件)
|
||||
"""
|
||||
role_key = serializers.CharField(max_length=100,required=True)
|
||||
menu_button_value = serializers.CharField(max_length=100,required=True)
|
||||
data_range = serializers.CharField(max_length=100, required=False)
|
||||
|
||||
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
|
||||
validated_data['menu_button'] = menu_button_id
|
||||
instance = super().create(validated_data)
|
||||
instance.dept.set([])
|
||||
return instance
|
||||
|
||||
class Meta:
|
||||
model = RoleMenuButtonPermission
|
||||
fields = ['role_key','menu_button_value','data_range','dept','creator', 'dept_belong_id']
|
||||
read_only_fields = ["id"]
|
||||
extra_kwargs = {
|
||||
'role': {'required': False},
|
||||
'menu': {'required': False},
|
||||
'creator': {'write_only': True},
|
||||
'dept_belong_id': {'write_only': True}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class ApiWhiteListInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化获取数信息(用于生成初始化json文件)
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = ApiWhiteList
|
||||
fields = ['url', 'method', 'enable_datasource', 'creator', 'dept_belong_id']
|
||||
read_only_fields = ["id"]
|
||||
extra_kwargs = {
|
||||
'creator': {'write_only': True},
|
||||
'dept_belong_id': {'write_only': True}
|
||||
}
|
||||
|
||||
|
||||
class DeptInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
递归深度获取数信息(用于生成初始化json文件)
|
||||
"""
|
||||
children = serializers.SerializerMethodField()
|
||||
|
||||
def get_children(self, obj: Dept):
|
||||
data = []
|
||||
instance = Dept.objects.filter(parent_id=obj.id)
|
||||
if instance:
|
||||
serializer = DeptInitSerializer(instance=instance, many=True)
|
||||
data = serializer.data
|
||||
return data
|
||||
|
||||
def save(self, **kwargs):
|
||||
instance = super().save(**kwargs)
|
||||
children = self.initial_data.get('children')
|
||||
if children:
|
||||
for menu_data in children:
|
||||
menu_data['parent'] = instance.id
|
||||
filter_data = {
|
||||
"name": menu_data['name'],
|
||||
"parent": menu_data['parent'],
|
||||
"key": menu_data['key']
|
||||
}
|
||||
instance_obj = Dept.objects.filter(**filter_data).first()
|
||||
if instance_obj and not self.initial_data.get('reset'):
|
||||
continue
|
||||
serializer = DeptInitSerializer(instance_obj, data=menu_data, request=self.request)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
|
||||
return instance
|
||||
|
||||
class Meta:
|
||||
model = Dept
|
||||
fields = ['name', 'sort', 'owner', 'phone', 'email', 'status', 'parent', 'creator', 'dept_belong_id',
|
||||
'children', 'key']
|
||||
extra_kwargs = {
|
||||
'creator': {'write_only': True},
|
||||
'dept_belong_id': {'write_only': True}
|
||||
}
|
||||
read_only_fields = ['id', 'children']
|
||||
|
||||
|
||||
class DictionaryInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化获取数信息(用于生成初始化json文件)
|
||||
"""
|
||||
children = serializers.SerializerMethodField()
|
||||
|
||||
def get_children(self, obj: Dictionary):
|
||||
data = []
|
||||
instance = Dictionary.objects.filter(parent_id=obj.id)
|
||||
if instance:
|
||||
serializer = DictionaryInitSerializer(instance=instance, many=True)
|
||||
data = serializer.data
|
||||
return data
|
||||
|
||||
def save(self, **kwargs):
|
||||
instance = super().save(**kwargs)
|
||||
children = self.initial_data.get('children')
|
||||
# 菜单表
|
||||
if children:
|
||||
for data in children:
|
||||
data['parent'] = instance.id
|
||||
filter_data = {
|
||||
"value": data['value'],
|
||||
"parent": data['parent']
|
||||
}
|
||||
instance_obj = Dictionary.objects.filter(**filter_data).first()
|
||||
if instance_obj and not self.initial_data.get('reset'):
|
||||
continue
|
||||
serializer = DictionaryInitSerializer(instance_obj, data=data, request=self.request)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
return instance
|
||||
|
||||
class Meta:
|
||||
model = Dictionary
|
||||
fields = ['label', 'value', 'parent', 'type', 'color', 'is_value', 'status', 'sort', 'remark', 'creator',
|
||||
'dept_belong_id', 'children']
|
||||
read_only_fields = ["id"]
|
||||
extra_kwargs = {
|
||||
'creator': {'write_only': True},
|
||||
'dept_belong_id': {'write_only': True}
|
||||
}
|
||||
|
||||
|
||||
class SystemConfigInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化获取数信息(用于生成初始化json文件)
|
||||
"""
|
||||
children = serializers.SerializerMethodField()
|
||||
|
||||
def get_children(self, obj: SystemConfig):
|
||||
data = []
|
||||
instance = SystemConfig.objects.filter(parent_id=obj.id)
|
||||
if instance:
|
||||
serializer = SystemConfigInitSerializer(instance=instance, many=True)
|
||||
data = serializer.data
|
||||
return data
|
||||
|
||||
def save(self, **kwargs):
|
||||
instance = super().save(**kwargs)
|
||||
children = self.initial_data.get('children')
|
||||
# 菜单表
|
||||
if children:
|
||||
for data in children:
|
||||
data['parent'] = instance.id
|
||||
filter_data = {
|
||||
"key": data['key'],
|
||||
"parent": data['parent']
|
||||
}
|
||||
instance_obj = SystemConfig.objects.filter(**filter_data).first()
|
||||
if instance_obj and not self.initial_data.get('reset'):
|
||||
continue
|
||||
serializer = SystemConfigInitSerializer(instance_obj, data=data, request=self.request)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
return instance
|
||||
|
||||
class Meta:
|
||||
model = SystemConfig
|
||||
fields = ['parent', 'title', 'key', 'value', 'sort', 'status', 'data_options', 'form_item_type', 'rule',
|
||||
'placeholder', 'setting', 'creator', 'dept_belong_id', 'children']
|
||||
read_only_fields = ["id"]
|
||||
extra_kwargs = {
|
||||
'creator': {'write_only': True},
|
||||
'dept_belong_id': {'write_only': True}
|
||||
}
|
||||
7
backend/dvadmin/system/fixtures/init_apiwhitelist.json
Normal file
7
backend/dvadmin/system/fixtures/init_apiwhitelist.json
Normal file
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{
|
||||
"url": "/api/system/dept_lazy_tree/",
|
||||
"method": 0,
|
||||
"enable_datasource": true
|
||||
}
|
||||
]
|
||||
36
backend/dvadmin/system/fixtures/init_dept.json
Normal file
36
backend/dvadmin/system/fixtures/init_dept.json
Normal file
@@ -0,0 +1,36 @@
|
||||
[
|
||||
{
|
||||
"name": "DVAdmin团队",
|
||||
"key": "dvadmin",
|
||||
"sort": 1,
|
||||
"owner": "",
|
||||
"phone": "",
|
||||
"email": "",
|
||||
"status": true,
|
||||
"parent": null,
|
||||
"children": [
|
||||
{
|
||||
"name": "运营部",
|
||||
"key": "",
|
||||
"sort": 2,
|
||||
"owner": "",
|
||||
"phone": "",
|
||||
"email": "",
|
||||
"status": true,
|
||||
"parent": 1,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"name": "技术部",
|
||||
"key": "technology",
|
||||
"sort": 1,
|
||||
"owner": "",
|
||||
"phone": "",
|
||||
"email": "",
|
||||
"status": true,
|
||||
"parent": 3,
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
550
backend/dvadmin/system/fixtures/init_dictionary.json
Normal file
550
backend/dvadmin/system/fixtures/init_dictionary.json
Normal file
@@ -0,0 +1,550 @@
|
||||
[
|
||||
{
|
||||
"label": "启用/禁用-布尔值",
|
||||
"value": "button_status_bool",
|
||||
"parent": null,
|
||||
"type": 0,
|
||||
"color": null,
|
||||
"is_value": false,
|
||||
"status": true,
|
||||
"sort": 1,
|
||||
"remark": null,
|
||||
"children": [
|
||||
{
|
||||
"label": "启用",
|
||||
"value": "true",
|
||||
"parent": 1,
|
||||
"type": 6,
|
||||
"color": "success",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 1,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "禁用",
|
||||
"value": "false",
|
||||
"parent": 1,
|
||||
"type": 6,
|
||||
"color": "danger",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 2,
|
||||
"remark": null,
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "系统按钮",
|
||||
"value": "system_button",
|
||||
"parent": null,
|
||||
"type": 0,
|
||||
"color": null,
|
||||
"is_value": false,
|
||||
"status": true,
|
||||
"sort": 2,
|
||||
"remark": null,
|
||||
"children": [
|
||||
{
|
||||
"label": "新增",
|
||||
"value": "Create",
|
||||
"parent": 4,
|
||||
"type": 0,
|
||||
"color": "success",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 1,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "编辑",
|
||||
"value": "Update",
|
||||
"parent": 4,
|
||||
"type": 0,
|
||||
"color": "primary",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 2,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "删除",
|
||||
"value": "Delete",
|
||||
"parent": 4,
|
||||
"type": 0,
|
||||
"color": "danger",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 3,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "详情",
|
||||
"value": "Retrieve",
|
||||
"parent": 4,
|
||||
"type": 0,
|
||||
"color": "info",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 4,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "查询",
|
||||
"value": "Search",
|
||||
"parent": 4,
|
||||
"type": 0,
|
||||
"color": "warning",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 5,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "保存",
|
||||
"value": "Save",
|
||||
"parent": 4,
|
||||
"type": 0,
|
||||
"color": "success",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 6,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "导入",
|
||||
"value": "Import",
|
||||
"parent": 4,
|
||||
"type": 0,
|
||||
"color": "primary",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 7,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "导出",
|
||||
"value": "Export",
|
||||
"parent": 4,
|
||||
"type": 0,
|
||||
"color": "warning",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 8,
|
||||
"remark": null,
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "启用/禁用-数字值",
|
||||
"value": "button_status_number",
|
||||
"parent": null,
|
||||
"type": 0,
|
||||
"color": null,
|
||||
"is_value": false,
|
||||
"status": true,
|
||||
"sort": 3,
|
||||
"remark": null,
|
||||
"children": [
|
||||
{
|
||||
"label": "启用",
|
||||
"value": "1",
|
||||
"parent": 13,
|
||||
"type": 1,
|
||||
"color": "success",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 1,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "禁用",
|
||||
"value": "0",
|
||||
"parent": 13,
|
||||
"type": 1,
|
||||
"color": "danger",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 2,
|
||||
"remark": null,
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "是/否-布尔值",
|
||||
"value": "button_whether_bool",
|
||||
"parent": null,
|
||||
"type": 0,
|
||||
"color": null,
|
||||
"is_value": false,
|
||||
"status": true,
|
||||
"sort": 4,
|
||||
"remark": null,
|
||||
"children": [
|
||||
{
|
||||
"label": "是",
|
||||
"value": "true",
|
||||
"parent": 16,
|
||||
"type": 6,
|
||||
"color": "success",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 1,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "否",
|
||||
"value": "false",
|
||||
"parent": 16,
|
||||
"type": 6,
|
||||
"color": "danger",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 2,
|
||||
"remark": null,
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "是/否-数字值",
|
||||
"value": "button_whether_number",
|
||||
"parent": null,
|
||||
"type": 0,
|
||||
"color": null,
|
||||
"is_value": false,
|
||||
"status": true,
|
||||
"sort": 5,
|
||||
"remark": null,
|
||||
"children": [
|
||||
{
|
||||
"label": "是",
|
||||
"value": "1",
|
||||
"parent": 19,
|
||||
"type": 1,
|
||||
"color": "success",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 1,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "否",
|
||||
"value": "2",
|
||||
"parent": 19,
|
||||
"type": 1,
|
||||
"color": "danger",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 2,
|
||||
"remark": null,
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "用户类型",
|
||||
"value": "user_type",
|
||||
"parent": null,
|
||||
"type": 0,
|
||||
"color": null,
|
||||
"is_value": false,
|
||||
"status": true,
|
||||
"sort": 6,
|
||||
"remark": null,
|
||||
"children": [
|
||||
{
|
||||
"label": "后台用户",
|
||||
"value": "0",
|
||||
"parent": 22,
|
||||
"type": 1,
|
||||
"color": null,
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 1,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "前台用户",
|
||||
"value": "1",
|
||||
"parent": 22,
|
||||
"type": 1,
|
||||
"color": null,
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 2,
|
||||
"remark": null,
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "表单类型",
|
||||
"value": "config_form_type",
|
||||
"parent": null,
|
||||
"type": 0,
|
||||
"color": null,
|
||||
"is_value": false,
|
||||
"status": true,
|
||||
"sort": 7,
|
||||
"remark": null,
|
||||
"children": [
|
||||
{
|
||||
"label": "text",
|
||||
"value": "0",
|
||||
"parent": 25,
|
||||
"type": 1,
|
||||
"color": null,
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 0,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "textarea",
|
||||
"value": "3",
|
||||
"parent": 25,
|
||||
"type": 1,
|
||||
"color": "",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 0,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "number",
|
||||
"value": "10",
|
||||
"parent": 25,
|
||||
"type": 1,
|
||||
"color": "",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 0,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "datetime",
|
||||
"value": "1",
|
||||
"parent": 25,
|
||||
"type": 1,
|
||||
"color": null,
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 1,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "date",
|
||||
"value": "2",
|
||||
"parent": 25,
|
||||
"type": 1,
|
||||
"color": null,
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 2,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "time",
|
||||
"value": "15",
|
||||
"parent": 25,
|
||||
"type": 1,
|
||||
"color": "",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 3,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "select",
|
||||
"value": "4",
|
||||
"parent": 25,
|
||||
"type": 1,
|
||||
"color": null,
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 4,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "checkbox",
|
||||
"value": "5",
|
||||
"parent": 25,
|
||||
"type": 1,
|
||||
"color": null,
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 5,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "radio",
|
||||
"value": "6",
|
||||
"parent": 25,
|
||||
"type": 1,
|
||||
"color": null,
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 6,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "switch",
|
||||
"value": "9",
|
||||
"parent": 25,
|
||||
"type": 1,
|
||||
"color": "",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 6,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "文件附件",
|
||||
"value": "8",
|
||||
"parent": 25,
|
||||
"type": 1,
|
||||
"color": "",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 7,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "图片(单张)",
|
||||
"value": "7",
|
||||
"parent": 25,
|
||||
"type": 1,
|
||||
"color": "",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 8,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "图片(多张)",
|
||||
"value": "12",
|
||||
"parent": 25,
|
||||
"type": 1,
|
||||
"color": "",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 9,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "数组",
|
||||
"value": "11",
|
||||
"parent": 25,
|
||||
"type": 1,
|
||||
"color": "",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 11,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "关联表",
|
||||
"value": "13",
|
||||
"parent": 25,
|
||||
"type": 1,
|
||||
"color": "",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 13,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "关联表(多选)",
|
||||
"value": "14",
|
||||
"parent": 25,
|
||||
"type": 1,
|
||||
"color": "",
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 14,
|
||||
"remark": null,
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "性别",
|
||||
"value": "gender",
|
||||
"parent": null,
|
||||
"type": 0,
|
||||
"color": null,
|
||||
"is_value": false,
|
||||
"status": true,
|
||||
"sort": 8,
|
||||
"remark": null,
|
||||
"children": [
|
||||
{
|
||||
"label": "未知",
|
||||
"value": "0",
|
||||
"parent": 42,
|
||||
"type": 1,
|
||||
"color": null,
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 0,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "男",
|
||||
"value": "1",
|
||||
"parent": 42,
|
||||
"type": 1,
|
||||
"color": null,
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 1,
|
||||
"remark": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"label": "女",
|
||||
"value": "2",
|
||||
"parent": 42,
|
||||
"type": 1,
|
||||
"color": null,
|
||||
"is_value": true,
|
||||
"status": true,
|
||||
"sort": 2,
|
||||
"remark": null,
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
646
backend/dvadmin/system/fixtures/init_menu.json
Normal file
646
backend/dvadmin/system/fixtures/init_menu.json
Normal file
@@ -0,0 +1,646 @@
|
||||
[
|
||||
{
|
||||
"name": "系统管理",
|
||||
"icon": "iconfont icon-xitongshezhi",
|
||||
"sort": 1,
|
||||
"is_link": false,
|
||||
"is_catalog": true,
|
||||
"web_path": "/system",
|
||||
"component": "",
|
||||
"component_name": "",
|
||||
"status": true,
|
||||
"cache": false,
|
||||
"visible": true,
|
||||
"parent": null,
|
||||
"children": [
|
||||
{
|
||||
"name": "菜单管理",
|
||||
"icon": "iconfont icon-caidan",
|
||||
"sort": 1,
|
||||
"is_link": false,
|
||||
"is_catalog": false,
|
||||
"web_path": "/menu",
|
||||
"component": "system/menu/index",
|
||||
"component_name": "menu",
|
||||
"status": true,
|
||||
"cache": false,
|
||||
"visible": true,
|
||||
"parent": 41,
|
||||
"children": [],
|
||||
"menu_button": [
|
||||
{
|
||||
"name": "查询",
|
||||
"value": "menu:Search",
|
||||
"api": "/api/system/menu/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "详情",
|
||||
"value": "menu:Retrieve",
|
||||
"api": "/api/system/menu/{id}/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "新增",
|
||||
"value": "menu:Create",
|
||||
"api": "/api/system/menu/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "编辑",
|
||||
"value": "menu:Update",
|
||||
"api": "/api/system/menu/{id}/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "删除",
|
||||
"value": "menu:Delete",
|
||||
"api": "/api/system/menu/{id}/",
|
||||
"method": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "菜单按钮",
|
||||
"icon": "dot-circle-o",
|
||||
"sort": 2,
|
||||
"is_link": false,
|
||||
"is_catalog": false,
|
||||
"web_path": "/menuButton",
|
||||
"component": "system/menuButton/index",
|
||||
"component_name": "menuButton",
|
||||
"status": true,
|
||||
"cache": false,
|
||||
"visible": false,
|
||||
"parent": 41,
|
||||
"children": [],
|
||||
"menu_button": [
|
||||
{
|
||||
"name": "查询",
|
||||
"value": "menu_button:Search",
|
||||
"api": "/api/system/menu_button/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "新增",
|
||||
"value": "menu_button:Create",
|
||||
"api": "/api/system/menu_button/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "编辑",
|
||||
"value": "menu_button:Update",
|
||||
"api": "/api/system/menu_button/{id}/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "删除",
|
||||
"value": "menu_button:Delete",
|
||||
"api": "/api/system/menu_button/{id}/",
|
||||
"method": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "部门管理",
|
||||
"icon": "ele-OfficeBuilding",
|
||||
"sort": 3,
|
||||
"is_link": false,
|
||||
"is_catalog": false,
|
||||
"web_path": "/dept",
|
||||
"component": "system/dept/index",
|
||||
"component_name": "dept",
|
||||
"status": true,
|
||||
"cache": false,
|
||||
"visible": true,
|
||||
"parent": 41,
|
||||
"children": [],
|
||||
"menu_button": [
|
||||
{
|
||||
"name": "查询",
|
||||
"value": "dept:Search",
|
||||
"api": "/api/system/dept/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "详情",
|
||||
"value": "dept:Retrieve",
|
||||
"api": "/api/system/dept/{id}/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "新增",
|
||||
"value": "dept:Create",
|
||||
"api": "/api/system/dept/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "编辑",
|
||||
"value": "dept:Update",
|
||||
"api": "/api/system/dept/{id}/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "删除",
|
||||
"value": "dept:Delete",
|
||||
"api": "/api/system/dept/{id}/",
|
||||
"method": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "角色管理",
|
||||
"icon": "ele-ColdDrink",
|
||||
"sort": 4,
|
||||
"is_link": false,
|
||||
"is_catalog": false,
|
||||
"web_path": "/role",
|
||||
"component": "system/role/index",
|
||||
"component_name": "role",
|
||||
"status": true,
|
||||
"cache": false,
|
||||
"visible": true,
|
||||
"parent": 41,
|
||||
"children": [],
|
||||
"menu_button": [
|
||||
{
|
||||
"name": "查询",
|
||||
"value": "role:Search",
|
||||
"api": "/api/system/role/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "详情",
|
||||
"value": "role:Retrieve",
|
||||
"api": "/api/system/role/{id}/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "新增",
|
||||
"value": "role:Create",
|
||||
"api": "/api/system/role/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "编辑",
|
||||
"value": "role:Update",
|
||||
"api": "/api/system/role/{id}/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "保存",
|
||||
"value": "role:Save",
|
||||
"api": "/api/system/role/{id}/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "删除",
|
||||
"value": "role:Delete",
|
||||
"api": "/api/system/role/{id}/",
|
||||
"method": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "用户管理",
|
||||
"icon": "iconfont icon-icon-",
|
||||
"sort": 6,
|
||||
"is_link": false,
|
||||
"is_catalog": false,
|
||||
"web_path": "/user",
|
||||
"component": "system/user/index",
|
||||
"component_name": "user",
|
||||
"status": true,
|
||||
"cache": false,
|
||||
"visible": true,
|
||||
"parent": 41,
|
||||
"children": [],
|
||||
"menu_button": [
|
||||
{
|
||||
"name": "查询",
|
||||
"value": "user:Search",
|
||||
"api": "/api/system/user/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "详情",
|
||||
"value": "user:Retrieve",
|
||||
"api": "/api/system/user/{id}/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "新增",
|
||||
"value": "user:Create",
|
||||
"api": "/api/system/user/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "导出",
|
||||
"value": "user:Export",
|
||||
"api": "/api/system/user/export/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "导入",
|
||||
"value": "user:Import",
|
||||
"api": "/api/system/user/import/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "编辑",
|
||||
"value": "user:Update",
|
||||
"api": "/api/system/user/{id}/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "重设密码",
|
||||
"value": "user:ResetPassword",
|
||||
"api": "/api/system/user/{id}/reset_password/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "重置密码",
|
||||
"value": "user:DefaultPassword",
|
||||
"api": "/api/system/user/{id}/reset_to_default_password/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "删除",
|
||||
"value": "user:Delete",
|
||||
"api": "/api/system/user/{id}/",
|
||||
"method": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "消息中心",
|
||||
"icon": "iconfont icon-xiaoxizhongxin",
|
||||
"sort": 7,
|
||||
"is_link": false,
|
||||
"is_catalog": false,
|
||||
"web_path": "/messageCenter",
|
||||
"component": "system/messageCenter/index",
|
||||
"component_name": "messageCenter",
|
||||
"status": true,
|
||||
"cache": false,
|
||||
"visible": true,
|
||||
"parent": 41,
|
||||
"children": [],
|
||||
"menu_button": [
|
||||
{
|
||||
"name": "查询",
|
||||
"value": "messageCenter:Search",
|
||||
"api": "/api/system/message_center/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "详情",
|
||||
"value": "messageCenter:Retrieve",
|
||||
"api": "/api/system/message_center/{id}/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "新增",
|
||||
"value": "messageCenter:Create",
|
||||
"api": "/api/system/message_center/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "编辑",
|
||||
"value": "messageCenter:Update",
|
||||
"api": "/api/system/message_center/{id}/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "删除",
|
||||
"value": "messageCenter:Delete",
|
||||
"api": "/api/system/menu/{id}/",
|
||||
"method": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "接口白名单",
|
||||
"icon": "ele-SetUp",
|
||||
"sort": 8,
|
||||
"is_link": false,
|
||||
"is_catalog": false,
|
||||
"web_path": "/apiWhiteList",
|
||||
"component": "system/whiteList/index",
|
||||
"component_name": "whiteList",
|
||||
"status": true,
|
||||
"cache": false,
|
||||
"visible": true,
|
||||
"parent": 41,
|
||||
"children": [],
|
||||
"menu_button": [
|
||||
{
|
||||
"name": "查询",
|
||||
"value": "api_white_list:Search",
|
||||
"api": "/api/system/api_white_list/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "详情",
|
||||
"value": "api_white_list:Retrieve",
|
||||
"api": "/api/system/api_white_list/{id}/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "新增",
|
||||
"value": "api_white_list:Create",
|
||||
"api": "/api/system/api_white_list/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "编辑",
|
||||
"value": "api_white_list:Update",
|
||||
"api": "/api/system/api_white_list/{id}/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "删除",
|
||||
"value": "api_white_list:Delete",
|
||||
"api": "/api/system/api_white_list/{id}/",
|
||||
"method": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"menu_button": []
|
||||
},
|
||||
{
|
||||
"name": "常规配置",
|
||||
"icon": "iconfont icon-configure",
|
||||
"sort": 2,
|
||||
"is_link": false,
|
||||
"is_catalog": true,
|
||||
"web_path": "/generalConfig",
|
||||
"component": "",
|
||||
"component_name": "",
|
||||
"status": true,
|
||||
"cache": false,
|
||||
"visible": true,
|
||||
"parent": null,
|
||||
"children": [
|
||||
{
|
||||
"name": "系统配置",
|
||||
"icon": "iconfont icon-system",
|
||||
"sort": 0,
|
||||
"is_link": false,
|
||||
"is_catalog": false,
|
||||
"web_path": "/config",
|
||||
"component": "system/config/index",
|
||||
"component_name": "config",
|
||||
"status": true,
|
||||
"cache": false,
|
||||
"visible": true,
|
||||
"parent": 49,
|
||||
"children": [],
|
||||
"menu_button": [
|
||||
{
|
||||
"name": "查询",
|
||||
"value": "system_config:Search",
|
||||
"api": "/api/system/system_config/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "详情",
|
||||
"value": "system_config:Retrieve",
|
||||
"api": "/api/system/system_config/{id}/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "新增",
|
||||
"value": "system_config:Create",
|
||||
"api": "/api/system/system_config/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "编辑",
|
||||
"value": "system_config:Update",
|
||||
"api": "/api/system/system_config/{id}/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "删除",
|
||||
"value": "system_config:Delete",
|
||||
"api": "/api/system/system_config/{id}/",
|
||||
"method": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "字典管理",
|
||||
"icon": "iconfont icon-dict",
|
||||
"sort": 1,
|
||||
"is_link": false,
|
||||
"is_catalog": false,
|
||||
"web_path": "/dictionary",
|
||||
"component": "system/dictionary/index",
|
||||
"component_name": "dictionary",
|
||||
"status": true,
|
||||
"cache": false,
|
||||
"visible": true,
|
||||
"parent": 49,
|
||||
"children": [],
|
||||
"menu_button": [
|
||||
{
|
||||
"name": "查询",
|
||||
"value": "dictionary:Search",
|
||||
"api": "/api/system/dictionary/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "详情",
|
||||
"value": "dictionary:Retrieve",
|
||||
"api": "/api/system/dictionary/{id}/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "新增",
|
||||
"value": "dictionary:Create",
|
||||
"api": "/api/system/dictionary/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "编辑",
|
||||
"value": "dictionary:Update",
|
||||
"api": "/api/system/dictionary/{id}/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "删除",
|
||||
"value": "dictionary:Delete",
|
||||
"api": "/api/system/dictionary/{id}/",
|
||||
"method": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "地区管理",
|
||||
"icon": "iconfont icon-Area",
|
||||
"sort": 2,
|
||||
"is_link": false,
|
||||
"is_catalog": false,
|
||||
"web_path": "/areas",
|
||||
"component": "system/areas/index",
|
||||
"component_name": "areas",
|
||||
"status": true,
|
||||
"cache": false,
|
||||
"visible": true,
|
||||
"parent": 49,
|
||||
"children": [],
|
||||
"menu_button": [
|
||||
{
|
||||
"name": "查询",
|
||||
"value": "area:Search",
|
||||
"api": "/api/system/area/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "详情",
|
||||
"value": "area:Retrieve",
|
||||
"api": "/api/system/area/{id}/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "新增",
|
||||
"value": "area:Create",
|
||||
"api": "/api/system/area/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "编辑",
|
||||
"value": "area:Update",
|
||||
"api": "/api/system/area/{id}/",
|
||||
"method": 2
|
||||
},
|
||||
{
|
||||
"name": "删除",
|
||||
"value": "area:Delete",
|
||||
"api": "/api/system/area/{id}/",
|
||||
"method": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "附件管理",
|
||||
"icon": "iconfont icon-file",
|
||||
"sort": 3,
|
||||
"is_link": false,
|
||||
"is_catalog": false,
|
||||
"web_path": "/file",
|
||||
"component": "system/fileList/index",
|
||||
"component_name": "file",
|
||||
"status": true,
|
||||
"cache": false,
|
||||
"visible": true,
|
||||
"parent": 49,
|
||||
"children": [],
|
||||
"menu_button": [
|
||||
{
|
||||
"name": "详情",
|
||||
"value": "file:Retrieve",
|
||||
"api": "/api/system/file/{id}/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "查询",
|
||||
"value": "file:Search",
|
||||
"api": "/api/system/file/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "编辑",
|
||||
"value": "file:Update",
|
||||
"api": "/api/system/file/{id}/",
|
||||
"method": 1
|
||||
},
|
||||
{
|
||||
"name": "删除",
|
||||
"value": "file:Delete",
|
||||
"api": "/api/system/file/{id}/",
|
||||
"method": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"menu_button": []
|
||||
},
|
||||
{
|
||||
"name": "日志管理",
|
||||
"icon": "iconfont icon-rizhi",
|
||||
"sort": 3,
|
||||
"is_link": false,
|
||||
"is_catalog": true,
|
||||
"web_path": "/log",
|
||||
"component": "",
|
||||
"component_name": "",
|
||||
"status": true,
|
||||
"cache": false,
|
||||
"visible": true,
|
||||
"parent": null,
|
||||
"children": [
|
||||
{
|
||||
"name": "登录日志",
|
||||
"icon": "iconfont icon-guanlidenglurizhi",
|
||||
"sort": 1,
|
||||
"is_link": false,
|
||||
"is_catalog": false,
|
||||
"web_path": "/loginLog",
|
||||
"component": "system/log/loginLog/index",
|
||||
"component_name": "loginLog",
|
||||
"status": true,
|
||||
"cache": false,
|
||||
"visible": true,
|
||||
"parent": 54,
|
||||
"children": [],
|
||||
"menu_button": [
|
||||
{
|
||||
"name": "查询",
|
||||
"value": "login_log:Search",
|
||||
"api": "/api/system/login_log/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "详情",
|
||||
"value": "login_log:Retrieve",
|
||||
"api": "/api/system/login_log/{id}/",
|
||||
"method": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "操作日志",
|
||||
"icon": "iconfont icon-caozuorizhi",
|
||||
"sort": 2,
|
||||
"is_link": false,
|
||||
"is_catalog": false,
|
||||
"web_path": "/operationLog",
|
||||
"component": "system/log/operationLog/index",
|
||||
"component_name": "operationLog",
|
||||
"status": true,
|
||||
"cache": false,
|
||||
"visible": true,
|
||||
"parent": 54,
|
||||
"children": [],
|
||||
"menu_button": [
|
||||
{
|
||||
"name": "详情",
|
||||
"value": "operation_log:Retrieve",
|
||||
"api": "/api/system/operation_log/{id}/",
|
||||
"method": 0
|
||||
},
|
||||
{
|
||||
"name": "查询",
|
||||
"value": "operation_log:Search",
|
||||
"api": "/api/system/operation_log/",
|
||||
"method": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"menu_button": []
|
||||
}
|
||||
]
|
||||
18
backend/dvadmin/system/fixtures/init_role.json
Normal file
18
backend/dvadmin/system/fixtures/init_role.json
Normal file
@@ -0,0 +1,18 @@
|
||||
[
|
||||
{
|
||||
"name": "管理员",
|
||||
"key": "admin",
|
||||
"sort": 1,
|
||||
"status": true,
|
||||
"admin": true,
|
||||
"remark": null
|
||||
},
|
||||
{
|
||||
"name": "用户",
|
||||
"key": "public",
|
||||
"sort": 2,
|
||||
"status": true,
|
||||
"admin": true,
|
||||
"remark": null
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,12 @@
|
||||
[
|
||||
{
|
||||
"role_key": "admin",
|
||||
"menu_button_value": "menu:Search",
|
||||
"data_range": 0
|
||||
},
|
||||
{
|
||||
"role_key": "public",
|
||||
"menu_button_value":"menu:Search",
|
||||
"data_range": 0
|
||||
}
|
||||
]
|
||||
10
backend/dvadmin/system/fixtures/init_rolemenupermission.json
Normal file
10
backend/dvadmin/system/fixtures/init_rolemenupermission.json
Normal file
@@ -0,0 +1,10 @@
|
||||
[
|
||||
{
|
||||
"role_key": "admin",
|
||||
"menu_component_name": "menu"
|
||||
},
|
||||
{
|
||||
"role_key": "public",
|
||||
"menu_component_name": "menu"
|
||||
}
|
||||
]
|
||||
197
backend/dvadmin/system/fixtures/init_systemconfig.json
Normal file
197
backend/dvadmin/system/fixtures/init_systemconfig.json
Normal file
@@ -0,0 +1,197 @@
|
||||
[
|
||||
{
|
||||
"parent": null,
|
||||
"title": "基础配置",
|
||||
"key": "base",
|
||||
"value": null,
|
||||
"sort": 0,
|
||||
"status": true,
|
||||
"data_options": null,
|
||||
"form_item_type": 0,
|
||||
"rule": null,
|
||||
"placeholder": null,
|
||||
"setting": null,
|
||||
"children": [
|
||||
{
|
||||
"parent": 10,
|
||||
"title": "开启验证码",
|
||||
"key": "captcha_state",
|
||||
"value": true,
|
||||
"sort": 1,
|
||||
"status": true,
|
||||
"data_options": null,
|
||||
"form_item_type": 9,
|
||||
"rule": [
|
||||
{
|
||||
"message": "必填项不能为空",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"placeholder": "请选择",
|
||||
"setting": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"parent": 10,
|
||||
"title": "创建用户默认密码",
|
||||
"key": "default_password",
|
||||
"value": "admin123456",
|
||||
"sort": 2,
|
||||
"status": true,
|
||||
"data_options": null,
|
||||
"form_item_type": 0,
|
||||
"rule": [
|
||||
{
|
||||
"message": "必填项不能为空",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"placeholder": "请输入默认密码",
|
||||
"setting": null,
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"parent": null,
|
||||
"title": "登录页配置",
|
||||
"key": "login",
|
||||
"value": null,
|
||||
"sort": 1,
|
||||
"status": true,
|
||||
"data_options": null,
|
||||
"form_item_type": 0,
|
||||
"rule": null,
|
||||
"placeholder": null,
|
||||
"setting": null,
|
||||
"children": [
|
||||
{
|
||||
"parent": 1,
|
||||
"title": "网站名称",
|
||||
"key": "site_name",
|
||||
"value": "企业级后台管理系统",
|
||||
"sort": 1,
|
||||
"status": true,
|
||||
"data_options": null,
|
||||
"form_item_type": 0,
|
||||
"rule": [
|
||||
{
|
||||
"message": "必填项不能为空",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"placeholder": "请输入网站名称",
|
||||
"setting": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"parent": 1,
|
||||
"title": "登录网站logo",
|
||||
"key": "site_logo",
|
||||
"value": null,
|
||||
"sort": 2,
|
||||
"status": true,
|
||||
"data_options": null,
|
||||
"form_item_type": 7,
|
||||
"rule": [],
|
||||
"placeholder": "请上传网站logo",
|
||||
"setting": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"parent": 1,
|
||||
"title": "登录页背景图",
|
||||
"key": "login_background",
|
||||
"value": null,
|
||||
"sort": 3,
|
||||
"status": true,
|
||||
"data_options": null,
|
||||
"form_item_type": 7,
|
||||
"rule": [],
|
||||
"placeholder": "请上传登录背景页",
|
||||
"setting": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"parent": 1,
|
||||
"title": "版权信息",
|
||||
"key": "copyright",
|
||||
"value": "2021-2022 django-vue-admin.com 版权所有",
|
||||
"sort": 4,
|
||||
"status": true,
|
||||
"data_options": null,
|
||||
"form_item_type": 0,
|
||||
"rule": [
|
||||
{
|
||||
"message": "必填项不能为空",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"placeholder": "请输入版权信息",
|
||||
"setting": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"parent": 1,
|
||||
"title": "备案信息",
|
||||
"key": "keep_record",
|
||||
"value": "晋ICP备18005113号-3",
|
||||
"sort": 5,
|
||||
"status": true,
|
||||
"data_options": null,
|
||||
"form_item_type": 0,
|
||||
"rule": [
|
||||
{
|
||||
"message": "必填项不能为空",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"placeholder": "请输入备案信息",
|
||||
"setting": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"parent": 1,
|
||||
"title": "帮助链接",
|
||||
"key": "help_url",
|
||||
"value": "https://django-vue-admin.com",
|
||||
"sort": 6,
|
||||
"status": true,
|
||||
"data_options": null,
|
||||
"form_item_type": 0,
|
||||
"rule": "",
|
||||
"placeholder": "请输入帮助信息",
|
||||
"setting": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"parent": 1,
|
||||
"title": "隐私链接",
|
||||
"key": "privacy_url",
|
||||
"value": "#",
|
||||
"sort": 7,
|
||||
"status": true,
|
||||
"data_options": null,
|
||||
"form_item_type": 0,
|
||||
"rule": [],
|
||||
"placeholder": "请填写隐私链接",
|
||||
"setting": null,
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"parent": 1,
|
||||
"title": "条款链接",
|
||||
"key": "clause_url",
|
||||
"value": "#",
|
||||
"sort": 8,
|
||||
"status": true,
|
||||
"data_options": null,
|
||||
"form_item_type": 0,
|
||||
"rule": [],
|
||||
"placeholder": "请输入条款链接",
|
||||
"setting": null,
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
59
backend/dvadmin/system/fixtures/init_users.json
Normal file
59
backend/dvadmin/system/fixtures/init_users.json
Normal file
@@ -0,0 +1,59 @@
|
||||
[
|
||||
{
|
||||
"username": "superadmin",
|
||||
"email": "dvadmin@django-vue-admin.com",
|
||||
"mobile": "13333333333",
|
||||
"avatar": null,
|
||||
"name": "超级管理员",
|
||||
"gender": 1,
|
||||
"user_type": 0,
|
||||
"role": [],
|
||||
"role_key": [
|
||||
"admin"
|
||||
],
|
||||
"dept_key": "dvadmin",
|
||||
"first_name": "",
|
||||
"last_name": "",
|
||||
"is_staff": true,
|
||||
"is_active": true,
|
||||
"password": "pbkdf2_sha256$260000$g17x5wlSiW1FZAh1Eudchw$ZeSAqj3Xak0io8v/pmPW0BX9EX5R2zFXDwbbD68oBFk=",
|
||||
"last_login": null,
|
||||
"is_superuser": true
|
||||
},
|
||||
{
|
||||
"username": "admin",
|
||||
"email": "dvadmin@django-vue-admin.com",
|
||||
"mobile": "18888888888",
|
||||
"avatar": "",
|
||||
"name": "管理员",
|
||||
"gender": 1,
|
||||
"user_type": 0,
|
||||
"role": [],
|
||||
"first_name": "",
|
||||
"last_name": "",
|
||||
"is_staff": true,
|
||||
"is_active": true,
|
||||
"password": "pbkdf2_sha256$260000$g17x5wlSiW1FZAh1Eudchw$ZeSAqj3Xak0io8v/pmPW0BX9EX5R2zFXDwbbD68oBFk=",
|
||||
"last_login": null,
|
||||
"is_superuser": false
|
||||
},
|
||||
{
|
||||
"username": "test",
|
||||
"email": "dvadmin@django-vue-admin.com",
|
||||
"mobile": "18888888888",
|
||||
"avatar": "",
|
||||
"name": "测试人员",
|
||||
"gender": 1,
|
||||
"user_type": 0,
|
||||
"role": [],
|
||||
"role_key": ["public"],
|
||||
"dept_key": "technology",
|
||||
"first_name": "",
|
||||
"last_name": "",
|
||||
"is_staff": true,
|
||||
"is_active": true,
|
||||
"password": "pbkdf2_sha256$260000$g17x5wlSiW1FZAh1Eudchw$ZeSAqj3Xak0io8v/pmPW0BX9EX5R2zFXDwbbD68oBFk=",
|
||||
"last_login": null,
|
||||
"is_superuser": false
|
||||
}
|
||||
]
|
||||
86
backend/dvadmin/system/fixtures/initialize.py
Normal file
86
backend/dvadmin/system/fixtures/initialize.py
Normal file
@@ -0,0 +1,86 @@
|
||||
# 初始化
|
||||
import os
|
||||
|
||||
import django
|
||||
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "application.settings")
|
||||
django.setup()
|
||||
|
||||
from dvadmin.utils.core_initialize import CoreInitialize
|
||||
from dvadmin.system.fixtures.initSerializer import UsersInitSerializer, DeptInitSerializer, RoleInitSerializer, \
|
||||
MenuInitSerializer, ApiWhiteListInitSerializer, DictionaryInitSerializer, SystemConfigInitSerializer, \
|
||||
RoleMenuInitSerializer, RoleMenuButtonInitSerializer
|
||||
|
||||
|
||||
class Initialize(CoreInitialize):
|
||||
|
||||
def init_dept(self):
|
||||
"""
|
||||
初始化部门信息
|
||||
"""
|
||||
self.init_base(DeptInitSerializer, unique_fields=['name', 'parent','key'])
|
||||
|
||||
def init_role(self):
|
||||
"""
|
||||
初始化角色信息
|
||||
"""
|
||||
self.init_base(RoleInitSerializer, unique_fields=['key'])
|
||||
|
||||
def init_users(self):
|
||||
"""
|
||||
初始化用户信息
|
||||
"""
|
||||
self.init_base(UsersInitSerializer, unique_fields=['username'])
|
||||
|
||||
def init_menu(self):
|
||||
"""
|
||||
初始化菜单信息
|
||||
"""
|
||||
self.init_base(MenuInitSerializer, unique_fields=['name', 'web_path', 'component', 'component_name'])
|
||||
|
||||
def init_role_menu(self):
|
||||
"""
|
||||
初始化角色菜单信息
|
||||
"""
|
||||
self.init_base(RoleMenuInitSerializer, unique_fields=['role', 'menu'])
|
||||
|
||||
def init_role_menu_button(self):
|
||||
"""
|
||||
初始化角色菜单按钮信息
|
||||
"""
|
||||
self.init_base(RoleMenuButtonInitSerializer, unique_fields=['role', 'menu_button'])
|
||||
|
||||
|
||||
def init_api_white_list(self):
|
||||
"""
|
||||
初始API白名单
|
||||
"""
|
||||
self.init_base(ApiWhiteListInitSerializer, unique_fields=['url', 'method', ])
|
||||
|
||||
def init_dictionary(self):
|
||||
"""
|
||||
初始化字典表
|
||||
"""
|
||||
self.init_base(DictionaryInitSerializer, unique_fields=['value', 'parent', ])
|
||||
|
||||
def init_system_config(self):
|
||||
"""
|
||||
初始化系统配置表
|
||||
"""
|
||||
self.init_base(SystemConfigInitSerializer, unique_fields=['key', 'parent', ])
|
||||
|
||||
def run(self):
|
||||
self.init_dept()
|
||||
self.init_role()
|
||||
self.init_users()
|
||||
self.init_menu()
|
||||
self.init_role_menu()
|
||||
self.init_role_menu_button()
|
||||
self.init_api_white_list()
|
||||
self.init_dictionary()
|
||||
self.init_system_config()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
Initialize(app='dvadmin.system').run()
|
||||
0
backend/dvadmin/system/management/__init__.py
Normal file
0
backend/dvadmin/system/management/__init__.py
Normal file
@@ -0,0 +1,95 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
import django
|
||||
from django.db.models import QuerySet
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
|
||||
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.fixtures.initSerializer import UsersInitSerializer, DeptInitSerializer, RoleInitSerializer, \
|
||||
MenuInitSerializer, ApiWhiteListInitSerializer, DictionaryInitSerializer, SystemConfigInitSerializer, \
|
||||
RoleMenuInitSerializer, RoleMenuButtonInitSerializer
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""
|
||||
生产初始化菜单: python3 manage.py generate_init_json 生成初始化的model名
|
||||
例如:
|
||||
全部生成:python3 manage.py generate_init_json
|
||||
只生成某个model的: python3 manage.py generate_init_json users
|
||||
"""
|
||||
|
||||
def serializer_data(self, serializer, query_set: QuerySet):
|
||||
serializer = serializer(query_set, many=True)
|
||||
data = json.loads(json.dumps(serializer.data, ensure_ascii=False))
|
||||
with open(os.path.join(BASE_DIR, f'init_{query_set.model._meta.model_name}.json'), 'w') as f:
|
||||
json.dump(data, f, indent=4, ensure_ascii=False)
|
||||
return
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("generate_name", nargs="*", type=str, help="初始化生成的表名")
|
||||
|
||||
def generate_users(self):
|
||||
self.serializer_data(UsersInitSerializer, Users.objects.all())
|
||||
|
||||
def generate_role(self):
|
||||
self.serializer_data(RoleInitSerializer, Role.objects.all())
|
||||
|
||||
def generate_dept(self):
|
||||
self.serializer_data(DeptInitSerializer, Dept.objects.filter(parent_id__isnull=True))
|
||||
|
||||
def generate_menu(self):
|
||||
self.serializer_data(MenuInitSerializer, Menu.objects.filter(parent_id__isnull=True))
|
||||
|
||||
def generate_api_white_list(self):
|
||||
self.serializer_data(ApiWhiteListInitSerializer, ApiWhiteList.objects.all())
|
||||
|
||||
def generate_dictionary(self):
|
||||
self.serializer_data(DictionaryInitSerializer, Dictionary.objects.filter(parent_id__isnull=True))
|
||||
|
||||
def generate_system_config(self):
|
||||
self.serializer_data(SystemConfigInitSerializer, SystemConfig.objects.filter(parent_id__isnull=True))
|
||||
|
||||
def handle(self, *args, **options):
|
||||
generate_name = options.get('generate_name')
|
||||
generate_name_dict = {
|
||||
"users": self.generate_users,
|
||||
"role": self.generate_role,
|
||||
"dept": self.generate_dept,
|
||||
"menu": self.generate_menu,
|
||||
"api_white_list": self.generate_api_white_list,
|
||||
"dictionary": self.generate_dictionary,
|
||||
"system_config": self.generate_system_config,
|
||||
}
|
||||
if not generate_name:
|
||||
for ele in generate_name_dict.keys():
|
||||
generate_name_dict[ele]()
|
||||
return
|
||||
|
||||
for generate_name in generate_name:
|
||||
if generate_name not in generate_name_dict:
|
||||
print(f"该初始化方法尚未配置\n{generate_name_dict}")
|
||||
raise Exception(f"该初始化方法尚未配置,已配置项:{list(generate_name_dict.keys())}")
|
||||
generate_name_dict[generate_name]()
|
||||
return
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# with open(os.path.join(BASE_DIR, 'temp_init_menu.json')) as f:
|
||||
# for menu_data in json.load(f):
|
||||
# menu_data['creator'] = 1
|
||||
# menu_data['modifier'] = 1
|
||||
# menu_data['dept_belong_id'] = 1
|
||||
# request.user = Users.objects.order_by('create_datetime').first()
|
||||
# serializer = MenuInitSerializer(data=menu_data, request=request)
|
||||
# serializer.is_valid(raise_exception=True)
|
||||
# serializer.save()
|
||||
a = Users.objects.filter()
|
||||
print(type(Users.objects.filter()))
|
||||
53
backend/dvadmin/system/management/commands/init.py
Normal file
53
backend/dvadmin/system/management/commands/init.py
Normal file
@@ -0,0 +1,53 @@
|
||||
import logging
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from application import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""
|
||||
项目初始化命令: python manage.py init
|
||||
"""
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"init_name",
|
||||
nargs="*",
|
||||
type=str,
|
||||
)
|
||||
parser.add_argument("-y", nargs="*")
|
||||
parser.add_argument("-Y", nargs="*")
|
||||
parser.add_argument("-n", nargs="*")
|
||||
parser.add_argument("-N", nargs="*")
|
||||
|
||||
def handle(self, *args, **options):
|
||||
reset = False
|
||||
if isinstance(options.get("y"), list) or isinstance(options.get("Y"), list):
|
||||
reset = True
|
||||
if isinstance(options.get("n"), list) or isinstance(options.get("N"), list):
|
||||
reset = False
|
||||
|
||||
for app in settings.INSTALLED_APPS:
|
||||
|
||||
try:
|
||||
exec(
|
||||
f"""
|
||||
from {app}.fixtures.initialize import Initialize
|
||||
Initialize(reset={reset},app="{app}").run()
|
||||
"""
|
||||
)
|
||||
except ModuleNotFoundError:
|
||||
# 兼容之前版本初始化
|
||||
try:
|
||||
exec(
|
||||
f"""
|
||||
from {app}.initialize import main
|
||||
main(reset={reset})
|
||||
"""
|
||||
)
|
||||
except ModuleNotFoundError:
|
||||
pass
|
||||
print("初始化数据完成!")
|
||||
83
backend/dvadmin/system/management/commands/init_area.py
Normal file
83
backend/dvadmin/system/management/commands/init_area.py
Normal file
@@ -0,0 +1,83 @@
|
||||
# 城市联动
|
||||
"""
|
||||
到乡级 使用方法
|
||||
1. https://www.npmjs.com/package/china-division 下载数据,把对应的json放入对应目录
|
||||
2. 修改此文件中对应json名
|
||||
3. 右击执行此py文件进行初始化
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
|
||||
import django
|
||||
import pypinyin
|
||||
from django.core.management import BaseCommand
|
||||
from django.db import connection
|
||||
|
||||
from application import dispatch
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
|
||||
django.setup()
|
||||
from application.settings import BASE_DIR
|
||||
from dvadmin.system.models import Area
|
||||
|
||||
area_code_list = []
|
||||
|
||||
|
||||
def area_list(code_list, pcode=None, depth=1):
|
||||
"""
|
||||
递归获取所有列表
|
||||
"""
|
||||
for code_dict in code_list:
|
||||
code = code_dict.get('code', None)
|
||||
name = code_dict.get('name', None)
|
||||
children = code_dict.get('children', None)
|
||||
pinyin = ''.join([''.join(i) for i in pypinyin.pinyin(name, style=pypinyin.NORMAL)])
|
||||
area_code_list.append(
|
||||
{
|
||||
"name": name,
|
||||
"code": code,
|
||||
"level": depth,
|
||||
"pinyin": pinyin,
|
||||
"initials": pinyin[0].upper() if pinyin else "#",
|
||||
"pcode_id": pcode,
|
||||
}
|
||||
)
|
||||
if children:
|
||||
area_list(code_list=children, pcode=code, depth=depth + 1)
|
||||
|
||||
|
||||
def main():
|
||||
with open(os.path.join(BASE_DIR, 'dvadmin', 'system', 'util', 'pca-code.json'), 'r', encoding="utf-8") as load_f:
|
||||
code_list = json.load(load_f)
|
||||
area_list(code_list)
|
||||
if Area.objects.count() == 0:
|
||||
Area.objects.bulk_create([Area(**ele) for ele in area_code_list])
|
||||
else:
|
||||
for ele in area_code_list:
|
||||
code = ele.pop("code")
|
||||
Area.objects.update_or_create(code=code, defaults=ele)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""
|
||||
项目初始化命令: python manage.py init
|
||||
"""
|
||||
|
||||
def add_arguments(self, parser):
|
||||
pass
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
||||
print(f"正在准备初始化省份数据...")
|
||||
|
||||
if dispatch.is_tenants_mode():
|
||||
from django_tenants.utils import get_tenant_model
|
||||
from django_tenants.utils import tenant_context
|
||||
for tenant in get_tenant_model().objects.exclude(schema_name='public'):
|
||||
with tenant_context(tenant):
|
||||
print(f"租户[{connection.tenant.schema_name}]初始化数据开始...")
|
||||
main()
|
||||
print(f"租户[{connection.tenant.schema_name}]初始化数据完成!")
|
||||
else:
|
||||
main()
|
||||
print("省份数据初始化数据完成!")
|
||||
0
backend/dvadmin/system/migrations/__init__.py
Normal file
0
backend/dvadmin/system/migrations/__init__.py
Normal file
556
backend/dvadmin/system/models.py
Normal file
556
backend/dvadmin/system/models.py
Normal file
@@ -0,0 +1,556 @@
|
||||
import hashlib
|
||||
import os
|
||||
|
||||
from django.contrib.auth.models import AbstractUser, UserManager
|
||||
from django.db import models
|
||||
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||
from application import dispatch
|
||||
from dvadmin.utils.models import CoreModel, table_prefix
|
||||
|
||||
STATUS_CHOICES = (
|
||||
(0, "禁用"),
|
||||
(1, "启用"),
|
||||
)
|
||||
|
||||
|
||||
class Role(CoreModel):
|
||||
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="角色顺序")
|
||||
status = models.BooleanField(default=True, verbose_name="角色状态", help_text="角色状态")
|
||||
admin = models.BooleanField(default=False, verbose_name="是否为admin", help_text="是否为admin")
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "system_role"
|
||||
verbose_name = "角色表"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("sort",)
|
||||
|
||||
|
||||
class CustomUserManager(UserManager):
|
||||
|
||||
def create_superuser(self, username, email=None, password=None, **extra_fields):
|
||||
user = super(CustomUserManager, self).create_superuser(username, email, password, **extra_fields)
|
||||
user.set_password(password)
|
||||
try:
|
||||
user.role.add(Role.objects.get(name="管理员"))
|
||||
user.save(using=self._db)
|
||||
return user
|
||||
except ObjectDoesNotExist:
|
||||
user.delete()
|
||||
raise ValidationError("角色`管理员`不存在, 创建失败, 请先执行python manage.py init")
|
||||
|
||||
|
||||
class Users(CoreModel, AbstractUser):
|
||||
username = models.CharField(max_length=150, unique=True, db_index=True, verbose_name="用户账号",
|
||||
help_text="用户账号")
|
||||
email = models.EmailField(max_length=255, verbose_name="邮箱", null=True, blank=True, help_text="邮箱")
|
||||
mobile = models.CharField(max_length=255, verbose_name="电话", null=True, blank=True, help_text="电话")
|
||||
avatar = models.CharField(max_length=255, verbose_name="头像", null=True, blank=True, help_text="头像")
|
||||
name = models.CharField(max_length=40, verbose_name="姓名", help_text="姓名")
|
||||
GENDER_CHOICES = (
|
||||
(0, "未知"),
|
||||
(1, "男"),
|
||||
(2, "女"),
|
||||
)
|
||||
gender = models.IntegerField(
|
||||
choices=GENDER_CHOICES, default=0, verbose_name="性别", null=True, blank=True, help_text="性别"
|
||||
)
|
||||
USER_TYPE = (
|
||||
(0, "后台用户"),
|
||||
(1, "前台用户"),
|
||||
)
|
||||
user_type = models.IntegerField(
|
||||
choices=USER_TYPE, default=0, verbose_name="用户类型", null=True, blank=True, help_text="用户类型"
|
||||
)
|
||||
post = models.ManyToManyField(to="Post", blank=True, verbose_name="关联岗位", db_constraint=False,
|
||||
help_text="关联岗位")
|
||||
role = models.ManyToManyField(to="Role", blank=True, verbose_name="关联角色", db_constraint=False,
|
||||
help_text="关联角色")
|
||||
dept = models.ForeignKey(
|
||||
to="Dept",
|
||||
verbose_name="所属部门",
|
||||
on_delete=models.PROTECT,
|
||||
db_constraint=False,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="关联部门",
|
||||
)
|
||||
objects = CustomUserManager()
|
||||
|
||||
def set_password(self, raw_password):
|
||||
super().set_password(hashlib.md5(raw_password.encode(encoding="UTF-8")).hexdigest())
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "system_users"
|
||||
verbose_name = "用户表"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("-create_datetime",)
|
||||
|
||||
|
||||
class Post(CoreModel):
|
||||
name = models.CharField(null=False, max_length=64, verbose_name="岗位名称", help_text="岗位名称")
|
||||
code = models.CharField(max_length=32, verbose_name="岗位编码", help_text="岗位编码")
|
||||
sort = models.IntegerField(default=1, verbose_name="岗位顺序", help_text="岗位顺序")
|
||||
STATUS_CHOICES = (
|
||||
(0, "离职"),
|
||||
(1, "在职"),
|
||||
)
|
||||
status = models.IntegerField(choices=STATUS_CHOICES, default=1, verbose_name="岗位状态", help_text="岗位状态")
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "system_post"
|
||||
verbose_name = "岗位表"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("sort",)
|
||||
|
||||
|
||||
class Dept(CoreModel):
|
||||
name = models.CharField(max_length=64, verbose_name="部门名称", help_text="部门名称")
|
||||
key = models.CharField(max_length=64, unique=True, null=True, blank=True, verbose_name="关联字符",
|
||||
help_text="关联字符")
|
||||
sort = models.IntegerField(default=1, verbose_name="显示排序", help_text="显示排序")
|
||||
owner = models.CharField(max_length=32, verbose_name="负责人", null=True, blank=True, help_text="负责人")
|
||||
phone = models.CharField(max_length=32, verbose_name="联系电话", null=True, blank=True, help_text="联系电话")
|
||||
email = models.EmailField(max_length=32, verbose_name="邮箱", null=True, blank=True, help_text="邮箱")
|
||||
status = models.BooleanField(default=True, verbose_name="部门状态", null=True, blank=True, help_text="部门状态")
|
||||
parent = models.ForeignKey(
|
||||
to="Dept",
|
||||
on_delete=models.CASCADE,
|
||||
default=None,
|
||||
verbose_name="上级部门",
|
||||
db_constraint=False,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="上级部门",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def recursion_all_dept(cls, dept_id: int, dept_all_list=None, dept_list=None):
|
||||
"""
|
||||
递归获取部门的所有下级部门
|
||||
:param dept_id: 需要获取的id
|
||||
:param dept_all_list: 所有列表
|
||||
:param dept_list: 递归list
|
||||
:return:
|
||||
"""
|
||||
if not dept_all_list:
|
||||
dept_all_list = Dept.objects.values("id", "parent")
|
||||
if dept_list is None:
|
||||
dept_list = [dept_id]
|
||||
for ele in dept_all_list:
|
||||
if ele.get("parent") == dept_id:
|
||||
dept_list.append(ele.get("id"))
|
||||
cls.recursion_all_dept(ele.get("id"), dept_all_list, dept_list)
|
||||
return list(set(dept_list))
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "system_dept"
|
||||
verbose_name = "部门表"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("sort",)
|
||||
|
||||
|
||||
class Menu(CoreModel):
|
||||
parent = models.ForeignKey(
|
||||
to="Menu",
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="上级菜单",
|
||||
null=True,
|
||||
blank=True,
|
||||
db_constraint=False,
|
||||
help_text="上级菜单",
|
||||
)
|
||||
icon = models.CharField(max_length=64, verbose_name="菜单图标", null=True, blank=True, help_text="菜单图标")
|
||||
name = models.CharField(max_length=64, verbose_name="菜单名称", help_text="菜单名称")
|
||||
sort = models.IntegerField(default=1, verbose_name="显示排序", null=True, blank=True, help_text="显示排序")
|
||||
ISLINK_CHOICES = (
|
||||
(0, "否"),
|
||||
(1, "是"),
|
||||
)
|
||||
is_link = models.BooleanField(default=False, verbose_name="是否外链", help_text="是否外链")
|
||||
is_catalog = models.BooleanField(default=False, verbose_name="是否目录", help_text="是否目录")
|
||||
web_path = models.CharField(max_length=128, verbose_name="路由地址", null=True, blank=True, help_text="路由地址")
|
||||
component = models.CharField(max_length=128, verbose_name="组件地址", null=True, blank=True, help_text="组件地址")
|
||||
component_name = models.CharField(max_length=50, verbose_name="组件名称", null=True, blank=True,
|
||||
help_text="组件名称")
|
||||
status = models.BooleanField(default=True, blank=True, verbose_name="菜单状态", help_text="菜单状态")
|
||||
cache = models.BooleanField(default=False, blank=True, verbose_name="是否页面缓存", help_text="是否页面缓存")
|
||||
visible = models.BooleanField(default=True, blank=True, verbose_name="侧边栏中是否显示",
|
||||
help_text="侧边栏中是否显示")
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "system_menu"
|
||||
verbose_name = "菜单表"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("sort",)
|
||||
|
||||
|
||||
class MenuButton(CoreModel):
|
||||
menu = models.ForeignKey(
|
||||
to="Menu",
|
||||
db_constraint=False,
|
||||
related_name="menuPermission",
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="关联菜单",
|
||||
help_text="关联菜单",
|
||||
)
|
||||
name = models.CharField(max_length=64, verbose_name="名称", help_text="名称")
|
||||
value = models.CharField(unique=True, max_length=64, verbose_name="权限值", help_text="权限值")
|
||||
api = models.CharField(max_length=200, verbose_name="接口地址", help_text="接口地址")
|
||||
METHOD_CHOICES = (
|
||||
(0, "GET"),
|
||||
(1, "POST"),
|
||||
(2, "PUT"),
|
||||
(3, "DELETE"),
|
||||
)
|
||||
method = models.IntegerField(default=0, verbose_name="接口请求方法", null=True, blank=True,
|
||||
help_text="接口请求方法")
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "system_menu_button"
|
||||
verbose_name = "菜单权限表"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("-name",)
|
||||
|
||||
|
||||
class RoleMenuPermission(CoreModel):
|
||||
role = models.ForeignKey(
|
||||
to="Role",
|
||||
db_constraint=False,
|
||||
related_name="role_menu",
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="关联角色",
|
||||
help_text="关联角色",
|
||||
)
|
||||
menu = models.ForeignKey(
|
||||
to="Menu",
|
||||
db_constraint=False,
|
||||
related_name="role_menu",
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="关联菜单",
|
||||
help_text="关联菜单",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "role_menu_permission"
|
||||
verbose_name = "角色菜单权限表"
|
||||
verbose_name_plural = verbose_name
|
||||
# ordering = ("-create_datetime",)
|
||||
|
||||
|
||||
class RoleMenuButtonPermission(CoreModel):
|
||||
role = models.ForeignKey(
|
||||
to="Role",
|
||||
db_constraint=False,
|
||||
related_name="role_menu_button",
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="关联角色",
|
||||
help_text="关联角色",
|
||||
)
|
||||
menu_button = models.ForeignKey(
|
||||
to="MenuButton",
|
||||
db_constraint=False,
|
||||
related_name="menu_button_permission",
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="关联菜单按钮",
|
||||
help_text="关联菜单按钮",
|
||||
null=True,
|
||||
blank=True
|
||||
)
|
||||
DATASCOPE_CHOICES = (
|
||||
(0, "仅本人数据权限"),
|
||||
(1, "本部门及以下数据权限"),
|
||||
(2, "本部门数据权限"),
|
||||
(3, "全部数据权限"),
|
||||
(4, "自定数据权限"),
|
||||
)
|
||||
data_range = models.IntegerField(default=0, choices=DATASCOPE_CHOICES, verbose_name="数据权限范围",
|
||||
help_text="数据权限范围")
|
||||
dept = models.ManyToManyField(to="Dept", blank=True, verbose_name="数据权限-关联部门", db_constraint=False,
|
||||
help_text="数据权限-关联部门")
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "role_menu_button_permission"
|
||||
verbose_name = "角色按钮权限表"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("-create_datetime",)
|
||||
|
||||
|
||||
class Dictionary(CoreModel):
|
||||
TYPE_LIST = (
|
||||
(0, "text"),
|
||||
(1, "number"),
|
||||
(2, "date"),
|
||||
(3, "datetime"),
|
||||
(4, "time"),
|
||||
(5, "files"),
|
||||
(6, "boolean"),
|
||||
(7, "images"),
|
||||
)
|
||||
label = models.CharField(max_length=100, blank=True, null=True, verbose_name="字典名称", help_text="字典名称")
|
||||
value = models.CharField(max_length=200, blank=True, null=True, verbose_name="字典编号",help_text="字典编号/实际值")
|
||||
parent = models.ForeignKey(
|
||||
to="self",
|
||||
related_name="sublist",
|
||||
db_constraint=False,
|
||||
on_delete=models.PROTECT,
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name="父级",
|
||||
help_text="父级",
|
||||
)
|
||||
type = models.IntegerField(choices=TYPE_LIST, default=0, verbose_name="数据值类型", help_text="数据值类型")
|
||||
color = models.CharField(max_length=20, blank=True, null=True, verbose_name="颜色", help_text="颜色")
|
||||
is_value = models.BooleanField(default=False, verbose_name="是否为value值",
|
||||
help_text="是否为value值,用来做具体值存放")
|
||||
status = models.BooleanField(default=True, verbose_name="状态", help_text="状态")
|
||||
sort = models.IntegerField(default=1, verbose_name="显示排序", null=True, blank=True, help_text="显示排序")
|
||||
remark = models.CharField(max_length=2000, blank=True, null=True, verbose_name="备注", help_text="备注")
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "system_dictionary"
|
||||
verbose_name = "字典表"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("sort",)
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
super().save(force_insert, force_update, using, update_fields)
|
||||
dispatch.refresh_dictionary() # 有更新则刷新字典配置
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
res = super().delete(using, keep_parents)
|
||||
dispatch.refresh_dictionary()
|
||||
return res
|
||||
|
||||
|
||||
class OperationLog(CoreModel):
|
||||
request_modular = models.CharField(max_length=64, verbose_name="请求模块", null=True, blank=True,
|
||||
help_text="请求模块")
|
||||
request_path = models.CharField(max_length=400, verbose_name="请求地址", null=True, blank=True,
|
||||
help_text="请求地址")
|
||||
request_body = models.TextField(verbose_name="请求参数", null=True, blank=True, help_text="请求参数")
|
||||
request_method = models.CharField(max_length=8, verbose_name="请求方式", null=True, blank=True,
|
||||
help_text="请求方式")
|
||||
request_msg = models.TextField(verbose_name="操作说明", null=True, blank=True, help_text="操作说明")
|
||||
request_ip = models.CharField(max_length=32, verbose_name="请求ip地址", null=True, blank=True,
|
||||
help_text="请求ip地址")
|
||||
request_browser = models.CharField(max_length=64, verbose_name="请求浏览器", null=True, blank=True,
|
||||
help_text="请求浏览器")
|
||||
response_code = models.CharField(max_length=32, verbose_name="响应状态码", null=True, blank=True,
|
||||
help_text="响应状态码")
|
||||
request_os = models.CharField(max_length=64, verbose_name="操作系统", null=True, blank=True, help_text="操作系统")
|
||||
json_result = models.TextField(verbose_name="返回信息", null=True, blank=True, help_text="返回信息")
|
||||
status = models.BooleanField(default=False, verbose_name="响应状态", help_text="响应状态")
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "system_operation_log"
|
||||
verbose_name = "操作日志"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("-create_datetime",)
|
||||
|
||||
|
||||
def media_file_name(instance, filename):
|
||||
h = instance.md5sum
|
||||
basename, ext = os.path.splitext(filename)
|
||||
return os.path.join("files", h[:1], h[1:2], h + ext.lower())
|
||||
|
||||
|
||||
class FileList(CoreModel):
|
||||
name = models.CharField(max_length=200, null=True, blank=True, verbose_name="名称", help_text="名称")
|
||||
url = models.FileField(upload_to=media_file_name, null=True, blank=True,)
|
||||
file_url = models.CharField(max_length=255, blank=True, verbose_name="文件地址", help_text="文件地址")
|
||||
engine = models.CharField(max_length=100, default='local', blank=True, verbose_name="引擎", help_text="引擎")
|
||||
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")
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
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
|
||||
if not self.file_url:
|
||||
url = media_file_name(self,self.name)
|
||||
self.file_url = f'media/{url}'
|
||||
super(FileList, self).save(*args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "system_file_list"
|
||||
verbose_name = "文件管理"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("-create_datetime",)
|
||||
|
||||
|
||||
class Area(CoreModel):
|
||||
name = models.CharField(max_length=100, verbose_name="名称", help_text="名称")
|
||||
code = models.CharField(max_length=20, verbose_name="地区编码", help_text="地区编码", unique=True, db_index=True)
|
||||
level = models.BigIntegerField(verbose_name="地区层级(1省份 2城市 3区县 4乡级)",
|
||||
help_text="地区层级(1省份 2城市 3区县 4乡级)")
|
||||
pinyin = models.CharField(max_length=255, verbose_name="拼音", help_text="拼音")
|
||||
initials = models.CharField(max_length=20, verbose_name="首字母", help_text="首字母")
|
||||
enable = models.BooleanField(default=True, verbose_name="是否启用", help_text="是否启用")
|
||||
pcode = models.ForeignKey(
|
||||
to="self",
|
||||
verbose_name="父地区编码",
|
||||
to_field="code",
|
||||
on_delete=models.CASCADE,
|
||||
db_constraint=False,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="父地区编码",
|
||||
)
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "system_area"
|
||||
verbose_name = "地区表"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("code",)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name}"
|
||||
|
||||
|
||||
class ApiWhiteList(CoreModel):
|
||||
url = models.CharField(max_length=200, help_text="url地址", verbose_name="url")
|
||||
METHOD_CHOICES = (
|
||||
(0, "GET"),
|
||||
(1, "POST"),
|
||||
(2, "PUT"),
|
||||
(3, "DELETE"),
|
||||
)
|
||||
method = models.IntegerField(default=0, verbose_name="接口请求方法", null=True, blank=True,
|
||||
help_text="接口请求方法")
|
||||
enable_datasource = models.BooleanField(default=True, verbose_name="激活数据权限", help_text="激活数据权限",
|
||||
blank=True)
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "api_white_list"
|
||||
verbose_name = "接口白名单"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("-create_datetime",)
|
||||
|
||||
|
||||
class SystemConfig(CoreModel):
|
||||
parent = models.ForeignKey(
|
||||
to="self",
|
||||
verbose_name="父级",
|
||||
on_delete=models.CASCADE,
|
||||
db_constraint=False,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="父级",
|
||||
)
|
||||
title = models.CharField(max_length=50, verbose_name="标题", help_text="标题")
|
||||
key = models.CharField(max_length=20, verbose_name="键", help_text="键", db_index=True)
|
||||
value = models.JSONField(max_length=100, verbose_name="值", help_text="值", null=True, blank=True)
|
||||
sort = models.IntegerField(default=0, verbose_name="排序", help_text="排序", blank=True)
|
||||
status = models.BooleanField(default=True, verbose_name="启用状态", help_text="启用状态")
|
||||
data_options = models.JSONField(verbose_name="数据options", help_text="数据options", null=True, blank=True)
|
||||
FORM_ITEM_TYPE_LIST = (
|
||||
(0, "text"),
|
||||
(1, "datetime"),
|
||||
(2, "date"),
|
||||
(3, "textarea"),
|
||||
(4, "select"),
|
||||
(5, "checkbox"),
|
||||
(6, "radio"),
|
||||
(7, "img"),
|
||||
(8, "file"),
|
||||
(9, "switch"),
|
||||
(10, "number"),
|
||||
(11, "array"),
|
||||
(12, "imgs"),
|
||||
(13, "foreignkey"),
|
||||
(14, "manytomany"),
|
||||
(15, "time"),
|
||||
)
|
||||
form_item_type = models.IntegerField(
|
||||
choices=FORM_ITEM_TYPE_LIST, verbose_name="表单类型", help_text="表单类型", default=0, blank=True
|
||||
)
|
||||
rule = models.JSONField(null=True, blank=True, verbose_name="校验规则", help_text="校验规则")
|
||||
placeholder = models.CharField(max_length=50, null=True, blank=True, verbose_name="提示信息", help_text="提示信息")
|
||||
setting = models.JSONField(null=True, blank=True, verbose_name="配置", help_text="配置")
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "system_config"
|
||||
verbose_name = "系统配置表"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("sort",)
|
||||
unique_together = (("key", "parent_id"),)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.title}"
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
super().save(force_insert, force_update, using, update_fields)
|
||||
dispatch.refresh_system_config() # 有更新则刷新系统配置
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
res = super().delete(using, keep_parents)
|
||||
dispatch.refresh_system_config()
|
||||
return res
|
||||
|
||||
|
||||
class LoginLog(CoreModel):
|
||||
LOGIN_TYPE_CHOICES = ((1, "普通登录"), (2, "微信扫码登录"),)
|
||||
username = models.CharField(max_length=32, verbose_name="登录用户名", null=True, blank=True, help_text="登录用户名")
|
||||
ip = models.CharField(max_length=32, verbose_name="登录ip", null=True, blank=True, help_text="登录ip")
|
||||
agent = models.TextField(verbose_name="agent信息", null=True, blank=True, help_text="agent信息")
|
||||
browser = models.CharField(max_length=200, verbose_name="浏览器名", null=True, blank=True, help_text="浏览器名")
|
||||
os = models.CharField(max_length=200, verbose_name="操作系统", null=True, blank=True, help_text="操作系统")
|
||||
continent = models.CharField(max_length=50, verbose_name="州", null=True, blank=True, help_text="州")
|
||||
country = models.CharField(max_length=50, verbose_name="国家", null=True, blank=True, help_text="国家")
|
||||
province = models.CharField(max_length=50, verbose_name="省份", null=True, blank=True, help_text="省份")
|
||||
city = models.CharField(max_length=50, verbose_name="城市", null=True, blank=True, help_text="城市")
|
||||
district = models.CharField(max_length=50, verbose_name="县区", null=True, blank=True, help_text="县区")
|
||||
isp = models.CharField(max_length=50, verbose_name="运营商", null=True, blank=True, help_text="运营商")
|
||||
area_code = models.CharField(max_length=50, verbose_name="区域代码", null=True, blank=True, help_text="区域代码")
|
||||
country_english = models.CharField(max_length=50, verbose_name="英文全称", null=True, blank=True,
|
||||
help_text="英文全称")
|
||||
country_code = models.CharField(max_length=50, verbose_name="简称", null=True, blank=True, help_text="简称")
|
||||
longitude = models.CharField(max_length=50, verbose_name="经度", null=True, blank=True, help_text="经度")
|
||||
latitude = models.CharField(max_length=50, verbose_name="纬度", null=True, blank=True, help_text="纬度")
|
||||
login_type = models.IntegerField(default=1, choices=LOGIN_TYPE_CHOICES, verbose_name="登录类型",
|
||||
help_text="登录类型")
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "system_login_log"
|
||||
verbose_name = "登录日志"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("-create_datetime",)
|
||||
|
||||
|
||||
class MessageCenter(CoreModel):
|
||||
title = models.CharField(max_length=100, verbose_name="标题", help_text="标题")
|
||||
content = models.TextField(verbose_name="内容", help_text="内容")
|
||||
target_type = models.IntegerField(default=0, verbose_name="目标类型", help_text="目标类型")
|
||||
target_user = models.ManyToManyField(to=Users, related_name='user', through='MessageCenterTargetUser',
|
||||
through_fields=('messagecenter', 'users'), blank=True, verbose_name="目标用户",
|
||||
help_text="目标用户")
|
||||
target_dept = models.ManyToManyField(to=Dept, blank=True, db_constraint=False,
|
||||
verbose_name="目标部门", help_text="目标部门")
|
||||
target_role = models.ManyToManyField(to=Role, blank=True, db_constraint=False,
|
||||
verbose_name="目标角色", help_text="目标角色")
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "message_center"
|
||||
verbose_name = "消息中心"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ("-create_datetime",)
|
||||
|
||||
|
||||
class MessageCenterTargetUser(CoreModel):
|
||||
users = models.ForeignKey(Users, related_name="target_user", on_delete=models.CASCADE, db_constraint=False,
|
||||
verbose_name="关联用户表", help_text="关联用户表")
|
||||
messagecenter = models.ForeignKey(MessageCenter, on_delete=models.CASCADE, db_constraint=False,
|
||||
verbose_name="关联消息中心表", help_text="关联消息中心表")
|
||||
is_read = models.BooleanField(default=False, blank=True, null=True, verbose_name="是否已读", help_text="是否已读")
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "message_center_target_user"
|
||||
verbose_name = "消息中心目标用户表"
|
||||
verbose_name_plural = verbose_name
|
||||
1
backend/dvadmin/system/tests.py
Normal file
1
backend/dvadmin/system/tests.py
Normal file
@@ -0,0 +1 @@
|
||||
from django.test import TestCase
|
||||
50
backend/dvadmin/system/urls.py
Normal file
50
backend/dvadmin/system/urls.py
Normal file
@@ -0,0 +1,50 @@
|
||||
from django.urls import path
|
||||
from rest_framework import routers
|
||||
|
||||
from dvadmin.system.views.api_white_list import ApiWhiteListViewSet
|
||||
from dvadmin.system.views.area import AreaViewSet
|
||||
from dvadmin.system.views.dept import DeptViewSet
|
||||
from dvadmin.system.views.dictionary import DictionaryViewSet
|
||||
from dvadmin.system.views.file_list import FileViewSet
|
||||
from dvadmin.system.views.login_log import LoginLogViewSet
|
||||
from dvadmin.system.views.menu import MenuViewSet
|
||||
from dvadmin.system.views.menu_button import MenuButtonViewSet
|
||||
from dvadmin.system.views.message_center import MessageCenterViewSet
|
||||
from dvadmin.system.views.operation_log import OperationLogViewSet
|
||||
from dvadmin.system.views.role import RoleViewSet
|
||||
from dvadmin.system.views.role_menu import RoleMenuPermissionViewSet
|
||||
from dvadmin.system.views.role_menu_button_permission import RoleMenuButtonPermissionViewSet
|
||||
from dvadmin.system.views.system_config import SystemConfigViewSet
|
||||
from dvadmin.system.views.user import UserViewSet
|
||||
|
||||
system_url = routers.SimpleRouter()
|
||||
system_url.register(r'menu', MenuViewSet)
|
||||
system_url.register(r'menu_button', MenuButtonViewSet)
|
||||
system_url.register(r'role', RoleViewSet)
|
||||
system_url.register(r'dept', DeptViewSet)
|
||||
system_url.register(r'user', UserViewSet)
|
||||
system_url.register(r'operation_log', OperationLogViewSet)
|
||||
system_url.register(r'dictionary', DictionaryViewSet)
|
||||
system_url.register(r'area', AreaViewSet)
|
||||
system_url.register(r'file', FileViewSet)
|
||||
system_url.register(r'api_white_list', ApiWhiteListViewSet)
|
||||
system_url.register(r'system_config', SystemConfigViewSet)
|
||||
system_url.register(r'message_center',MessageCenterViewSet)
|
||||
system_url.register(r'role_menu_button_permission', RoleMenuButtonPermissionViewSet)
|
||||
system_url.register(r'role_menu_permission', RoleMenuPermissionViewSet)
|
||||
|
||||
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('user/export/', UserViewSet.as_view({'post': 'export_data', })),
|
||||
path('user/import/', UserViewSet.as_view({'get': 'import_data', 'post': 'import_data'})),
|
||||
path('system_config/save_content/', SystemConfigViewSet.as_view({'put': 'save_content'})),
|
||||
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('dept_lazy_tree/', DeptViewSet.as_view({'get': 'dept_lazy_tree'})),
|
||||
]
|
||||
urlpatterns += system_url.urls
|
||||
1
backend/dvadmin/system/util/pca-code.json
Normal file
1
backend/dvadmin/system/util/pca-code.json
Normal file
File diff suppressed because one or more lines are too long
39
backend/dvadmin/system/views/api_white_list.py
Normal file
39
backend/dvadmin/system/views/api_white_list.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@author: 猿小天
|
||||
@contact: QQ:1638245306
|
||||
@Created on: 2022/1/1 001 9:34
|
||||
@Remark:
|
||||
"""
|
||||
from dvadmin.system.models import ApiWhiteList
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
|
||||
|
||||
class ApiWhiteListSerializer(CustomModelSerializer):
|
||||
"""
|
||||
接口白名单-序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = ApiWhiteList
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class ApiWhiteListViewSet(CustomModelViewSet):
|
||||
"""
|
||||
接口白名单
|
||||
list:查询
|
||||
create:新增
|
||||
update:修改
|
||||
retrieve:单例
|
||||
destroy:删除
|
||||
"""
|
||||
queryset = ApiWhiteList.objects.all()
|
||||
serializer_class = ApiWhiteListSerializer
|
||||
# permission_classes = []
|
||||
71
backend/dvadmin/system/views/area.py
Normal file
71
backend/dvadmin/system/views/area.py
Normal file
@@ -0,0 +1,71 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.db.models import Q
|
||||
from rest_framework import serializers
|
||||
|
||||
from dvadmin.system.models import Area
|
||||
from dvadmin.utils.json_response import SuccessResponse
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
|
||||
|
||||
class AreaSerializer(CustomModelSerializer):
|
||||
"""
|
||||
地区-序列化器
|
||||
"""
|
||||
pcode_count = serializers.SerializerMethodField(read_only=True)
|
||||
hasChild = serializers.SerializerMethodField()
|
||||
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__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class AreaCreateUpdateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
地区管理 创建/更新时的列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = Area
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class AreaViewSet(CustomModelViewSet):
|
||||
"""
|
||||
地区管理接口
|
||||
list:查询
|
||||
create:新增
|
||||
update:修改
|
||||
retrieve:单例
|
||||
destroy:删除
|
||||
"""
|
||||
queryset = Area.objects.all()
|
||||
serializer_class = AreaSerializer
|
||||
extra_filter_class = []
|
||||
|
||||
def get_queryset(self):
|
||||
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:
|
||||
if pcode:
|
||||
queryset = self.queryset.filter(enable=True, pcode=pcode)
|
||||
else:
|
||||
queryset = self.queryset.filter(enable=True)
|
||||
else:
|
||||
queryset = self.queryset.filter(enable=True, pcode__isnull=True)
|
||||
return queryset
|
||||
|
||||
157
backend/dvadmin/system/views/dept.py
Normal file
157
backend/dvadmin/system/views/dept.py
Normal file
@@ -0,0 +1,157 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@author: H0nGzA1
|
||||
@contact: QQ:2505811377
|
||||
@Remark: 部门管理
|
||||
"""
|
||||
from rest_framework import serializers
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from dvadmin.system.models import Dept, RoleMenuButtonPermission
|
||||
from dvadmin.utils.json_response import DetailResponse, SuccessResponse
|
||||
from dvadmin.utils.permission import AnonymousUserPermission
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
|
||||
|
||||
class DeptSerializer(CustomModelSerializer):
|
||||
"""
|
||||
部门-序列化器
|
||||
"""
|
||||
parent_name = serializers.CharField(read_only=True, source='parent.name')
|
||||
status_label = serializers.SerializerMethodField()
|
||||
has_children = serializers.SerializerMethodField()
|
||||
hasChild = serializers.SerializerMethodField()
|
||||
|
||||
def get_hasChild(self, instance):
|
||||
hasChild = Dept.objects.filter(parent=instance.id)
|
||||
if hasChild:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_status_label(self, obj: Dept):
|
||||
if obj.status:
|
||||
return "启用"
|
||||
return "禁用"
|
||||
|
||||
def get_has_children(self, obj: Dept):
|
||||
return Dept.objects.filter(parent_id=obj.id).count()
|
||||
|
||||
class Meta:
|
||||
model = Dept
|
||||
fields = '__all__'
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class DeptImportSerializer(CustomModelSerializer):
|
||||
"""
|
||||
部门-导入-序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = Dept
|
||||
fields = '__all__'
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class DeptCreateUpdateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
部门管理 创建/更新时的列化器
|
||||
"""
|
||||
|
||||
def create(self, validated_data):
|
||||
value = validated_data.get('parent',None)
|
||||
if value is None:
|
||||
validated_data['parent'] = self.request.user.dept
|
||||
instance = super().create(validated_data)
|
||||
instance.dept_belong_id = instance.id
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
class Meta:
|
||||
model = Dept
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class DeptViewSet(CustomModelViewSet):
|
||||
"""
|
||||
部门管理接口
|
||||
list:查询
|
||||
create:新增
|
||||
update:修改
|
||||
retrieve:单例
|
||||
destroy:删除
|
||||
"""
|
||||
queryset = Dept.objects.all()
|
||||
serializer_class = DeptSerializer
|
||||
create_serializer_class = DeptCreateUpdateSerializer
|
||||
update_serializer_class = DeptCreateUpdateSerializer
|
||||
filter_fields = ['name', 'id', 'parent']
|
||||
search_fields = []
|
||||
# extra_filter_class = []
|
||||
import_serializer_class = DeptImportSerializer
|
||||
import_field_dict = {
|
||||
"name": "部门名称",
|
||||
"key": "部门标识",
|
||||
}
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
# 如果懒加载,则只返回父级
|
||||
request.query_params._mutable = True
|
||||
params = request.query_params
|
||||
parent = params.get('parent', None)
|
||||
page = params.get('page', None)
|
||||
limit = params.get('limit', None)
|
||||
if page:
|
||||
del params['page']
|
||||
if limit:
|
||||
del params['limit']
|
||||
if params:
|
||||
if parent:
|
||||
queryset = self.queryset.filter(status=True, parent=parent)
|
||||
else:
|
||||
queryset = self.queryset.filter(status=True)
|
||||
else:
|
||||
queryset = self.queryset.filter(status=True, parent__isnull=True)
|
||||
queryset = self.filter_queryset(queryset)
|
||||
serializer = DeptSerializer(queryset, many=True, request=request)
|
||||
data = serializer.data
|
||||
return SuccessResponse(data=data)
|
||||
|
||||
@action(methods=["GET"], detail=False, permission_classes=[IsAuthenticated], extra_filter_class=[])
|
||||
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:
|
||||
role_ids = request.user.role.values_list('id',flat=True)
|
||||
data_range = RoleMenuButtonPermission.objects.filter(role__in=role_ids).values_list('data_range', flat=True)
|
||||
user_dept_id = request.user.dept.id
|
||||
dept_list = [user_dept_id]
|
||||
data_range_list = list(set(data_range))
|
||||
for item in data_range_list:
|
||||
if item in [0,2]:
|
||||
dept_list = [user_dept_id]
|
||||
elif item == 1:
|
||||
dept_list = Dept.recursion_dept_info(dept_id=user_dept_id)
|
||||
elif item == 3:
|
||||
dept_list = Dept.objects.values_list('id',flat=True)
|
||||
elif item == 4:
|
||||
dept_list = request.user.role.values_list('dept',flat=True)
|
||||
else:
|
||||
dept_list = []
|
||||
queryset = Dept.objects.filter(id__in=dept_list).values('id', 'name', 'parent')
|
||||
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')
|
||||
return DetailResponse(data=data, msg="获取成功")
|
||||
107
backend/dvadmin/system/views/dictionary.py
Normal file
107
backend/dvadmin/system/views/dictionary.py
Normal file
@@ -0,0 +1,107 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@author: 猿小天
|
||||
@contact: QQ:1638245306
|
||||
@Created on: 2021/6/3 003 0:30
|
||||
@Remark: 字典管理
|
||||
"""
|
||||
from rest_framework import serializers
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from application import dispatch
|
||||
from dvadmin.system.models import Dictionary
|
||||
from dvadmin.utils.json_response import SuccessResponse
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
|
||||
|
||||
class DictionarySerializer(CustomModelSerializer):
|
||||
"""
|
||||
字典-序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = Dictionary
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class DictionaryCreateUpdateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
字典管理 创建/更新时的列化器
|
||||
"""
|
||||
value = serializers.CharField(max_length=100)
|
||||
|
||||
def validate_value(self, value):
|
||||
"""
|
||||
在父级的字典编号验证重复性
|
||||
"""
|
||||
initial_data = self.initial_data
|
||||
parent = initial_data.get('parent',None)
|
||||
if parent is None:
|
||||
unique = Dictionary.objects.filter(value=value).exists()
|
||||
if unique:
|
||||
raise serializers.ValidationError("字典编号不能重复")
|
||||
return value
|
||||
|
||||
class Meta:
|
||||
model = Dictionary
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class DictionaryViewSet(CustomModelViewSet):
|
||||
"""
|
||||
字典管理接口
|
||||
list:查询
|
||||
create:新增
|
||||
update:修改
|
||||
retrieve:单例
|
||||
destroy:删除
|
||||
"""
|
||||
queryset = Dictionary.objects.all()
|
||||
serializer_class = DictionarySerializer
|
||||
create_serializer_class = DictionaryCreateUpdateSerializer
|
||||
extra_filter_class = []
|
||||
search_fields = ['label']
|
||||
|
||||
def get_queryset(self):
|
||||
if self.action =='list':
|
||||
params = self.request.query_params
|
||||
parent = params.get('parent', None)
|
||||
if params:
|
||||
if parent:
|
||||
queryset = self.queryset.filter(parent=parent)
|
||||
else:
|
||||
queryset = self.queryset.filter(parent__isnull=True)
|
||||
else:
|
||||
queryset = self.queryset.filter(parent__isnull=True)
|
||||
return queryset
|
||||
else:
|
||||
return self.queryset
|
||||
|
||||
|
||||
class InitDictionaryViewSet(APIView):
|
||||
"""
|
||||
获取初始化配置
|
||||
"""
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
queryset = Dictionary.objects.all()
|
||||
|
||||
def get(self, request):
|
||||
dictionary_key = self.request.query_params.get('dictionary_key')
|
||||
if dictionary_key:
|
||||
if dictionary_key == 'all':
|
||||
data = [ele for ele in dispatch.get_dictionary_config().values()]
|
||||
if not data:
|
||||
dispatch.refresh_dictionary()
|
||||
data = [ele for ele in dispatch.get_dictionary_config().values()]
|
||||
else:
|
||||
data = self.queryset.filter(parent__value=dictionary_key, status=True).values('label', 'value', 'type',
|
||||
'color')
|
||||
return SuccessResponse(data=data, msg="获取成功")
|
||||
return SuccessResponse(data=[], msg="获取成功")
|
||||
79
backend/dvadmin/system/views/file_list.py
Normal file
79
backend/dvadmin/system/views/file_list.py
Normal file
@@ -0,0 +1,79 @@
|
||||
import hashlib
|
||||
import mimetypes
|
||||
|
||||
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.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
|
||||
|
||||
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)}')
|
||||
|
||||
class Meta:
|
||||
model = FileList
|
||||
fields = "__all__"
|
||||
|
||||
def create(self, validated_data):
|
||||
file_engine = dispatch.get_system_config_values("fileStorageConfig.file_engine") or 'local'
|
||||
file_backup = dispatch.get_system_config_values("fileStorageConfig.file_backup")
|
||||
file = self.initial_data.get('file')
|
||||
file_size = file.size
|
||||
validated_data['name'] = str(file)
|
||||
validated_data['size'] = file_size
|
||||
md5 = hashlib.md5()
|
||||
for chunk in file.chunks():
|
||||
md5.update(chunk)
|
||||
validated_data['md5sum'] = md5.hexdigest()
|
||||
validated_data['engine'] = file_engine
|
||||
validated_data['mime_type'] = file.content_type
|
||||
if file_backup:
|
||||
validated_data['url'] = file
|
||||
if file_engine == 'oss':
|
||||
from dvadmin_cloud_storage.views.aliyun import ali_oss_upload
|
||||
file_path = ali_oss_upload(file)
|
||||
if file_path:
|
||||
validated_data['file_url'] = file_path
|
||||
else:
|
||||
raise ValueError("上传失败")
|
||||
elif file_engine == 'cos':
|
||||
from dvadmin_cloud_storage.views.tencent import tencent_cos_upload
|
||||
file_path = tencent_cos_upload(file)
|
||||
if file_path:
|
||||
validated_data['file_url'] = file_path
|
||||
else:
|
||||
raise ValueError("上传失败")
|
||||
else:
|
||||
validated_data['url'] = file
|
||||
# 审计字段
|
||||
try:
|
||||
request_user = self.request.user
|
||||
validated_data['dept_belong_id'] = request_user.dept.id
|
||||
validated_data['creator'] = request_user.id
|
||||
validated_data['modifier'] = request_user.id
|
||||
except:
|
||||
pass
|
||||
return super().create(validated_data)
|
||||
|
||||
|
||||
class FileViewSet(CustomModelViewSet):
|
||||
"""
|
||||
文件管理接口
|
||||
list:查询
|
||||
create:新增
|
||||
update:修改
|
||||
retrieve:单例
|
||||
destroy:删除
|
||||
"""
|
||||
queryset = FileList.objects.all()
|
||||
serializer_class = FileSerializer
|
||||
filter_fields = ['name', ]
|
||||
permission_classes = []
|
||||
251
backend/dvadmin/system/views/login.py
Normal file
251
backend/dvadmin/system/views/login.py
Normal file
@@ -0,0 +1,251 @@
|
||||
import base64
|
||||
import hashlib
|
||||
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.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.views import APIView
|
||||
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
|
||||
from rest_framework_simplejwt.views import TokenObtainPairView
|
||||
from django.conf import settings
|
||||
from application import dispatch
|
||||
from dvadmin.system.models import Users
|
||||
from dvadmin.utils.json_response import ErrorResponse, DetailResponse
|
||||
from dvadmin.utils.request_util import save_login_log
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.validator import CustomValidationError
|
||||
|
||||
|
||||
class CaptchaView(APIView):
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
|
||||
@swagger_auto_schema(
|
||||
responses={"200": openapi.Response("获取成功")},
|
||||
security=[],
|
||||
operation_id="captcha-get",
|
||||
operation_description="验证码获取",
|
||||
)
|
||||
def get(self, request):
|
||||
data = {}
|
||||
if dispatch.get_system_config_values("base.captcha_state"):
|
||||
hashkey = CaptchaStore.generate_key()
|
||||
id = CaptchaStore.objects.filter(hashkey=hashkey).first().id
|
||||
imgage = captcha_image(request, hashkey)
|
||||
# 将图片转换为base64
|
||||
image_base = base64.b64encode(imgage.content)
|
||||
data = {
|
||||
"key": id,
|
||||
"image_base": "data:image/png;base64," + image_base.decode("utf-8"),
|
||||
}
|
||||
return DetailResponse(data=data)
|
||||
|
||||
|
||||
class LoginSerializer(TokenObtainPairSerializer):
|
||||
"""
|
||||
登录的序列化器:
|
||||
重写djangorestframework-simplejwt的序列化器
|
||||
"""
|
||||
captcha = serializers.CharField(
|
||||
max_length=6, required=False, allow_null=True, allow_blank=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Users
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
default_error_messages = {"no_active_account": _("账号/密码错误")}
|
||||
|
||||
def validate(self, attrs):
|
||||
captcha = self.initial_data.get("captcha", None)
|
||||
if dispatch.get_system_config_values("base.captcha_state"):
|
||||
if captcha is None:
|
||||
raise CustomValidationError("验证码不能为空")
|
||||
self.image_code = CaptchaStore.objects.filter(
|
||||
id=self.initial_data["captchaKey"]
|
||||
).first()
|
||||
five_minute_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0)
|
||||
if self.image_code and five_minute_ago > self.image_code.expiration:
|
||||
self.image_code and self.image_code.delete()
|
||||
raise CustomValidationError("验证码过期")
|
||||
else:
|
||||
if self.image_code and (
|
||||
self.image_code.response == captcha
|
||||
or self.image_code.challenge == captcha
|
||||
):
|
||||
self.image_code and self.image_code.delete()
|
||||
else:
|
||||
self.image_code and self.image_code.delete()
|
||||
raise CustomValidationError("图片验证码错误")
|
||||
|
||||
user = Users.objects.get(username=attrs['username'])
|
||||
if not user.is_active:
|
||||
raise CustomValidationError("账号被锁定")
|
||||
|
||||
data = super().validate(attrs)
|
||||
data["name"] = self.user.name
|
||||
data["userId"] = self.user.id
|
||||
data["avatar"] = self.user.avatar
|
||||
data['user_type'] = self.user.user_type
|
||||
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:
|
||||
data['role_info'] = role.values('id', 'name', 'key')
|
||||
request = self.context.get("request")
|
||||
request.user = self.user
|
||||
# 记录登录日志
|
||||
save_login_log(request=request)
|
||||
return {"code": 2000, "msg": "请求成功", "data": data}
|
||||
|
||||
|
||||
class LoginView(TokenObtainPairView):
|
||||
"""
|
||||
登录接口
|
||||
"""
|
||||
serializer_class = LoginSerializer
|
||||
permission_classes = []
|
||||
|
||||
# def post(self, request, *args, **kwargs):
|
||||
# # username可能携带的不止是用户名,可能还是用户的其它唯一标识 手机号 邮箱
|
||||
# username = request.data.get('username',None)
|
||||
# if username is None:
|
||||
# return ErrorResponse(msg="参数错误")
|
||||
# password = request.data.get('password',None)
|
||||
# if password is None:
|
||||
# return ErrorResponse(msg="参数错误")
|
||||
# captcha = request.data.get('captcha',None)
|
||||
# if captcha is None:
|
||||
# return ErrorResponse(msg="参数错误")
|
||||
# captchaKey = request.data.get('captchaKey',None)
|
||||
# if captchaKey is None:
|
||||
# return ErrorResponse(msg="参数错误")
|
||||
# if dispatch.get_system_config_values("base.captcha_state"):
|
||||
# if captcha is None:
|
||||
# raise CustomValidationError("验证码不能为空")
|
||||
# self.image_code = CaptchaStore.objects.filter(
|
||||
# id=captchaKey
|
||||
# ).first()
|
||||
# five_minute_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0)
|
||||
# if self.image_code and five_minute_ago > self.image_code.expiration:
|
||||
# self.image_code and self.image_code.delete()
|
||||
# raise CustomValidationError("验证码过期")
|
||||
# else:
|
||||
# if self.image_code and (
|
||||
# self.image_code.response == captcha
|
||||
# or self.image_code.challenge == captcha
|
||||
# ):
|
||||
# self.image_code and self.image_code.delete()
|
||||
# else:
|
||||
# self.image_code and self.image_code.delete()
|
||||
# raise CustomValidationError("图片验证码错误")
|
||||
# try:
|
||||
# # 手动通过 user 签发 jwt-token
|
||||
# user = Users.objects.get(username=username)
|
||||
# except:
|
||||
# return DetailResponse(msg='该账号未注册')
|
||||
# # 获得用户后,校验密码并签发token
|
||||
# print(make_password(password),user.password)
|
||||
# if check_password(make_password(password),user.password):
|
||||
# return DetailResponse(msg='密码错误')
|
||||
# result = {
|
||||
# "name":user.name,
|
||||
# "userId":user.id,
|
||||
# "avatar":user.avatar,
|
||||
# }
|
||||
# dept = getattr(user, 'dept', None)
|
||||
# if dept:
|
||||
# result['dept_info'] = {
|
||||
# 'dept_id': dept.id,
|
||||
# 'dept_name': dept.name,
|
||||
# 'dept_key': dept.key
|
||||
# }
|
||||
# role = getattr(user, 'role', None)
|
||||
# if role:
|
||||
# result['role_info'] = role.values('id', 'name', 'key')
|
||||
# refresh = LoginSerializer.get_token(user)
|
||||
# result["refresh"] = str(refresh)
|
||||
# result["access"] = str(refresh.access_token)
|
||||
# # 记录登录日志
|
||||
# request.user = user
|
||||
# save_login_log(request=request)
|
||||
# return DetailResponse(data=result,msg="获取成功")
|
||||
|
||||
|
||||
class LoginTokenSerializer(TokenObtainPairSerializer):
|
||||
"""
|
||||
登录的序列化器:
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = Users
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
default_error_messages = {"no_active_account": _("账号/密码不正确")}
|
||||
|
||||
def validate(self, attrs):
|
||||
if not getattr(settings, "LOGIN_NO_CAPTCHA_AUTH", False):
|
||||
return {"code": 4000, "msg": "该接口暂未开通!", "data": None}
|
||||
data = super().validate(attrs)
|
||||
data["name"] = self.user.name
|
||||
data["userId"] = self.user.id
|
||||
return {"code": 2000, "msg": "请求成功", "data": data}
|
||||
|
||||
|
||||
class LoginTokenView(TokenObtainPairView):
|
||||
"""
|
||||
登录获取token接口
|
||||
"""
|
||||
|
||||
serializer_class = LoginTokenSerializer
|
||||
permission_classes = []
|
||||
|
||||
|
||||
class LogoutView(APIView):
|
||||
def post(self, request):
|
||||
return DetailResponse(msg="注销成功")
|
||||
|
||||
|
||||
class ApiLoginSerializer(CustomModelSerializer):
|
||||
"""接口文档登录-序列化器"""
|
||||
|
||||
username = serializers.CharField()
|
||||
password = serializers.CharField()
|
||||
|
||||
class Meta:
|
||||
model = Users
|
||||
fields = ["username", "password"]
|
||||
|
||||
|
||||
class ApiLogin(APIView):
|
||||
"""接口文档的登录接口"""
|
||||
|
||||
serializer_class = ApiLoginSerializer
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
|
||||
def post(self, request):
|
||||
username = request.data.get("username")
|
||||
password = request.data.get("password")
|
||||
user_obj = auth.authenticate(
|
||||
request,
|
||||
username=username,
|
||||
password=hashlib.md5(password.encode(encoding="UTF-8")).hexdigest(),
|
||||
)
|
||||
if user_obj:
|
||||
login(request, user_obj)
|
||||
return redirect("/")
|
||||
else:
|
||||
return ErrorResponse(msg="账号/密码错误")
|
||||
36
backend/dvadmin/system/views/login_log.py
Normal file
36
backend/dvadmin/system/views/login_log.py
Normal file
@@ -0,0 +1,36 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@author: 猿小天
|
||||
@contact: QQ:1638245306
|
||||
@Created on: 2021/6/3 003 0:30
|
||||
@Remark: 按钮权限管理
|
||||
"""
|
||||
from dvadmin.system.models import LoginLog
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
|
||||
|
||||
class LoginLogSerializer(CustomModelSerializer):
|
||||
"""
|
||||
登录日志权限-序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = LoginLog
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class LoginLogViewSet(CustomModelViewSet):
|
||||
"""
|
||||
登录日志接口
|
||||
list:查询
|
||||
create:新增
|
||||
update:修改
|
||||
retrieve:单例
|
||||
destroy:删除
|
||||
"""
|
||||
queryset = LoginLog.objects.all()
|
||||
serializer_class = LoginLogSerializer
|
||||
extra_filter_class = []
|
||||
152
backend/dvadmin/system/views/menu.py
Normal file
152
backend/dvadmin/system/views/menu.py
Normal file
@@ -0,0 +1,152 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@author: 猿小天
|
||||
@contact: QQ:1638245306
|
||||
@Created on: 2021/6/1 001 22:38
|
||||
@Remark: 菜单模块
|
||||
"""
|
||||
from rest_framework import serializers
|
||||
from rest_framework.decorators import action
|
||||
|
||||
from dvadmin.system.models import Menu, MenuButton, RoleMenuPermission
|
||||
from dvadmin.utils.json_response import SuccessResponse
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
|
||||
|
||||
class MenuSerializer(CustomModelSerializer):
|
||||
"""
|
||||
菜单表的简单序列化器
|
||||
"""
|
||||
menuPermission = serializers.SerializerMethodField(read_only=True)
|
||||
hasChild = serializers.SerializerMethodField()
|
||||
|
||||
def get_menuPermission(self, instance):
|
||||
queryset = instance.menuPermission.order_by('-name').values_list('name', flat=True)
|
||||
if queryset:
|
||||
return queryset
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_hasChild(self, instance):
|
||||
hasChild = Menu.objects.filter(parent=instance.id)
|
||||
if hasChild:
|
||||
return True
|
||||
return False
|
||||
|
||||
class Meta:
|
||||
model = Menu
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class MenuCreateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
菜单表的创建序列化器
|
||||
"""
|
||||
name = serializers.CharField(required=False)
|
||||
|
||||
class Meta:
|
||||
model = Menu
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class WebRouterSerializer(CustomModelSerializer):
|
||||
"""
|
||||
前端菜单路由的简单序列化器
|
||||
"""
|
||||
path = serializers.CharField(source="web_path")
|
||||
title = serializers.CharField(source="name")
|
||||
|
||||
class Meta:
|
||||
model = Menu
|
||||
fields = (
|
||||
'id', 'parent', 'icon', 'sort', 'path', 'name', 'title', 'is_link', 'is_catalog', 'web_path', 'component',
|
||||
'component_name', 'cache', 'visible', 'status')
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class MenuViewSet(CustomModelViewSet):
|
||||
"""
|
||||
菜单管理接口
|
||||
list:查询
|
||||
create:新增
|
||||
update:修改
|
||||
retrieve:单例
|
||||
destroy:删除
|
||||
"""
|
||||
queryset = Menu.objects.all()
|
||||
serializer_class = MenuSerializer
|
||||
create_serializer_class = MenuCreateSerializer
|
||||
update_serializer_class = MenuCreateSerializer
|
||||
search_fields = ['name', 'status']
|
||||
filter_fields = ['parent', 'name', 'status', 'is_link', 'visible', 'cache', 'is_catalog']
|
||||
|
||||
# extra_filter_class = []
|
||||
|
||||
@action(methods=['GET'], detail=False, permission_classes=[])
|
||||
def web_router(self, request):
|
||||
"""用于前端获取当前角色的路由"""
|
||||
user = request.user
|
||||
is_admin = user.role.values_list('admin', flat=True)
|
||||
if user.is_superuser or True in is_admin:
|
||||
queryset = self.queryset.filter(status=1)
|
||||
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)
|
||||
print("role_list", role_list)
|
||||
print("menu_list",menu_list)
|
||||
queryset = Menu.objects.filter(id__in=menu_list)
|
||||
print(queryset)
|
||||
serializer = WebRouterSerializer(queryset, many=True, request=request)
|
||||
data = serializer.data
|
||||
return SuccessResponse(data=data, total=len(data), msg="获取成功")
|
||||
|
||||
@action(methods=['GET'], detail=False, permission_classes=[])
|
||||
def get_all_menu(self, request):
|
||||
"""用于菜单管理获取所有的菜单"""
|
||||
user = request.user
|
||||
queryset = self.queryset.all()
|
||||
if not user.is_superuser:
|
||||
role_list = user.role.values_list('id', flat=True)
|
||||
menu_list = RoleMenuPermission.objects.filter(role__in=role_list).values_list('menu_id')
|
||||
queryset = Menu.objects.filter(id__in=menu_list)
|
||||
serializer = WebRouterSerializer(queryset, many=True, request=request)
|
||||
data = serializer.data
|
||||
return SuccessResponse(data=data, total=len(data), msg="获取成功")
|
||||
|
||||
@action(methods=['POST'], detail=False, permission_classes=[])
|
||||
def drag_menu(self, request):
|
||||
"""前端拖拽菜单之后重写parent"""
|
||||
menu_id = request.data['menu_id']
|
||||
parent_id = request.data['parent_id']
|
||||
menu = Menu.objects.get(id=menu_id)
|
||||
menu.parent_id = parent_id
|
||||
menu.save()
|
||||
|
||||
return SuccessResponse(data=[], msg="拖拽菜单成功")
|
||||
|
||||
def list(self, request):
|
||||
"""懒加载"""
|
||||
request.query_params._mutable = True
|
||||
params = request.query_params
|
||||
parent = params.get('parent', None)
|
||||
page = params.get('page', None)
|
||||
limit = params.get('limit', None)
|
||||
if page:
|
||||
del params['page']
|
||||
if limit:
|
||||
del params['limit']
|
||||
if params:
|
||||
if parent:
|
||||
queryset = self.queryset.filter(parent=parent)
|
||||
else:
|
||||
queryset = self.queryset.filter()
|
||||
else:
|
||||
queryset = self.queryset.filter(parent__isnull=True)
|
||||
queryset = self.filter_queryset(queryset)
|
||||
serializer = MenuSerializer(queryset, many=True, request=request)
|
||||
data = serializer.data
|
||||
return SuccessResponse(data=data)
|
||||
72
backend/dvadmin/system/views/menu_button.py
Normal file
72
backend/dvadmin/system/views/menu_button.py
Normal file
@@ -0,0 +1,72 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@author: 猿小天
|
||||
@contact: QQ:1638245306
|
||||
@Created on: 2021/6/3 003 0:30
|
||||
@Remark: 菜单按钮管理
|
||||
"""
|
||||
from django.db.models import F
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from dvadmin.system.models import MenuButton, RoleMenuButtonPermission
|
||||
from dvadmin.utils.json_response import DetailResponse
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
|
||||
|
||||
class MenuButtonSerializer(CustomModelSerializer):
|
||||
"""
|
||||
菜单按钮-序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = MenuButton
|
||||
fields = ['id', 'name', 'value', 'api', 'method','menu']
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
|
||||
|
||||
class MenuButtonCreateUpdateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化菜单按钮-序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = MenuButton
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class MenuButtonViewSet(CustomModelViewSet):
|
||||
"""
|
||||
菜单按钮接口
|
||||
list:查询
|
||||
create:新增
|
||||
update:修改
|
||||
retrieve:单例
|
||||
destroy:删除
|
||||
"""
|
||||
queryset = MenuButton.objects.all()
|
||||
serializer_class = MenuButtonSerializer
|
||||
create_serializer_class = MenuButtonCreateUpdateSerializer
|
||||
update_serializer_class = MenuButtonCreateUpdateSerializer
|
||||
extra_filter_class = []
|
||||
|
||||
@action(methods=['get'],detail=False,permission_classes=[IsAuthenticated])
|
||||
def menu_button_all_permission(self,request):
|
||||
"""
|
||||
获取所有的按钮权限
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
is_superuser = request.user.is_superuser
|
||||
is_admin = request.user.role.values_list('admin', flat=True)
|
||||
if is_superuser or True in is_admin:
|
||||
queryset = MenuButton.objects.values_list('value',flat=True)
|
||||
else:
|
||||
role_id = request.user.role.values_list('id', flat=True)
|
||||
queryset = RoleMenuButtonPermission.objects.filter(role__in=role_id).values_list('menu_button__value',flat=True).distinct()
|
||||
return DetailResponse(data=queryset)
|
||||
218
backend/dvadmin/system/views/message_center.py
Normal file
218
backend/dvadmin/system/views/message_center.py
Normal file
@@ -0,0 +1,218 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
|
||||
from asgiref.sync import async_to_sync
|
||||
from channels.layers import get_channel_layer
|
||||
from django_restql.fields import DynamicSerializerMethodField
|
||||
from rest_framework import serializers
|
||||
from rest_framework.decorators import action, permission_classes
|
||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||
|
||||
from dvadmin.system.models import MessageCenter, Users, MessageCenterTargetUser
|
||||
from dvadmin.utils.json_response import SuccessResponse, DetailResponse
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
|
||||
|
||||
class MessageCenterSerializer(CustomModelSerializer):
|
||||
"""
|
||||
消息中心-序列化器
|
||||
"""
|
||||
role_info = DynamicSerializerMethodField()
|
||||
user_info = DynamicSerializerMethodField()
|
||||
dept_info = DynamicSerializerMethodField()
|
||||
is_read = serializers.BooleanField(read_only=True, source='target_user__is_read')
|
||||
|
||||
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):
|
||||
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__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class MessageCenterTargetUserSerializer(CustomModelSerializer):
|
||||
"""
|
||||
目标用户序列化器-序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = MessageCenterTargetUser
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class MessageCenterTargetUserListSerializer(CustomModelSerializer):
|
||||
"""
|
||||
目标用户序列化器-序列化器
|
||||
"""
|
||||
is_read = serializers.SerializerMethodField()
|
||||
|
||||
def get_is_read(self, instance):
|
||||
user_id = self.request.user.id
|
||||
message_center_id = instance.id
|
||||
queryset = MessageCenterTargetUser.objects.filter(messagecenter__id=message_center_id, users_id=user_id).first()
|
||||
if queryset:
|
||||
return queryset.is_read
|
||||
return False
|
||||
|
||||
class Meta:
|
||||
model = MessageCenter
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
def websocket_push(user_id, message):
|
||||
"""
|
||||
主动推送消息
|
||||
"""
|
||||
username = "user_" + str(user_id)
|
||||
channel_layer = get_channel_layer()
|
||||
async_to_sync(channel_layer.group_send)(
|
||||
username,
|
||||
{
|
||||
"type": "push.message",
|
||||
"json": message
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class MessageCenterCreateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
消息中心-新增-序列化器
|
||||
"""
|
||||
|
||||
def save(self, **kwargs):
|
||||
data = super().save(**kwargs)
|
||||
initial_data = self.initial_data
|
||||
target_type = initial_data.get('target_type')
|
||||
# 在保存之前,根据目标类型,把目标用户查询出来并保存
|
||||
users = initial_data.get('target_user', [])
|
||||
if target_type in [1]: # 按角色
|
||||
target_role = initial_data.get('target_role', [])
|
||||
users = Users.objects.filter(role__id__in=target_role).values_list('id', flat=True)
|
||||
if target_type in [2]: # 按部门
|
||||
target_dept = initial_data.get('target_dept', [])
|
||||
users = Users.objects.filter(dept__id__in=target_dept).values_list('id', flat=True)
|
||||
if target_type in [3]: # 系统通知
|
||||
users = Users.objects.values_list('id', flat=True)
|
||||
targetuser_data = []
|
||||
for user in users:
|
||||
targetuser_data.append({
|
||||
"messagecenter": data.id,
|
||||
"users": user
|
||||
})
|
||||
targetuser_instance = MessageCenterTargetUserSerializer(data=targetuser_data, many=True, request=self.request)
|
||||
targetuser_instance.is_valid(raise_exception=True)
|
||||
targetuser_instance.save()
|
||||
for user in users:
|
||||
unread_count = MessageCenterTargetUser.objects.filter(users__id=user, is_read=False).count()
|
||||
websocket_push(user, message={"sender": 'system', "contentType": 'SYSTEM',
|
||||
"content": '您有一条新消息~', "unread": unread_count})
|
||||
return data
|
||||
|
||||
class Meta:
|
||||
model = MessageCenter
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class MessageCenterViewSet(CustomModelViewSet):
|
||||
"""
|
||||
消息中心接口
|
||||
list:查询
|
||||
create:新增
|
||||
update:修改
|
||||
retrieve:单例
|
||||
destroy:删除
|
||||
"""
|
||||
queryset = MessageCenter.objects.order_by('create_datetime')
|
||||
serializer_class = MessageCenterSerializer
|
||||
create_serializer_class = MessageCenterCreateSerializer
|
||||
extra_filter_backends = []
|
||||
|
||||
def get_queryset(self):
|
||||
if self.action == 'list':
|
||||
return MessageCenter.objects.filter(creator=self.request.user.id).all()
|
||||
return MessageCenter.objects.all()
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
"""
|
||||
重写查看
|
||||
"""
|
||||
pk = kwargs.get('pk')
|
||||
user_id = self.request.user.id
|
||||
queryset = MessageCenterTargetUser.objects.filter(users__id=user_id, messagecenter__id=pk).first()
|
||||
if queryset:
|
||||
queryset.is_read = True
|
||||
queryset.save()
|
||||
instance = self.get_object()
|
||||
serializer = self.get_serializer(instance)
|
||||
# 主动推送消息
|
||||
unread_count = MessageCenterTargetUser.objects.filter(users__id=user_id, is_read=False).count()
|
||||
websocket_push(user_id, message={"sender": 'system', "contentType": 'TEXT',
|
||||
"content": '您查看了一条消息~', "unread": unread_count})
|
||||
return DetailResponse(data=serializer.data, msg="获取成功")
|
||||
|
||||
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def get_self_receive(self, request):
|
||||
"""
|
||||
获取接收到的消息
|
||||
"""
|
||||
self_user_id = self.request.user.id
|
||||
# queryset = MessageCenterTargetUser.objects.filter(users__id=self_user_id).order_by('-create_datetime')
|
||||
queryset = MessageCenter.objects.filter(target_user__id=self_user_id)
|
||||
# queryset = self.filter_queryset(queryset)
|
||||
page = self.paginate_queryset(queryset)
|
||||
if page is not None:
|
||||
serializer = MessageCenterTargetUserListSerializer(page, many=True, request=request)
|
||||
return self.get_paginated_response(serializer.data)
|
||||
serializer = MessageCenterTargetUserListSerializer(queryset, many=True, request=request)
|
||||
return SuccessResponse(data=serializer.data, msg="获取成功")
|
||||
|
||||
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def get_newest_msg(self, request):
|
||||
"""
|
||||
获取最新的一条消息
|
||||
"""
|
||||
self_user_id = self.request.user.id
|
||||
queryset = MessageCenterTargetUser.objects.filter(users__id=self_user_id).order_by('create_datetime').last()
|
||||
data = None
|
||||
if queryset:
|
||||
serializer = MessageCenterTargetUserListSerializer(queryset.messagecenter, many=False, request=request)
|
||||
data = serializer.data
|
||||
return DetailResponse(data=data, msg="获取成功")
|
||||
47
backend/dvadmin/system/views/operation_log.py
Normal file
47
backend/dvadmin/system/views/operation_log.py
Normal file
@@ -0,0 +1,47 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@author: 李强
|
||||
@contact: QQ:1206709430
|
||||
@Created on: 2021/6/8 003 0:30
|
||||
@Remark: 操作日志管理
|
||||
"""
|
||||
|
||||
from dvadmin.system.models import OperationLog
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
|
||||
|
||||
class OperationLogSerializer(CustomModelSerializer):
|
||||
"""
|
||||
日志-序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = OperationLog
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class OperationLogCreateUpdateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
操作日志 创建/更新时的列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = OperationLog
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class OperationLogViewSet(CustomModelViewSet):
|
||||
"""
|
||||
操作日志接口
|
||||
list:查询
|
||||
create:新增
|
||||
update:修改
|
||||
retrieve:单例
|
||||
destroy:删除
|
||||
"""
|
||||
queryset = OperationLog.objects.order_by('-create_datetime')
|
||||
serializer_class = OperationLogSerializer
|
||||
# permission_classes = []
|
||||
98
backend/dvadmin/system/views/role.py
Normal file
98
backend/dvadmin/system/views/role.py
Normal file
@@ -0,0 +1,98 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@author: 猿小天
|
||||
@contact: QQ:1638245306
|
||||
@Created on: 2021/6/3 003 0:30
|
||||
@Remark: 角色管理
|
||||
"""
|
||||
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.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.json_response import SuccessResponse, DetailResponse
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.validator import CustomUniqueValidator
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
|
||||
|
||||
class RoleSerializer(CustomModelSerializer):
|
||||
"""
|
||||
角色-序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = Role
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class RoleCreateUpdateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
角色管理 创建/更新时的列化器
|
||||
"""
|
||||
menu = MenuSerializer(many=True, read_only=True)
|
||||
dept = DeptSerializer(many=True, read_only=True)
|
||||
permission = MenuButtonSerializer(many=True, read_only=True)
|
||||
key = serializers.CharField(max_length=50,
|
||||
validators=[CustomUniqueValidator(queryset=Role.objects.all(), message="权限字符必须唯一")])
|
||||
name = serializers.CharField(max_length=50, validators=[CustomUniqueValidator(queryset=Role.objects.all())])
|
||||
|
||||
def validate(self, attrs: dict):
|
||||
return super().validate(attrs)
|
||||
|
||||
def save(self, **kwargs):
|
||||
is_superuser = self.request.user.is_superuser
|
||||
if not is_superuser:
|
||||
self.validated_data.pop('admin')
|
||||
data = super().save(**kwargs)
|
||||
return data
|
||||
|
||||
class Meta:
|
||||
model = Role
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class MenuPermissonSerializer(CustomModelSerializer):
|
||||
"""
|
||||
菜单的按钮权限
|
||||
"""
|
||||
menuPermission = serializers.SerializerMethodField()
|
||||
|
||||
def get_menuPermission(self, instance):
|
||||
is_superuser = self.request.user.is_superuser
|
||||
if is_superuser:
|
||||
queryset = MenuButton.objects.filter(menu__id=instance.id)
|
||||
else:
|
||||
menu_permission_id_list = self.request.user.role.values_list('permission',flat=True)
|
||||
queryset = MenuButton.objects.filter(id__in=menu_permission_id_list,menu__id=instance.id)
|
||||
serializer = MenuButtonSerializer(queryset,many=True, read_only=True)
|
||||
return serializer.data
|
||||
|
||||
class Meta:
|
||||
model = Menu
|
||||
fields = ['id', 'parent', 'name', 'menuPermission']
|
||||
|
||||
|
||||
class RoleViewSet(CustomModelViewSet,FastCrudMixin):
|
||||
"""
|
||||
角色管理接口
|
||||
list:查询
|
||||
create:新增
|
||||
update:修改
|
||||
retrieve:单例
|
||||
destroy:删除
|
||||
"""
|
||||
queryset = Role.objects.all()
|
||||
serializer_class = RoleSerializer
|
||||
create_serializer_class = RoleCreateUpdateSerializer
|
||||
update_serializer_class = RoleCreateUpdateSerializer
|
||||
search_fields = ['name', 'key']
|
||||
86
backend/dvadmin/system/views/role_menu.py
Normal file
86
backend/dvadmin/system/views/role_menu.py
Normal file
@@ -0,0 +1,86 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from django.db.models import F
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from dvadmin.system.models import RoleMenuPermission, Menu, MenuButton
|
||||
from dvadmin.utils.json_response import DetailResponse, ErrorResponse
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
|
||||
|
||||
class RoleMenuPermissionSerializer(CustomModelSerializer):
|
||||
"""
|
||||
菜单按钮-序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = RoleMenuPermission
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class RoleMenuPermissionInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化菜单按钮-序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = RoleMenuPermission
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
class RoleMenuPermissionCreateUpdateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化菜单按钮-序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = RoleMenuPermission
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class RoleMenuPermissionViewSet(CustomModelViewSet):
|
||||
"""
|
||||
菜单按钮接口
|
||||
list:查询
|
||||
create:新增
|
||||
update:修改
|
||||
retrieve:单例
|
||||
destroy:删除
|
||||
"""
|
||||
queryset = RoleMenuPermission.objects.all()
|
||||
serializer_class = RoleMenuPermissionSerializer
|
||||
create_serializer_class = RoleMenuPermissionCreateUpdateSerializer
|
||||
update_serializer_class = RoleMenuPermissionCreateUpdateSerializer
|
||||
extra_filter_class = []
|
||||
|
||||
@action(methods=['post'],detail=False)
|
||||
def save_auth(self,request):
|
||||
"""
|
||||
保存页面菜单授权
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
body = request.data
|
||||
role_id = body.get('role',None)
|
||||
if role_id is None:
|
||||
return ErrorResponse(msg="未获取到角色参数")
|
||||
menu_list = body.get('menu',None)
|
||||
if menu_list is None:
|
||||
return ErrorResponse(msg="未获取到菜单参数")
|
||||
obj_list = RoleMenuPermission.objects.filter(role__id=role_id).values_list('menu__id',flat=True)
|
||||
old_set = set(obj_list)
|
||||
new_set = set(menu_list)
|
||||
#need_update = old_set.intersection(new_set) # 需要更新的
|
||||
need_del = old_set.difference(new_set) # 需要删除的
|
||||
need_add = new_set.difference(old_set) # 需要新增的
|
||||
RoleMenuPermission.objects.filter(role__id=role_id,menu__in=list(need_del)).delete()
|
||||
data = [{"role": role_id, "menu": item} for item in list(need_add)]
|
||||
serializer = RoleMenuPermissionSerializer(data=data,many=True,request=request)
|
||||
if serializer.is_valid(raise_exception=True):
|
||||
serializer.save()
|
||||
return DetailResponse(msg="保存成功",data=serializer.data)
|
||||
280
backend/dvadmin/system/views/role_menu_button_permission.py
Normal file
280
backend/dvadmin/system/views/role_menu_button_permission.py
Normal file
@@ -0,0 +1,280 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@author: 猿小天
|
||||
@contact: QQ:1638245306
|
||||
@Created on: 2021/6/3 003 0:30
|
||||
@Remark: 菜单按钮管理
|
||||
"""
|
||||
from django.db.models import F, Subquery, OuterRef, Exists
|
||||
from rest_framework import serializers
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from dvadmin.system.models import RoleMenuButtonPermission, Menu, MenuButton, Dept, RoleMenuPermission
|
||||
from dvadmin.utils.json_response import DetailResponse, ErrorResponse
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
|
||||
|
||||
class RoleMenuButtonPermissionSerializer(CustomModelSerializer):
|
||||
"""
|
||||
菜单按钮-序列化器
|
||||
"""
|
||||
class Meta:
|
||||
model = RoleMenuButtonPermission
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class RoleMenuButtonPermissionInitSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化菜单按钮-序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = RoleMenuButtonPermission
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
class RoleMenuButtonPermissionCreateUpdateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
初始化菜单按钮-序列化器
|
||||
"""
|
||||
menu_button__name = serializers.CharField(source='menu_button.name', read_only=True)
|
||||
menu_button__value= serializers.CharField(source='menu_button.value', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = RoleMenuButtonPermission
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
|
||||
"""
|
||||
菜单按钮接口
|
||||
list:查询
|
||||
create:新增
|
||||
update:修改
|
||||
retrieve:单例
|
||||
destroy:删除
|
||||
"""
|
||||
queryset = RoleMenuButtonPermission.objects.all()
|
||||
serializer_class = RoleMenuButtonPermissionSerializer
|
||||
create_serializer_class = RoleMenuButtonPermissionCreateUpdateSerializer
|
||||
update_serializer_class = RoleMenuButtonPermissionCreateUpdateSerializer
|
||||
extra_filter_class = []
|
||||
|
||||
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def role_get_menu(self, request):
|
||||
"""根据当前用户的角色返回角色拥有的菜单"""
|
||||
data = []
|
||||
is_superuser = request.user.is_superuser
|
||||
is_admin = request.user.role.values_list('admin', flat=True)
|
||||
if is_superuser or True in is_admin:
|
||||
queryset = Menu.objects.filter(status=1).values('name','parent','is_catalog',menu_id=F('id'))
|
||||
for item in queryset:
|
||||
btn_name = MenuButton.objects.filter(menu=item['menu_id']).values_list(
|
||||
'name', flat=True)
|
||||
data.append({'menu_id': item['menu_id'], 'name': item['name'], 'parent': item['parent'],
|
||||
'permission': ','.join(btn_name), 'is_catalog': item['is_catalog']})
|
||||
else:
|
||||
role_id = request.user.role.values_list('id',flat=True)
|
||||
queryset = RoleMenuPermission.objects.filter(role__in=role_id).values('menu_id',name=F('menu__name'),parent=F('menu__parent'),is_catalog=F('menu__is_catalog')).distinct()
|
||||
for item in queryset:
|
||||
btn_name = RoleMenuButtonPermission.objects.filter(menu_button__menu=item['menu_id']).values_list('menu_button__name',flat=True)
|
||||
data.append({'menu_id':item['menu_id'], 'name':item['name'], 'parent':item['parent'],'permission':','.join(btn_name),'is_catalog':item['is_catalog']})
|
||||
return DetailResponse(data=data)
|
||||
|
||||
@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
|
||||
is_admin = request.user.role.values_list('admin', flat=True)
|
||||
if is_superuser or True in is_admin:
|
||||
queryset = MenuButton.objects.filter(menu=menu_id).values('id', 'name')
|
||||
else:
|
||||
role_list = request.user.role.values_list('id',flat=True)
|
||||
queryset = RoleMenuButtonPermission.objects.filter(role__in=role_list,menu_button__menu=menu_id).values(
|
||||
btn_id=F('menu_button__id'),
|
||||
name=F('menu_button__name')
|
||||
)
|
||||
return DetailResponse(data=queryset)
|
||||
return ErrorResponse(msg="参数错误")
|
||||
|
||||
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def data_scope(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)
|
||||
else:
|
||||
data = []
|
||||
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="参数错误")
|
||||
|
||||
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
|
||||
def role_to_dept_all(self, request):
|
||||
"""
|
||||
当前用户角色下所能授权的部门:角色授权页面使用
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
params = request.query_params
|
||||
is_superuser = request.user.is_superuser
|
||||
is_admin = request.user.role.values_list('admin', flat=True)
|
||||
if is_superuser or True in is_admin:
|
||||
queryset = Dept.objects.values('id','name','parent')
|
||||
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)
|
||||
|
||||
|
||||
|
||||
@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
|
||||
is_admin = request.user.role.values_list('admin', flat=True)
|
||||
if is_superuser or True in is_admin:
|
||||
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)
|
||||
|
||||
234
backend/dvadmin/system/views/system_config.py
Normal file
234
backend/dvadmin/system/views/system_config.py
Normal file
@@ -0,0 +1,234 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@author: 猿小天
|
||||
@contact: QQ:1638245306
|
||||
@Created on: 2022/1/21 003 0:30
|
||||
@Remark: 系统配置
|
||||
"""
|
||||
import django_filters
|
||||
from django.db.models import Q
|
||||
from django_filters.rest_framework import BooleanFilter
|
||||
from rest_framework import serializers
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from application import dispatch
|
||||
from dvadmin.system.models import SystemConfig
|
||||
from dvadmin.utils.json_response import DetailResponse, SuccessResponse, ErrorResponse
|
||||
from dvadmin.utils.models import get_all_models_objects
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.validator import CustomValidationError
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
|
||||
|
||||
class SystemConfigCreateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
系统配置-新增时使用-序列化器
|
||||
"""
|
||||
form_item_type_label = serializers.CharField(source='get_form_item_type_display', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = SystemConfig
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
def validate_key(self, value):
|
||||
"""
|
||||
验证key是否允许重复
|
||||
parent为空时不允许重复,反之允许
|
||||
"""
|
||||
instance = SystemConfig.objects.filter(key=value, parent__isnull=True).exists()
|
||||
if instance:
|
||||
raise CustomValidationError('已存在相同变量名')
|
||||
return value
|
||||
|
||||
|
||||
|
||||
|
||||
class SystemConfigSerializer(CustomModelSerializer):
|
||||
"""
|
||||
系统配置-序列化器
|
||||
"""
|
||||
form_item_type_label = serializers.CharField(source='get_form_item_type_display', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = SystemConfig
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class SystemConfigChinldernSerializer(CustomModelSerializer):
|
||||
"""
|
||||
系统配置子级-序列化器
|
||||
"""
|
||||
children = serializers.SerializerMethodField()
|
||||
form_item_type_label = serializers.CharField(source='get_form_item_type_display', read_only=True)
|
||||
|
||||
def get_children(self, instance):
|
||||
queryset = SystemConfig.objects.filter(parent=instance)
|
||||
serializer = SystemConfigSerializer(queryset, many=True)
|
||||
return serializer.data
|
||||
|
||||
class Meta:
|
||||
model = SystemConfig
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class SystemConfigListSerializer(CustomModelSerializer):
|
||||
"""
|
||||
系统配置下模块的保存-序列化器
|
||||
"""
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
instance_mapping = {obj.id: obj for obj in instance}
|
||||
data_mapping = {item['id']: item for item in validated_data}
|
||||
for obj_id, data in data_mapping.items():
|
||||
instance_obj = instance_mapping.get(obj_id, None)
|
||||
if instance_obj is None:
|
||||
return SystemConfig.objects.create(**data)
|
||||
else:
|
||||
return instance_obj.objects.update(**data)
|
||||
|
||||
class Meta:
|
||||
model = SystemConfig
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class SystemConfigSaveSerializer(serializers.Serializer):
|
||||
class Meta:
|
||||
read_only_fields = ["id"]
|
||||
list_serializer_class = SystemConfigListSerializer
|
||||
|
||||
|
||||
class SystemConfigFilter(django_filters.rest_framework.FilterSet):
|
||||
"""
|
||||
过滤器
|
||||
"""
|
||||
parent__isnull = BooleanFilter(field_name='parent', lookup_expr="isnull")
|
||||
|
||||
class Meta:
|
||||
model = SystemConfig
|
||||
fields = ['id', 'parent', 'status', 'parent__isnull']
|
||||
|
||||
|
||||
class SystemConfigViewSet(CustomModelViewSet):
|
||||
"""
|
||||
系统配置接口
|
||||
"""
|
||||
queryset = SystemConfig.objects.order_by('sort', 'create_datetime')
|
||||
serializer_class = SystemConfigChinldernSerializer
|
||||
create_serializer_class = SystemConfigCreateSerializer
|
||||
retrieve_serializer_class = SystemConfigChinldernSerializer
|
||||
# filter_fields = ['id','parent']
|
||||
filter_class = SystemConfigFilter
|
||||
|
||||
def save_content(self, request):
|
||||
body = request.data
|
||||
data_mapping = {item['id']: item for item in body}
|
||||
for obj_id, data in data_mapping.items():
|
||||
instance_obj = SystemConfig.objects.filter(id=obj_id).first()
|
||||
if instance_obj is None:
|
||||
# return SystemConfig.objects.create(**data)
|
||||
serializer = SystemConfigCreateSerializer(data=data)
|
||||
else:
|
||||
serializer = SystemConfigCreateSerializer(instance_obj, data=data)
|
||||
if serializer.is_valid(raise_exception=True):
|
||||
serializer.save()
|
||||
return DetailResponse(msg="保存成功")
|
||||
|
||||
def get_association_table(self, request):
|
||||
"""
|
||||
获取所有的model及字段信息
|
||||
"""
|
||||
res = [ele.get('table') for ele in get_all_models_objects().values()]
|
||||
return DetailResponse(msg="获取成功", data=res)
|
||||
|
||||
def get_table_data(self, request, pk):
|
||||
"""
|
||||
动态获取关联表的数据
|
||||
"""
|
||||
instance = SystemConfig.objects.filter(id=pk).first()
|
||||
if instance is None:
|
||||
return ErrorResponse(msg="查询出错了~")
|
||||
setting = instance.setting
|
||||
if setting is None:
|
||||
return ErrorResponse(msg="查询出错了~")
|
||||
table = setting.get('table') # 获取model名
|
||||
model = get_all_models_objects(table).get("object", {})
|
||||
# 自己判断一下不存在
|
||||
queryset = model.objects.values()
|
||||
body = request.query_params
|
||||
search_value = body.get('search', None)
|
||||
if search_value:
|
||||
search_fields = setting.get('searchField')
|
||||
filters = Q()
|
||||
filters.connector = 'OR'
|
||||
for item in search_fields:
|
||||
filed = '{0}__icontains'.format(item.get('field'))
|
||||
filters.children.append((filed, search_value))
|
||||
queryset = model.objects.filter(filters).values()
|
||||
page = self.paginate_queryset(queryset)
|
||||
if page is not None:
|
||||
return self.get_paginated_response(queryset)
|
||||
return SuccessResponse(msg="获取成功", data=queryset, total=len(queryset))
|
||||
|
||||
def get_relation_info(self, request):
|
||||
"""
|
||||
查询关联的模板信息
|
||||
"""
|
||||
body = request.query_params
|
||||
var_name = body.get('varName', None)
|
||||
table = body.get('table', None)
|
||||
instance = SystemConfig.objects.filter(key=var_name, setting__table=table).first()
|
||||
if instance is None:
|
||||
return ErrorResponse(msg="未获取到关联信息")
|
||||
relation_id = body.get('relationIds', None)
|
||||
relationIds = []
|
||||
if relation_id is None:
|
||||
return ErrorResponse(msg="未获取到关联信息")
|
||||
if instance.form_item_type in [13]:
|
||||
relationIds = [relation_id]
|
||||
elif instance.form_item_type in [14]:
|
||||
relationIds = relation_id.split(',')
|
||||
queryset = SystemConfig.objects.filter(value__in=relationIds).first()
|
||||
if queryset is None:
|
||||
return ErrorResponse(msg="未获取到关联信息")
|
||||
serializer = SystemConfigChinldernSerializer(queryset.parent)
|
||||
return DetailResponse(msg="查询成功", data=serializer.data)
|
||||
|
||||
|
||||
class InitSettingsViewSet(APIView):
|
||||
"""
|
||||
获取初始化配置
|
||||
"""
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
|
||||
def filter_system_config_values(self, data: dict):
|
||||
"""
|
||||
过滤系统初始化配置
|
||||
:param data:
|
||||
:return:
|
||||
"""
|
||||
if not self.request.query_params.get('key', ''):
|
||||
return data
|
||||
new_data = {}
|
||||
for key in self.request.query_params.get('key', '').split('|'):
|
||||
if key:
|
||||
new_data.update(**dict(filter(lambda x: x[0].startswith(key), data.items())))
|
||||
return new_data
|
||||
|
||||
def get(self, request):
|
||||
data = dispatch.get_system_config()
|
||||
if not data:
|
||||
dispatch.refresh_system_config()
|
||||
data = dispatch.get_system_config()
|
||||
# 不返回后端专用配置
|
||||
backend_config = [f"{ele.get('parent__key')}.{ele.get('key')}" for ele in
|
||||
SystemConfig.objects.filter(status=False, parent_id__isnull=False).values('parent__key',
|
||||
'key')]
|
||||
data = dict(filter(lambda x: x[0] not in backend_config, data.items()))
|
||||
data = self.filter_system_config_values(data=data)
|
||||
return DetailResponse(data=data)
|
||||
365
backend/dvadmin/system/views/user.py
Normal file
365
backend/dvadmin/system/views/user.py
Normal file
@@ -0,0 +1,365 @@
|
||||
import hashlib
|
||||
|
||||
from django.contrib.auth.hashers import make_password, check_password
|
||||
from django_restql.fields import DynamicSerializerMethodField
|
||||
from rest_framework import serializers
|
||||
from rest_framework.decorators import action, permission_classes
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from django.db import connection
|
||||
from application import dispatch
|
||||
from dvadmin.system.models import Users, Role, Dept
|
||||
from dvadmin.system.views.role import RoleSerializer
|
||||
from dvadmin.utils.json_response import ErrorResponse, DetailResponse
|
||||
from dvadmin.utils.serializers import CustomModelSerializer
|
||||
from dvadmin.utils.validator import CustomUniqueValidator
|
||||
from dvadmin.utils.viewset import CustomModelViewSet
|
||||
|
||||
|
||||
def recursion(instance, parent, result):
|
||||
new_instance = getattr(instance, parent, None)
|
||||
res = []
|
||||
data = getattr(instance, result, None)
|
||||
if data:
|
||||
res.append(data)
|
||||
if new_instance:
|
||||
array = recursion(new_instance, parent, result)
|
||||
res += (array)
|
||||
return res
|
||||
|
||||
|
||||
class UserSerializer(CustomModelSerializer):
|
||||
"""
|
||||
用户管理-序列化器
|
||||
"""
|
||||
dept_name = serializers.CharField(source='dept.name', read_only=True)
|
||||
role_info = DynamicSerializerMethodField()
|
||||
dept_name_all = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Users
|
||||
read_only_fields = ["id"]
|
||||
exclude = ["password"]
|
||||
extra_kwargs = {
|
||||
"post": {"required": False},
|
||||
"mobile": {"required": False},
|
||||
}
|
||||
|
||||
def get_dept_name_all(self, instance):
|
||||
dept_name_all = recursion(instance.dept, "parent", "name")
|
||||
dept_name_all.reverse()
|
||||
return "/".join(dept_name_all)
|
||||
|
||||
def get_role_info(self, instance, parsed_query):
|
||||
roles = instance.role.all()
|
||||
# You can do what ever you want in here
|
||||
# `parsed_query` param is passed to BookSerializer to allow further querying
|
||||
serializer = RoleSerializer(
|
||||
roles,
|
||||
many=True,
|
||||
parsed_query=parsed_query
|
||||
)
|
||||
return serializer.data
|
||||
|
||||
|
||||
class UserCreateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
用户新增-序列化器
|
||||
"""
|
||||
|
||||
username = serializers.CharField(
|
||||
max_length=50,
|
||||
validators=[
|
||||
CustomUniqueValidator(queryset=Users.objects.all(), message="账号必须唯一")
|
||||
],
|
||||
)
|
||||
password = serializers.CharField(
|
||||
required=False,
|
||||
)
|
||||
|
||||
def validate_password(self, value):
|
||||
"""
|
||||
对密码进行验证
|
||||
"""
|
||||
md5 = hashlib.md5()
|
||||
md5.update(value.encode('utf-8'))
|
||||
md5_password = md5.hexdigest()
|
||||
return make_password(md5_password)
|
||||
|
||||
def save(self, **kwargs):
|
||||
data = super().save(**kwargs)
|
||||
data.dept_belong_id = data.dept_id
|
||||
data.save()
|
||||
data.post.set(self.initial_data.get("post", []))
|
||||
return data
|
||||
|
||||
class Meta:
|
||||
model = Users
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
extra_kwargs = {
|
||||
"post": {"required": False},
|
||||
"mobile": {"required": False},
|
||||
}
|
||||
|
||||
|
||||
class UserUpdateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
用户修改-序列化器
|
||||
"""
|
||||
|
||||
username = serializers.CharField(
|
||||
max_length=50,
|
||||
validators=[
|
||||
CustomUniqueValidator(queryset=Users.objects.all(), message="账号必须唯一")
|
||||
],
|
||||
)
|
||||
|
||||
# password = serializers.CharField(required=False, allow_blank=True)
|
||||
# mobile = serializers.CharField(
|
||||
# max_length=50,
|
||||
# validators=[
|
||||
# CustomUniqueValidator(queryset=Users.objects.all(), message="手机号必须唯一")
|
||||
# ],
|
||||
# allow_blank=True
|
||||
# )
|
||||
|
||||
def save(self, **kwargs):
|
||||
data = super().save(**kwargs)
|
||||
data.dept_belong_id = data.dept_id
|
||||
data.save()
|
||||
data.post.set(self.initial_data.get("post", []))
|
||||
return data
|
||||
|
||||
class Meta:
|
||||
model = Users
|
||||
read_only_fields = ["id", "password"]
|
||||
fields = "__all__"
|
||||
extra_kwargs = {
|
||||
"post": {"required": False, "read_only": True},
|
||||
"mobile": {"required": False},
|
||||
}
|
||||
|
||||
|
||||
class UserInfoUpdateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
用户修改-序列化器
|
||||
"""
|
||||
mobile = serializers.CharField(
|
||||
max_length=50,
|
||||
validators=[
|
||||
CustomUniqueValidator(queryset=Users.objects.all(), message="手机号必须唯一")
|
||||
],
|
||||
allow_blank=True
|
||||
)
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
class Meta:
|
||||
model = Users
|
||||
fields = ['email', 'mobile', 'avatar', 'name', 'gender']
|
||||
extra_kwargs = {
|
||||
"post": {"required": False, "read_only": True},
|
||||
"mobile": {"required": False},
|
||||
}
|
||||
|
||||
|
||||
class ExportUserProfileSerializer(CustomModelSerializer):
|
||||
"""
|
||||
用户导出 序列化器
|
||||
"""
|
||||
|
||||
last_login = serializers.DateTimeField(
|
||||
format="%Y-%m-%d %H:%M:%S", required=False, read_only=True
|
||||
)
|
||||
is_active = serializers.SerializerMethodField(read_only=True)
|
||||
dept_name = serializers.CharField(source="dept.name", default="")
|
||||
dept_owner = serializers.CharField(source="dept.owner", default="")
|
||||
gender = serializers.CharField(source="get_gender_display", read_only=True)
|
||||
|
||||
def get_is_active(self, instance):
|
||||
return "启用" if instance.is_active else "停用"
|
||||
|
||||
class Meta:
|
||||
model = Users
|
||||
fields = (
|
||||
"username",
|
||||
"name",
|
||||
"email",
|
||||
"mobile",
|
||||
"gender",
|
||||
"is_active",
|
||||
"last_login",
|
||||
"dept_name",
|
||||
"dept_owner",
|
||||
)
|
||||
|
||||
|
||||
class UserProfileImportSerializer(CustomModelSerializer):
|
||||
password = serializers.CharField(read_only=True, required=False)
|
||||
def save(self, **kwargs):
|
||||
data = super().save(**kwargs)
|
||||
password = hashlib.new(
|
||||
"md5", str(self.initial_data.get("password", "admin123456")).encode(encoding="UTF-8")
|
||||
).hexdigest()
|
||||
data.set_password(password)
|
||||
data.save()
|
||||
return data
|
||||
|
||||
class Meta:
|
||||
model = Users
|
||||
exclude = (
|
||||
"post",
|
||||
"user_permissions",
|
||||
"groups",
|
||||
"is_superuser",
|
||||
"date_joined",
|
||||
)
|
||||
|
||||
|
||||
class UserViewSet(CustomModelViewSet):
|
||||
"""
|
||||
用户接口
|
||||
list:查询
|
||||
create:新增
|
||||
update:修改
|
||||
retrieve:单例
|
||||
destroy:删除
|
||||
"""
|
||||
|
||||
queryset = Users.objects.exclude(is_superuser=1).all()
|
||||
serializer_class = UserSerializer
|
||||
create_serializer_class = UserCreateSerializer
|
||||
update_serializer_class = UserUpdateSerializer
|
||||
filter_fields = ["name", "username", "gender", "is_active", "dept", "user_type"]
|
||||
search_fields = ["username", "name", "gender", "dept__name", "role__name"]
|
||||
# 导出
|
||||
export_field_label = {
|
||||
"username": "用户账号",
|
||||
"name": "用户名称",
|
||||
"email": "用户邮箱",
|
||||
"mobile": "手机号码",
|
||||
"gender": "用户性别",
|
||||
"is_active": "帐号状态",
|
||||
"last_login": "最后登录时间",
|
||||
"dept_name": "部门名称",
|
||||
"dept_owner": "部门负责人",
|
||||
}
|
||||
export_serializer_class = ExportUserProfileSerializer
|
||||
# 导入
|
||||
import_serializer_class = UserProfileImportSerializer
|
||||
import_field_dict = {
|
||||
"username": "登录账号",
|
||||
"name": "用户名称",
|
||||
"email": "用户邮箱",
|
||||
"mobile": "手机号码",
|
||||
"gender": {
|
||||
"title": "用户性别",
|
||||
"choices": {
|
||||
"data": {"未知": 2, "男": 1, "女": 0},
|
||||
}
|
||||
},
|
||||
"is_active": {
|
||||
"title": "帐号状态",
|
||||
"choices": {
|
||||
"data": {"启用": True, "禁用": False},
|
||||
}
|
||||
},
|
||||
"dept": {"title": "部门", "choices": {"queryset": Dept.objects.filter(status=True), "values_name": "name"}},
|
||||
"role": {"title": "角色", "choices": {"queryset": Role.objects.filter(status=True), "values_name": "name"}},
|
||||
}
|
||||
|
||||
@action(methods=["GET"], detail=False, permission_classes=[IsAuthenticated])
|
||||
def user_info(self, request):
|
||||
"""获取当前用户信息"""
|
||||
user = request.user
|
||||
result = {
|
||||
"id": user.id,
|
||||
"username": user.username,
|
||||
"name": user.name,
|
||||
"mobile": user.mobile,
|
||||
"user_type": user.user_type,
|
||||
"gender": user.gender,
|
||||
"email": user.email,
|
||||
"avatar": user.avatar,
|
||||
"dept": user.dept_id,
|
||||
"is_superuser": user.is_superuser,
|
||||
"role": user.role.values_list('id', flat=True),
|
||||
}
|
||||
if hasattr(connection, 'tenant'):
|
||||
result['tenant_id'] = connection.tenant and connection.tenant.id
|
||||
result['tenant_name'] = connection.tenant and connection.tenant.name
|
||||
dept = getattr(user, 'dept', None)
|
||||
if dept:
|
||||
result['dept_info'] = {
|
||||
'dept_id': dept.id,
|
||||
'dept_name': dept.name
|
||||
}
|
||||
else:
|
||||
result['dept_info'] = {
|
||||
'dept_id': None,
|
||||
'dept_name': "暂无部门"
|
||||
}
|
||||
role = getattr(user, 'role', None)
|
||||
if role:
|
||||
result['role_info'] = role.values('id', 'name', 'key')
|
||||
return DetailResponse(data=result, msg="获取成功")
|
||||
|
||||
@action(methods=["PUT"], detail=False, permission_classes=[IsAuthenticated])
|
||||
def update_user_info(self, request):
|
||||
"""修改当前用户信息"""
|
||||
serializer = UserInfoUpdateSerializer(request.user, data=request.data, request=request)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
return DetailResponse(data=None, msg="修改成功")
|
||||
|
||||
@action(methods=["PUT"], detail=True, permission_classes=[IsAuthenticated])
|
||||
def change_password(self, request, *args, **kwargs):
|
||||
"""密码修改"""
|
||||
data = request.data
|
||||
old_pwd = data.get("oldPassword")
|
||||
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:
|
||||
return ErrorResponse(msg="参数不能为空")
|
||||
if new_pwd != new_pwd2:
|
||||
return ErrorResponse(msg="两次密码不匹配")
|
||||
verify_password = check_password(old_pwd, self.request.user.password)
|
||||
if not verify_password:
|
||||
verify_password = check_password(hashlib.md5(old_pwd.encode(encoding='UTF-8')).hexdigest(), self.request.user.password)
|
||||
if verify_password:
|
||||
request.user.password = make_password(new_pwd)
|
||||
request.user.save()
|
||||
return DetailResponse(data=None, msg="修改成功")
|
||||
else:
|
||||
return ErrorResponse(msg="旧密码不正确")
|
||||
|
||||
@action(methods=["PUT"], detail=True, permission_classes=[IsAuthenticated])
|
||||
def reset_to_default_password(self, request, *args, **kwargs):
|
||||
"""恢复默认密码"""
|
||||
instance = Users.objects.filter(id=kwargs.get("pk")).first()
|
||||
if instance:
|
||||
instance.set_password(dispatch.get_system_config_values("base.default_password"))
|
||||
instance.save()
|
||||
return DetailResponse(data=None, msg="密码重置成功")
|
||||
else:
|
||||
return ErrorResponse(msg="未获取到用户")
|
||||
|
||||
@action(methods=["PUT"], detail=True)
|
||||
def reset_password(self, request, pk):
|
||||
"""
|
||||
密码重置
|
||||
"""
|
||||
instance = Users.objects.filter(id=pk).first()
|
||||
data = request.data
|
||||
new_pwd = data.get("newPassword")
|
||||
new_pwd2 = data.get("newPassword2")
|
||||
if instance:
|
||||
if new_pwd != new_pwd2:
|
||||
return ErrorResponse(msg="两次密码不匹配")
|
||||
else:
|
||||
instance.password = make_password(new_pwd)
|
||||
instance.save()
|
||||
return DetailResponse(data=None, msg="修改成功")
|
||||
else:
|
||||
return ErrorResponse(msg="未获取到用户")
|
||||
39
backend/dvadmin/utils/backends.py
Normal file
39
backend/dvadmin/utils/backends.py
Normal file
@@ -0,0 +1,39 @@
|
||||
import hashlib
|
||||
import logging
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.backends import ModelBackend
|
||||
from django.contrib.auth.hashers import check_password
|
||||
from django.utils import timezone
|
||||
|
||||
from dvadmin.utils.validator import CustomValidationError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
UserModel = get_user_model()
|
||||
|
||||
|
||||
class CustomBackend(ModelBackend):
|
||||
"""
|
||||
Django原生认证方式
|
||||
"""
|
||||
|
||||
def authenticate(self, request, username=None, password=None, **kwargs):
|
||||
msg = '%s 正在使用本地登录...' % username
|
||||
logger.info(msg)
|
||||
if username is None:
|
||||
username = kwargs.get(UserModel.USERNAME_FIELD)
|
||||
try:
|
||||
user = UserModel._default_manager.get_by_natural_key(username)
|
||||
except UserModel.DoesNotExist:
|
||||
UserModel().set_password(password)
|
||||
else:
|
||||
verify_password = check_password(password, user.password)
|
||||
if not verify_password:
|
||||
password = hashlib.md5(password.encode(encoding='UTF-8')).hexdigest()
|
||||
verify_password = check_password(password, user.password)
|
||||
if verify_password:
|
||||
if self.user_can_authenticate(user):
|
||||
user.last_login = timezone.now()
|
||||
user.save()
|
||||
return user
|
||||
raise CustomValidationError("当前用户已被禁用,请联系管理员!")
|
||||
89
backend/dvadmin/utils/core_initialize.py
Normal file
89
backend/dvadmin/utils/core_initialize.py
Normal file
@@ -0,0 +1,89 @@
|
||||
# 初始化基类
|
||||
import json
|
||||
import os
|
||||
|
||||
from django.apps import apps
|
||||
from rest_framework import request
|
||||
|
||||
from application import settings
|
||||
from dvadmin.system.models import Users
|
||||
|
||||
|
||||
class CoreInitialize:
|
||||
"""
|
||||
使用方法:继承此类,重写 run方法,在 run 中调用 save 进行数据初始化
|
||||
"""
|
||||
creator_id = None
|
||||
reset = False
|
||||
request = request
|
||||
file_path = None
|
||||
|
||||
def __init__(self, reset=False, creator_id=None, app=None):
|
||||
"""
|
||||
reset: 是否重置初始化数据
|
||||
creator_id: 创建人id
|
||||
"""
|
||||
self.reset = reset or self.reset
|
||||
self.creator_id = creator_id or self.creator_id
|
||||
self.app = app or ''
|
||||
self.request.user = Users.objects.order_by('create_datetime').first()
|
||||
|
||||
def init_base(self, Serializer, unique_fields=None):
|
||||
model = Serializer.Meta.model
|
||||
path_file = os.path.join(apps.get_app_config(self.app.split('.')[-1]).path, 'fixtures',
|
||||
f'init_{Serializer.Meta.model._meta.model_name}.json')
|
||||
if not os.path.isfile(path_file):
|
||||
print("文件不存在,跳过初始化")
|
||||
return
|
||||
with open(path_file,encoding="utf-8") as f:
|
||||
for data in json.load(f):
|
||||
filter_data = {}
|
||||
# 配置过滤条件,如果有唯一标识字段则使用唯一标识字段,否则使用全部字段
|
||||
if unique_fields:
|
||||
for field in unique_fields:
|
||||
if field in data:
|
||||
filter_data[field] = data[field]
|
||||
else:
|
||||
for key, value in data.items():
|
||||
if isinstance(value, list) or value == None or value == '':
|
||||
continue
|
||||
filter_data[key] = value
|
||||
instance = model.objects.filter(**filter_data).first()
|
||||
data["reset"] = self.reset
|
||||
serializer = Serializer(instance, data=data, request=self.request)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
print(f"[{self.app}][{model._meta.model_name}]初始化完成")
|
||||
|
||||
def save(self, obj, data: list, name=None, no_reset=False):
|
||||
name = name or obj._meta.verbose_name
|
||||
print(f"正在初始化[{obj._meta.label} => {name}]")
|
||||
if not no_reset and self.reset and obj not in settings.INITIALIZE_RESET_LIST:
|
||||
try:
|
||||
obj.objects.all().delete()
|
||||
settings.INITIALIZE_RESET_LIST.append(obj)
|
||||
except Exception:
|
||||
pass
|
||||
for ele in data:
|
||||
m2m_dict = {}
|
||||
new_data = {}
|
||||
for key, value in ele.items():
|
||||
# 判断传的 value 为 list 的多对多进行抽离,使用set 进行更新
|
||||
if isinstance(value, list) and value and isinstance(value[0], int):
|
||||
m2m_dict[key] = value
|
||||
else:
|
||||
new_data[key] = value
|
||||
object, _ = obj.objects.get_or_create(id=ele.get("id"), defaults=new_data)
|
||||
for key, m2m in m2m_dict.items():
|
||||
m2m = list(set(m2m))
|
||||
if m2m and len(m2m) > 0 and m2m[0]:
|
||||
exec(f"""
|
||||
if object.{key}:
|
||||
values_list = object.{key}.all().values_list('id', flat=True)
|
||||
values_list = list(set(list(values_list) + {m2m}))
|
||||
object.{key}.set(values_list)
|
||||
""")
|
||||
print(f"初始化完成[{obj._meta.label} => {name}]")
|
||||
|
||||
def run(self):
|
||||
raise NotImplementedError('.run() must be overridden')
|
||||
155
backend/dvadmin/utils/crud_mixin.py
Normal file
155
backend/dvadmin/utils/crud_mixin.py
Normal file
@@ -0,0 +1,155 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import AllowAny
|
||||
|
||||
from dvadmin.utils.json_response import DetailResponse
|
||||
|
||||
|
||||
class FastCrudMixin:
|
||||
"""
|
||||
定义快速CRUD数据操作的通用方法
|
||||
"""
|
||||
# 需要CRUD的字段
|
||||
crud_fields = None
|
||||
# 排除CRUD的字段
|
||||
exclude_fields = None
|
||||
# 自定义CRUD的JSON
|
||||
custom_crud_json = None
|
||||
# 需要修改的CRUD键值对
|
||||
crud_update_key_value = None
|
||||
|
||||
# 将Django的字段类型处理为JS类型
|
||||
def __handle_type(self, type):
|
||||
if type in ['BigAutoField', 'CharField']:
|
||||
return "input"
|
||||
if type == 'DateTimeField':
|
||||
return "datetime"
|
||||
if type == 'DateField':
|
||||
return "date"
|
||||
if type == 'IntegerField':
|
||||
return "number"
|
||||
if type == 'BooleanField':
|
||||
return "dict-switch"
|
||||
|
||||
# 获取字段属性信息
|
||||
def __get_field_attribute(self):
|
||||
result = []
|
||||
queryset = self.get_queryset()
|
||||
__name = ""
|
||||
__verbose_name = ""
|
||||
__type = "text"
|
||||
# 判断指定CRUD字段
|
||||
if self.crud_fields and type(self.crud_fields == list):
|
||||
for item in self.crud_fields:
|
||||
try:
|
||||
field = queryset.model._meta.get_field(item)
|
||||
field_type = field.get_internal_type()
|
||||
__name = field.name
|
||||
# 判断类型是否为外键类型,外键类型需要特殊方式获取verbose_name
|
||||
if field_type in ['ForeignKey', 'OneToOneField', 'ManyToManyField']:
|
||||
continue
|
||||
# try:
|
||||
# verbose_name = Users._meta.get_field(str(field.name)).verbose_name
|
||||
# except:
|
||||
# pass
|
||||
else:
|
||||
__verbose_name = field.verbose_name
|
||||
__type = self.__handle_type(field_type)
|
||||
except:
|
||||
continue
|
||||
result.append({"key": __name, "title": __verbose_name, "type": __type})
|
||||
else:
|
||||
# 获取model的所有字段及属性
|
||||
model_fields = queryset.model._meta.get_fields()
|
||||
# 遍历所有字段属性
|
||||
for field in model_fields:
|
||||
field_type = field.get_internal_type()
|
||||
__name = field.name
|
||||
# 判断需要排除的CRUD字段
|
||||
if self.exclude_fields and type(self.exclude_fields == list):
|
||||
if __name in self.exclude_fields:
|
||||
continue
|
||||
# 判断类型是否为外键类型,外键类型需要特殊方式获取verbose_name
|
||||
if field_type in ['ForeignKey', 'OneToOneField', 'ManyToManyField']:
|
||||
continue
|
||||
# try:
|
||||
# verbose_name = Users._meta.get_field(str(field.name)).verbose_name
|
||||
# except:
|
||||
# pass
|
||||
else:
|
||||
__verbose_name = field.verbose_name
|
||||
__type = self.__handle_type(field_type)
|
||||
result.append({"key": __name, "title": __verbose_name, "type": __type})
|
||||
return result
|
||||
|
||||
#获取key
|
||||
def __find_key(self,dct: dict,
|
||||
target_key: str,
|
||||
level: int = -1,
|
||||
index: int = -1) -> tuple:
|
||||
"""Find a key within a nested dictionary and return its level and index."""
|
||||
for k, v in dct.items():
|
||||
level += 1
|
||||
index += 1
|
||||
if k == target_key:
|
||||
return level, index
|
||||
elif isinstance(v, list):
|
||||
for i, dct_ in enumerate(v):
|
||||
if isinstance(dct_, dict):
|
||||
result = self.__find_key(dct_, target_key)
|
||||
if result is not None:
|
||||
return result
|
||||
else:
|
||||
continue
|
||||
elif isinstance(v, str) or isinstance(v, int) or isinstance(v, float):
|
||||
continue
|
||||
|
||||
# 修改字典中key的value
|
||||
def __update_nested_dict(self,nested_dict: dict,
|
||||
target_key: str,
|
||||
new_value) -> dict:
|
||||
"""Update a nested dictionary with a new value."""
|
||||
split_target_key = target_key.split('.')
|
||||
if len(split_target_key) > 1:
|
||||
new_dict = nested_dict[split_target_key[0]]
|
||||
for item in split_target_key[1:-1]:
|
||||
new_dict = new_dict[item]
|
||||
self.__update_nested_dict(new_dict, split_target_key[-1], new_value)
|
||||
else:
|
||||
nested_dict[target_key] = new_value
|
||||
return nested_dict
|
||||
|
||||
# 处理crud,返回columns
|
||||
def __handle_crud(self):
|
||||
result = self.__get_field_attribute()
|
||||
columns = dict()
|
||||
for item in result:
|
||||
key = item.get('key')
|
||||
title = item.get('title')
|
||||
type = item.get('type')
|
||||
columns[key] = {
|
||||
"title": title,
|
||||
"key": key,
|
||||
"type": type
|
||||
}
|
||||
# 对自定义的crud配置合并
|
||||
if self.custom_crud_json and isinstance(self.custom_crud_json,dict):
|
||||
columns = columns | self.custom_crud_json
|
||||
# 对curd进行修改配置
|
||||
if self.crud_update_key_value and isinstance(self.crud_update_key_value,dict):
|
||||
for key, value in self.crud_update_key_value.items():
|
||||
columns = self.__update_nested_dict(columns,key,value)
|
||||
return columns
|
||||
@action(methods=['get'], detail=False,permission_classes=[AllowAny])
|
||||
def init_crud(self, request):
|
||||
self.permission_classes = [AllowAny]
|
||||
columns = self.__handle_crud()
|
||||
expose = "({expose,dict})=>{"
|
||||
ret = "return {"
|
||||
res = "}}"
|
||||
data = f"""{expose}
|
||||
{ret}
|
||||
columns:{columns}
|
||||
{res}
|
||||
"""
|
||||
return DetailResponse(data=data)
|
||||
70
backend/dvadmin/utils/exception.py
Normal file
70
backend/dvadmin/utils/exception.py
Normal file
@@ -0,0 +1,70 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@author: 猿小天
|
||||
@contact: QQ:1638245306
|
||||
@Created on: 2021/6/2 002 16:06
|
||||
@Remark: 自定义异常处理
|
||||
"""
|
||||
import logging
|
||||
import traceback
|
||||
|
||||
from django.db.models import ProtectedError
|
||||
from django.http import Http404
|
||||
from rest_framework.exceptions import APIException as DRFAPIException, AuthenticationFailed, NotAuthenticated
|
||||
from rest_framework.status import HTTP_401_UNAUTHORIZED
|
||||
from rest_framework.views import set_rollback, exception_handler
|
||||
|
||||
from dvadmin.utils.json_response import ErrorResponse
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CustomAuthenticationFailed(NotAuthenticated):
|
||||
# 设置 status_code 属性为 400
|
||||
status_code = 400
|
||||
|
||||
def CustomExceptionHandler(ex, context):
|
||||
"""
|
||||
统一异常拦截处理
|
||||
目的:(1)取消所有的500异常响应,统一响应为标准错误返回
|
||||
(2)准确显示错误信息
|
||||
:param ex:
|
||||
:param context:
|
||||
:return:
|
||||
"""
|
||||
msg = ''
|
||||
code = 4000
|
||||
# 调用默认的异常处理函数
|
||||
response = exception_handler(ex, context)
|
||||
if isinstance(ex, AuthenticationFailed):
|
||||
# 如果是身份验证错误
|
||||
if response and response.data.get('detail') == "Given token not valid for any token type":
|
||||
code = 401
|
||||
msg = ex.detail
|
||||
elif response and response.data.get('detail') == "Token is blacklisted":
|
||||
# token在黑名单
|
||||
return ErrorResponse(status=HTTP_401_UNAUTHORIZED)
|
||||
else:
|
||||
code = 401
|
||||
msg = ex.detail
|
||||
elif isinstance(ex,Http404):
|
||||
code = 400
|
||||
msg = "接口地址不正确"
|
||||
elif isinstance(ex, DRFAPIException):
|
||||
set_rollback()
|
||||
msg = ex.detail
|
||||
if isinstance(msg,dict):
|
||||
for k, v in msg.items():
|
||||
for i in v:
|
||||
msg = "%s:%s" % (k, i)
|
||||
elif isinstance(ex, ProtectedError):
|
||||
set_rollback()
|
||||
msg = "删除失败:该条数据与其他数据有相关绑定"
|
||||
# elif isinstance(ex, DatabaseError):
|
||||
# set_rollback()
|
||||
# msg = "接口服务器异常,请联系管理员"
|
||||
elif isinstance(ex, Exception):
|
||||
logger.exception(traceback.format_exc())
|
||||
msg = str(ex)
|
||||
return ErrorResponse(msg=msg, code=code)
|
||||
331
backend/dvadmin/utils/filters.py
Normal file
331
backend/dvadmin/utils/filters.py
Normal file
@@ -0,0 +1,331 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@author: 猿小天
|
||||
@contact: QQ:1638245306
|
||||
@Created on: 2021/6/6 006 12:39
|
||||
@Remark: 自定义过滤器
|
||||
"""
|
||||
import operator
|
||||
import re
|
||||
from collections import OrderedDict
|
||||
from functools import reduce
|
||||
|
||||
import six
|
||||
from django.db.models import Q, F
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django_filters import utils
|
||||
from django_filters.filters import CharFilter
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from django_filters.utils import get_model_field
|
||||
from rest_framework.filters import BaseFilterBackend
|
||||
|
||||
from dvadmin.system.models import Dept, ApiWhiteList, RoleMenuButtonPermission
|
||||
|
||||
|
||||
def get_dept(dept_id: int, dept_all_list=None, dept_list=None):
|
||||
"""
|
||||
递归获取部门的所有下级部门
|
||||
:param dept_id: 需要获取的部门id
|
||||
:param dept_all_list: 所有部门列表
|
||||
:param dept_list: 递归部门list
|
||||
:return:
|
||||
"""
|
||||
if not dept_all_list:
|
||||
dept_all_list = Dept.objects.all().values("id", "parent")
|
||||
if dept_list is None:
|
||||
dept_list = [dept_id]
|
||||
for ele in dept_all_list:
|
||||
if ele.get("parent") == dept_id:
|
||||
dept_list.append(ele.get("id"))
|
||||
get_dept(ele.get("id"), dept_all_list, dept_list)
|
||||
return list(set(dept_list))
|
||||
|
||||
|
||||
class DataLevelPermissionsFilter(BaseFilterBackend):
|
||||
"""
|
||||
数据 级权限过滤器
|
||||
0. 获取用户的部门id,没有部门则返回空
|
||||
1. 判断过滤的数据是否有创建人所在部门 "creator" 字段,没有则返回全部
|
||||
2. 如果用户没有关联角色则返回本部门数据
|
||||
3. 根据角色的最大权限进行数据过滤(会有多个角色,进行去重取最大权限)
|
||||
3.1 判断用户是否为超级管理员角色/如果有1(所有数据) 则返回所有数据
|
||||
|
||||
4. 只为仅本人数据权限时只返回过滤本人数据,并且部门为自己本部门(考虑到用户会变部门,只能看当前用户所在的部门数据)
|
||||
5. 自定数据权限 获取部门,根据部门过滤
|
||||
"""
|
||||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
"""
|
||||
接口白名单是否认证数据权限
|
||||
"""
|
||||
api = request.path # 当前请求接口
|
||||
method = request.method # 当前请求方法
|
||||
methodList = ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
|
||||
method = methodList.index(method)
|
||||
# ***接口白名单***
|
||||
api_white_list = ApiWhiteList.objects.filter(enable_datasource=False).values(
|
||||
permission__api=F("url"), permission__method=F("method")
|
||||
)
|
||||
api_white_list = [
|
||||
str(item.get("permission__api").replace("{id}", ".*?"))
|
||||
+ ":"
|
||||
+ str(item.get("permission__method"))
|
||||
for item in api_white_list
|
||||
if item.get("permission__api")
|
||||
]
|
||||
for item in api_white_list:
|
||||
new_api = f"{api}:{method}"
|
||||
matchObj = re.match(item, new_api, re.M | re.I)
|
||||
if matchObj is None:
|
||||
continue
|
||||
else:
|
||||
return queryset
|
||||
"""
|
||||
判断是否为超级管理员:
|
||||
如果不是超级管理员,则进入下一步权限判断
|
||||
"""
|
||||
if request.user.is_superuser == 0:
|
||||
return self._extracted_from_filter_queryset_33(request, queryset, api, method)
|
||||
else:
|
||||
return queryset
|
||||
|
||||
# TODO Rename this here and in `filter_queryset`
|
||||
def _extracted_from_filter_queryset_33(self, request, queryset, api, method):
|
||||
# 0. 获取用户的部门id,没有部门则返回空
|
||||
user_dept_id = getattr(request.user, "dept_id", None)
|
||||
if not user_dept_id:
|
||||
return queryset.none()
|
||||
|
||||
# 1. 判断过滤的数据是否有创建人所在部门 "dept_belong_id" 字段
|
||||
if not getattr(queryset.model, "dept_belong_id", None):
|
||||
return queryset
|
||||
|
||||
# 2. 如果用户没有关联角色则返回本部门数据
|
||||
if not hasattr(request.user, "role"):
|
||||
return queryset.filter(dept_belong_id=user_dept_id)
|
||||
|
||||
# 3. 根据所有角色 获取所有权限范围
|
||||
# (0, "仅本人数据权限"),
|
||||
# (1, "本部门及以下数据权限"),
|
||||
# (2, "本部门数据权限"),
|
||||
# (3, "全部数据权限"),
|
||||
# (4, "自定数据权限")
|
||||
replace_str = re.compile('\d')
|
||||
re_api = replace_str.sub('{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',
|
||||
role_admin=F('role__admin')
|
||||
)
|
||||
dataScope_list = [] # 权限范围列表
|
||||
for ele in role_permission_list:
|
||||
# 判断用户是否为超级管理员角色/如果拥有[全部数据权限]则返回所有数据
|
||||
if ele.get("data_range") == 3 or ele.get("role_admin") == True:
|
||||
return queryset
|
||||
dataScope_list.append(ele.get("data_range"))
|
||||
dataScope_list = list(set(dataScope_list))
|
||||
|
||||
# 4. 只为仅本人数据权限时只返回过滤本人数据,并且部门为自己本部门(考虑到用户会变部门,只能看当前用户所在的部门数据)
|
||||
if 0 in dataScope_list:
|
||||
return queryset.filter(
|
||||
creator=request.user, dept_belong_id=user_dept_id
|
||||
)
|
||||
|
||||
# 5. 自定数据权限 获取部门,根据部门过滤
|
||||
dept_list = []
|
||||
for ele in dataScope_list:
|
||||
if ele == 1:
|
||||
dept_list.append(user_dept_id)
|
||||
dept_list.extend(
|
||||
get_dept(
|
||||
user_dept_id,
|
||||
)
|
||||
)
|
||||
elif ele == 2:
|
||||
dept_list.append(user_dept_id)
|
||||
elif ele == 4:
|
||||
dept_list.extend(
|
||||
request.user.role.filter(status=1).values_list(
|
||||
"dept__id", flat=True
|
||||
)
|
||||
)
|
||||
if queryset.model._meta.model_name == 'dept':
|
||||
return queryset.filter(id__in=list(set(dept_list)))
|
||||
return queryset.filter(dept_belong_id__in=list(set(dept_list)))
|
||||
|
||||
|
||||
class CustomDjangoFilterBackend(DjangoFilterBackend):
|
||||
lookup_prefixes = {
|
||||
"^": "istartswith",
|
||||
"=": "iexact",
|
||||
"@": "search",
|
||||
"$": "iregex",
|
||||
"~": "icontains",
|
||||
}
|
||||
|
||||
def construct_search(self, field_name, lookup_expr=None):
|
||||
lookup = self.lookup_prefixes.get(field_name[0])
|
||||
if lookup:
|
||||
field_name = field_name[1:]
|
||||
else:
|
||||
lookup = lookup_expr
|
||||
if field_name.endswith(lookup):
|
||||
return field_name
|
||||
return LOOKUP_SEP.join([field_name, lookup])
|
||||
|
||||
def find_filter_lookups(self, orm_lookups, search_term_key):
|
||||
for lookup in orm_lookups:
|
||||
# if lookup.find(search_term_key) >= 0:
|
||||
new_lookup = lookup.split("__")[0]
|
||||
# 修复条件搜索错误 bug
|
||||
if new_lookup == search_term_key:
|
||||
return lookup
|
||||
return None
|
||||
|
||||
def get_filterset_class(self, view, queryset=None):
|
||||
"""
|
||||
Return the `FilterSet` class used to filter the queryset.
|
||||
"""
|
||||
filterset_class = getattr(view, "filterset_class", None)
|
||||
filterset_fields = getattr(view, "filterset_fields", None)
|
||||
|
||||
# TODO: remove assertion in 2.1
|
||||
if filterset_class is None and hasattr(view, "filter_class"):
|
||||
utils.deprecate(
|
||||
"`%s.filter_class` attribute should be renamed `filterset_class`."
|
||||
% view.__class__.__name__
|
||||
)
|
||||
filterset_class = getattr(view, "filter_class", None)
|
||||
|
||||
# TODO: remove assertion in 2.1
|
||||
if filterset_fields is None and hasattr(view, "filter_fields"):
|
||||
utils.deprecate(
|
||||
"`%s.filter_fields` attribute should be renamed `filterset_fields`."
|
||||
% view.__class__.__name__
|
||||
)
|
||||
filterset_fields = getattr(view, "filter_fields", None)
|
||||
|
||||
if filterset_class:
|
||||
filterset_model = filterset_class._meta.model
|
||||
|
||||
# FilterSets do not need to specify a Meta class
|
||||
if filterset_model and queryset is not None:
|
||||
assert issubclass(
|
||||
queryset.model, filterset_model
|
||||
), "FilterSet model %s does not match queryset model %s" % (
|
||||
filterset_model,
|
||||
queryset.model,
|
||||
)
|
||||
|
||||
return filterset_class
|
||||
|
||||
if filterset_fields and queryset is not None:
|
||||
MetaBase = getattr(self.filterset_base, "Meta", object)
|
||||
|
||||
class AutoFilterSet(self.filterset_base):
|
||||
@classmethod
|
||||
def get_filters(cls):
|
||||
"""
|
||||
Get all filters for the filterset. This is the combination of declared and
|
||||
generated filters.
|
||||
"""
|
||||
|
||||
# No model specified - skip filter generation
|
||||
if not cls._meta.model:
|
||||
return cls.declared_filters.copy()
|
||||
|
||||
# Determine the filters that should be included on the filterset.
|
||||
filters = OrderedDict()
|
||||
fields = cls.get_fields()
|
||||
undefined = []
|
||||
|
||||
for field_name, lookups in fields.items():
|
||||
field = get_model_field(cls._meta.model, field_name)
|
||||
from django.db import models
|
||||
from timezone_field import TimeZoneField
|
||||
|
||||
# 不进行 过滤的model 类
|
||||
if isinstance(field, (models.JSONField, TimeZoneField)):
|
||||
continue
|
||||
# warn if the field doesn't exist.
|
||||
if field is None:
|
||||
undefined.append(field_name)
|
||||
# 更新默认字符串搜索为模糊搜索
|
||||
if isinstance(field, (models.CharField)) and filterset_fields == '__all__' and lookups == [
|
||||
'exact']:
|
||||
lookups = ['icontains']
|
||||
for lookup_expr in lookups:
|
||||
filter_name = cls.get_filter_name(field_name, lookup_expr)
|
||||
|
||||
# If the filter is explicitly declared on the class, skip generation
|
||||
if filter_name in cls.declared_filters:
|
||||
filters[filter_name] = cls.declared_filters[filter_name]
|
||||
continue
|
||||
|
||||
if field is not None:
|
||||
filters[filter_name] = cls.filter_for_field(
|
||||
field, field_name, lookup_expr
|
||||
)
|
||||
|
||||
# Allow Meta.fields to contain declared filters *only* when a list/tuple
|
||||
if isinstance(cls._meta.fields, (list, tuple)):
|
||||
undefined = [
|
||||
f for f in undefined if f not in cls.declared_filters
|
||||
]
|
||||
|
||||
if undefined:
|
||||
raise TypeError(
|
||||
"'Meta.fields' must not contain non-model field names: %s"
|
||||
% ", ".join(undefined)
|
||||
)
|
||||
|
||||
# Add in declared filters. This is necessary since we don't enforce adding
|
||||
# declared filters to the 'Meta.fields' option
|
||||
filters.update(cls.declared_filters)
|
||||
return filters
|
||||
|
||||
class Meta(MetaBase):
|
||||
model = queryset.model
|
||||
fields = filterset_fields
|
||||
|
||||
return AutoFilterSet
|
||||
|
||||
return None
|
||||
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
filterset = self.get_filterset(request, queryset, view)
|
||||
if filterset is None:
|
||||
return queryset
|
||||
if filterset.__class__.__name__ == "AutoFilterSet":
|
||||
queryset = filterset.queryset
|
||||
orm_lookups = []
|
||||
for search_field in filterset.filters:
|
||||
if isinstance(filterset.filters[search_field], CharFilter):
|
||||
orm_lookups.append(
|
||||
self.construct_search(six.text_type(search_field), filterset.filters[search_field].lookup_expr)
|
||||
)
|
||||
else:
|
||||
orm_lookups.append(search_field)
|
||||
conditions = []
|
||||
queries = []
|
||||
for search_term_key in filterset.data.keys():
|
||||
orm_lookup = self.find_filter_lookups(orm_lookups, search_term_key)
|
||||
if not orm_lookup:
|
||||
continue
|
||||
query = Q(**{orm_lookup: filterset.data[search_term_key]})
|
||||
queries.append(query)
|
||||
if len(queries) > 0:
|
||||
conditions.append(reduce(operator.and_, queries))
|
||||
queryset = queryset.filter(reduce(operator.and_, conditions))
|
||||
return queryset
|
||||
else:
|
||||
return queryset
|
||||
|
||||
if not filterset.is_valid() and self.raise_exception:
|
||||
raise utils.translate_validation(filterset.errors)
|
||||
return filterset.qs
|
||||
104
backend/dvadmin/utils/git_utils.py
Normal file
104
backend/dvadmin/utils/git_utils.py
Normal file
@@ -0,0 +1,104 @@
|
||||
import os
|
||||
from git.repo import Repo
|
||||
from git.repo.fun import is_git_dir
|
||||
|
||||
|
||||
class GitRepository(object):
|
||||
"""
|
||||
git仓库管理
|
||||
"""
|
||||
|
||||
def __init__(self, local_path, repo_url, branch='master'):
|
||||
self.local_path = local_path
|
||||
self.repo_url = repo_url
|
||||
self.repo = None
|
||||
self.initial(self.repo_url, branch)
|
||||
|
||||
def initial(self, repo_url, branch):
|
||||
"""
|
||||
初始化git仓库
|
||||
:param repo_url:
|
||||
:param branch:
|
||||
:return:
|
||||
"""
|
||||
if not os.path.exists(self.local_path):
|
||||
os.makedirs(self.local_path)
|
||||
git_local_path = os.path.join(self.local_path, '.git')
|
||||
if not is_git_dir(git_local_path):
|
||||
self.repo = Repo.clone_from(repo_url, to_path=self.local_path, branch=branch)
|
||||
else:
|
||||
self.repo = Repo(self.local_path)
|
||||
|
||||
def pull(self):
|
||||
"""
|
||||
从线上拉最新代码
|
||||
:return:
|
||||
"""
|
||||
self.repo.git.pull()
|
||||
|
||||
def branches(self):
|
||||
"""
|
||||
获取所有分支
|
||||
:return:
|
||||
"""
|
||||
branches = self.repo.remote().refs
|
||||
return [item.remote_head for item in branches if item.remote_head not in ['HEAD', ]]
|
||||
|
||||
def commits(self):
|
||||
"""
|
||||
获取所有提交记录
|
||||
:return:
|
||||
"""
|
||||
commit_log = self.repo.git.log('--pretty={"commit":"%h","author":"%an","summary":"%s","date":"%cd"}',
|
||||
max_count=50,
|
||||
date='format:%Y-%m-%d %H:%M')
|
||||
log_list = commit_log.split("\n")
|
||||
return [eval(item) for item in log_list]
|
||||
|
||||
def tags(self):
|
||||
"""
|
||||
获取所有tag
|
||||
:return:
|
||||
"""
|
||||
return [tag.name for tag in self.repo.tags]
|
||||
|
||||
def tags_exists(self, tag):
|
||||
"""
|
||||
tag是否存在
|
||||
:return:
|
||||
"""
|
||||
return tag in self.tags()
|
||||
|
||||
def change_to_branch(self, branch):
|
||||
"""
|
||||
切换分支
|
||||
:param branch:
|
||||
:return:
|
||||
"""
|
||||
self.repo.git.checkout(branch)
|
||||
|
||||
def change_to_commit(self, branch, commit):
|
||||
"""
|
||||
切换commit
|
||||
:param branch:
|
||||
:param commit:
|
||||
:return:
|
||||
"""
|
||||
self.change_to_branch(branch=branch)
|
||||
self.repo.git.reset('--hard', commit)
|
||||
|
||||
def change_to_tag(self, tag):
|
||||
"""
|
||||
切换tag
|
||||
:param tag:
|
||||
:return:
|
||||
"""
|
||||
self.repo.git.checkout(tag)
|
||||
|
||||
# if __name__ == '__main__':
|
||||
# local_path = os.path.join('codes', 't1')
|
||||
# repo = GitRepository(local_path, remote_path)
|
||||
# branch_list = repo.branches()
|
||||
# print(branch_list)
|
||||
# repo.change_to_branch('dev')
|
||||
# repo.pull()
|
||||
89
backend/dvadmin/utils/import_export.py
Normal file
89
backend/dvadmin/utils/import_export.py
Normal file
@@ -0,0 +1,89 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
import openpyxl
|
||||
from django.conf import settings
|
||||
|
||||
from dvadmin.utils.validator import CustomValidationError
|
||||
|
||||
|
||||
def import_to_data(file_url, field_data, m2m_fields=None):
|
||||
"""
|
||||
读取导入的excel文件
|
||||
:param file_url:
|
||||
:param field_data: 首行数据源
|
||||
:param m2m_fields: 多对多字段
|
||||
:return:
|
||||
"""
|
||||
# 读取excel 文件
|
||||
file_path_dir = os.path.join(settings.BASE_DIR, file_url)
|
||||
workbook = openpyxl.load_workbook(file_path_dir)
|
||||
table = workbook[workbook.sheetnames[0]]
|
||||
theader = tuple(table.values)[0] #Excel的表头
|
||||
is_update = '更新主键(勿改)' in theader #是否导入更新
|
||||
if is_update is False: #不是更新时,删除id列
|
||||
field_data.pop('id')
|
||||
# 获取参数映射
|
||||
validation_data_dict = {}
|
||||
for key, value in field_data.items():
|
||||
if isinstance(value, dict):
|
||||
choices = value.get("choices", {})
|
||||
data_dict = {}
|
||||
if choices.get("data"):
|
||||
for k, v in choices.get("data").items():
|
||||
data_dict[k] = v
|
||||
elif choices.get("queryset") and choices.get("values_name"):
|
||||
data_list = choices.get("queryset").values(choices.get("values_name"), "id")
|
||||
for ele in data_list:
|
||||
data_dict[ele.get(choices.get("values_name"))] = ele.get("id")
|
||||
else:
|
||||
continue
|
||||
validation_data_dict[key] = data_dict
|
||||
# 创建一个空列表,存储Excel的数据
|
||||
tables = []
|
||||
for i, row in enumerate(range(table.max_row)):
|
||||
if i == 0:
|
||||
continue
|
||||
array = {}
|
||||
for index, item in enumerate(field_data.items()):
|
||||
items = list(item)
|
||||
key = items[0]
|
||||
values = items[1]
|
||||
value_type = 'str'
|
||||
if isinstance(values, dict):
|
||||
value_type = values.get('type','str')
|
||||
cell_value = table.cell(row=row + 1, column=index + 2).value
|
||||
if cell_value is None or cell_value=='':
|
||||
continue
|
||||
elif value_type == 'date':
|
||||
print(61, datetime.strptime(str(cell_value), '%Y-%m-%d %H:%M:%S').date())
|
||||
try:
|
||||
cell_value = datetime.strptime(str(cell_value), '%Y-%m-%d %H:%M:%S').date()
|
||||
except:
|
||||
raise CustomValidationError('日期格式不正确')
|
||||
elif value_type == 'datetime':
|
||||
cell_value = datetime.strptime(str(cell_value), '%Y-%m-%d %H:%M:%S')
|
||||
else:
|
||||
# 由于excel导入数字类型后,会出现数字加 .0 的,进行处理
|
||||
if type(cell_value) is float and str(cell_value).split(".")[1] == "0":
|
||||
cell_value = int(str(cell_value).split(".")[0])
|
||||
elif type(cell_value) is str:
|
||||
cell_value = cell_value.strip(" \t\n\r")
|
||||
if key in validation_data_dict:
|
||||
array[key] = validation_data_dict.get(key, {}).get(cell_value, None)
|
||||
if key in m2m_fields:
|
||||
array[key] = list(
|
||||
filter(
|
||||
lambda x: x,
|
||||
[
|
||||
validation_data_dict.get(key, {}).get(value, None)
|
||||
for value in re.split(r"[,;:|.,;:\s]\s*", cell_value)
|
||||
],
|
||||
)
|
||||
)
|
||||
else:
|
||||
array[key] = cell_value
|
||||
tables.append(array)
|
||||
return tables
|
||||
345
backend/dvadmin/utils/import_export_mixin.py
Normal file
345
backend/dvadmin/utils/import_export_mixin.py
Normal file
@@ -0,0 +1,345 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from urllib.parse import quote
|
||||
|
||||
from django.db import transaction
|
||||
from django.http import HttpResponse
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.worksheet.datavalidation import DataValidation
|
||||
from openpyxl.utils import get_column_letter, quote_sheetname
|
||||
from openpyxl.worksheet.table import Table, TableStyleInfo
|
||||
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.request_util import get_verbose_name
|
||||
|
||||
|
||||
class ImportSerializerMixin:
|
||||
"""
|
||||
自定义导入模板、导入功能
|
||||
"""
|
||||
|
||||
# 导入字段
|
||||
import_field_dict = {}
|
||||
# 导入序列化器
|
||||
import_serializer_class = None
|
||||
# 表格表头最大宽度,默认50个字符
|
||||
export_column_width = 50
|
||||
|
||||
def is_number(self,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(self, string):
|
||||
"""
|
||||
获取字符串最大长度
|
||||
:param string:
|
||||
:return:
|
||||
"""
|
||||
length = 4
|
||||
if string is None:
|
||||
return length
|
||||
if self.is_number(string):
|
||||
return length
|
||||
for char in string:
|
||||
length += 2.1 if ord(char) > 256 else 1
|
||||
return round(length, 1) if length <= self.export_column_width else self.export_column_width
|
||||
|
||||
@action(methods=['get','post'],detail=False)
|
||||
@transaction.atomic # Django 事务,防止出错
|
||||
def import_data(self, request: Request, *args, **kwargs):
|
||||
"""
|
||||
导入模板
|
||||
:param request:
|
||||
:param args:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
assert self.import_field_dict, "'%s' 请配置对应的导出模板字段。" % self.__class__.__name__
|
||||
# 导出模板
|
||||
if request.method == "GET":
|
||||
# 示例数据
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
# 导出excel 表
|
||||
response = HttpResponse(content_type="application/msexcel")
|
||||
response["Access-Control-Expose-Headers"] = f"Content-Disposition"
|
||||
response[
|
||||
"Content-Disposition"
|
||||
] = f'attachment;filename={quote(str(f"导入{get_verbose_name(queryset)}模板.xlsx"))}'
|
||||
wb = Workbook()
|
||||
ws1 = wb.create_sheet("data", 1)
|
||||
ws1.sheet_state = "hidden"
|
||||
ws = wb.active
|
||||
row = get_column_letter(len(self.import_field_dict) + 1)
|
||||
column = 10
|
||||
header_data = [
|
||||
"序号",
|
||||
]
|
||||
validation_data_dict = {}
|
||||
for index, ele in enumerate(self.import_field_dict.values()):
|
||||
if isinstance(ele, dict):
|
||||
header_data.append(ele.get("title"))
|
||||
choices = ele.get("choices", {})
|
||||
if choices.get("data"):
|
||||
data_list = []
|
||||
data_list.extend(choices.get("data").keys())
|
||||
validation_data_dict[ele.get("title")] = data_list
|
||||
elif choices.get("queryset") and choices.get("values_name"):
|
||||
data_list = choices.get("queryset").values_list(choices.get("values_name"), flat=True)
|
||||
validation_data_dict[ele.get("title")] = list(data_list)
|
||||
else:
|
||||
continue
|
||||
column_letter = get_column_letter(len(validation_data_dict))
|
||||
dv = DataValidation(
|
||||
type="list",
|
||||
formula1=f"{quote_sheetname('data')}!${column_letter}$2:${column_letter}${len(validation_data_dict[ele.get('title')]) + 1}",
|
||||
allow_blank=True,
|
||||
)
|
||||
ws.add_data_validation(dv)
|
||||
dv.add(f"{get_column_letter(index + 2)}2:{get_column_letter(index + 2)}1048576")
|
||||
else:
|
||||
header_data.append(ele)
|
||||
# 添加数据列
|
||||
ws1.append(list(validation_data_dict.keys()))
|
||||
for index, validation_data in enumerate(validation_data_dict.values()):
|
||||
for inx, ele in enumerate(validation_data):
|
||||
ws1[f"{get_column_letter(index + 1)}{inx + 2}"] = ele
|
||||
# 插入导出模板正式数据
|
||||
df_len_max = [self.get_string_len(ele) for ele in header_data]
|
||||
ws.append(header_data)
|
||||
# 更新列宽
|
||||
for index, width in enumerate(df_len_max):
|
||||
ws.column_dimensions[get_column_letter(index + 1)].width = width
|
||||
tab = Table(displayName="Table1", ref=f"A1:{row}{column}") # 名称管理器
|
||||
style = TableStyleInfo(
|
||||
name="TableStyleLight11",
|
||||
showFirstColumn=True,
|
||||
showLastColumn=True,
|
||||
showRowStripes=True,
|
||||
showColumnStripes=True,
|
||||
)
|
||||
tab.tableStyleInfo = style
|
||||
ws.add_table(tab)
|
||||
wb.save(response)
|
||||
return response
|
||||
else:
|
||||
# 从excel中组织对应的数据结构,然后使用序列化器保存
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
# 获取多对多字段
|
||||
m2m_fields = [
|
||||
ele.name
|
||||
for ele in queryset.model._meta.get_fields()
|
||||
if hasattr(ele, "many_to_many") and ele.many_to_many == True
|
||||
]
|
||||
import_field_dict = {'id':'更新主键(勿改)',**self.import_field_dict}
|
||||
data = import_to_data(request.data.get("url"), import_field_dict, m2m_fields)
|
||||
for ele in data:
|
||||
filter_dic = {'id':ele.get('id')}
|
||||
instance = filter_dic and queryset.filter(**filter_dic).first()
|
||||
# print(156,ele)
|
||||
serializer = self.import_serializer_class(instance, data=ele, request=request)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
serializer.save()
|
||||
return DetailResponse(msg=f"导入成功!")
|
||||
|
||||
@action(methods=['get'],detail=False)
|
||||
def update_template(self,request):
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
assert self.import_field_dict, "'%s' 请配置对应的导入模板字段。" % self.__class__.__name__
|
||||
assert self.import_serializer_class, "'%s' 请配置对应的导入序列化器。" % self.__class__.__name__
|
||||
data = self.import_serializer_class(queryset, many=True, request=request).data
|
||||
# 导出excel 表
|
||||
response = HttpResponse(content_type="application/msexcel")
|
||||
response["Access-Control-Expose-Headers"] = f"Content-Disposition"
|
||||
response["content-disposition"] = f'attachment;filename={quote(str(f"导出{get_verbose_name(queryset)}.xlsx"))}'
|
||||
wb = Workbook()
|
||||
ws1 = wb.create_sheet("data", 1)
|
||||
ws1.sheet_state = "hidden"
|
||||
ws = wb.active
|
||||
import_field_dict = {}
|
||||
header_data = ["序号","更新主键(勿改)"]
|
||||
hidden_header = ["#","id"]
|
||||
#----设置选项----
|
||||
validation_data_dict = {}
|
||||
for index, item in enumerate(self.import_field_dict.items()):
|
||||
items = list(item)
|
||||
key = items[0]
|
||||
value = items[1]
|
||||
if isinstance(value, dict):
|
||||
header_data.append(value.get("title"))
|
||||
hidden_header.append(value.get('display'))
|
||||
choices = value.get("choices", {})
|
||||
if choices.get("data"):
|
||||
data_list = []
|
||||
data_list.extend(choices.get("data").keys())
|
||||
validation_data_dict[value.get("title")] = data_list
|
||||
elif choices.get("queryset") and choices.get("values_name"):
|
||||
data_list = choices.get("queryset").values_list(choices.get("values_name"), flat=True)
|
||||
validation_data_dict[value.get("title")] = list(data_list)
|
||||
else:
|
||||
continue
|
||||
column_letter = get_column_letter(len(validation_data_dict))
|
||||
dv = DataValidation(
|
||||
type="list",
|
||||
formula1=f"{quote_sheetname('data')}!${column_letter}$2:${column_letter}${len(validation_data_dict[value.get('title')]) + 1}",
|
||||
allow_blank=True,
|
||||
)
|
||||
ws.add_data_validation(dv)
|
||||
dv.add(f"{get_column_letter(index + 3)}2:{get_column_letter(index + 3)}1048576")
|
||||
else:
|
||||
header_data.append(value)
|
||||
hidden_header.append(key)
|
||||
# 添加数据列
|
||||
ws1.append(list(validation_data_dict.keys()))
|
||||
for index, validation_data in enumerate(validation_data_dict.values()):
|
||||
for inx, ele in enumerate(validation_data):
|
||||
ws1[f"{get_column_letter(index + 1)}{inx + 2}"] = ele
|
||||
#--------
|
||||
df_len_max = [self.get_string_len(ele) for ele in header_data]
|
||||
row = get_column_letter(len(hidden_header) + 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,list):
|
||||
results_list.append(str(val))
|
||||
else:
|
||||
results_list.append(val)
|
||||
# 计算最大列宽度
|
||||
if isinstance(val,str):
|
||||
result_column_width = self.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)
|
||||
wb.save(response)
|
||||
return response
|
||||
|
||||
|
||||
class ExportSerializerMixin:
|
||||
"""
|
||||
自定义导出功能
|
||||
"""
|
||||
|
||||
# 导出字段
|
||||
export_field_label = []
|
||||
# 导出序列化器
|
||||
export_serializer_class = None
|
||||
# 表格表头最大宽度,默认50个字符
|
||||
export_column_width = 50
|
||||
|
||||
def is_number(self,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(self, string):
|
||||
"""
|
||||
获取字符串最大长度
|
||||
:param string:
|
||||
:return:
|
||||
"""
|
||||
length = 4
|
||||
if string is None:
|
||||
return length
|
||||
if self.is_number(string):
|
||||
return length
|
||||
for char in string:
|
||||
length += 2.1 if ord(char) > 256 else 1
|
||||
return round(length, 1) if length <= self.export_column_width else self.export_column_width
|
||||
|
||||
@action(methods=['get'],detail=False)
|
||||
def export_data(self, request: Request, *args, **kwargs):
|
||||
"""
|
||||
导出功能
|
||||
:param request:
|
||||
:param args:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
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
|
||||
# 导出excel 表
|
||||
response = HttpResponse(content_type="application/msexcel")
|
||||
response["Access-Control-Expose-Headers"] = f"Content-Disposition"
|
||||
response["content-disposition"] = f'attachment;filename={quote(str(f"导出{get_verbose_name(queryset)}.xlsx"))}'
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
header_data = ["序号", *self.export_field_label.values()]
|
||||
hidden_header = ["#", *self.export_field_label.keys()]
|
||||
df_len_max = [self.get_string_len(ele) for ele in header_data]
|
||||
row = get_column_letter(len(self.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("")
|
||||
else:
|
||||
results_list.append(val)
|
||||
# 计算最大列宽度
|
||||
result_column_width = self.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)
|
||||
wb.save(response)
|
||||
return response
|
||||
61
backend/dvadmin/utils/json_response.py
Normal file
61
backend/dvadmin/utils/json_response.py
Normal file
@@ -0,0 +1,61 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@author: 猿小天
|
||||
@contact: QQ:1638245306
|
||||
@Created on: 2021/6/2 002 14:43
|
||||
@Remark: 自定义的JsonResonpse文件
|
||||
"""
|
||||
|
||||
from rest_framework.response import Response
|
||||
|
||||
|
||||
class SuccessResponse(Response):
|
||||
"""
|
||||
标准响应成功的返回, SuccessResponse(data)或者SuccessResponse(data=data)
|
||||
(1)默认code返回2000, 不支持指定其他返回码
|
||||
"""
|
||||
|
||||
def __init__(self, data=None, msg='success', status=None, template_name=None, headers=None, exception=False,
|
||||
content_type=None,page=1,limit=1,total=1):
|
||||
std_data = {
|
||||
"code": 2000,
|
||||
"page": page,
|
||||
"limit": limit,
|
||||
"total": total,
|
||||
"data": data,
|
||||
"msg": msg
|
||||
}
|
||||
super().__init__(std_data, status, template_name, headers, exception, content_type)
|
||||
|
||||
|
||||
class DetailResponse(Response):
|
||||
"""
|
||||
不包含分页信息的接口返回,主要用于单条数据查询
|
||||
(1)默认code返回2000, 不支持指定其他返回码
|
||||
"""
|
||||
|
||||
def __init__(self, data=None, msg='success', status=None, template_name=None, headers=None, exception=False,
|
||||
content_type=None,):
|
||||
std_data = {
|
||||
"code": 2000,
|
||||
"data": data,
|
||||
"msg": msg
|
||||
}
|
||||
super().__init__(std_data, status, template_name, headers, exception, content_type)
|
||||
|
||||
|
||||
class ErrorResponse(Response):
|
||||
"""
|
||||
标准响应错误的返回,ErrorResponse(msg='xxx')
|
||||
(1)默认错误码返回400, 也可以指定其他返回码:ErrorResponse(code=xxx)
|
||||
"""
|
||||
|
||||
def __init__(self, data=None, msg='error', code=400, status=None, template_name=None, headers=None,
|
||||
exception=False, content_type=None):
|
||||
std_data = {
|
||||
"code": code,
|
||||
"data": data,
|
||||
"msg": msg
|
||||
}
|
||||
super().__init__(std_data, status, template_name, headers, exception, content_type)
|
||||
89
backend/dvadmin/utils/middleware.py
Normal file
89
backend/dvadmin/utils/middleware.py
Normal file
@@ -0,0 +1,89 @@
|
||||
"""
|
||||
日志 django中间件
|
||||
"""
|
||||
import json
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
|
||||
from dvadmin.system.models import OperationLog
|
||||
from dvadmin.utils.request_util import get_request_user, get_request_ip, get_request_data, get_request_path, get_os, \
|
||||
get_browser, get_verbose_name
|
||||
|
||||
|
||||
class ApiLoggingMiddleware(MiddlewareMixin):
|
||||
"""
|
||||
用于记录API访问日志中间件
|
||||
"""
|
||||
|
||||
def __init__(self, get_response=None):
|
||||
super().__init__(get_response)
|
||||
self.enable = getattr(settings, 'API_LOG_ENABLE', None) or False
|
||||
self.methods = getattr(settings, 'API_LOG_METHODS', None) or set()
|
||||
self.operation_log_id = None
|
||||
|
||||
@classmethod
|
||||
def __handle_request(cls, request):
|
||||
request.request_ip = get_request_ip(request)
|
||||
request.request_data = get_request_data(request)
|
||||
request.request_path = get_request_path(request)
|
||||
|
||||
def __handle_response(self, request, response):
|
||||
# request_data,request_ip由PermissionInterfaceMiddleware中间件中添加的属性
|
||||
body = getattr(request, 'request_data', {})
|
||||
# 请求含有password则用*替换掉(暂时先用于所有接口的password请求参数)
|
||||
if isinstance(body, dict) and body.get('password', ''):
|
||||
body['password'] = '*' * len(body['password'])
|
||||
if not hasattr(response, 'data') or not isinstance(response.data, dict):
|
||||
response.data = {}
|
||||
try:
|
||||
if not response.data and response.content:
|
||||
content = json.loads(response.content.decode())
|
||||
response.data = content if isinstance(content, dict) else {}
|
||||
except Exception:
|
||||
return
|
||||
user = get_request_user(request)
|
||||
info = {
|
||||
'request_ip': getattr(request, 'request_ip', 'unknown'),
|
||||
'creator': user if not isinstance(user, AnonymousUser) else None,
|
||||
'dept_belong_id': getattr(request.user, 'dept_id', None),
|
||||
'request_method': request.method,
|
||||
'request_path': request.request_path,
|
||||
'request_body': body,
|
||||
'response_code': response.data.get('code'),
|
||||
'request_os': get_os(request),
|
||||
'request_browser': get_browser(request),
|
||||
'request_msg': request.session.get('request_msg'),
|
||||
'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)
|
||||
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()
|
||||
|
||||
def process_view(self, request, view_func, view_args, view_kwargs):
|
||||
if hasattr(view_func, 'cls') and hasattr(view_func.cls, 'queryset'):
|
||||
if self.enable:
|
||||
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
|
||||
|
||||
return
|
||||
|
||||
def process_request(self, request):
|
||||
self.__handle_request(request)
|
||||
|
||||
def process_response(self, request, response):
|
||||
"""
|
||||
主要请求处理完之后记录
|
||||
:param request:
|
||||
:param response:
|
||||
:return:
|
||||
"""
|
||||
if self.enable:
|
||||
if self.methods == 'ALL' or request.method in self.methods:
|
||||
self.__handle_response(request, response)
|
||||
return response
|
||||
114
backend/dvadmin/utils/models.py
Normal file
114
backend/dvadmin/utils/models.py
Normal file
@@ -0,0 +1,114 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@author: 猿小天
|
||||
@contact: QQ:1638245306
|
||||
@Created on: 2021/5/31 031 22:08
|
||||
@Remark: 公共基础model类
|
||||
"""
|
||||
import uuid
|
||||
|
||||
from django.apps import apps
|
||||
from django.db import models
|
||||
from django.db.models import QuerySet
|
||||
|
||||
from application import settings
|
||||
table_prefix = settings.TABLE_PREFIX # 数据库表名前缀
|
||||
|
||||
|
||||
class SoftDeleteQuerySet(QuerySet):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
class SoftDeleteManager(models.Manager):
|
||||
"""支持软删除"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.__add_is_del_filter = False
|
||||
super(SoftDeleteManager, self).__init__(*args, **kwargs)
|
||||
|
||||
def filter(self, *args, **kwargs):
|
||||
# 考虑是否主动传入is_deleted
|
||||
if not kwargs.get('is_deleted') is None:
|
||||
self.__add_is_del_filter = True
|
||||
return super(SoftDeleteManager, self).filter(*args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
if self.__add_is_del_filter:
|
||||
return SoftDeleteQuerySet(self.model, using=self._db).exclude(is_deleted=False)
|
||||
return SoftDeleteQuerySet(self.model).exclude(is_deleted=True)
|
||||
|
||||
def get_by_natural_key(self,name):
|
||||
return SoftDeleteQuerySet(self.model).get(username=name)
|
||||
|
||||
|
||||
class SoftDeleteModel(models.Model):
|
||||
"""
|
||||
软删除模型
|
||||
一旦继承,就将开启软删除
|
||||
"""
|
||||
is_deleted = models.BooleanField(verbose_name="是否软删除", help_text='是否软删除', default=False, db_index=True)
|
||||
objects = SoftDeleteManager()
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
verbose_name = '软删除模型'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def delete(self, using=None, soft_delete=True, *args, **kwargs):
|
||||
"""
|
||||
重写删除方法,直接开启软删除
|
||||
"""
|
||||
self.is_deleted = True
|
||||
self.save(using=using)
|
||||
|
||||
|
||||
class CoreModel(models.Model):
|
||||
"""
|
||||
核心标准抽象模型模型,可直接继承使用
|
||||
增加审计字段, 覆盖字段时, 字段名称请勿修改, 必须统一审计字段名称
|
||||
"""
|
||||
id = models.BigAutoField(primary_key=True, help_text="Id", verbose_name="Id")
|
||||
description = models.CharField(max_length=255, verbose_name="描述", null=True, blank=True, help_text="描述")
|
||||
creator = models.ForeignKey(to=settings.AUTH_USER_MODEL, related_query_name='creator_query', null=True,
|
||||
verbose_name='创建人', help_text="创建人", on_delete=models.SET_NULL, db_constraint=False)
|
||||
modifier = models.CharField(max_length=255, null=True, blank=True, help_text="修改人", verbose_name="修改人")
|
||||
dept_belong_id = models.CharField(max_length=255, help_text="数据归属部门", null=True, blank=True, verbose_name="数据归属部门")
|
||||
update_datetime = models.DateTimeField(auto_now=True, null=True, blank=True, help_text="修改时间", verbose_name="修改时间")
|
||||
create_datetime = models.DateTimeField(auto_now_add=True, null=True, blank=True, help_text="创建时间",
|
||||
verbose_name="创建时间")
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
verbose_name = '核心模型'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
|
||||
|
||||
|
||||
def get_all_models_objects(model_name=None):
|
||||
"""
|
||||
获取所有 models 对象
|
||||
:return: {}
|
||||
"""
|
||||
settings.ALL_MODELS_OBJECTS = {}
|
||||
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": []
|
||||
}
|
||||
for field in item._meta.fields:
|
||||
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:
|
||||
return settings.ALL_MODELS_OBJECTS[model_name] or {}
|
||||
return settings.ALL_MODELS_OBJECTS or {}
|
||||
85
backend/dvadmin/utils/pagination.py
Normal file
85
backend/dvadmin/utils/pagination.py
Normal file
@@ -0,0 +1,85 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@author: 猿小天
|
||||
|
||||
@contact: QQ:1638245306
|
||||
|
||||
@Created on: 2020/4/16 23:35
|
||||
"""
|
||||
from collections import OrderedDict
|
||||
|
||||
from django.core import paginator
|
||||
from django.core.paginator import Paginator as DjangoPaginator, InvalidPage
|
||||
from rest_framework.pagination import PageNumberPagination
|
||||
from rest_framework.response import Response
|
||||
|
||||
|
||||
class CustomPagination(PageNumberPagination):
|
||||
page_size = 10
|
||||
page_size_query_param = "limit"
|
||||
max_page_size = 999
|
||||
django_paginator_class = DjangoPaginator
|
||||
|
||||
def paginate_queryset(self, queryset, request, view=None):
|
||||
"""
|
||||
Paginate a queryset if required, either returning a
|
||||
page object, or `None` if pagination is not configured for this view.
|
||||
"""
|
||||
empty = True
|
||||
|
||||
page_size = self.get_page_size(request)
|
||||
if not page_size:
|
||||
return None
|
||||
|
||||
paginator = self.django_paginator_class(queryset, page_size)
|
||||
page_number = request.query_params.get(self.page_query_param, 1)
|
||||
if page_number in self.last_page_strings:
|
||||
page_number = paginator.num_pages
|
||||
|
||||
try:
|
||||
self.page = paginator.page(page_number)
|
||||
except InvalidPage as exc:
|
||||
|
||||
# msg = self.invalid_page_message.format(
|
||||
# page_number=page_number, message=str(exc)
|
||||
# )
|
||||
# raise NotFound(msg)
|
||||
empty = False
|
||||
pass
|
||||
|
||||
if paginator.num_pages > 1 and self.template is not None:
|
||||
# The browsable API should display pagination controls.
|
||||
self.display_page_controls = True
|
||||
|
||||
self.request = request
|
||||
|
||||
if not empty:
|
||||
self.page = []
|
||||
|
||||
return list(self.page)
|
||||
def get_paginated_response(self, data):
|
||||
code = 2000
|
||||
msg = 'success'
|
||||
page =int(self.get_page_number(self.request, paginator)) or 1
|
||||
total=self.page.paginator.count if self.page else 0
|
||||
limit= int(self.get_page_size(self.request)) or 10
|
||||
is_next= self.page.has_next()
|
||||
is_previous= self.page.has_previous()
|
||||
data=data
|
||||
|
||||
if not data:
|
||||
code = 2000
|
||||
msg = "暂无数据"
|
||||
data = []
|
||||
|
||||
return Response(OrderedDict([
|
||||
('code', code),
|
||||
('msg', msg),
|
||||
('page', page),
|
||||
('limit', limit),
|
||||
('total',total),
|
||||
('is_next',is_next),
|
||||
('is_previous', is_previous),
|
||||
('data', data)
|
||||
]))
|
||||
98
backend/dvadmin/utils/permission.py
Normal file
98
backend/dvadmin/utils/permission.py
Normal file
@@ -0,0 +1,98 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@author: 猿小天
|
||||
@contact: QQ:1638245306
|
||||
@Created on: 2021/6/6 006 10:30
|
||||
@Remark: 自定义权限
|
||||
"""
|
||||
import re
|
||||
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.db.models import F
|
||||
from rest_framework.permissions import BasePermission
|
||||
|
||||
from dvadmin.system.models import ApiWhiteList, RoleMenuButtonPermission
|
||||
|
||||
|
||||
def ValidationApi(reqApi, validApi):
|
||||
"""
|
||||
验证当前用户是否有接口权限
|
||||
:param reqApi: 当前请求的接口
|
||||
:param validApi: 用于验证的接口
|
||||
:return: True或者False
|
||||
"""
|
||||
if validApi is not None:
|
||||
valid_api = validApi.replace('{id}', '.*?')
|
||||
matchObj = re.match(valid_api, reqApi, re.M | re.I)
|
||||
if matchObj:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class AnonymousUserPermission(BasePermission):
|
||||
"""
|
||||
匿名用户权限
|
||||
"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
if isinstance(request.user, AnonymousUser):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def ReUUID(api):
|
||||
"""
|
||||
将接口的uuid替换掉
|
||||
:param api:
|
||||
:return:
|
||||
"""
|
||||
pattern = re.compile(r'[a-f\d]{4}(?:[a-f\d]{4}-){4}[a-f\d]{12}/$')
|
||||
m = pattern.search(api)
|
||||
if m:
|
||||
res = api.replace(m.group(0), ".*/")
|
||||
return res
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class CustomPermission(BasePermission):
|
||||
"""自定义权限"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
if isinstance(request.user, AnonymousUser):
|
||||
return False
|
||||
# 判断是否是超级管理员
|
||||
if request.user.is_superuser:
|
||||
return True
|
||||
else:
|
||||
api = request.path # 当前请求接口
|
||||
method = request.method # 当前请求方法
|
||||
methodList = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH']
|
||||
method = methodList.index(method)
|
||||
# ***接口白名单***
|
||||
api_white_list = ApiWhiteList.objects.values(permission__api=F('url'), permission__method=F('method'))
|
||||
api_white_list = [
|
||||
str(item.get('permission__api').replace('{id}', '([a-zA-Z0-9-]+)')) + ":" + str(
|
||||
item.get('permission__method')) + '$' for item in api_white_list if item.get('permission__api')]
|
||||
# ********#
|
||||
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')) # 获取当前用户的角色拥有的所有接口
|
||||
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')]
|
||||
new_api_ist = api_white_list + ApiList
|
||||
new_api = api + ":" + str(method)
|
||||
for item in new_api_ist:
|
||||
matchObj = re.match(item, new_api, re.M | re.I)
|
||||
if matchObj is None:
|
||||
continue
|
||||
else:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
219
backend/dvadmin/utils/request_util.py
Normal file
219
backend/dvadmin/utils/request_util.py
Normal file
@@ -0,0 +1,219 @@
|
||||
"""
|
||||
Request工具类
|
||||
"""
|
||||
import json
|
||||
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AbstractBaseUser
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.urls.resolvers import ResolverMatch
|
||||
from rest_framework_simplejwt.authentication import JWTAuthentication
|
||||
from user_agents import parse
|
||||
|
||||
from dvadmin.system.models import LoginLog
|
||||
|
||||
|
||||
def get_request_user(request):
|
||||
"""
|
||||
获取请求user
|
||||
(1)如果request里的user没有认证,那么则手动认证一次
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
user: AbstractBaseUser = getattr(request, 'user', None)
|
||||
if user and user.is_authenticated:
|
||||
return user
|
||||
try:
|
||||
user, tokrn = JWTAuthentication().authenticate(request)
|
||||
except Exception as e:
|
||||
pass
|
||||
return user or AnonymousUser()
|
||||
|
||||
|
||||
def get_request_ip(request):
|
||||
"""
|
||||
获取请求IP
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '')
|
||||
if x_forwarded_for:
|
||||
ip = x_forwarded_for.split(',')[-1].strip()
|
||||
return ip
|
||||
ip = request.META.get('REMOTE_ADDR', '') or getattr(request, 'request_ip', None)
|
||||
return ip or 'unknown'
|
||||
|
||||
|
||||
def get_request_data(request):
|
||||
"""
|
||||
获取请求参数
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
request_data = getattr(request, 'request_data', None)
|
||||
if request_data:
|
||||
return request_data
|
||||
data: dict = {**request.GET.dict(), **request.POST.dict()}
|
||||
if not data:
|
||||
try:
|
||||
body = request.body
|
||||
if body:
|
||||
data = json.loads(body)
|
||||
except Exception as e:
|
||||
pass
|
||||
if not isinstance(data, dict):
|
||||
data = {'data': data}
|
||||
return data
|
||||
|
||||
|
||||
def get_request_path(request, *args, **kwargs):
|
||||
"""
|
||||
获取请求路径
|
||||
:param request:
|
||||
:param args:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
request_path = getattr(request, 'request_path', None)
|
||||
if request_path:
|
||||
return request_path
|
||||
values = []
|
||||
for arg in args:
|
||||
if len(arg) == 0:
|
||||
continue
|
||||
if isinstance(arg, str):
|
||||
values.append(arg)
|
||||
elif isinstance(arg, (tuple, set, list)):
|
||||
values.extend(arg)
|
||||
elif isinstance(arg, dict):
|
||||
values.extend(arg.values())
|
||||
if len(values) == 0:
|
||||
return request.path
|
||||
path: str = request.path
|
||||
for value in values:
|
||||
path = path.replace('/' + value, '/' + '{id}')
|
||||
return path
|
||||
|
||||
|
||||
def get_request_canonical_path(request, ):
|
||||
"""
|
||||
获取请求路径
|
||||
:param request:
|
||||
:param args:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
request_path = getattr(request, 'request_canonical_path', None)
|
||||
if request_path:
|
||||
return request_path
|
||||
path: str = request.path
|
||||
resolver_match: ResolverMatch = request.resolver_match
|
||||
for value in resolver_match.args:
|
||||
path = path.replace(f"/{value}", "/{id}")
|
||||
for key, value in resolver_match.kwargs.items():
|
||||
if key == 'pk':
|
||||
path = path.replace(f"/{value}", f"/{{id}}")
|
||||
continue
|
||||
path = path.replace(f"/{value}", f"/{{{key}}}")
|
||||
|
||||
return path
|
||||
|
||||
|
||||
def get_browser(request, ):
|
||||
"""
|
||||
获取浏览器名
|
||||
:param request:
|
||||
:param args:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
ua_string = request.META['HTTP_USER_AGENT']
|
||||
user_agent = parse(ua_string)
|
||||
return user_agent.get_browser()
|
||||
|
||||
|
||||
def get_os(request, ):
|
||||
"""
|
||||
获取操作系统
|
||||
:param request:
|
||||
:param args:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
ua_string = request.META['HTTP_USER_AGENT']
|
||||
user_agent = parse(ua_string)
|
||||
return user_agent.get_os()
|
||||
|
||||
|
||||
def get_verbose_name(queryset=None, view=None, model=None):
|
||||
"""
|
||||
获取 verbose_name
|
||||
:param request:
|
||||
:param view:
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
if queryset is not None and hasattr(queryset, 'model'):
|
||||
model = queryset.model
|
||||
elif view and hasattr(view.get_queryset(), 'model'):
|
||||
model = view.get_queryset().model
|
||||
elif view and hasattr(view.get_serializer(), 'Meta') and hasattr(view.get_serializer().Meta, 'model'):
|
||||
model = view.get_serializer().Meta.model
|
||||
if model:
|
||||
return getattr(model, '_meta').verbose_name
|
||||
else:
|
||||
model = queryset.model._meta.verbose_name
|
||||
except Exception as e:
|
||||
pass
|
||||
return model if model else ""
|
||||
|
||||
|
||||
def get_ip_analysis(ip):
|
||||
"""
|
||||
获取ip详细概略
|
||||
:param ip: ip地址
|
||||
:return:
|
||||
"""
|
||||
data = {
|
||||
"continent": "",
|
||||
"country": "",
|
||||
"province": "",
|
||||
"city": "",
|
||||
"district": "",
|
||||
"isp": "",
|
||||
"area_code": "",
|
||||
"country_english": "",
|
||||
"country_code": "",
|
||||
"longitude": "",
|
||||
"latitude": ""
|
||||
}
|
||||
if ip != 'unknown' and ip:
|
||||
if getattr(settings, 'ENABLE_LOGIN_ANALYSIS_LOG', True):
|
||||
try:
|
||||
res = requests.get(url='https://ip.django-vue-admin.com/ip/analysis', params={"ip": ip}, timeout=5)
|
||||
if res.status_code == 200:
|
||||
res_data = res.json()
|
||||
if res_data.get('code') == 0:
|
||||
data = res_data.get('data')
|
||||
return data
|
||||
except Exception as e:
|
||||
print(e)
|
||||
return data
|
||||
|
||||
|
||||
def save_login_log(request):
|
||||
"""
|
||||
保存登录日志
|
||||
:return:
|
||||
"""
|
||||
ip = get_request_ip(request=request)
|
||||
analysis_data = get_ip_analysis(ip)
|
||||
analysis_data['username'] = request.user.username
|
||||
analysis_data['ip'] = ip
|
||||
analysis_data['agent'] = str(parse(request.META['HTTP_USER_AGENT']))
|
||||
analysis_data['browser'] = get_browser(request)
|
||||
analysis_data['os'] = get_os(request)
|
||||
analysis_data['creator_id'] = request.user.id
|
||||
analysis_data['dept_belong_id'] = getattr(request.user, 'dept_id', '')
|
||||
LoginLog.objects.create(**analysis_data)
|
||||
170
backend/dvadmin/utils/serializers.py
Normal file
170
backend/dvadmin/utils/serializers.py
Normal file
@@ -0,0 +1,170 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@author: 猿小天
|
||||
@contact: QQ:1638245306
|
||||
@Created on: 2021/6/1 001 22:47
|
||||
@Remark: 自定义序列化器
|
||||
"""
|
||||
from rest_framework import serializers
|
||||
from rest_framework.fields import empty
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from django.utils.functional import cached_property
|
||||
from rest_framework.utils.serializer_helpers import BindingDict
|
||||
|
||||
from dvadmin.system.models import Users
|
||||
from django_restql.mixins import DynamicFieldsMixin
|
||||
|
||||
|
||||
class CustomModelSerializer(DynamicFieldsMixin, ModelSerializer):
|
||||
"""
|
||||
增强DRF的ModelSerializer,可自动更新模型的审计字段记录
|
||||
(1)self.request能获取到rest_framework.request.Request对象
|
||||
"""
|
||||
|
||||
# 修改人的审计字段名称, 默认modifier, 继承使用时可自定义覆盖
|
||||
modifier_field_id = "modifier"
|
||||
modifier_name = serializers.SerializerMethodField(read_only=True)
|
||||
|
||||
def get_modifier_name(self, instance):
|
||||
if not hasattr(instance, "modifier"):
|
||||
return None
|
||||
queryset = (
|
||||
Users.objects.filter(id=instance.modifier)
|
||||
.values_list("name", flat=True)
|
||||
.first()
|
||||
)
|
||||
if queryset:
|
||||
return queryset
|
||||
return None
|
||||
|
||||
# 创建人的审计字段名称, 默认creator, 继承使用时可自定义覆盖
|
||||
creator_field_id = "creator"
|
||||
creator_name = serializers.SlugRelatedField(
|
||||
slug_field="name", source="creator", read_only=True
|
||||
)
|
||||
# 数据所属部门字段
|
||||
dept_belong_id_field_name = "dept_belong_id"
|
||||
# 添加默认时间返回格式
|
||||
create_datetime = serializers.DateTimeField(
|
||||
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
|
||||
)
|
||||
|
||||
def __init__(self, instance=None, data=empty, request=None, **kwargs):
|
||||
super().__init__(instance, data, **kwargs)
|
||||
self.request: Request = request or self.context.get("request", None)
|
||||
|
||||
def save(self, **kwargs):
|
||||
return super().save(**kwargs)
|
||||
|
||||
def create(self, validated_data):
|
||||
if self.request:
|
||||
if str(self.request.user) != "AnonymousUser":
|
||||
if self.modifier_field_id in self.fields.fields:
|
||||
validated_data[self.modifier_field_id] = self.get_request_user_id()
|
||||
if self.creator_field_id in self.fields.fields:
|
||||
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
|
||||
):
|
||||
validated_data[self.dept_belong_id_field_name] = getattr(
|
||||
self.request.user, "dept_id", None
|
||||
)
|
||||
return super().create(validated_data)
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
if self.request:
|
||||
if str(self.request.user) != "AnonymousUser":
|
||||
if self.modifier_field_id in self.fields.fields:
|
||||
validated_data[self.modifier_field_id] = self.get_request_user_id()
|
||||
if hasattr(self.instance, self.modifier_field_id):
|
||||
setattr(
|
||||
self.instance, self.modifier_field_id, self.get_request_user_id()
|
||||
)
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
def get_request_username(self):
|
||||
if getattr(self.request, "user", None):
|
||||
return getattr(self.request.user, "username", None)
|
||||
return None
|
||||
|
||||
def get_request_name(self):
|
||||
if getattr(self.request, "user", None):
|
||||
return getattr(self.request.user, "name", None)
|
||||
return None
|
||||
|
||||
def get_request_user_id(self):
|
||||
if getattr(self.request, "user", None):
|
||||
return getattr(self.request.user, "id", None)
|
||||
return None
|
||||
|
||||
@property
|
||||
def errors(self):
|
||||
# get errors
|
||||
errors = super().errors
|
||||
verbose_errors = {}
|
||||
|
||||
# fields = { field.name: field.verbose_name } for each field in model
|
||||
fields = {field.name: field.verbose_name for field in
|
||||
self.Meta.model._meta.get_fields() if hasattr(field, 'verbose_name')}
|
||||
|
||||
# iterate over errors and replace error key with verbose name if exists
|
||||
for field_name, error in errors.items():
|
||||
if field_name in fields:
|
||||
verbose_errors[str(fields[field_name])] = error
|
||||
else:
|
||||
verbose_errors[field_name] = error
|
||||
return verbose_errors
|
||||
|
||||
# @cached_property
|
||||
# def fields(self):
|
||||
# fields = BindingDict(self)
|
||||
# for key, value in self.get_fields().items():
|
||||
# fields[key] = value
|
||||
#
|
||||
# if not hasattr(self, '_context'):
|
||||
# return fields
|
||||
# is_root = self.root == self
|
||||
# parent_is_list_root = self.parent == self.root and getattr(self.parent, 'many', False)
|
||||
# if not (is_root or parent_is_list_root):
|
||||
# return fields
|
||||
#
|
||||
# try:
|
||||
# request = self.request or self.context['request']
|
||||
# except KeyError:
|
||||
# return fields
|
||||
# params = getattr(
|
||||
# request, 'query_params', getattr(request, 'GET', None)
|
||||
# )
|
||||
# if params is None:
|
||||
# pass
|
||||
# try:
|
||||
# filter_fields = params.get('_fields', None).split(',')
|
||||
# except AttributeError:
|
||||
# filter_fields = None
|
||||
#
|
||||
# try:
|
||||
# omit_fields = params.get('_exclude', None).split(',')
|
||||
# except AttributeError:
|
||||
# omit_fields = []
|
||||
#
|
||||
# existing = set(fields.keys())
|
||||
# if filter_fields is None:
|
||||
# allowed = existing
|
||||
# else:
|
||||
# allowed = set(filter(None, filter_fields))
|
||||
#
|
||||
# omitted = set(filter(None, omit_fields))
|
||||
# for field in existing:
|
||||
# if field not in allowed:
|
||||
# fields.pop(field, None)
|
||||
# if field in omitted:
|
||||
# fields.pop(field, None)
|
||||
#
|
||||
# return fields
|
||||
42
backend/dvadmin/utils/string_util.py
Normal file
42
backend/dvadmin/utils/string_util.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@author: 猿小天
|
||||
@contact: QQ:1638245306
|
||||
@Created on: 2021/8/21 021 9:48
|
||||
@Remark:
|
||||
"""
|
||||
import hashlib
|
||||
import random
|
||||
|
||||
CHAR_SET = ("2", "3", "4", "5",
|
||||
"6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H",
|
||||
"J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V",
|
||||
"W", "X", "Y", "Z")
|
||||
|
||||
|
||||
def random_str(number=16):
|
||||
"""
|
||||
返回特定长度的随机字符串(非进制)
|
||||
:return:
|
||||
"""
|
||||
result = ""
|
||||
for i in range(0, number):
|
||||
inx = random.randint(0, len(CHAR_SET) - 1)
|
||||
result += CHAR_SET[inx]
|
||||
return result
|
||||
|
||||
|
||||
def has_md5(str, salt='123456'):
|
||||
"""
|
||||
md5 加密
|
||||
:param str:
|
||||
:param salt:
|
||||
:return:
|
||||
"""
|
||||
# satl是盐值,默认是123456
|
||||
str = str + salt
|
||||
md = hashlib.md5() # 构造一个md5对象
|
||||
md.update(str.encode())
|
||||
res = md.hexdigest()
|
||||
return res
|
||||
46
backend/dvadmin/utils/swagger.py
Normal file
46
backend/dvadmin/utils/swagger.py
Normal file
@@ -0,0 +1,46 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@author: 猿小天
|
||||
@contact: QQ:1638245306
|
||||
@Created on: 2021/8/12 012 10:25
|
||||
@Remark: swagger配置
|
||||
"""
|
||||
from drf_yasg.generators import OpenAPISchemaGenerator
|
||||
from drf_yasg.inspectors import SwaggerAutoSchema
|
||||
|
||||
from application.settings import SWAGGER_SETTINGS
|
||||
|
||||
|
||||
def get_summary(string):
|
||||
if string is not None:
|
||||
result = string.strip().replace(" ","").split("\n")
|
||||
return result[0]
|
||||
|
||||
class CustomSwaggerAutoSchema(SwaggerAutoSchema):
|
||||
def get_tags(self, operation_keys=None):
|
||||
tags = super().get_tags(operation_keys)
|
||||
if "api" in tags and operation_keys:
|
||||
# `operation_keys` 内容像这样 ['v1', 'prize_join_log', 'create']
|
||||
tags[0] = operation_keys[SWAGGER_SETTINGS.get('AUTO_SCHEMA_TYPE', 2)]
|
||||
return tags
|
||||
|
||||
def get_summary_and_description(self):
|
||||
summary_and_description = super().get_summary_and_description()
|
||||
summary = get_summary(self.__dict__.get('view').__doc__)
|
||||
description = summary_and_description[1]
|
||||
return summary,description
|
||||
|
||||
|
||||
class CustomOpenAPISchemaGenerator(OpenAPISchemaGenerator):
|
||||
def get_schema(self, request=None, public=False):
|
||||
"""Generate a :class:`.Swagger` object with custom tags"""
|
||||
|
||||
swagger = super().get_schema(request, public)
|
||||
swagger.tags = [
|
||||
{
|
||||
"name": "token",
|
||||
"description": "认证相关"
|
||||
},
|
||||
]
|
||||
return swagger
|
||||
73
backend/dvadmin/utils/validator.py
Normal file
73
backend/dvadmin/utils/validator.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@author: 猿小天
|
||||
@contact: QQ:1638245306
|
||||
@Created on: 2021/6/2 002 17:03
|
||||
@Remark: 自定义验证器
|
||||
"""
|
||||
|
||||
from django.db import DataError
|
||||
from rest_framework.exceptions import APIException
|
||||
from rest_framework.validators import UniqueValidator
|
||||
|
||||
|
||||
class CustomValidationError(APIException):
|
||||
"""
|
||||
继承并重写验证器返回的结果,避免暴露字段
|
||||
"""
|
||||
|
||||
def __init__(self, detail):
|
||||
self.detail = detail
|
||||
|
||||
|
||||
def qs_exists(queryset):
|
||||
try:
|
||||
return queryset.exists()
|
||||
except (TypeError, ValueError, DataError):
|
||||
return False
|
||||
|
||||
|
||||
def qs_filter(queryset, **kwargs):
|
||||
try:
|
||||
return queryset.filter(**kwargs)
|
||||
except (TypeError, ValueError, DataError):
|
||||
return queryset.none()
|
||||
|
||||
|
||||
class CustomUniqueValidator(UniqueValidator):
|
||||
"""
|
||||
继承,重写必填字段的验证器结果,防止字段暴露
|
||||
"""
|
||||
|
||||
def filter_queryset(self, value, queryset, field_name):
|
||||
"""
|
||||
Filter the queryset to all instances matching the given attribute.
|
||||
"""
|
||||
filter_kwargs = {'%s__%s' % (field_name, self.lookup): value}
|
||||
return qs_filter(queryset, **filter_kwargs)
|
||||
|
||||
def exclude_current_instance(self, queryset, instance):
|
||||
"""
|
||||
If an instance is being updated, then do not include
|
||||
that instance itself as a uniqueness conflict.
|
||||
"""
|
||||
if instance is not None:
|
||||
return queryset.exclude(pk=instance.pk)
|
||||
return queryset
|
||||
|
||||
def __call__(self, value, serializer_field):
|
||||
# Determine the underlying model field name. This may not be the
|
||||
# same as the serializer field name if `source=<>` is set.
|
||||
field_name = serializer_field.source_attrs[-1]
|
||||
# Determine the existing instance, if this is an update operation.
|
||||
instance = getattr(serializer_field.parent, 'instance', None)
|
||||
|
||||
queryset = self.queryset
|
||||
queryset = self.filter_queryset(value, queryset, field_name)
|
||||
queryset = self.exclude_current_instance(queryset, instance)
|
||||
if qs_exists(queryset):
|
||||
raise CustomValidationError(self.message)
|
||||
|
||||
def __repr__(self):
|
||||
return super().__repr__()
|
||||
125
backend/dvadmin/utils/viewset.py
Normal file
125
backend/dvadmin/utils/viewset.py
Normal file
@@ -0,0 +1,125 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@author: 猿小天
|
||||
@contact: QQ:1638245306
|
||||
@Created on: 2021/6/1 001 22:57
|
||||
@Remark: 自定义视图集
|
||||
"""
|
||||
import uuid
|
||||
|
||||
from django.db import transaction
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.viewsets import ModelViewSet
|
||||
|
||||
from dvadmin.utils.filters import DataLevelPermissionsFilter
|
||||
from dvadmin.utils.import_export_mixin import ExportSerializerMixin, ImportSerializerMixin
|
||||
from dvadmin.utils.json_response import SuccessResponse, ErrorResponse, DetailResponse
|
||||
from dvadmin.utils.permission import CustomPermission
|
||||
from django_restql.mixins import QueryArgumentsMixin
|
||||
|
||||
|
||||
class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMixin, QueryArgumentsMixin):
|
||||
"""
|
||||
自定义的ModelViewSet:
|
||||
统一标准的返回格式;新增,查询,修改可使用不同序列化器
|
||||
(1)ORM性能优化, 尽可能使用values_queryset形式
|
||||
(2)xxx_serializer_class 某个方法下使用的序列化器(xxx=create|update|list|retrieve|destroy)
|
||||
(3)filter_fields = '__all__' 默认支持全部model中的字段查询(除json字段外)
|
||||
(4)import_field_dict={} 导入时的字段字典 {model值: model的label}
|
||||
(5)export_field_label = [] 导出时的字段
|
||||
"""
|
||||
values_queryset = None
|
||||
ordering_fields = '__all__'
|
||||
create_serializer_class = None
|
||||
update_serializer_class = None
|
||||
filter_fields = '__all__'
|
||||
search_fields = ()
|
||||
extra_filter_class = [DataLevelPermissionsFilter]
|
||||
permission_classes = [CustomPermission]
|
||||
import_field_dict = {}
|
||||
export_field_label = {}
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
for backend in set(set(self.filter_backends) | set(self.extra_filter_class or [])):
|
||||
queryset = backend().filter_queryset(self.request, queryset, self)
|
||||
return queryset
|
||||
|
||||
def get_queryset(self):
|
||||
if getattr(self, 'values_queryset', None):
|
||||
return self.values_queryset
|
||||
return super().get_queryset()
|
||||
|
||||
def get_serializer_class(self):
|
||||
action_serializer_name = f"{self.action}_serializer_class"
|
||||
action_serializer_class = getattr(self, action_serializer_name, None)
|
||||
if action_serializer_class:
|
||||
return action_serializer_class
|
||||
return super().get_serializer_class()
|
||||
|
||||
# 通过many=True直接改造原有的API,使其可以批量创建
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
serializer_class = self.get_serializer_class()
|
||||
kwargs.setdefault('context', self.get_serializer_context())
|
||||
if isinstance(self.request.data, list):
|
||||
with transaction.atomic():
|
||||
return serializer_class(many=True, *args, **kwargs)
|
||||
else:
|
||||
return serializer_class(*args, **kwargs)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.data, request=request)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
self.perform_create(serializer)
|
||||
return DetailResponse(data=serializer.data, msg="新增成功")
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
page = self.paginate_queryset(queryset)
|
||||
if page is not None:
|
||||
serializer = self.get_serializer(page, many=True, request=request)
|
||||
return self.get_paginated_response(serializer.data)
|
||||
serializer = self.get_serializer(queryset, many=True, request=request)
|
||||
return SuccessResponse(data=serializer.data, msg="获取成功")
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
serializer = self.get_serializer(instance)
|
||||
return DetailResponse(data=serializer.data, msg="获取成功")
|
||||
|
||||
def update(self, request, *args, **kwargs):
|
||||
partial = kwargs.pop('partial', False)
|
||||
instance = self.get_object()
|
||||
serializer = self.get_serializer(instance, data=request.data, request=request, partial=partial)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
self.perform_update(serializer)
|
||||
|
||||
if getattr(instance, '_prefetched_objects_cache', None):
|
||||
# If 'prefetch_related' has been applied to a queryset, we need to
|
||||
# forcibly invalidate the prefetch cache on the instance.
|
||||
instance._prefetched_objects_cache = {}
|
||||
return DetailResponse(data=serializer.data, msg="更新成功")
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
instance.delete()
|
||||
return DetailResponse(data=[], msg="删除成功")
|
||||
|
||||
keys = openapi.Schema(description='主键列表', type=openapi.TYPE_ARRAY, items=openapi.TYPE_STRING)
|
||||
|
||||
@swagger_auto_schema(request_body=openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
required=['keys'],
|
||||
properties={'keys': keys}
|
||||
), operation_summary='批量删除')
|
||||
@action(methods=['delete'], detail=False)
|
||||
def multiple_delete(self, request, *args, **kwargs):
|
||||
request_data = request.data
|
||||
keys = request_data.get('keys', None)
|
||||
if keys:
|
||||
self.get_queryset().filter(id__in=keys).delete()
|
||||
return SuccessResponse(data=[], msg="删除成功")
|
||||
else:
|
||||
return ErrorResponse(msg="未获取到keys字段")
|
||||
Reference in New Issue
Block a user