366 Commits

Author SHA1 Message Date
liqiang
3a2739774a refactor(file): 调整文件列表视图的认证类配置 2025-12-07 00:23:15 +08:00
liqiang
012f3a2f63 refactor(file): 调整文件列表视图的认证类配置 2025-12-06 23:03:50 +08:00
ahui
64c5d505e7 Merge branch 'develop' of https://gitee.com/huge-dream/django-vue3-admin into develop 2025-12-01 10:41:43 +08:00
ahui
3914432d9f 修复权限过滤器DataLevelPermissionsSubFilter单独使用时没有经过数据权限过滤的问题 2025-12-01 10:28:51 +08:00
ahui
cc6ac68223 角色授权按钮更新 2025-12-01 10:24:45 +08:00
1638245306
05ee833fe5 chore(layout): 移除用户头像在线状态徽章
- 注释掉了用户头像的在线状态徽章组件
- 移除了相关的头像图片显示逻辑
-保留了用户名显示功能
2025-11-01 22:28:48 +08:00
1638245306
ff736aae93 feat(layout): 添加搜索功能图标并调整布局
- 在用户面包屑导航中添加搜索图标
- 调整图标点击事件处理- 移除重复的搜索图标代码
- 优化布局结构和样式
2025-11-01 22:15:55 +08:00
liyimin
c0a68f91ca 首页优化 2025-11-01 16:14:36 +08:00
liyimin
8d6abeb891 登陆界面美化 2025-10-31 23:26:27 +08:00
1638245306
163e5eb2db fix(role): 更新角色授权用户权限检查
- 将授权用户按钮的权限检查从 role:AuthorizedAdd 更改为 role:SetUserRole
-保持授权用户搜索权限检查不变- 确保只有具有适当权限的用户才能访问角色分配功能
2025-10-23 22:16:03 +08:00
1638245306
e786f60cdd feat(websocket): 实现 WebSocket 消息推送功能
- 配置 ASGI 支持 WebSocket 连接
- 新增 WebSocket 路由和消费者类 MegCenter
- 实现消息序列化和推送逻辑
- 前端集成 WebSocket 连接状态管理和重连机制
- 添加用户在线状态提示和未读消息提醒- 更新角色权限配置显示条件
- 扩展用户信息存储结构支持 WebSocket 状态字段
2025-10-19 16:03:59 +08:00
1638245306
abe2db3c82 feat(websocket): 实现 WebSocket 消息推送功能
- 配置 ASGI 支持 WebSocket 连接
- 新增 WebSocket 路由和消费者类 MegCenter
- 实现消息序列化和推送逻辑
- 前端集成 WebSocket 连接状态管理和重连机制
- 添加用户在线状态提示和未读消息提醒- 更新角色权限配置显示条件
- 扩展用户信息存储结构支持 WebSocket 状态字段
2025-10-19 16:03:16 +08:00
ahui
fa734dd479 管理部门数据级过滤器优化 2025-08-22 18:03:51 +08:00
ahui
6e9b94aed2 用户的管理部门权限功能 2025-08-14 10:35:51 +08:00
ahui
2a9f5be895 页面优化 2025-08-13 10:29:52 +08:00
ahui
2ea34bfbd5 工具函数更新 2025-08-07 14:10:07 +08:00
ahui
edbd6862a2 用户表增加当前角色字段 2025-08-07 14:09:48 +08:00
liqiang
6c99a78821 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	web/src/views/system/login/index.vue
2025-08-07 13:54:07 +08:00
dvadmin
3db048e650 !128 【轻量PR】: 修复前端请求配置与自定义配置合并逻辑
Merge pull request !128 from lorenzo/develop_v2
2025-08-07 05:45:06 +00:00
ahui
e470a716e5 忽略文件更新 2025-08-07 11:30:21 +08:00
lorenzo
95bf503529 fix(web): 修复前端请求配置与自定义配置合并逻辑 2025-08-01 21:37:25 +08:00
猿小天
4e9155f09b feat(viewset): 通过 IDS列表获取数据
- 在通用的 ViewSet 中添加 get_by_ids 方法
- 接收 POST 请求,从请求体中获取 ids列表
- 根据 ids 列表查询数据并返回- 若 ids 列表为空或只包含空字符串,则返回空数据
2025-07-21 13:45:09 +08:00
ahui
3e60c5b5f9 工具函数同步 2025-07-17 11:00:28 +08:00
dvadmin
b72fb5238f !125 fix(application): 修复普通用户接收消息数量统计错误
Merge pull request !125 from 木子-李/sse
2025-07-15 14:34:58 +00:00
1638245306
74d89c3968 refactor(web): 优化代码和构建配置
- 修改用户服务条款中的企业用户描述
- 在 package.json 中添加新的构建命令 build:flowH5
- 引入新的依赖库 @meetjs/vant4-kit 和 vue-qr
- 添加 rollup 插件以优化构建过程
2025-07-10 15:18:16 +08:00
1638245306
5eab2b6e4f refactor(system): 更新登录页面版权信息
- 将版权年份从 2024 修改为 2023
- 更新版权公司名称为北京巨梦科技有限公司
- 修改备案号为晋ICP备18005113号-3
2025-07-10 15:04:39 +08:00
1638245306
d2f14e41ad refactor(system): 更新登录页面版权信息
- 将版权年份从 2024 修改为 2023
- 更新版权公司名称为北京巨梦科技有限公司
- 修改备案号为晋ICP备18005113号-3
2025-07-10 15:04:21 +08:00
阿辉
483863e238 同步权限 2025-07-06 16:54:26 +08:00
李小涛
ad95bea301 fix(application): 修复普通用户接收消息数量统计错误
- 移除了对 user_id == 1 的特殊判断,统一了消息数量统计逻辑
- 优化了代码结构,提高了代码的可读性和维护性
2025-06-26 17:51:36 +08:00
李小涛
344f754fc7 actor(webref): 优化代码结构和功能
- 注释掉 oauth2.vue 中的 getBackends() 调用
- 修改 sse_views.py 中的消息中心未读消息计数逻辑
- 优化 userNews.vue 中的新闻列表数据加载
2025-06-26 17:50:53 +08:00
dvadmin
749f5d80d2 !124 refactor(theme): 优化主题样式并移除冗余代码
Merge pull request !124 from 木子-李/sse
2025-06-25 08:03:25 +00:00
李小涛
ac3bfb6b80 refactor(theme): 优化主题样式并移除冗余代码
- 将 @import 替换为 @use,提高代码的可维护性
- 统一使用 index 作为命名空间,避免变量名冲突
- 移除不必要的注释和空格,精简代码
- 删除未使用的 isSocketOpen 属性,简化数据结构
2025-06-25 10:42:01 +08:00
liqiang
ef48bdd0df refactor: 更新登录页面版权信息和备案号
- 将默认版权信息修改为 "2021-2025 django-vue-admin.com"
- 将默认备案号修改为 "晋ICP备18005113号-3"
2025-06-25 06:55:15 +08:00
liqiang
a2e07a89e1 build(web): 更新图标字体并调整相关配置
- 新增 iconfont-01 和 iconfont-02 两个图标字体文件夹- 在 setIconfont.ts 中注释掉原有的图标字体 URL
- 在 main.ts 中引入新的图标字体 CSS 文件
- 更新 package.json
2025-06-24 09:59:29 +08:00
dvadmin
7b55a3db64 !116 兼容OAuth2
Merge pull request !116 from 木子-李/N/A
2025-06-22 13:18:46 +00:00
dvadmin
3c8b4b6ac0 !122 refactor(system): 重构消息中心功能
Merge pull request !122 from 木子-李/sse
2025-06-22 12:25:31 +00:00
李小涛
e8212501e2 refactor(system): 重构消息中心功能
- 移除 WebSocket相关代码
- 新增 SSE (Server-Sent Events) 实现消息推送
- 优化消息中心未读数量展示和更新逻辑- 调整消息中心相关 API 和前端展示
2025-06-22 13:09:49 +08:00
1638245306
fa063a8611 Merge remote-tracking branch 'origin/develop' into develop 2025-06-19 22:59:47 +08:00
1638245306
b89f1671c3 fix(system): 修复新增菜单未选择父菜单时的提交问题- 在提交菜单表单时,如果未选择父菜单,将 parent 字段设置为 null
-确保在更新或添加菜单时,父菜单字段的值是正确的
2025-06-19 22:59:14 +08:00
阿辉
c6c54d8013 用户信息更新 2025-06-19 13:41:57 +08:00
1638245306
0005d45d85 style(icon-selector): 调整图标选择器标题的位置属性
- 将 .icon-selector-warp-title 类的 position 属性从 absolute改为 relative
- 此修改解决了标题在某些情况下的定位问题,确保布局的稳定性
2025-06-18 19:03:24 +08:00
1638245306
1052f6a07b feat(user): 添加用户数据导出功能
- 新增导出数据功能,位于用户管理页面的导出按钮
- 点击导出按钮后,弹出确认框,提示用户是否确定导出数据
- 确定导出后,调用 exportData 函数执行导出操作
2025-06-18 18:59:44 +08:00
1638245306
5dcbae292a refactor(system): 修复角色权限字典类型错误 2025-06-18 18:47:51 +08:00
1638245306
ed915aa2cb feat(core): 新增软删除和工作流状态筛选功能
- 添加 CoreModelManager 类,实现软删除和工作流状态的筛选
- 在 CoreModel 中集成新功能- 增加 objects 和 all_objects 两个 Manager,支持不同查询需求
2025-06-17 11:35:46 +08:00
liqiang
82a0ef612a Merge remote-tracking branch 'origin/develop' into develop 2025-06-15 17:55:24 +08:00
liqiang
8ea49866bc feat(system): 添加文件存储引擎功能
- 新增文件存储引擎配置选项,支持本地、阿里云oss和腾讯云cos
- 在系统配置中添加文件存储相关设置- 实现阿里云oss和腾讯云cos的文件上传功能
- 更新文件列表视图,支持不同存储引擎的文件上传和访问
2025-06-12 06:10:47 +08:00
liqiang
a0a7c25b18 refactor(del_migrations):增加 .venv 目录排除
- 在需要排除的目录列表中添加了 .venv,以避免删除虚拟环境目录中的迁移文件
2025-06-11 15:58:25 +08:00
1638245306
f8c8f2963b Merge remote-tracking branch 'origin/develop' into develop 2025-06-09 15:09:04 +08:00
1638245306
5a980f3b54 feat(user): 添加用户 ID 属性并进行相关处理
- 在 UserInfosState 接口中添加 id属性
- 在 userInfo store 中添加用户 ID 相关逻辑
- 更新 getUserInfos 和 updateUserInfos 方法以处理用户 ID
- 注释掉水平菜单滚动定位代码
2025-06-09 15:08:51 +08:00
猿小天
c0be63afcd Merge remote-tracking branch 'origin/develop' into develop 2025-06-01 19:55:05 +08:00
猿小天
150b92163f 功能变化:
初始化生成utf-8编码
2025-06-01 19:51:25 +08:00
liqiang
3a25cdb53c refactor(system): 移除 Role 模型中的 FlowBaseModel继承
- 从 Role 类中删除了对 FlowBaseModel 的继承
- 这个改动简化了 Role 模型的结构,可能会影响与流程相关的功能
2025-05-08 05:22:32 +08:00
木子-李
45dcda0cc0 兼容OAuth2
Signed-off-by: 木子-李 <1537080775@qq.com>
2025-05-06 05:49:02 +00:00
liqiang
b4ffb2105f Merge remote-tracking branch 'origin/develop' into develop 2025-05-06 10:06:22 +08:00
1638245306
3398aa3ba9 build(flowH5): 更新库入口文件路径和外部依赖
- 修改入口文件路径为 src/views/plugins/dvadmin3-flow-web/src/flowH5/index.ts
- 添加 xe-utils 到外部依赖列表
2025-05-01 00:04:01 +08:00
1638245306
94149161b3 build(flowH5): 添加 flowH5 项目的配置文件
- 新增 flowH5.config.ts 文件,配置 Vite 构建项目- 设置 Vue 插件和路径别名
- 配置 Roll
2025-04-30 12:55:58 +08:00
liqiang
1907f1ac0a Merge remote-tracking branch 'origin/develop' into develop 2025-04-30 10:35:52 +08:00
1638245306
bda002398c refactor: 修改 API 文档权限为仅允许认证用户访问
- 将 API 文档的权限设置从 AllowAny 更改为 IsAuthenticated- 确保只有经过身份验证的用户才能访问 API 文档
2025-04-29 14:45:45 +08:00
阿辉
66c28bd389 日历选择器组件 2025-04-07 15:20:52 +08:00
1638245306
8eacc4aad3 feat(system): 角色模型添加工作流支持
- 引入 FlowBaseModel 以支持工作流功能
- 使 Role 模型继承 CoreModel 和 FlowBaseModel
2025-03-28 18:24:13 +08:00
1638245306
7a152d3591 feat(system): 添加获取用户递归部门名称功能
- 在 Department模型中添加了 _recursion 类方法,用于递归获取指定属性值
- 新增 get_region_name 类方法,用于获取用户的所有上级部门名称
- 该功能可以用于显示用户所在的完整部门路径
2025-03-28 15:08:56 +08:00
1638245306
4b05a28f4c fix(system): 修改用户密码设置逻辑
- 移除了对新密码进行 MD5 加密的步骤- 直接使用明文密码进行加密存储
- 保留了密码修改次数的计数功能
2025-03-28 14:44:39 +08:00
1638245306
0392b3b101 feat(plugins): 动态加载插件
- 新增插件动态加载逻辑,遍历已发现的插件列表
- 对于每个插件,尝试导入其 index.ts 文件
- 导入成功后,将插件注册到应用中,并打印加载信息
-导入失败时,打印错误信息,提示插件下无 index.ts 文件
2025-03-28 13:39:44 +08:00
liqiang
fad2cb2e18 README.zh.md更新 2025-03-21 04:42:46 +08:00
liqiang
df0b78cafc README.zh.md更新 2025-03-21 04:41:36 +08:00
liqiang
c69ea7b33e Merge remote-tracking branch 'origin/develop' into develop 2025-03-21 04:34:30 +08:00
liqiang
ce5c4c9d8d README.zh.md更新 2025-03-21 04:33:45 +08:00
liqiang
3f7aaa0228 Merge remote-tracking branch 'origin/develop' into develop 2025-03-21 04:09:33 +08:00
liqiang
e37909d478 feat(core): 新增核心工具模块并优化通知功能
- 新增 cores.tsx 文件,实现核心工具模块
- 添加任务列表和事件总线功能
- 实现系统通知和任务处理逻辑
- 在 App.vue 中集成新功能
- 优化通知显示逻辑,支持不同内容类型的通知
2025-03-21 04:09:26 +08:00
dvadmin
1578c9d710 !108 修复字段权限筛选错误,update backend/dvadmin/utils/viewset.py.
Merge pull request !108 from lxy/N/A
2025-03-20 19:54:37 +00:00
dvadmin
01604a27db !109 修复无法下载问题 update web/src/views/system/downloadCenter/crud.tsx.
Merge pull request !109 from lxy/N/A
2025-03-20 19:53:57 +00:00
dvadmin
58036dbeb9 !110 修复文件管理点击地址无法下载问题 update web/src/views/system/fileList/crud.tsx.
Merge pull request !110 from lxy/N/A
2025-03-20 19:53:34 +00:00
lxy
7917a118c8 修复文件管理点击地址无法下载问题 update web/src/views/system/fileList/crud.tsx.
Signed-off-by: lxy <46486798@qq.com>
2025-03-20 11:33:01 +00:00
lxy
86c202b94a 修复无法下载问题 update web/src/views/system/downloadCenter/crud.tsx.
Signed-off-by: lxy <46486798@qq.com>
2025-03-20 11:28:09 +00:00
lxy
a5cc87eb55 修复字段权限筛选错误,update backend/dvadmin/utils/viewset.py.
解决不是超级管理员用户加载报错,匿名用户没有角色报错

Signed-off-by: lxy <46486798@qq.com>
2025-03-20 06:47:21 +00:00
1638245306
2fabc210b2 refactor(system): 恢复密码加密逻辑
- 将明文密码修改为经过 MD5 加密的密码
- 保留了原有的密码加密方式,提高了系统安全性
2025-03-19 15:54:58 +08:00
1638245306
4c644ae8cb build(development): 修改开发环境 API 地址并添加新的构建脚本
- 将开发环境 API 地址从 http://127.0.0.1:8000 改为 http://127.0.0.1:8001- 在 package.json 中添加了 build:dev 脚本,用于开发环境的构建
2025-03-17 14:40:12 +08:00
liqiang
351bae1fea build(.env): 修改 API 请求基础 URL
- 将 VITE_API_URL 从 'http://dvadmin3api.django.icu:8001' 修改为 'http://127.0.0.1:8000'- 此修改便于本地开发和测试
2025-03-15 14:52:29 +08:00
1638245306
bc7bc3cda6 feat(system): 优化配置页面表单组件
- 在 crudTable组件中添加 afterSubmit 钩子,更新 modelValue
-重构 formContent 组件中的 formData,使用 ref 替代 reactive
- 优化 formContent 组件的提交逻辑,简化代码结构
2025-03-14 17:33:51 +08:00
liqiang
aea5ca938b Merge branch 'develop' of gitee.com:huge-dream/django-vue3-admin into develop 2025-03-14 16:59:46 +08:00
1638245306
8706de5ef4 feat(system): 优化配置组件的样式和功能
- 调整表单布局,设置宽度为 500px,栏数为 24
- 在标题、键名和键值字段的表单中添加占位符
2025-03-14 16:42:04 +08:00
dvadmin
ed96740ab1 !106 完善初始化配置导出
Merge pull request !106 from lorenzo/dev
2025-03-13 10:32:43 +00:00
dvadmin
f719241938 !105 feat: 从表结构生成前端基础文件
Merge pull request !105 from lorenzo/master
2025-03-13 10:31:04 +00:00
1638245306
ac22e47883 docs(README): 添加给框架点赞二维码图片
- 在 README.md 中添加了支付宝和微信的二维码图片链接
- 通过此修改,方便用户扫描二维码给框架进行赞赏
2025-03-13 17:49:35 +08:00
1638245306
7555ba1707 Merge remote-tracking branch 'origin/develop' into develop 2025-03-13 17:40:39 +08:00
1638245306
341ea62412 feat(system): 优化配置管理数组展示方式
- 新增 crudTable 组件用于数组数据的展示和编辑
- 替换原有的 vxe-table 实现,简化代码结构
- 优化数组类型表单项的渲染逻辑
- 调整环境变量中的 API 地址
2025-03-13 17:40:30 +08:00
liqiang
2ab80758f0 fix(permission): 修复权限获取 bug
- 将 MenuButton 查询结果改为 values_list,获取多个匹配项的 ID
- 更新 RoleMenuButtonPermission 查询条件,支持多个菜单按钮 ID
2025-03-02 01:00:43 +08:00
liqiang
a030409c46 fix(system): 修复权限获取 bug
- 优化权限查询逻辑,先判断菜单按钮对象是否存在
- 修复了在某些情况下可能导致权限获取失败的问题
2025-03-02 00:05:44 +08:00
liqiang
036d8da9b6 build(web): 更新基础镜像版本
- 将 dvadmin3-base-web 镜像版本从16.19-alpine升级到 18.20-alpine
2025-03-02 00:04:42 +08:00
阿辉
7fb219bb31 文件选择器修复清空时会报错null的bug 2025-02-28 18:42:28 +08:00
阿辉
b10c3ebdc0 文件选择器修复windows下文件路径反斜杠问题 2025-02-24 10:26:49 +08:00
阿辉
778401dd2d Merge branch 'develop' of https://e.coding.net/dvadmin-private/code/dvadmin3 into develop 2025-02-21 17:11:44 +08:00
阿辉
418c78fa83 文件选择器多选图片情况下无法删除图片 2025-02-21 17:11:41 +08:00
1638245306
48d3d86017 Merge remote-tracking branch 'origin/develop' into develop 2025-02-09 23:17:35 +08:00
1638245306
c6a2073537 feat(utils): 完善字段权限控制并添加角色过滤
- 添加了对普通用户进行字段权限过滤的逻辑
- 使用 deepcopy 复制 serializer_class.Meta 以避免直接修改原类
- 修改 get_menu_field 方法,根据用户角色过滤字段权限
2025-02-09 23:17:26 +08:00
7emotions
78ec0ce069 fix: 修复导出Users配置缺失的role与dept 2025-02-06 20:20:12 +08:00
7emotions
857d2940f8 fix: 修复RoleMenuButtonInitSerializer重置逻辑 2025-02-06 19:56:20 +08:00
7emotions
82e689fd83 feat: 导出角色权限配置与按钮权限 2025-02-06 19:21:14 +08:00
liqiang
0cda4c04f7 默认添加dvadmin3-celery 2025-02-06 16:37:52 +08:00
7emotions
91742ee27b feat: 从表结构生成前端基础文件 2025-02-04 00:10:46 +08:00
liqiang
3c6afdac76 支持npm 安装的@great-dream插件动态菜单 2025-02-02 09:04:13 +08:00
liqiang
f299889c4a 支持npm 安装的@great-dream插件动态菜单 2025-02-01 23:29:52 +08:00
liqiang
c82fcbb468 celery 优化 2025-02-01 18:29:51 +08:00
liqiang
eaf5fdcc55 框架优化 2025-02-01 18:29:43 +08:00
liqiang
40b7bd2c94 组件更新 2025-01-30 12:55:58 +08:00
liqiang
868621a3f1 Merge branch 'master' into develop 2025-01-30 12:47:26 +08:00
dvadmin
eefc43d863 !95 角色授权增加默认接口权限
1.授权时带入默认权限
2.查看权限,如果单接口权限与默认接口权限一致,显示默认接口权限
3.复选框等宽显示
2025-01-30 04:44:58 +00:00
dvadmin
8b21a69a15 !104 feat: 优化docker部署
Merge pull request !104 from lorenzo/master
2025-01-30 04:44:28 +00:00
dvadmin
0964c6300a !103 [fix]修复bug
[fix]修复更新时间晒选取值错误bug
[fix]优化commonCrudConfig写法,兼容ts语法,修复使用commonCrudConfig IDE提示飘红的问题&增加width可自定义功能
[fix]修复版本号生成可能为undefind的bug&开发环境版本不进行版本校验代码不生效bug
2025-01-30 04:43:07 +00:00
dvadmin
8c2db4f5ff !101 增加角色批量授权用户
Merge pull request !101 from lxy/cherry-pick-1736306630
2025-01-30 04:42:23 +00:00
liqiang
e15a49a2bd 支持外链接 2025-01-30 12:40:30 +08:00
lorenzo
ecfad1952a feat: 优化docker部署
Signed-off-by: lorenzo <paradise_c@qq.com>
2025-01-27 15:57:12 +00:00
lorenzo
92b8cde7ae feat: 优化docker部署
Signed-off-by: lorenzo <paradise_c@qq.com>
2025-01-27 15:54:13 +00:00
vFeng
6fbe3a214d [fix]注释无效接口dept_lazy_tree
Signed-off-by: vFeng <1914007838@qq.com>
2025-01-27 09:01:17 +00:00
vFeng
4695066e11 [fix]修复开发环境无需校验前端版本bug
Signed-off-by: vFeng <1914007838@qq.com>
2025-01-27 06:33:37 +00:00
vFeng
d125eac5a6 [fix]修复版本号生成可能为undefind的bug&开发环境版本不进行版本校验代码不生效bug
Signed-off-by: vFeng <1914007838@qq.com>
2025-01-27 06:22:21 +00:00
vFeng
83d6565cad [fix]优化commonCrudConfig写法,兼容ts语法,修复使用commonCrudConfig IDE提示飘红的问题&增加width可自定义功能
Signed-off-by: vFeng <1914007838@qq.com>
2025-01-27 05:33:54 +00:00
vFeng
1e2a9a652e [fix]修复更新时间晒选取值错误bug
Signed-off-by: vFeng <1914007838@qq.com>
2025-01-27 05:25:49 +00:00
阿辉
c781d1f559 文件选择器支持image类型的多选 2025-01-22 15:53:01 +08:00
lxy
ffc6246105 增加角色批量授权用户
(cherry picked commit from <gitee.com//lxy0722/django-vue3-admin/commit/702eb7d7aeb2a9c30416a8ebb503a4b8e3367492>
2025-01-08 03:23:50 +00:00
dvadmin
3271f00f87 !100 正式发布v3.1.0版本
1.[新增] 文件选择器添加插槽功能
2.[新增] 初次登录必须修改密码功能
3.[新增] 下载中心模块和异步导出功能
4.[新增] 批量删除功能(示例代码在菜单管理里面)
5.[新增] 角色批量授权用户功能
6.[优化] 菜单排序逻辑
7.[优化] 优化列权限逻辑
8.[修复] 允许路由在框架外显示 bug
9.[修复] 用户管理crud无法显示用户头像问题
10.[修复] 字段筛选排除 file文件字段
11.[修复] 更新import_export.py已过滤空数据表
12.[修复] 日期期间条件过滤不包含截止日期当天数据的bug

后续规划
1.审批流插件正在测试中
2.报表插件规划中
3.新版文档规划中
2025-01-07 13:44:34 +00:00
1638245306
77bfc87679 Merge remote-tracking branch 'origin/develop' into develop 2025-01-07 21:38:22 +08:00
李强
1fe0a89338 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	web/src/views/system/dept/components/DeptUserCom/crud.tsx
#	web/src/views/system/user/crud.tsx
2025-01-07 21:36:48 +08:00
dvadmin
58971a3781 !96 增加角色批量授权用户
Merge pull request !96 from 木子-李/20250103-role
2025-01-07 13:28:08 +00:00
dvadmin
502e1f4d27 !99 修复用户管理crud无法显示用户头像问题
Merge pull request !99 from lxy/cherry-pick-1736165358
2025-01-07 13:26:22 +00:00
1638245306
6ab0c3e758 refactor: 修复主键列表字段的 Swagger 文档生成
- 更新 keys 变量定义,使用 Schema 嵌套来正确表示主键列表的类型
-优化 Swagger 文档中的请求体定义,提高 API 文档的准确性和可读性
2025-01-07 20:06:24 +08:00
阿辉
2015db53ab 用户管理密码设置优化 2025-01-07 17:38:01 +08:00
lxy
73edafb95f 修复没有设置头像的显示异常 2025-01-07 10:30:29 +08:00
lxy
e8f5edd9c3 同步部门管理页面 2025-01-07 09:59:39 +08:00
lxy
2d69633660 修复用户管理无法显示头像问题
(cherry picked commit from <gitee.com//lxy0722/django-vue3-admin/commit/4a01e55143967f0a01d3509804d3ba146724928a>
2025-01-06 12:09:18 +00:00
李小涛
b5c583ba7d feat(20250103-role): 增加角色批量授权用户 2025-01-03 14:41:30 +08:00
lxy
9d230e4752 角色授权增加默认设置
(cherry picked commit from <gitee.com//huge-dream/django-vue3-admin/commit/06a772fcd5335c272159d9c45590cf8b559210d1>
2025-01-02 06:28:06 +00:00
dvadmin
21be91a894 !86 【角色管理】-【权限配置】优化
1、优化角色权限分配逻辑
2、采用实时保存,提高速度
2024-12-29 09:32:08 +00:00
dvadmin
a527d367d9 !85 字段筛选排除文件字段 update backend/dvadmin/utils/filters.py.
Merge pull request !85 from lxy/N/A
2024-12-29 09:31:11 +00:00
dvadmin
eeda1bea4a !80 修复: 更新import_export.py以过滤空数据表
Merge pull request !80 from xwz1024/develop
2024-12-29 09:30:09 +00:00
dvadmin
2a0de4f0d3 !88 优化列权限逻辑
Merge pull request !88 from 木子-李/20241226-fieldpermission
2024-12-29 09:28:59 +00:00
李小涛
792a22e606 feat(20241226-fieldpermission): 优化列权限逻辑
1、优化后端合并权限代码
2024-12-26 17:37:08 +08:00
李小涛
6726d0167e feat(20241226-fieldpermission): 优化列权限逻辑
1、优化后端合并权限代码
2024-12-26 10:26:27 +08:00
李小涛
dddafa4826 feat(20241226-fieldpermission): 优化列权限逻辑
1、后端优化,当多个角色的时候,合并列权限配置
2、前端优化,有多级表头时,列权限设置无效的bug
2024-12-26 08:56:19 +08:00
李小涛
db27235f61 refactor(20241225-role): 角色权限分配优化
1、优化角色权限分配逻辑
2、采用实时保存,提高速度
2024-12-25 08:59:54 +08:00
李小涛
282ab9a6a1 refactor(20241225-role): 角色权限分配优化
1、优化角色权限分配逻辑
2、采用实时保存,提高速度
2024-12-25 08:59:25 +08:00
阿辉
15c87ddd26 修复字典管理新增值时is_value错误的bug 2024-12-17 10:58:13 +08:00
阿辉
a36dcfa1e5 下载中心优化 2024-12-16 15:39:13 +08:00
lxy
ba5c2ab490 字段筛选排除文件字段 update backend/dvadmin/utils/filters.py.
url参数过滤将导致报错

Signed-off-by: lxy <46486798@qq.com>
2024-12-06 07:26:22 +00:00
李强
584bf57344 Merge remote-tracking branch 'upstream/develop' 2024-12-05 14:56:41 +08:00
阿辉
0c38343aca 文件选择器添加插槽 2024-12-05 14:37:38 +08:00
阿辉
d8f41919ea 文件选择器添加插槽,修复在下拉框多选情况下数据不同步的bug 2024-12-05 11:56:15 +08:00
阿辉
81d2fac8b6 文件选择器网络链接功能优化 2024-12-04 17:13:20 +08:00
阿辉
69d36cf858 文件选择器网络链接功能优化 2024-12-04 17:11:28 +08:00
阿辉
2701cf9352 文件选择器删除功能控制 2024-12-04 16:03:34 +08:00
阿辉
84b5426932 文件选择器删除功能控制 2024-12-04 16:01:44 +08:00
阿辉
36a8471dd0 文件选择器的url和附件管理的url优化 2024-12-04 15:31:26 +08:00
阿辉
1d02f3e138 文件列表序列化器更新 2024-12-04 15:16:48 +08:00
阿辉
da0f084b0c 文件选择器优化 2024-12-03 17:24:55 +08:00
阿辉
1993fdd509 文件选择器优化 2024-12-03 16:55:09 +08:00
阿辉
9fb5e95a55 文件选择器添加网络资源获取功能 2024-12-03 16:40:01 +08:00
阿辉
706986e187 文件选择器样式优化 2024-12-03 14:36:52 +08:00
阿辉
e3291fe22b 文件选择器删除功能 2024-12-03 14:28:47 +08:00
阿辉
ec137c9f84 文件选择器tab切换相关功能完善 2024-12-03 13:53:06 +08:00
阿辉
1158bbb790 文件选择器内置文件筛选完善 2024-12-03 11:16:11 +08:00
阿辉
e880af6f1e 文件选择器添加系统内置文件筛选(功能未完善) 2024-12-02 19:53:04 +08:00
阿辉
547cc30818 附件管理添加预览 2024-12-02 15:06:35 +08:00
阿辉
d0f562b6ed 文件选择器只获取文件选择器上传的列表 2024-12-02 10:58:02 +08:00
阿辉
ad3a190e96 文件选择器的样式属性传递优化 2024-11-29 15:05:46 +08:00
阿辉
9cc071edcd 添加onSave,onClose和onClosed事件,优化数据保存逻辑:表单会在选择时实时变化但不点确定则不会进行实际数据修改(表单数据会复原)(实时变化是否修改待定) 2024-11-27 16:46:32 +08:00
阿辉
4177f84a62 文件选择器代码错误复原 2024-11-26 21:45:59 +08:00
阿辉
c1db1c21d0 样式优化 2024-11-23 18:40:24 +08:00
阿辉
422f86da22 优化template的结构,包一层,否则唯一标识不会生效 2024-11-23 15:55:43 +08:00
阿辉
dac7ea90ae 文件选择器给modal加class防止被样式污染 2024-11-23 15:35:15 +08:00
阿辉
8ff773af3b 文件选择器的删除按钮优化 2024-11-22 15:54:33 +08:00
阿辉
b6557de0ca 优化 2024-11-21 16:29:32 +08:00
阿辉
4a68bf2f2b 用户初次登录修改密码优化 2024-11-20 17:55:55 +08:00
阿辉
9c370169d3 文件选择器添加类,样式,禁用选项 2024-11-19 18:52:48 +08:00
阿辉
f62f0b440d 添加filelist的crud中对文件选择器的测试代码 2024-11-19 15:00:34 +08:00
阿辉
4a8f907c7f 文件选择器的video类型优化 2024-11-19 15:00:07 +08:00
阿辉
1301346772 文件选择器的audio类型优化 2024-11-19 14:55:43 +08:00
阿辉
012c5a9c9c 修复文件选择器的image表单类型显示错误的bug 2024-11-19 14:50:58 +08:00
阿辉
683b5164aa 修复文件选择器的属性valueKey为id时的选择显示错误的bug 2024-11-19 14:41:34 +08:00
阿辉
3eeea14b97 文件选择器支持audio的表单类型选项,优化部分样式 2024-11-19 14:29:23 +08:00
阿辉
7a9ef47a68 文件选择器支持video的表单类型选项,添加一些错误属性值判断,多选模式有限制 2024-11-19 14:19:48 +08:00
阿辉
495976726e clear优化 2024-11-19 13:48:01 +08:00
阿辉
50bcc4346f clear优化 2024-11-19 13:42:34 +08:00
阿辉
2f4a6e6b1f 文件选择器添加表单类型选项,目前支持selector和image类型 2024-11-19 12:39:11 +08:00
阿辉
836b645507 Merge branch 'develop' of https://e.coding.net/dvadmin-private/code/dvadmin3 into develop 2024-11-15 18:54:17 +08:00
阿辉
ed3e3f12e0 修复文件选择器只有1个tab时默认选中错误的bug 2024-11-15 18:54:15 +08:00
1638245306
7280cbbb68 Merge remote-tracking branch 'origin/develop' into develop 2024-11-15 10:47:38 +08:00
1638245306
848e4a62af refactor(system): 调整菜单排序逻辑
- 将菜单排序方式从 id 改为 sort 字段
- 优化了前端获取当前角色路由的逻辑,提高用户体验
2024-11-15 10:47:25 +08:00
阿辉
98413bf125 文件选择器优化 2024-11-11 18:30:07 +08:00
阿辉
21d61794bc 文件选择器优化 2024-11-10 19:06:36 +08:00
阿辉
72e046fd6d 优化 2024-11-08 23:30:25 +08:00
阿辉
4f85de3247 文件选择器 2024-11-08 23:20:17 +08:00
阿辉
6ad048b86a 附件model更新 2024-11-07 23:08:25 +08:00
阿辉
b9976cc2dd 附件管理的过滤 2024-11-07 23:08:11 +08:00
阿辉
7a453da303 修复头像选择器组件上传时url传值问题 2024-11-07 20:23:41 +08:00
1638245306
421e89823a 1.初次登录必须修改密码 2024-11-06 13:09:09 +08:00
1638245306
436a722ce8 Merge remote-tracking branch 'origin/develop' into develop 2024-11-06 01:39:28 +08:00
1638245306
3ea38a59b7 1.优化登录页面;
2.新增初次登录强制修改密码;
2024-11-06 01:39:20 +08:00
李强
665d675deb Merge remote-tracking branch 'origin/master' 2024-10-28 21:17:12 +08:00
李强
46a1854e24 Merge remote-tracking branch 'origin/develop' into develop 2024-10-28 21:13:07 +08:00
dvadmin-开发-李强
a3c4e02aa1 Accept Merge Request #25: (develop -> master)
Merge Request: 合并

Created By: @dvadmin-开发-李强
Accepted By: @dvadmin-开发-李强
URL: https://dvadmin-private.coding.net/p/code/d/dvadmin3/git/merge/25?initial=true
2024-10-28 21:11:05 +08:00
阿辉
199a75293d 下载中心模块和异步导出功能 2024-10-24 22:08:24 +08:00
1638245306
3f58c1cb7a Merge remote-tracking branch 'origin/develop' into develop 2024-09-25 23:32:42 +08:00
1638245306
2c8b51f463 修复:允许路由在框架外显示 2024-09-25 23:32:35 +08:00
李强
7a752b1803 系统配置支持redis缓存 2024-09-18 21:05:35 +08:00
zhulj
69d23c6f69 修复: 更新import_export.py以过滤空数据表 2024-09-18 15:48:49 +08:00
dvadmin
652ad1355a !71 update web/src/views/system/personal/index.vue.
Merge pull request !71 from lxy/N/A
2024-08-29 01:10:32 +00:00
dvadmin
c0bd8c42a7 !70 update backend/dvadmin/system/views/user.py.
Merge pull request !70 from lxy/N/A
2024-08-29 01:10:01 +00:00
dvadmin
3657878d25 !69 update backend/dvadmin/system/views/message_center.py.
Merge pull request !69 from star/N/A
2024-08-29 01:06:44 +00:00
dvadmin
0e5d343f9f !68 update backend/dvadmin/utils/models.py 处理级联删除
Merge pull request !68 from 老高/N/A
2024-08-29 01:05:57 +00:00
李强
95f7046e49 feat: 线上版本号优化 2024-08-29 09:04:32 +08:00
李强
233774a981 feat: 优化部门权限 2024-08-29 09:04:13 +08:00
李强
47b0ee7fe7 feat: 添加信号signals 2024-08-29 09:03:39 +08:00
李强
b9c6ab6fcd Merge remote-tracking branch 'origin/develop' into develop 2024-08-29 08:33:31 +08:00
李强
a042a65555 feat: 更新package.json依赖 2024-08-29 08:32:23 +08:00
dvadmin
4ca0edc104 !66 更改npm镜像源为npmmirror.com
Merge pull request !66 from 老高/N/A
2024-08-29 00:30:07 +00:00
dvadmin
9c7e8097db !72 修复日期期间条件过滤不包含截止日期当天数据的bug
Merge pull request !72 from 好奇宝宝/N/A
2024-08-29 00:24:46 +00:00
dvadmin
3b2fd573bc !73 修复登录次数没有保存问题 update backend/dvadmin/system/views/login.py.
Merge pull request !73 from lxy/N/A
2024-08-29 00:16:30 +00:00
dvadmin
83a6e41015 !74 解决深色模式显示异常问题
Merge pull request !74 from lxy/cherry-pick-1722331014
2024-08-29 00:15:07 +00:00
dvadmin
f12dcb3f1f !75 修复celery时区问题
Merge pull request !75 from 木子-李/N/A
2024-08-29 00:14:27 +00:00
dvadmin
c11cf378cb !77 dictionary的key改成可选参数避免提示报错
dictionary的key改成可选参数避免提示报错
2024-08-29 00:13:45 +00:00
dvadmin
0764383989 !79 增加批量删除
增加表格多选
运行跨页多选
显示多选数据
移除多选数据
增加批量删除
示例代码在菜单管理里面
2024-08-29 00:13:00 +00:00
李小涛
b6e05c997d feat(20240827_BatchDelete): 增加批量删除
1. 增加表格多选
2. 运行跨页多选
3. 显示多选数据
4. 移除多选数据
5. 增加批量删除
2024-08-27 17:31:29 +08:00
李强
2576de48ec requirements.txt 2024-08-19 09:01:02 +08:00
快点洗澡睡觉吧
787b7b61c9 dictionary的key改成可选参数避免提示报错
Signed-off-by: 快点洗澡睡觉吧 <16840517@qq.com>
2024-08-12 12:45:45 +00:00
木子-李
5cf2eef7ad 修复celery时区问题
Signed-off-by: 木子-李 <1537080775@qq.com>
2024-08-01 14:05:49 +00:00
lxy
1981567c59 fixed 92a17fa from https://gitee.com/lxy0722/django-vue3-admin/pulls/1
解决在深色模式下的背景色显示异常问题
2024-07-30 09:16:55 +00:00
lxy
3979628281 修复登录次数没有保存问题 update backend/dvadmin/system/views/login.py.
修复登录次数没有保存问题

Signed-off-by: lxy <46486798@qq.com>
2024-07-29 03:19:29 +00:00
好奇宝宝
9a8506448f 优化日期期间条件过滤,包含截止日期当前数据
因为创建日期是一个datetime数据类型,直接使用lte不会包含截止日的数据

Signed-off-by: 好奇宝宝 <11259906+haoqibb@user.noreply.gitee.com>
2024-07-25 01:56:55 +00:00
lxy
e7b63a61ba update web/src/views/system/personal/index.vue.
修改密码时,原密码显示明文

Signed-off-by: lxy <46486798@qq.com>
2024-07-25 00:43:46 +00:00
lxy
e106324c70 update backend/dvadmin/system/views/user.py.
在创建用户时, 设置非默认密码,修改密码时提示密码错误,创建用户时调用了多次md5进行存储

Signed-off-by: lxy <46486798@qq.com>
2024-07-25 00:33:24 +00:00
star
a42ccd0e18 update backend/dvadmin/system/views/message_center.py.
消息中心模板类型是按角色、公告、部门时不返回用户信息,系统用户数据量过多时接口查询很慢

Signed-off-by: star <it_gjl@sina.com>
2024-07-24 15:59:40 +00:00
老高
5fc1390598 update backend/dvadmin/utils/models.py 处理级联删除
当模型中指定了`on_delete=models.CASCADE`实现级联软删除关联对象的逻辑。对于一对一、一对多和多对多关系的对象,都会进行软删除处理。

Signed-off-by: 老高 <794071084@qq.com>
2024-07-15 06:59:44 +00:00
老高
1942f1af4e 更改npm镜像源为npmmirror.com
将npm安装命令中的registry从淘宝npm源改为npmmirror.com,原有npm源已经失效

Signed-off-by: 老高 <794071084@qq.com>
2024-07-10 02:22:37 +00:00
dvadmin
c8e235bed6 !63 update backend/dvadmin/utils/field_permission.py.
Merge pull request !63 from lxy/N/A
2024-07-10 00:15:56 +00:00
dvadmin
cf93402763 !62 update backend/dvadmin/utils/middleware.py.
Merge pull request !62 from lxy/N/A
2024-07-10 00:13:49 +00:00
lxy
3dcef90bbe update backend/dvadmin/utils/field_permission.py.
修复一个账号拥有多个角色权限,导致前端某些模块没处理好导致的无权限问题(地区管理、登录日志),合并权限,不再返回多个相同的字段名权限,相同字段名的权限True值保留

Signed-off-by: lxy <46486798@qq.com>
2024-07-09 03:56:45 +00:00
lxy
6f8bae8d5c update backend/dvadmin/utils/middleware.py.
当同一时刻进来多个请求且都没有完成响应时,operation_log_id会保留最后一个进来的ID,导致之前按进来的请求记录到同一个id上,导致日志记录丢失

Signed-off-by: lxy <46486798@qq.com>
2024-07-08 09:33:51 +00:00
dvadmin
888a6f1c63 !61 地区管理
Merge pull request !61 from 木子-李/20240705_area
2024-07-08 00:16:36 +00:00
李小涛
6d587fc1e2 feat(20240705_area): 地区管理
- 优化地区管理:增删改查
- 优化tableSelect组件:增加树形结构和懒加载
2024-07-05 17:24:36 +08:00
李小涛
630ec1e774 feat(20240705_tableSelector): 表格选择组件
- 增加树形结构懒加载
2024-07-05 10:24:07 +08:00
dvadmin
8a17d6f82b !58 update backend/dvadmin/utils/models.py.
Merge pull request !58 from 木子-李/N/A
2024-07-05 00:47:11 +00:00
dvadmin
326149195f !59 update web/src/utils/columnPermission.ts.
Merge pull request !59 from 木子-李/N/A
2024-07-05 00:46:42 +00:00
木子-李
71ca7370e2 update web/src/utils/columnPermission.ts.
Signed-off-by: 木子-李 <1537080775@qq.com>
2024-07-04 14:56:42 +00:00
木子-李
0dcf8ae794 update backend/dvadmin/utils/models.py.
跨models引用模型的时候,


Signed-off-by: 木子-李 <1537080775@qq.com>
2024-07-04 05:51:38 +00:00
dvadmin
9c5fe4f483 !57 update backend/dvadmin/system/views/file_list.py.
Merge pull request !57 from 木子-李/N/A
2024-07-03 14:45:17 +00:00
dvadmin
c9ff9d0716 !56 消息中心
Merge pull request !56 from 木子-李/20240703_mesCenter
2024-07-03 14:44:58 +00:00
木子-李
27c9eff716 update backend/dvadmin/system/views/file_list.py.
修复lssuess问题(https://gitee.com/huge-dream/django-vue3-admin/issues/IA8ROD)
- 附件管理无法打开文件和图片
- 富文本无法打开文件和图片

Signed-off-by: 木子-李 <1537080775@qq.com>
2024-07-03 09:21:24 +00:00
李小涛
de603df07c fix(20240703_mesCenter): 消息中心
- 优化了前端index和crud
- 修复“我的接收”在点击查看,不显示目标内容bug
2024-07-03 15:12:30 +08:00
dvadmin
6793a09b8b !55 update backend/dvadmin/system/views/menu_button.py.
Merge pull request !55 from 木子-李/N/A
2024-07-02 13:20:03 +00:00
dvadmin
f60f84964a !54 列权限管控
Merge pull request !54 from 木子-李/20240701_FieldPermission
2024-07-02 13:19:47 +00:00
木子-李
bba4472009 update backend/dvadmin/system/views/menu_button.py.
Signed-off-by: 木子-李 <1537080775@qq.com>
2024-07-01 10:11:11 +00:00
李小涛
438480b2f1 feat(20240701_FieldPermission): 列权限管控
- 地区管理:增加列权限管控
- 登录日志:增加列权限管控
2024-07-01 15:48:00 +08:00
dvadmin
3129c33adc !53 update backend/dvadmin/system/views/role_menu_button_permission.py.
Merge pull request !53 from 木子-李/N/A
2024-06-30 07:58:15 +00:00
dvadmin
ac0aaefe0f !51 update web/src/views/system/role/components/PermissionComNew/index.vue.
Merge pull request !51 from 木子-李/N/A
2024-06-30 07:57:37 +00:00
dvadmin
ef43dc4900 !50 update web/src/views/system/menu/components/MenuFormCom/index.vue.
Merge pull request !50 from 木子-李/N/A
2024-06-30 07:57:23 +00:00
木子-李
03f467abfa update backend/dvadmin/system/views/role_menu_button_permission.py.
修复列权限BUG

Signed-off-by: 木子-李 <1537080775@qq.com>
2024-06-30 06:46:26 +00:00
木子-李
68d31dc515 update web/src/views/system/role/components/PermissionComNew/index.vue.
修复按钮权限保存失败bug

Signed-off-by: 木子-李 <1537080775@qq.com>
2024-06-30 06:36:50 +00:00
木子-李
9215cfd105 update web/src/views/system/menu/components/MenuFormCom/index.vue.
XEUtils 包重复引用

Signed-off-by: 木子-李 <1537080775@qq.com>
2024-06-30 05:37:10 +00:00
dvadmin
9c765c67e1 !49 优化权限配置
Merge pull request !49 from 木子-李/20240629_role_menu
2024-06-29 14:48:50 +00:00
dvadmin
2fd2c76bdb !48 update web/src/views/system/menu/components/MenuFormCom/index.vue.
Merge pull request !48 from 木子-李/N/A
2024-06-29 14:48:31 +00:00
李小涛
798f9e8a7f feat(20240629_role_menu): 优化权限配置
- 重构权限配置前端页面布局
2024-06-29 22:43:33 +08:00
木子-李
1879d0d2fd update web/src/views/system/menu/components/MenuFormCom/index.vue.
优化在新增修改菜单的时候,父级菜单tree只显示目录

Signed-off-by: 木子-李 <1537080775@qq.com>
2024-06-29 06:49:54 +00:00
李小涛
e1d9f555c8 feat(20240629_role_menu): 优化菜单管理
- 优化在新增修改菜单的时候,父级菜单tree只显示目录
2024-06-29 14:46:37 +08:00
dvadmin
d3c2bbbb5b !47 优化权限配置
Merge pull request !47 from 木子-李/role_menu_20240628
2024-06-29 05:40:48 +00:00
李小涛
453d1e3875 feat(role_menu_20240628): 优化权限配置
- 优化保存菜单按钮错误bug
- 优化非管理员角色给其他角色分配列权限禁用逻辑
- 优化按钮自定义数据权限后端逻辑
2024-06-29 13:30:18 +08:00
李小涛
d03a40d04f feat(role_menu_20240628): 优化权限配置
- 修复非管理员角色给其他角色分配权限的bug
- 修复列权限禁用判断逻辑
- 修复自定义数据权限部门判断逻辑
2024-06-29 11:46:45 +08:00
dvadmin
f2f0e06cd6 !30 在核心标准抽象模型模型中设置插入和更新模型的方法
Merge pull request !30 from pbb/develop
2024-06-29 02:26:49 +00:00
dvadmin
ae74105b74 !46 优化权限配置
Merge pull request !46 from 木子-李/role_menu_20240628
2024-06-28 15:56:38 +00:00
李小涛
314d7d79c2 feat(role_menu_20240628): 优化权限配置,增强用户体验
- 清除后端打印
- 统一变量名
2024-06-28 23:46:26 +08:00
李小涛
c2b0c3f25b feat(role_menu_20240628): 优化权限配置,增强用户体验
- 修复无法保存菜单授权bug
- 列权限增加禁用状态的逻辑判断
2024-06-28 23:35:05 +08:00
李强
4a26e1476a feat: 清除默认账号密码 2024-06-28 21:06:24 +08:00
李强
735f17c2d8 Merge remote-tracking branch 'origin/develop' into develop 2024-06-28 21:04:46 +08:00
dvadmin
47bccac7f9 !44 优化权限配置
Merge pull request !44 from 木子-李/role_per_20240628
2024-06-28 13:03:51 +00:00
dvadmin
f7f35151ac !41 修复登录错误次数写入的BUG
Merge pull request !41 from 好奇宝宝/N/A
2024-06-28 12:58:21 +00:00
dvadmin
b49b1f0cc6 !40 增加邮箱和手机号登录支持
Merge pull request !40 from 好奇宝宝/develop
2024-06-28 12:57:17 +00:00
dvadmin
11ec6fb150 !37 添加copy,Import及Import按钮,修改拼接的URL地址为menu_obj.component_name
Merge pull request !37 from 北风南里/N/A
2024-06-28 12:56:00 +00:00
dvadmin
591360b879 !36 update docker_env/django/Dockerfile.
Merge pull request !36 from 木子-李/N/A
2024-06-28 12:54:58 +00:00
李小涛
8c7e8aee9f feat(role_per_20240628): 优化权限配置
1、点击权限小齿轮,自动带出默认值
2024-06-28 16:22:16 +08:00
李小涛
8554bf18f4 fix(role_per_20240628): 优化权限配置
1、修复第一次选择自定义数据权限无法设置的bug
2024-06-28 16:02:29 +08:00
李小涛
0779cc1a84 fix(role_per_20240628): 优化权限配置
1、修复第一次选择自定义数据权限无法设置的bug
2024-06-28 15:46:44 +08:00
李小涛
1b8a502d66 fix(role_per_20240628): 优化权限配置
1、修复第一次选择自定义数据权限无法设置的bug
2024-06-28 15:36:38 +08:00
李小涛
087d478094 feat(role_per_20240628): 优化权限配置
1、修复首次打开权限配置,按钮小齿轮数据权限不显示bug
2、优化自定义数据权限:在input里面显示当前配置用户已有的权限
3、优化自定义数据权限:在树形选择器中,禁用掉当前登录用户没有权限的部门
2024-06-28 15:19:57 +08:00
猿小天
82d0b19bc2 1.更新并优化的权限管理 2024-06-28 11:20:19 +08:00
好奇宝宝
5cb7ec500c update backend/dvadmin/system/views/login.py.
修复登录错误次数写入的BUG

Signed-off-by: 好奇宝宝 <11259906+haoqibb@user.noreply.gitee.com>
2024-06-28 02:18:39 +00:00
好奇宝宝
7a21f44eab 删除文件 web/public/version-build 2024-06-28 02:15:47 +00:00
好奇宝宝
9383508a85 增加邮箱和手机号登录支持 2024-06-28 10:13:11 +08:00
北风南里
b6a4be25f2 添加copy,Import及Import按钮,修改拼接的URL地址为menu_obj.component_name
原来URL地址是拼接前端的路由地址,这与实际后端地址不匹配

Signed-off-by: 北风南里 <wskaudh@qq.com>
2024-06-26 09:04:54 +00:00
木子-李
7234d2b3e9 update docker_env/django/Dockerfile.
Signed-off-by: 木子-李 <1537080775@qq.com>
2024-06-26 05:53:20 +00:00
dvadmin
14640be036 !35 django-cors-headers依赖升级
Merge pull request !35 from vFeng/fix-update_django-cors-headers
2024-06-26 02:58:55 +00:00
dvadmin
af60e9a0fa !33 增加nginx 不缓存index.html&优化前端写法
Merge pull request !33 from vFeng/develop
2024-06-26 02:58:33 +00:00
周继风
259c51b23c [fix]更新django-cors-headers版本至4.4.0, 现在4.3.0版本的CorsMiddleware中间件会导致SessionMiddleware中无法设置session 2024-06-26 00:00:14 +08:00
周继风
5b60f60b70 [fix]更新django-cors-headers版本至4.4.0, 现在4.3.0版本的CorsMiddleware中间件会导致SessionMiddleware中无法设置session 2024-06-25 23:53:54 +08:00
周继风
38ad2db7a7 [feat]增加nginx不缓存index.html配置,搭配前端版本自动升级功能使用(不加此配置可能会导致用户刷新后还是旧版本) 2024-06-25 10:17:36 +08:00
周继风
0529c2747a [fix]优化前端代码写法 2024-06-25 10:16:11 +08:00
dvadmin
fe5e44913d !31 增加生产环境下前端代码版本判断,当前端版本发生改变时自动更新前端代码至最新版本
Merge pull request !31 from vFeng/develop
2024-06-24 14:46:40 +00:00
周继风
d7edbde434 [feat]增加生产环境中前端代码更新版本后用户端自动升级为最新前端版本 2024-06-24 22:35:17 +08:00
李强
b6c013dad7 feat: 优化登录认证失败刷新页面 2024-06-24 07:22:05 +08:00
猿小天
452bc0a63a 修复BUG: 子角色授权问题 2024-06-23 10:54:40 +08:00
acjzdpbb
275d380fe0 feat: 在核心标准抽象模型模型中设置插入和更新模型的方法 2024-06-23 10:07:48 +08:00
猿小天
354d230c2a 功能变化: 优化个人中心修改密码后强制退出登录状态 2024-06-22 22:53:59 +08:00
猿小天
37a0167193 Merge remote-tracking branch 'origin/develop' into develop 2024-06-22 22:24:25 +08:00
猿小天
6f4f5a771e 功能变化:
1..优化部门管理相关接口;
2024-06-22 22:23:35 +08:00
李强
297c4df74f logo.icns 2024-06-22 21:20:19 +08:00
李强
b1b49aa0db Merge remote-tracking branch 'origin/develop' into develop 2024-06-22 19:51:52 +08:00
1638245306
b1f9faca0f Merge remote-tracking branch 'origin/develop' into develop 2024-06-22 16:10:49 +08:00
1638245306
05f659bc87 修复用户管理的接口 2024-06-22 16:10:42 +08:00
李强
218036c0ea dvadmin3-build 兼容支持 2024-06-22 11:45:20 +08:00
dvadmin-开发-李强
666f072b58 Accept Merge Request #22: (develop -> master)
Merge Request: add nginx config

Created By: @dvadmin-开发-李强
Accepted By: @dvadmin-开发-李强
URL: https://dvadmin-private.coding.net/p/code/d/dvadmin3/git/merge/22?initial=true
2024-06-21 08:32:02 +08:00
liqianglog
3086addd10 Merge pull request #10 from stevenchen0018/master
add nginx config
2024-06-21 08:29:56 +08:00
1638245306
e72f2ce01c 修改授权的文字描述 2024-06-21 08:28:23 +08:00
dvadmin-开发-李强
de638e7e4b Accept Merge Request #21: (develop -> master)
Merge Request: 更新 static

Created By: @dvadmin-开发-李强
Accepted By: @dvadmin-开发-李强
URL: https://dvadmin-private.coding.net/p/code/d/dvadmin3/git/merge/21?initial=true
2024-06-21 08:14:54 +08:00
李强
500433e06f 更新 static 2024-06-21 08:14:11 +08:00
dvadmin-开发-李强
e29ae657f6 Accept Merge Request #20: (develop -> master)
Merge Request: 新功能

Created By: @dvadmin-开发-李强
Accepted By: @dvadmin-开发-李强
URL: https://dvadmin-private.coding.net/p/code/d/dvadmin3/git/merge/20?initial=true
2024-06-21 08:08:05 +08:00
李强
57d90e8a71 Merge branch 'master' of https://gitee.com/huge-dream/django-vue3-admin into develop
# Conflicts:
#	backend/dvadmin/system/views/role_menu_button_permission.py
#	web/src/views/system/role/components/PermissionComNew/index.vue
#	web/src/views/system/role/components/PermissionComNew/types.ts
2024-06-21 08:02:59 +08:00
李强
3332d1abb5 删除 hooks-uvicorn 2024-06-21 08:01:31 +08:00
李强
a6d6fb3c54 Merge remote-tracking branch 'origin/develop' into develop 2024-06-21 07:57:54 +08:00
dvadmin
b3ceb8ce6d !17 update docker_env/web/DockerfileBuild.
Merge pull request !17 from linshui/N/A
2024-06-20 23:53:55 +00:00
dvadmin
773690677e !22 修改columnPermission函数
Merge pull request !22 from 木子-李/columnPer20240528
2024-06-20 23:52:27 +00:00
dvadmin
becbe7e7f9 !21 新增菜单接口权限批量创建功能
Merge pull request !21 from 好奇宝宝/develop
2024-06-20 23:51:17 +00:00
dvadmin
a812f1304b !19 修改权限配置页面的布局
Merge pull request !19 from 木子-李/muzili
2024-06-20 23:50:31 +00:00
dvadmin
5e3bf1b9c4 !28 列字段权限→自动匹配→添加model页面增加模糊搜索框
Merge pull request !28 from 北风南里/N/A
2024-06-20 23:49:44 +00:00
dvadmin-开发-李强
f379ac754d Accept Merge Request #19: (develop -> master)
Merge Request: 正式发布v3.0.3版本

Created By: @dvadmin-开发-李强
Accepted By: @dvadmin-开发-李强
URL: https://dvadmin-private.coding.net/p/code/d/dvadmin3/git/merge/19?initial=true
2024-06-21 07:48:11 +08:00
李强
f34b7967d5 refactor: 优化main.py,移除无效代码和浏览器自动打开功能
- 移除了`extra-hooks/__init__.py`中的注释,该文件现在为空。
- 在`main.py`中移除了未使用的导入语句和函数,以及启动浏览器的代码。
- 删除了`main.spec`文件,因为它是PyInstaller打包配置的一部分,现在的更改侧重于源代码的清理和优化。
2024-06-21 07:36:31 +08:00
李强
60ca268851 新增:打包编译功能 2024-06-18 14:11:41 +08:00
李强
38bb65f9d5 新增:打包编译功能 2024-06-18 12:46:57 +08:00
李强
1f81ac5854 feat: 取消升级提示 2024-06-18 12:26:34 +08:00
stevenchen
95a046c2fe add nginx config
Signed-off-by: stevenchen <stevenchen@stevenchendeMacBook-Air.local>
2024-06-13 10:53:38 +08:00
好奇宝宝
92fa2f2d76 update backend/dvadmin/system/views/menu_button.py.
Signed-off-by: 好奇宝宝 <11259906+haoqibb@user.noreply.gitee.com>
2024-06-05 00:56:38 +00:00
猿小天
8a0420c14f 文档地址修改
Signed-off-by: 猿小天 <1638245306@qq.com>
2024-06-04 13:52:05 +00:00
china_ahhui
bcc5e2f77a 修复document的title出现undefined的bug 2024-06-02 12:41:31 +08:00
china_ahhui
389add2a8a 修复角色权限操作界面打开某个集时会出现两块相似内容的bug 2024-06-02 12:25:33 +08:00
china_ahhui
30d7467369 优化登录按钮交互 2024-06-02 12:07:46 +08:00
china_ahhui
6e3f05c58f 登录界面扫码按钮ui优化 2024-06-02 11:33:48 +08:00
china_ahhui
7ed699593c 更新初始化文件以及初始化逻辑(角色菜单和角色菜单按钮) 2024-06-02 11:28:34 +08:00
北风南里
00fc9cdad4 列字段权限→自动匹配→添加model页面增加模糊搜索框
Signed-off-by: 北风南里 <wskaudh@qq.com>
2024-05-31 04:15:05 +00:00
李小涛
1e38f73829 feat(muzili20240528): 修改columnPermission函数
在权限列表设置列表不可见,默认不显示,但在前端还是可以通过列控制选择出来,
因此在columnPermission函数中增加了列表可见判断条件:“如果列表可见的false,则禁止在列设置中选择”
2024-05-28 15:23:15 +08:00
好奇宝宝
bee0d672b3 修复新增接口方法错误 2024-05-28 11:52:56 +08:00
李小涛
089e0ca5c1 feat(muzili): 修改权限配置页面
增加菜单页面显示权限配置
2024-05-27 17:59:47 +08:00
好奇宝宝
1dbdc816af 增加菜单权限接口批量创建功能 2024-05-27 16:30:31 +08:00
李小涛
b542ef9086 feat(muzili): 修改权限配置页面
1、修改了权限配置页面布局
2、修复权限页面,保存菜单授权按钮的后端逻辑
2024-05-24 22:17:31 +08:00
猿小天
3720fbe4a0 功能变化: 1.允许动态修改网站小图标;
2.允许动态修改网站标题;
2024-05-22 21:50:42 +08:00
猿小天
91d15ec15b 功能变化: 账号密码错误超过5次进行封号 2024-05-22 21:47:58 +08:00
猿小天
e8d39d0150 功能变化: 修复授权管理的bug 2024-05-21 21:48:58 +08:00
猿小天
3786b4123b 功能变化: 登录加入判断,超过5次账号密码不正确则锁定 2024-05-21 21:46:11 +08:00
猿小天
767cbbe366 修复BUG: 用户管理导入导出按钮权限 2024-05-10 10:23:00 +08:00
猿小天
49d56f6378 修复BUG: 无任何菜单权限时,无法进入首页问题 2024-05-09 11:24:33 +08:00
猿小天
e6f898bef4 功能变化: 删除打印 2024-05-09 11:20:41 +08:00
猿小天
0d9ee1f4d4 修复BUG:
1.密码重置;
2.个人中心密码修改
2024-05-09 11:11:23 +08:00
猿小天
a03b14a32d 修复BUG: 修复demo缺少文件问题 2024-05-09 10:20:41 +08:00
linshui
5a5861a0e8 update docker_env/web/DockerfileBuild.
更新淘宝npmurl

Signed-off-by: linshui <9517545+linshuinew@user.noreply.gitee.com>
2024-05-04 10:14:04 +00:00
china_ahhui
a6e20d88da init命令添加可指定app进行初始化,'python manage.py init -app app1 app2 ...'或 'python manage.py init -A app1 app2 ...' 2024-04-11 16:57:14 +08:00
李强
94767d8bc1 feat: 修复e-icon-picker 2024-04-04 23:27:36 +08:00
李强
28a96de928 修复授权时无列权限报错bug 2024-03-31 01:02:25 +08:00
李强
665e5ce092 docker-compose.yml 更新 2024-03-30 21:47:24 +08:00
李强
b604893697 redis 默认密码 2024-03-30 21:45:08 +08:00
李强
cdf3d7cab8 部署环境优化 2024-03-30 21:41:59 +08:00
猿小天
18b44e2aa4 修复BUG: 修复页面缓存问题 2024-03-18 22:21:58 +08:00
猿小天
69b05e94b4 Merge remote-tracking branch 'origin/develop' into develop 2024-03-18 22:18:48 +08:00
猿小天
93cdd46e64 修复BUG: 修复页面缓存问题 2024-03-18 22:17:00 +08:00
李强
5bd57232d8 feat: 优化docker部署 2024-03-18 21:22:22 +08:00
李强
2cfd8f0798 feat: 前端lodash包优化成lodash-es 2024-03-18 21:21:19 +08:00
李强
edf45c08d1 feat: fast-crud版本更新 2024-03-18 18:18:44 +08:00
李强
988daf03dd 优化系统配置表的key值大小 2024-03-18 14:28:25 +08:00
李强
c686a6e74a Merge remote-tracking branch 'origin/master' 2024-03-12 23:01:08 +08:00
dvadmin
dffe47e79c !13 正式发布v3.0.1版本
Merge pull request !13 from dvadmin/develop
2024-01-01 15:23:20 +00:00
dvadmin
fd3adeed57 !9 正式发布v3.0.0版本
Merge pull request !9 from dvadmin/develop
2023-11-30 15:59:22 +00:00
375 changed files with 20545 additions and 4104 deletions

4
.gitignore vendored
View File

@@ -4,4 +4,6 @@
.history/
.vscode/
web/package-lock.json
web/package-lock.json
*.bat

View File

@@ -1,214 +1,245 @@
# Django-Vue3-Admin
[![img](https://img.shields.io/badge/license-MIT-blue.svg)](https://gitee.com/liqianglog/django-vue-admin/blob/master/LICENSE) [![img](https://img.shields.io/badge/python-%3E=3.7.x-green.svg)](https://python.org/) [![PyPI - Django Version badge](https://img.shields.io/badge/django%20versions-3.2-blue)](https://docs.djangoproject.com/zh-hans/3.2/) [![img](https://img.shields.io/badge/node-%3E%3D%2012.0.0-brightgreen)](https://nodejs.org/zh-cn/) [![img](https://gitee.com/liqianglog/django-vue-admin/badge/star.svg?theme=dark)](https://gitee.com/liqianglog/django-vue-admin)
[预 览](https://demo.dvadmin.com) | [官网文档](https://www.django-vue-admin.com) | [群聊](https://qm.qq.com/cgi-bin/qm/qr?k=fOdnHhC8DJlRHGYSnyhoB8P5rgogA6Vs&jump_from=webapi) | [社区](https://bbs.django-vue-admin.com) | [插件市场](https://bbs.django-vue-admin.com/plugMarket.html) | [Github](https://github.com/liqianglog/django-vue-admin)
💡 **「关于」**
我们是一群热爱代码的青年在这个炙热的时代下我们希望静下心来通过Code带来一点我们的色彩和颜色。
因为热爱,所以拥抱未来!
## 平台简介
💡 [django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin.git) 是一套全部开源的快速开发平台,毫无保留给个人免费使用、团体授权使用。
django-vue3-admin 基于RBAC模型的权限控制的一整套基础开发平台权限粒度达到列级别前后端分离后端采用django + django-rest-framework前端采用基于 vue3 + CompositionAPI + typescript + vite + element plus
* 🧑‍🤝‍🧑前端采用 Vue3+TS+pinia+fastcrud(感谢[vue-next-admin](https://lyt-top.gitee.io/vue-next-admin-doc-preview/))
* 👭后端采用 Python 语言 Django 框架以及强大的 [Django REST Framework](https://pypi.org/project/djangorestframework)。
* 👫权限认证使用[Django REST Framework SimpleJWT](https://pypi.org/project/djangorestframework-simplejwt),支持多终端认证系统。
* 👬支持加载动态权限菜单,多方式轻松权限控制。
* 👬全新的列权限管控,粒度细化到每一列。
* 💏特别鸣谢:[vue-next-admin](https://lyt-top.gitee.io/vue-next-admin-doc-preview/)。
* 💡特别感谢[jetbrains](https://www.jetbrains.com/) 为本开源项目提供免费的 IntelliJ IDEA 授权。
#### 🏭 环境支持
| Edge | Firefox | Chrome | Safari |
| --------- | ------------ | ----------- | ----------- |
| Edge ≥ 79 | Firefox ≥ 78 | Chrome ≥ 64 | Safari ≥ 12 |
> 由于 Vue3 不再支持 IE11故而 ElementPlus 也不支持 IE11 及之前版本。
## 在线体验
👩‍👧‍👦演示地址:[https://demo.dvadmin.com](https://demo.dvadmin.com)
- 账号superadmin
- 密码admin123456
👩‍👦‍👦文档地址:[coding](https://dvadmin-private.coding.net/share/km/cec69f3d-30fe-47d5-bd97-e9e851f0b776/K-2)
## 交流
- 交流社区:[戳我](https://bbs.django-vue-admin.com)👩‍👦‍👦
- 插件市场:[戳我](https://bbs.django-vue-admin.com/plugMarket.html)👩‍👦‍👦
- django-vue-admin交流01群(已满)812482043 [点击链接加入群聊](https://qm.qq.com/cgi-bin/qm/qr?k=aJVwjDvH-Es4MPJQuoO32N0SucK22TE5&jump_from=webapi)
- django-vue-admin交流02群(已满)687252418 [点击链接加入群聊](https://qm.qq.com/cgi-bin/qm/qr?k=4jJN4IjWGfxJ8YJXbb_gTsuWjR34WLdc&jump_from=webapi)
- django-vue-admin交流03群442108213 [点击链接加入群聊](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=wsm5oSz3K8dElBYUDtLTcQSEPhINFkl8&authKey=M6sbER0z59ZakgBr5erFeZyFZU15CI52bErNZa%2FxSvvGIuVAbY0N5866v89hm%2FK4&noverify=0&group_code=442108213)
- 二维码
<img src='https://images.gitee.com/uploads/images/2022/0530/233203_5fb11883_5074988.jpeg' width='200'>
## 源码地址
gitee地址(主推)[https://gitee.com/huge-dream/django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin)👩‍👦‍👦
github地址[https://github.com/huge-dream/django-vue3-admin](https://github.com/huge-dream/django-vue3-admin)👩‍👦‍👦
## 内置功能
1. 👨‍⚕️菜单管理:配置系统菜单,操作权限,按钮权限标识、后端接口权限等。
2. 🧑‍⚕️部门管理:配置系统组织机构(公司、部门、角色)。
3. 👩‍⚕️角色管理:角色菜单权限分配、数据权限分配、设置角色按部门进行数据范围权限划分。
4. 🧑‍🎓按钮权限控制:授权角色的按钮权限和接口权限,可做到每一个接口都能授权数据范围。
5. 🧑‍🎓字段列权限控制:授权页面的字段显示权限,具体到某一列的显示权限。
7. 👨‍🎓用户管理:用户是系统操作者,该功能主要完成系统用户配置
8. 👬接口白名单:配置不需要进行权限校验的接口
9. 🧑‍🔧字典管理:对系统中经常使用的一些较为固定的数据进行维护
10. 🧑‍🔧地区管理:对省市县区域进行管理
11. 📁附件管理:对平台上所有文件、图片等进行统一管理
12. 🗓️操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询
13. 🔌[插件市场 ](https://bbs.django-vue-admin.com/plugMarket.html)基于Django-Vue-Admin框架开发的应用和插件
## 插件市场 🔌
更新中...
## 仓库分支说明 💈
主分支master稳定版本
开发分支develop
## 准备工作
~~~
Python >= 3.11.0 (最低3.9+版本)
nodejs >= 16.0
Mysql >= 8.0 (可选默认数据库sqlite3支持5.7+推荐8.0版本)
Redis (可选,最新版)
~~~
## 前端♝
```bash
# 克隆项目
git clone https://gitee.com/huge-dream/django-vue3-admin.git
# 进入项目目录
cd web
# 安装依赖
npm install yarn
yarn install --registry=https://registry.npm.taobao.org
# 启动服务
yarn build
# 浏览器访问 http://localhost:8080
# .env.development 文件中可配置启动端口等参数
# 构建生产环境
# yarn run build
```
## 后端💈
~~~bash
1. 进入项目目录 cd backend
2. 在项目根目录中,复制 ./conf/env.example.py 文件为一份新的到 ./conf 文件夹下,并重命名为 env.py
3. 在 env.py 中配置数据库信息
mysql数据库版本建议8.0
mysql数据库字符集utf8mb4
4. 安装依赖环境
pip3 install -r requirements.txt
5. 执行迁移命令:
python3 manage.py makemigrations
python3 manage.py migrate
6. 初始化数据
python3 manage.py init
7. 初始化省市县数据:
python3 manage.py init_area
8. 启动项目
python3 manage.py runserver 0.0.0.0:8000
或使用 uvicorn :
uvicorn application.asgi:application --port 8000 --host 0.0.0.0 --workers 8
~~~
## 开发建议
前后端backend与web各自单独一个窗口打开进行开发
### 访问项目
- 访问地址:[http://localhost:8080](http://localhost:8080) (默认为此地址,如有修改请按照配置文件)
- 账号:`superadmin` 密码:`admin123456`
### docker-compose 运行
~~~shell
# 先安装docker-compose (自行百度安装),执行此命令等待安装如有使用celery插件请打开docker-compose.yml中celery 部分注释
docker-compose up -d
# 初始化后端数据(第一次执行即可)
docker exec -ti dvadmin3-django bash
python manage.py makemigrations
python manage.py migrate
python manage.py init_area
python manage.py init
exit
前端地址http://127.0.0.1:8080
后端地址http://127.0.0.1:8080/api
# 在服务器上请把127.0.0.1 换成自己公网ip
账号superadmin 密码admin123456
# docker-compose 停止
docker-compose down
# docker-compose 重启
docker-compose restart
# docker-compose 启动时重新进行 build
docker-compose up -d --build
~~~
## 演示图✅
![image-01](https://foruda.gitee.com/images/1701348994587355489/1bc749e7_5074988.png)
![image-02](https://foruda.gitee.com/images/1701349037811908960/80d361db_5074988.png)
![image-03](https://foruda.gitee.com/images/1701349224478845203/954f0a7b_5074988.png)
![image-04](https://foruda.gitee.com/images/1701349248928658877/64926724_5074988.png)
![image-05](https://foruda.gitee.com/images/1701349259068943299/1306ba40_5074988.png)
![image-06](https://foruda.gitee.com/images/1701349294894429495/e3b3a8cf_5074988.png)
![image-07](https://foruda.gitee.com/images/1701350432536247561/3b26685e_5074988.png)
![image-08](https://foruda.gitee.com/images/1701350455264771992/b364c57f_5074988.png)
![image-09](https://foruda.gitee.com/images/1701350479266000753/e4e4f7c5_5074988.png)
![image-10](https://foruda.gitee.com/images/1701350501421625746/f8dd215e_5074988.png)
# Django-Vue3-Admin
[![img](https://img.shields.io/badge/license-MIT-blue.svg)](https://gitee.com/liqianglog/django-vue-admin/blob/master/LICENSE) [![img](https://img.shields.io/badge/python-%3E=3.7.x-green.svg)](https://python.org/) [![PyPI - Django Version badge](https://img.shields.io/badge/django%20versions-3.2-blue)](https://docs.djangoproject.com/zh-hans/3.2/) [![img](https://img.shields.io/badge/node-%3E%3D%2012.0.0-brightgreen)](https://nodejs.org/zh-cn/) [![img](https://gitee.com/liqianglog/django-vue-admin/badge/star.svg?theme=dark)](https://gitee.com/liqianglog/django-vue-admin)
[预 览](https://demo.dvadmin.com) | [官网文档](https://www.django-vue-admin.com) | [群聊](https://qm.qq.com/cgi-bin/qm/qr?k=fOdnHhC8DJlRHGYSnyhoB8P5rgogA6Vs&jump_from=webapi) | [社区](https://bbs.django-vue-admin.com) | [插件市场](https://bbs.django-vue-admin.com/plugMarket.html) | [Github](https://github.com/liqianglog/django-vue-admin)
💡 **「关于」**
我们是一群热爱代码的青年在这个炙热的时代下我们希望静下心来通过Code带来一点我们的色彩和颜色。
因为热爱,所以拥抱未来!
## 平台简介
💡 [django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin.git) 是一套全部开源的快速开发平台,毫无保留给个人免费使用、团体授权使用。
django-vue3-admin 基于RBAC模型的权限控制的一整套基础开发平台权限粒度达到列级别前后端分离后端采用django + django-rest-framework前端采用基于 vue3 + CompositionAPI + typescript + vite + element plus
* 🧑‍🤝‍🧑前端采用 Vue3+TS+pinia+fastcrud(感谢[vue-next-admin](https://lyt-top.gitee.io/vue-next-admin-doc-preview/))
* 👭后端采用 Python 语言 Django 框架以及强大的 [Django REST Framework](https://pypi.org/project/djangorestframework)。
* 👫权限认证使用[Django REST Framework SimpleJWT](https://pypi.org/project/djangorestframework-simplejwt),支持多终端认证系统。
* 👬支持加载动态权限菜单,多方式轻松权限控制。
* 👬全新的列权限管控,粒度细化到每一列。
* 💏特别鸣谢:[vue-next-admin](https://lyt-top.gitee.io/vue-next-admin-doc-preview/)。
* 💡特别感谢[jetbrains](https://www.jetbrains.com/) 为本开源项目提供免费的 IntelliJ IDEA 授权。
#### 🏭 环境支持
| Edge | Firefox | Chrome | Safari |
| --------- | ------------ | ----------- | ----------- |
| Edge ≥ 79 | Firefox ≥ 78 | Chrome ≥ 64 | Safari ≥ 12 |
> 由于 Vue3 不再支持 IE11故而 ElementPlus 也不支持 IE11 及之前版本。
## 在线体验
👩‍👧‍👦演示地址:[https://demo.dvadmin.com](https://demo.dvadmin.com)
- 账号superadmin
- 密码admin123456
👩‍👦‍👦文档地址:[DVAdmin官网](https://www.django-vue-admin.com)
## 交流
- 交流社区:[戳我](https://bbs.django-vue-admin.com)👩‍👦‍👦
- 插件市场:[戳我](https://bbs.django-vue-admin.com/plugMarket.html)👩‍👦‍👦
- django-vue-admin交流01群(已满)812482043 [点击链接加入群聊](https://qm.qq.com/cgi-bin/qm/qr?k=aJVwjDvH-Es4MPJQuoO32N0SucK22TE5&jump_from=webapi)
- django-vue-admin交流02群(已满)687252418 [点击链接加入群聊](https://qm.qq.com/cgi-bin/qm/qr?k=4jJN4IjWGfxJ8YJXbb_gTsuWjR34WLdc&jump_from=webapi)
- django-vue-admin交流03群(已满)442108213 [点击链接加入群聊](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=wsm5oSz3K8dElBYUDtLTcQSEPhINFkl8&authKey=M6sbER0z59ZakgBr5erFeZyFZU15CI52bErNZa%2FxSvvGIuVAbY0N5866v89hm%2FK4&noverify=0&group_code=442108213)
- django-vue-admin交流04群442108213 [点击链接加入群聊](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=wsm5oSz3K8dElBYUDtLTcQSEPhINFkl8&authKey=M6sbER0z59ZakgBr5erFeZyFZU15CI52bErNZa%2FxSvvGIuVAbY0N5866v89hm%2FK4&noverify=0&group_code=442108213)
## 给框架点赞
<div style="display: flex; gap: 10px;">
<img src='https://django-vue-admin.com/alipay.jpg' width='200'>
<img src='https://django-vue-admin.com/wechat.jpg' width='200'>
</div>
## 源码地址
gitee地址(主推)[https://gitee.com/huge-dream/django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin)👩‍👦‍👦
github地址[https://github.com/huge-dream/django-vue3-admin](https://github.com/huge-dream/django-vue3-admin)👩‍👦‍👦
## 内置功能
1. 👨‍⚕️菜单管理:配置系统菜单,操作权限,按钮权限标识、后端接口权限等
2. 🧑‍⚕️部门管理:配置系统组织机构(公司、部门、角色)
3. 👩‍⚕️角色管理:角色菜单权限分配、数据权限分配、设置角色按部门进行数据范围权限划分
4. 🧑‍🎓按钮权限控制:授权角色的按钮权限和接口权限,可做到每一个接口都能授权数据范围
5. 🧑‍🎓字段列权限控制:授权页面的字段显示权限,具体到某一列的显示权限
7. 👨‍🎓用户管理:用户是系统操作者,该功能主要完成系统用户配置
8. 👬接口白名单:配置不需要进行权限校验的接口
9. 🧑‍🔧字典管理:对系统中经常使用的一些较为固定的数据进行维护。
10. 🧑‍🔧地区管理:对省市县区域进行管理。
11. 📁附件管理:对平台上所有文件、图片等进行统一管理。
12. 🗓️操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
13. 🔌[插件市场 ](https://bbs.django-vue-admin.com/plugMarket.html)基于Django-Vue-Admin框架开发的应用和插件。
## 插件市场 🔌
1. #### [dvadmin3-folw 后台审批流插件](https://bbs.django-vue-admin.com/plugMarket/139.html)
2. #### [dvadmin3 celery插件前端](https://bbs.django-vue-admin.com/plugMarket/134.html)
3. #### [dvadmin3 celery插件后端](https://bbs.django-vue-admin.com/plugMarket/133.html)
4. #### [dvadmin3-build插件](https://bbs.django-vue-admin.com/plugMarket/136.html)
5. #### [dvadmin3-uniapp](https://e.coding.net/dvadmin-private/code/dvadmin3-uniapp-app.git)
6. #### dvadmin3-folw-uniapp 审批(开发中,近期上线)
## 仓库分支说明 💈
主分支master稳定版本
开发分支develop
## 准备工作
~~~
Python >= 3.11.0 (最低3.9+版本)
nodejs >= 16.0
Mysql >= 8.0 (可选默认数据库sqlite3支持5.7+推荐8.0版本)
Redis (可选,最新版)
~~~
## 前端♝
```bash
# 克隆项目
git clone https://gitee.com/huge-dream/django-vue3-admin.git
# 进入项目目录
cd web
# 安装依赖
npm install yarn
yarn install --registry=https://registry.npmmirror.com
# 启动服务
yarn build
# 浏览器访问 http://localhost:8080
# .env.development 文件中可配置启动端口等参数
# 构建生产环境
# yarn run build
```
## 后端💈
~~~bash
1. 进入项目目录 cd backend
2. 在项目根目录中,复制 ./conf/env.example.py 文件为一份新的到 ./conf 文件夹下,并重命名为 env.py
3. 在 env.py 中配置数据库信息
mysql数据库版本建议8.0
mysql数据库字符集utf8mb4
4. 安装依赖环境
pip3 install -r requirements.txt
5. 执行迁移命令:
python3 manage.py makemigrations
python3 manage.py migrate
6. 初始化数据
python3 manage.py init
7. 初始化省市县数据:
python3 manage.py init_area
8. 启动项目
python3 manage.py runserver 0.0.0.0:8000
或使用 uvicorn :
uvicorn application.asgi:application --port 8000 --host 0.0.0.0 --workers 8
~~~
## 开发建议
前后端backend与web各自单独一个窗口打开进行开发
### 访问项目
- 访问地址:[http://localhost:8080](http://localhost:8080) (默认为此地址,如有修改请按照配置文件)
- 账号:`superadmin` 密码:`admin123456`
### docker-compose 运行
~~~shell
# 先安装docker-compose (自行百度安装),执行此命令等待安装如有使用celery插件请打开docker-compose.yml中celery 部分注释
docker-compose up -d
# 初始化后端数据(第一次执行即可)
docker exec -ti dvadmin3-django bash
python manage.py makemigrations
python manage.py migrate
python manage.py init_area
python manage.py init
exit
前端地址:http://127.0.0.1:8080
后端地址http://127.0.0.1:8080/api
# 在服务器上请把127.0.0.1 换成自己公网ip
账号superadmin 密码admin123456
# docker-compose 停止
docker-compose down
# docker-compose 重启
docker-compose restart
# docker-compose 启动时重新进行 build
docker-compose up -d --build
~~~
## 演示图✅
![image-01](https://foruda.gitee.com/images/1701348994587355489/1bc749e7_5074988.png)
![image-02](https://foruda.gitee.com/images/1701349037811908960/80d361db_5074988.png)
![image-03](https://foruda.gitee.com/images/1701349224478845203/954f0a7b_5074988.png)
![image-04](https://foruda.gitee.com/images/1701349248928658877/64926724_5074988.png)
![image-05](https://foruda.gitee.com/images/1701349259068943299/1306ba40_5074988.png)
![image-06](https://foruda.gitee.com/images/1701349294894429495/e3b3a8cf_5074988.png)
![image-07](https://foruda.gitee.com/images/1701350432536247561/3b26685e_5074988.png)
![image-08](https://foruda.gitee.com/images/1701350455264771992/b364c57f_5074988.png)
![image-09](https://foruda.gitee.com/images/1701350479266000753/e4e4f7c5_5074988.png)
![image-10](https://foruda.gitee.com/images/1701350501421625746/f8dd215e_5074988.png)
## 审批流插件
![输入链接说明](https://bbs.django-vue-admin.com/uploads/20250321/97fbbf29673edfd66a1edd49237791bb.png)
![输入链接说明](https://bbs.django-vue-admin.com/uploads/20250321/c43aa51278cbc478287c718d22397479.png)
![输入链接说明](https://bbs.django-vue-admin.com/uploads/20250321/9732a5cca9c1166d1a65c35e313ab90d.png)
![输入链接说明](https://bbs.django-vue-admin.com/uploads/20250321/3ca9dd0801ce76d21435abcc8a3d505a.png)
![输入链接说明](https://bbs.django-vue-admin.com/uploads/20250321/a87a8d2329ef66880af5b0f16c5ff823.png)

View File

@@ -0,0 +1,5 @@
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery import app as celery_app
__all__ = ('celery_app',)

View File

@@ -8,25 +8,25 @@ https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/
"""
import os
from channels.auth import AuthMiddlewareStack
from channels.security.websocket import AllowedHostsOriginValidator
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
http_application = get_asgi_application()
from application.routing import websocket_urlpatterns
from application.ws_routing import websocket_urlpatterns
application = ProtocolTypeRouter({
"http": http_application,
'websocket': AllowedHostsOriginValidator(
AuthMiddlewareStack(
URLRouter(
websocket_urlpatterns # 指明路由文件是devops/routing.py
AuthMiddlewareStack(
URLRouter(
websocket_urlpatterns # 指明路由文件是devops/routing.py
)
)
)
),
),
})

View File

@@ -1,6 +1,8 @@
import functools
import os
from celery.signals import task_postrun
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
from django.conf import settings
@@ -15,7 +17,7 @@ else:
from celery import Celery
app = Celery(f"application")
app.config_from_object('django.conf:settings')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
platforms.C_FORCE_ROOT = True
@@ -38,3 +40,12 @@ def retry_base_task_error():
return wrapper
return wraps
@task_postrun.connect
def add_periodic_task_name(sender, task_id, task, args, kwargs, **extras):
periodic_task_name = kwargs.get('periodic_task_name')
if periodic_task_name:
from django_celery_results.models import TaskResult
# 更新 TaskResult 表中的 periodic_task_name 字段
TaskResult.objects.filter(task_id=task_id).update(periodic_task_name=periodic_task_name)

View File

@@ -2,6 +2,10 @@
# -*- coding: utf-8 -*-
from django.conf import settings
from django.db import connection
from django.core.cache import cache
from dvadmin.utils.validator import CustomValidationError
dispatch_db_type = getattr(settings, 'DISPATCH_DB_TYPE', 'memory') # redis
def is_tenants_mode():
@@ -68,6 +72,9 @@ def init_dictionary():
:return:
"""
try:
if dispatch_db_type == 'redis':
cache.set(f"init_dictionary", _get_all_dictionary())
return
if is_tenants_mode():
from django_tenants.utils import tenant_context, get_tenant_model
@@ -88,7 +95,9 @@ def init_system_config():
:return:
"""
try:
if dispatch_db_type == 'redis':
cache.set(f"init_system_config", _get_all_system_config())
return
if is_tenants_mode():
from django_tenants.utils import tenant_context, get_tenant_model
@@ -107,6 +116,9 @@ def refresh_dictionary():
刷新字典配置
:return:
"""
if dispatch_db_type == 'redis':
cache.set(f"init_dictionary", _get_all_dictionary())
return
if is_tenants_mode():
from django_tenants.utils import tenant_context, get_tenant_model
@@ -122,6 +134,9 @@ def refresh_system_config():
刷新系统配置
:return:
"""
if dispatch_db_type == 'redis':
cache.set(f"init_system_config", _get_all_system_config())
return
if is_tenants_mode():
from django_tenants.utils import tenant_context, get_tenant_model
@@ -141,6 +156,11 @@ def get_dictionary_config(schema_name=None):
:param schema_name: 对应字典配置的租户schema_name值
:return:
"""
if dispatch_db_type == 'redis':
init_dictionary_data = cache.get(f"init_dictionary")
if not init_dictionary_data:
refresh_dictionary()
return cache.get(f"init_dictionary") or {}
if not settings.DICTIONARY_CONFIG:
refresh_dictionary()
if is_tenants_mode():
@@ -157,6 +177,12 @@ def get_dictionary_values(key, schema_name=None):
:param schema_name: 对应字典配置的租户schema_name值
:return:
"""
if dispatch_db_type == 'redis':
dictionary_config = cache.get(f"init_dictionary")
if not dictionary_config:
refresh_dictionary()
dictionary_config = cache.get(f"init_dictionary")
return dictionary_config.get(key)
dictionary_config = get_dictionary_config(schema_name)
return dictionary_config.get(key)
@@ -169,8 +195,8 @@ def get_dictionary_label(key, name, schema_name=None):
:param schema_name: 对应字典配置的租户schema_name值
:return:
"""
children = get_dictionary_values(key, schema_name) or []
for ele in children:
res = get_dictionary_values(key, schema_name) or []
for ele in res.get('children'):
if ele.get("value") == str(name):
return ele.get("label")
return ""
@@ -187,6 +213,11 @@ def get_system_config(schema_name=None):
:param schema_name: 对应字典配置的租户schema_name值
:return:
"""
if dispatch_db_type == 'redis':
init_dictionary_data = cache.get(f"init_system_config")
if not init_dictionary_data:
refresh_system_config()
return cache.get(f"init_system_config") or {}
if not settings.SYSTEM_CONFIG:
refresh_system_config()
if is_tenants_mode():
@@ -203,10 +234,32 @@ def get_system_config_values(key, schema_name=None):
:param schema_name: 对应系统配置的租户schema_name值
:return:
"""
if dispatch_db_type == 'redis':
system_config = cache.get(f"init_system_config")
if not system_config:
refresh_system_config()
system_config = cache.get(f"init_system_config")
return system_config.get(key)
system_config = get_system_config(schema_name)
return system_config.get(key)
def get_system_config_values_to_dict(key, schema_name=None):
"""
获取系统配置数据并转换为字典 **仅限于数组类型系统配置
:param key: 对应系统配置的key值(字典编号)
:param schema_name: 对应系统配置的租户schema_name值
:return:
"""
values_dict = {}
config_values = get_system_config_values(key, schema_name)
if not isinstance(config_values, list):
raise CustomValidationError("该方式仅限于数组类型系统配置")
for ele in get_system_config_values(key, schema_name):
values_dict[ele.get('key')] = ele.get('value')
return values_dict
def get_system_config_label(key, name, schema_name=None):
"""
获取获取系统配置label值

View File

@@ -399,12 +399,16 @@ DICTIONARY_CONFIG = {}
# ================================================= #
# 租户共享app
TENANT_SHARED_APPS = []
# 普通租户独有app
TENANT_EXCLUSIVE_APPS = []
# 插件 urlpatterns
PLUGINS_URL_PATTERNS = []
# 所有模式有的
SHARED_APPS = []
# ********** 一键导入插件配置开始 **********
# 例如:
# from dvadmin_upgrade_center.settings import * # 升级中心
# from dvadmin_celery.settings import * # celery 异步任务
from dvadmin3_celery.settings import * # celery 异步任务
# from dvadmin_third.settings import * # 第三方用户管理
# from dvadmin_ak_sk.settings import * # 秘钥管理管理
# from dvadmin_tenants.settings import * # 租户管理

View File

@@ -0,0 +1,33 @@
# views.py
import time
import jwt
from django.http import StreamingHttpResponse
from application import settings
from dvadmin.system.models import MessageCenterTargetUser
from django.core.cache import cache
def event_stream(user_id):
last_sent_time = 0
while True:
# 从 Redis 中获取最后数据库变更时间
last_db_change_time = cache.get('last_db_change_time', 0)
# 只有当数据库发生变化时才检查总数
if last_db_change_time and last_db_change_time > last_sent_time:
count = MessageCenterTargetUser.objects.filter(users=user_id, is_read=False).count()
yield f"data: {count}\n\n"
last_sent_time = time.time()
time.sleep(1)
def sse_view(request):
token = request.GET.get('token')
decoded = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256'])
user_id = decoded.get('user_id')
response = StreamingHttpResponse(event_stream(user_id), content_type='text/event-stream')
response['Cache-Control'] = 'no-cache'
return response

View File

@@ -15,6 +15,7 @@ Including another URLconf
"""
from django.conf.urls.static import static
from django.urls import path, include, re_path
from django.views.static import serve
from drf_yasg import openapi
from drf_yasg.views import get_schema_view
from rest_framework import permissions
@@ -24,6 +25,7 @@ from rest_framework_simplejwt.views import (
from application import dispatch
from application import settings
from application.sse_views import sse_view
from dvadmin.system.views.dictionary import InitDictionaryViewSet
from dvadmin.system.views.login import (
LoginView,
@@ -40,6 +42,7 @@ dispatch.init_system_config()
dispatch.init_dictionary()
# =========== 初始化系统配置 =================
permission_classes = [permissions.AllowAny, ] if settings.DEBUG else [permissions.IsAuthenticated, ]
schema_view = get_schema_view(
openapi.Info(
title="Snippets API",
@@ -50,9 +53,36 @@ schema_view = get_schema_view(
license=openapi.License(name="BSD License"),
),
public=True,
permission_classes=(permissions.AllowAny,),
permission_classes=permission_classes,
generator_class=CustomOpenAPISchemaGenerator,
)
# 前端页面映射
from django.http import Http404, HttpResponse
from django.shortcuts import render
import mimetypes
import os
def web_view(request):
return render(request, 'web/index.html')
def serve_web_files(request, filename):
# 设定文件路径
filepath = os.path.join(settings.BASE_DIR, 'templates', 'web', filename)
# 检查文件是否存在
if not os.path.exists(filepath):
raise Http404("File does not exist")
# 根据文件扩展名,确定 MIME 类型
mime_type, _ = mimetypes.guess_type(filepath)
# 打开文件并读取内容
with open(filepath, 'rb') as f:
response = HttpResponse(f.read(), content_type=mime_type)
return response
urlpatterns = (
[
@@ -85,6 +115,11 @@ urlpatterns = (
# 仅用于开发,上线需关闭
path("api/token/", LoginTokenView.as_view()),
# 前端页面映射
path('web/', web_view, name='web_view'),
path('web/<path:filename>', serve_web_files, name='serve_web_files'),
# sse
path('sse/', sse_view, name='sse'),
]
+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+ static(settings.STATIC_URL, document_root=settings.STATIC_URL)

View File

@@ -180,4 +180,4 @@ def create_message_push(title: str, content: str, target_type: int = 0, target_u
"type": "push.message",
"json": {**message, 'unread': unread_count}
}
)
)

View File

@@ -4,4 +4,4 @@ from application.websocketConfig import MegCenter
websocket_urlpatterns = [
path('ws/<str:service_uid>/', MegCenter.as_asgi()), # consumers.DvadminWebSocket 是该路由的消费者
]
]

View File

@@ -21,13 +21,15 @@ DATABASE_PORT = 3306
# # 数据库用户名
DATABASE_USER = "root"
# # 数据库密码
DATABASE_PASSWORD = "DVADMIN3"
DATABASE_PASSWORD = 'DVADMIN3'
# 表前缀
TABLE_PREFIX = "dvadmin_"
# ================================================= #
# ******** redis配置无redis 可不进行配置 ******** #
# ================================================= #
REDIS_DB = 1
CELERY_BROKER_DB = 3
REDIS_PASSWORD = 'DVADMIN3'
REDIS_HOST = '127.0.0.1'
REDIS_URL = f'redis://:{REDIS_PASSWORD or ""}@{REDIS_HOST}:6379'

View File

@@ -2,7 +2,7 @@
import os
exclude = ["venv"] # 需要排除的文件目录
exclude = ["venv", ".venv"] # 需要排除的文件目录
for root, dirs, files in os.walk('.'):
dirs[:] = list(set(dirs) - set(exclude))
if 'migrations' in dirs:

View File

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

View File

@@ -4,3 +4,7 @@ from django.apps import AppConfig
class SystemConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'dvadmin.system'
def ready(self):
# 注册信号
import dvadmin.system.signals # 确保路径正确

View File

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

View File

@@ -546,5 +546,50 @@
"children": []
}
]
},
{
"label": "文件存储引擎",
"value": "file_engine",
"type": 0,
"color": null,
"is_value": false,
"status": true,
"sort": 9,
"remark": null,
"children": [
{
"label": "本地",
"value": "local",
"type": 0,
"color": "primary",
"is_value": true,
"status": true,
"sort": 1,
"remark": null,
"children": []
},
{
"label": "阿里云oss",
"value": "oss",
"type": 0,
"color": "success",
"is_value": true,
"status": true,
"sort": 2,
"remark": null,
"children": []
},
{
"label": "腾讯cos",
"value": "cos",
"type": 0,
"color": "warning",
"is_value": true,
"status": true,
"sort": 3,
"remark": null,
"children": []
}
]
}
]

View File

@@ -11,332 +11,11 @@
"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": 1,
"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:SearchAll",
"api": "/api/system/menu/get_all_menu/",
"method": 0
},
{
"name": "路由",
"value": "menu:router",
"api": "/api/system/menu/web_router/",
"method": 0
},
{
"name": "查询按钮权限",
"value": "btn:Search",
"api": "/api/system/menu_button/",
"method": 0
},
{
"name": "查询列权限",
"value": "column:Search",
"api": "/api/system/column/",
"method": 0
},
{
"name": "新增",
"value": "menu:Create",
"api": "/api/system/menu/",
"method": 1
},
{
"name": "上移",
"value": "menu:MoveUp",
"api": "/api/system/menu/mode_up/",
"method": 1
},
{
"name": "下移",
"value": "menu:MoveDown",
"api": "/api/system/menu/mode_down/",
"method": 1
},
{
"name": "新增按钮权限",
"value": "btn:Create",
"api": "/api/system/menu_button/",
"method": 1
},
{
"name": "新增列权限",
"value": "column:Create",
"api": "/api/system/column/",
"method": 1
},
{
"name": "自动匹配列权限",
"value": "column:Match",
"api": "/api/system/column/auto_match_fields/",
"method": 1
},
{
"name": "编辑",
"value": "menu:Update",
"api": "/api/system/menu/{id}/",
"method": 2
},
{
"name": "修改按钮权限",
"value": "btn:Update",
"api": "/api/system/menu_button/{id}/",
"method": 2
},
{
"name": "编辑列权限",
"value": "column:Update",
"api": "/api/system/column/{id}/",
"method": 2
},
{
"name": "删除",
"value": "menu:Delete",
"api": "/api/system/menu/{id}/",
"method": 3
},
{
"name": "删除按钮权限",
"value": "btn:Delete",
"api": "/api/system/menu_button/{id}/",
"method": 3
},
{
"name": "删除列权限",
"value": "column:Delete",
"api": "/api/system/column/{id}/",
"method": 3
}
],
"menu_field": []
},
{
"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": 1,
"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:SearchAll",
"api": "/api/system/dept/all_dept/",
"method": 0
},
{
"name": "懒加载查询所有",
"value": "dept:LazySearchAll",
"api": "/api/system/dept/dept_lazy_tree/",
"method": 0
},
{
"name": "头信息",
"value": "dept:HeaderInfo",
"api": "/api/system/dept/dept_info/",
"method": 0
},
{
"name": "新增",
"value": "dept:Create",
"api": "/api/system/dept/",
"method": 1
},
{
"name": "上移",
"value": "dept:MoveUp",
"api": "/api/system/dept/mode_up/",
"method": 1
},
{
"name": "下移",
"value": "dept:MoveDown",
"api": "/api/system/dept/mode_down/",
"method": 1
},
{
"name": "编辑",
"value": "dept:Update",
"api": "/api/system/dept/{id}/",
"method": 2
},
{
"name": "删除",
"value": "dept:Delete",
"api": "/api/system/dept/{id}/",
"method": 3
}
],
"menu_field": []
},
{
"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": 1,
"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:Permission",
"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
}
],
"menu_field": [
{
"field_name": "create_datetime",
"title": "创建时间",
"model": "Role"
},
{
"field_name": "creator",
"title": "创建人",
"model": "Role"
},
{
"field_name": "dept_belong_id",
"title": "数据归属部门",
"model": "Role"
},
{
"field_name": "description",
"title": "描述",
"model": "Role"
},
{
"field_name": "id",
"title": "Id",
"model": "Role"
},
{
"field_name": "key",
"title": "权限字符",
"model": "Role"
},
{
"field_name": "modifier",
"title": "修改人",
"model": "Role"
},
{
"field_name": "name",
"title": "角色名称",
"model": "Role"
},
{
"field_name": "sort",
"title": "角色顺序",
"model": "Role"
},
{
"field_name": "status",
"title": "角色状态",
"model": "Role"
},
{
"field_name": "update_datetime",
"title": "修改时间",
"model": "Role"
}
]
},
{
"name": "用户管理",
"icon": "iconfont icon-icon-",
"sort": 6,
"sort": 1,
"is_link": false,
"is_catalog": false,
"web_path": "/user",
@@ -345,7 +24,6 @@
"status": true,
"cache": false,
"visible": true,
"parent": 1,
"children": [],
"menu_button": [
{
@@ -354,18 +32,24 @@
"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:Update",
"api": "/api/system/user/{id}/",
"method": 2
},
{
"name": "删除",
"value": "user:Delete",
"api": "/api/system/user/{id}/",
"method": 3
},
{
"name": "导出",
"value": "user:Export",
@@ -379,10 +63,16 @@
"method": 1
},
{
"name": "编辑",
"value": "user:Update",
"api": "/api/system/user/{id}/",
"method": 2
"name": "获取导入模板",
"value": "user:ImportTemplate",
"api": "/api/system/user/import/",
"method": 0
},
{
"name": "批量更新模板",
"value": "user:BatchUpdateTemplate",
"api": "/api/system/user/update_template/",
"method": 0
},
{
"name": "重设密码",
@@ -392,15 +82,9 @@
},
{
"name": "重置密码",
"value": "user:DefaultPassword",
"value": "user:ResetDefaultPassword",
"api": "/api/system/user/{id}/reset_to_default_password/",
"method": 2
},
{
"name": "删除",
"value": "user:Delete",
"api": "/api/system/user/{id}/",
"method": 3
}
],
"menu_field": [
@@ -481,6 +165,359 @@
}
]
},
{
"name": "菜单管理",
"icon": "iconfont icon-caidan",
"sort": 2,
"is_link": false,
"is_catalog": false,
"web_path": "/menu",
"component": "system/menu/index",
"component_name": "menu",
"status": true,
"cache": false,
"visible": true,
"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": "查询所有",
"value": "menu:SearchAll",
"api": "/api/system/menu/get_all_menu/",
"method": 0
},
{
"name": "路由",
"value": "menu:router",
"api": "/api/system/menu/web_router/",
"method": 0
},
{
"name": "查询按钮",
"value": "menu:SearchButton",
"api": "/api/system/menu_button/",
"method": 0
},
{
"name": "新增按钮",
"value": "menu:CreateButton",
"api": "/api/system/menu_button/",
"method": 1
},
{
"name": "编辑按钮",
"value": "menu:UpdateButton",
"api": "/api/system/menu_button/{id}/",
"method": 2
},
{
"name": "删除按钮",
"value": "menu:DeleteButton",
"api": "/api/system/menu_button/{id}/",
"method": 3
},
{
"name": "上移",
"value": "menu:MoveUp",
"api": "/api/system/menu/mode_up/",
"method": 1
},
{
"name": "下移",
"value": "menu:MoveDown",
"api": "/api/system/menu/mode_down/",
"method": 1
},
{
"name": "查询列权限",
"value": "column:Search",
"api": "/api/system/column/",
"method": 0
},
{
"name": "新增列权限",
"value": "column:Create",
"api": "/api/system/column/",
"method": 1
},
{
"name": "编辑列权限",
"value": "column:Update",
"api": "/api/system/column/{id}/",
"method": 2
},
{
"name": "删除列权限",
"value": "column:Delete",
"api": "/api/system/column/{id}/",
"method": 3
},
{
"name": "自动匹配列权限",
"value": "column:Match",
"api": "/api/system/column/auto_match_fields/",
"method": 1
}
],
"menu_field": []
},
{
"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,
"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:SearchAll",
"api": "/api/system/dept/all_dept/",
"method": 0
},
{
"name": "部门顶部信息",
"value": "dept:HeaderInfo",
"api": "/api/system/dept/dept_info/",
"method": 0
},
{
"name": "新增",
"value": "dept:Create",
"api": "/api/system/dept/",
"method": 1
},
{
"name": "上移",
"value": "dept:MoveUp",
"api": "/api/system/dept/mode_up/",
"method": 1
},
{
"name": "下移",
"value": "dept:MoveDown",
"api": "/api/system/dept/mode_down/",
"method": 1
},
{
"name": "编辑",
"value": "dept:Update",
"api": "/api/system/dept/{id}/",
"method": 2
},
{
"name": "删除",
"value": "dept:Delete",
"api": "/api/system/dept/{id}/",
"method": 3
}
],
"menu_field": []
},
{
"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,
"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:Delete",
"api": "/api/system/role/{id}/",
"method": 3
},
{
"name": "获取所有可授权数据范围的部门",
"value": "role:AllDataRangeDept",
"api": "/api/system/role_menu_button_permision/role_to_dept_all/",
"method": 0
},
{
"name": "获取所有可授权菜单",
"value": "role:AllCanMenu",
"api": "/api/system/role_menu_button_permision/get_role_menu/",
"method": 0
},
{
"name": "获取所有已授权用户",
"value": "role:AllAuthorizedUser",
"api": "/api/system/role/get_role_users/",
"method": 0
},
{
"name": "获取菜单所有可授权按钮",
"value": "role:AllMenuButton",
"api": "/api/system/role_menu_button_permision/get_role_menu_btn_field/",
"method": 0
},
{
"name": "授权菜单",
"value": "role:SetMenu",
"api": "/api/system/role_menu_button_permision/set_role_menu/",
"method": 2
},
{
"name": "授权菜单按钮",
"value": "role:SetMenuButton",
"api": "/api/system/role_menu_button_permision/set_role_menu_btn/",
"method": 2
},
{
"name": "授权数据范围",
"value": "role:SetDataRange",
"api": "/api/system/role_menu_button_permision/set_role_menu_btn_data_range/",
"method": 2
},
{
"name": "获取所有用户",
"value": "role:AllUser",
"api": "/api/system/user/",
"method": 0
},
{
"name": "授权用户予角色",
"value": "role:SetUserRole",
"api": "/api/system/role/{id}/set_role_users/",
"method": 2
}
],
"menu_field": [
{
"field_name": "create_datetime",
"title": "创建时间",
"model": "Role"
},
{
"field_name": "creator",
"title": "创建人",
"model": "Role"
},
{
"field_name": "dept_belong_id",
"title": "数据归属部门",
"model": "Role"
},
{
"field_name": "description",
"title": "描述",
"model": "Role"
},
{
"field_name": "id",
"title": "Id",
"model": "Role"
},
{
"field_name": "key",
"title": "权限字符",
"model": "Role"
},
{
"field_name": "modifier",
"title": "修改人",
"model": "Role"
},
{
"field_name": "name",
"title": "角色名称",
"model": "Role"
},
{
"field_name": "sort",
"title": "角色顺序",
"model": "Role"
},
{
"field_name": "status",
"title": "角色状态",
"model": "Role"
},
{
"field_name": "update_datetime",
"title": "修改时间",
"model": "Role"
}
]
},
{
"name": "消息中心",
"icon": "iconfont icon-xiaoxizhongxin",
@@ -678,6 +715,29 @@
"model": "ApiWhiteList"
}
]
},
{
"name": "下载中心",
"icon": "ele-Download",
"sort": 9,
"is_link": false,
"is_catalog": false,
"web_path": "/downloadCenter",
"component": "system/downloadCenter/index",
"component_name": "downloadCenter",
"status": true,
"cache": false,
"visible": true,
"parent": 277,
"children": [],
"menu_button": [
{
"name": "查询",
"value": "downloadCenter:Search",
"api": "/api/system/download_center/"
}
],
"menu_field": []
}
],
"menu_button": [],

View File

@@ -1,12 +1,7 @@
[
{
"role_key": "admin",
"menu_button_value": "menu:Search",
"data_range": 0
},
{
"role_key": "public",
"menu_button_value":"menu:Search",
"role__key": "admin",
"menu_button__value": "menu:Search",
"data_range": 0
}
]

View File

@@ -1,10 +1,12 @@
[
{
"role_key": "admin",
"menu_component_name": "menu"
"role__key": "admin",
"menu__web_path": "/system",
"menu__component_name": ""
},
{
"role_key": "public",
"menu_component_name": "menu"
"role__key": "admin",
"menu__web_path": "/menu",
"menu__component_name": "menu"
}
]

View File

@@ -12,6 +12,34 @@
"placeholder": null,
"setting": null,
"children": [
{
"parent": 10,
"title": "网页标题",
"key": "web_title",
"value": "DVAdmin",
"sort": 1,
"status": true,
"data_options": null,
"form_item_type": 0,
"rule": [],
"placeholder": "请输入网站标题",
"setting": null,
"children": []
},
{
"parent": 10,
"title": "网站小图标",
"key": "web_favicon",
"value": "",
"sort": 1,
"status": true,
"data_options": null,
"form_item_type": 0,
"rule": [],
"placeholder": "请输入网站小图标",
"setting": null,
"children": []
},
{
"parent": 10,
"title": "开启验证码",
@@ -207,5 +235,252 @@
"children": []
}
]
}
},
{
"title": "文件存储配置",
"key": "file_storage",
"value": null,
"sort": 0,
"status": true,
"data_options": null,
"form_item_type": 0,
"rule": null,
"placeholder": null,
"setting": null,
"children": [
{
"title": "存储引擎",
"key": "file_engine",
"value": "local",
"sort": 1,
"status": true,
"data_options": null,
"form_item_type": 4,
"rule": [
{
"required": false,
"message": "必填项不能为空"
}
],
"placeholder": "请选择存储引擎",
"setting": "file_engine",
"children": []
},
{
"title": "文件是否备份",
"key": "file_backup",
"value": false,
"sort": 2,
"status": true,
"data_options": null,
"form_item_type": 9,
"rule": [
{
"required": false,
"message": "必填项不能为空"
}
],
"placeholder": "启用云存储时,文件是否备份到本地",
"setting": null,
"children": []
},
{
"title": "阿里云-AccessKey",
"key": "aliyun_access_key",
"value": null,
"sort": 3,
"status": false,
"data_options": null,
"form_item_type": 0,
"rule": [
{
"required": false,
"message": "必填项不能为空"
}
],
"placeholder": "请输入AccessKey",
"setting": null,
"children": []
},
{
"title": "阿里云-Secret",
"key": "aliyun_access_secret",
"value": null,
"sort": 4,
"status": false,
"data_options": null,
"form_item_type": 0,
"rule": [
{
"required": false,
"message": "必填项不能为空"
}
],
"placeholder": "请输入Secret",
"setting": null,
"children": []
},
{
"title": "阿里云-Endpoint",
"key": "aliyun_endpoint",
"value": null,
"sort": 5,
"status": false,
"data_options": null,
"form_item_type": 0,
"rule": [
{
"required": false,
"message": "必填项不能为空"
}
],
"placeholder": "请输入Endpoint",
"setting": null,
"children": []
},
{
"title": "阿里云-上传路径",
"key": "aliyun_path",
"value": "/media/",
"sort": 5,
"status": false,
"data_options": null,
"form_item_type": 0,
"rule": [
{
"required": false,
"message": "必填项不能为空"
}
],
"placeholder": "请输入上传路径",
"setting": null,
"children": []
},
{
"title": "阿里云-Bucket",
"key": "aliyun_bucket",
"value": null,
"sort": 7,
"status": false,
"data_options": null,
"form_item_type": 0,
"rule": [
{
"required": false,
"message": "必填项不能为空"
}
],
"placeholder": "请输入Bucket",
"setting": null,
"children": []
},{
"title": "阿里云-cdn地址",
"key": "aliyun_cdn_url",
"value": null,
"sort": 7,
"status": false,
"data_options": null,
"form_item_type": 0,
"rule": [
{
"required": false,
"message": "必填项不能为空"
}
],
"placeholder": "请输入cdn地址",
"setting": null,
"children": []
},
{
"title": "腾讯云-SecretId",
"key": "tencent_secret_id",
"value": null,
"sort": 8,
"status": false,
"data_options": null,
"form_item_type": 0,
"rule": [
{
"required": false,
"message": "必填项不能为空"
}
],
"placeholder": "请输入SecretId",
"setting": null,
"children": []
},
{
"title": "腾讯云-SecretKey",
"key": "tencent_secret_key",
"value": null,
"sort": 9,
"status": false,
"data_options": null,
"form_item_type": 0,
"rule": [
{
"required": false,
"message": "必填项不能为空"
}
],
"placeholder": "请输入SecretKey",
"setting": null,
"children": []
},
{
"title": "腾讯云-Region",
"key": "tencent_region",
"value": null,
"sort": 10,
"status": false,
"data_options": null,
"form_item_type": 0,
"rule": [
{
"required": false,
"message": "必填项不能为空"
}
],
"placeholder": "请输入Region",
"setting": null,
"children": []
},
{
"title": "腾讯云-Bucket",
"key": "tencent_bucket",
"value": null,
"sort": 11,
"status": false,
"data_options": null,
"form_item_type": 0,
"rule": [
{
"required": false,
"message": "必填项不能为空"
}
],
"placeholder": "请输入Bucket",
"setting": null,
"children": []
},
{
"title": "腾讯云-上传路径",
"key": "tencent_path",
"value": "/media/",
"sort": 12,
"status": false,
"data_options": null,
"form_item_type": 0,
"rule": [
{
"required": false,
"message": "必填项不能为空"
}
],
"placeholder": "请输入上传路径",
"setting": null,
"children": []
}
]
}
]

View File

@@ -45,13 +45,13 @@ class Initialize(CoreInitialize):
"""
初始化角色菜单信息
"""
self.init_base(RoleMenuInitSerializer, unique_fields=['role', 'menu'])
self.init_base(RoleMenuInitSerializer, unique_fields=['role__key', 'menu__web_path', 'menu__component_name'])
def init_role_menu_button(self):
"""
初始化角色菜单按钮信息
"""
self.init_base(RoleMenuButtonInitSerializer, unique_fields=['role', 'menu_button'])
self.init_base(RoleMenuButtonInitSerializer, unique_fields=['role__key', 'menu_button__value'])
def init_api_white_list(self):
"""

View File

@@ -10,7 +10,7 @@ django.setup()
from django.core.management.base import BaseCommand
from application.settings import BASE_DIR
from dvadmin.system.models import Menu, Users, Dept, Role, ApiWhiteList, Dictionary, SystemConfig
from dvadmin.system.models import Menu, Users, Dept, Role, ApiWhiteList, Dictionary, SystemConfig, RoleMenuButtonPermission, RoleMenuPermission
from dvadmin.system.fixtures.initSerializer import UsersInitSerializer, DeptInitSerializer, RoleInitSerializer, \
MenuInitSerializer, ApiWhiteListInitSerializer, DictionaryInitSerializer, SystemConfigInitSerializer, \
RoleMenuInitSerializer, RoleMenuButtonInitSerializer
@@ -29,7 +29,7 @@ class Command(BaseCommand):
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:
with open(os.path.join(BASE_DIR, f'init_{query_set.model._meta.model_name}.json'), 'w',encoding='utf-8') as f:
json.dump(data, f, indent=4, ensure_ascii=False)
return
@@ -57,6 +57,12 @@ class Command(BaseCommand):
def generate_system_config(self):
self.serializer_data(SystemConfigInitSerializer, SystemConfig.objects.filter(parent_id__isnull=True))
def generate_role_menu(self):
self.serializer_data(RoleMenuInitSerializer, RoleMenuPermission.objects.all())
def generate_role_menu_button(self):
self.serializer_data(RoleMenuButtonInitSerializer, RoleMenuButtonPermission.objects.all())
def handle(self, *args, **options):
generate_name = options.get('generate_name')
generate_name_dict = {
@@ -67,6 +73,8 @@ class Command(BaseCommand):
"api_white_list": self.generate_api_white_list,
"dictionary": self.generate_dictionary,
"system_config": self.generate_system_config,
"role_menu": self.generate_role_menu,
"role_menu_button": self.generate_role_menu_button,
}
if not generate_name:
for ele in generate_name_dict.keys():

View File

@@ -22,6 +22,8 @@ class Command(BaseCommand):
parser.add_argument("-Y", nargs="*")
parser.add_argument("-n", nargs="*")
parser.add_argument("-N", nargs="*")
parser.add_argument("-app", nargs="*")
parser.add_argument("-A", nargs="*")
def handle(self, *args, **options):
reset = False
@@ -29,9 +31,10 @@ class Command(BaseCommand):
reset = True
if isinstance(options.get("n"), list) or isinstance(options.get("N"), list):
reset = False
assign_apps = options.get("app") or options.get("A") or []
for app in settings.INSTALLED_APPS:
if assign_apps and app not in assign_apps:
continue
try:
exec(
f"""

View File

@@ -1,5 +1,7 @@
import hashlib
import os
from time import time
from pathlib import PurePosixPath
from django.contrib.auth.models import AbstractUser, UserManager
from django.db import models
@@ -61,6 +63,8 @@ class Users(CoreModel, AbstractUser):
help_text="关联岗位")
role = models.ManyToManyField(to="Role", blank=True, verbose_name="关联角色", db_constraint=False,
help_text="关联角色")
current_role = models.ForeignKey(to=Role, null=True, blank=True, db_constraint=False, on_delete=models.SET_NULL,
verbose_name="当前登录角色", help_text="当前登录角色", related_name='current_role_set')
dept = models.ForeignKey(
to="Dept",
verbose_name="所属部门",
@@ -70,10 +74,26 @@ class Users(CoreModel, AbstractUser):
blank=True,
help_text="关联部门",
)
manage_dept = models.ManyToManyField(
to="Dept",
verbose_name="管理部门",
db_constraint=False,
blank=True,
help_text="管理部门",
related_name='manage_dept_set'
)
login_error_count = models.IntegerField(default=0, verbose_name="登录错误次数", help_text="登录错误次数")
pwd_change_count = models.IntegerField(default=0,blank=True, verbose_name="密码修改次数", help_text="密码修改次数")
objects = CustomUserManager()
def set_password(self, raw_password):
super().set_password(hashlib.md5(raw_password.encode(encoding="UTF-8")).hexdigest())
if raw_password:
super().set_password(hashlib.md5(raw_password.encode(encoding="UTF-8")).hexdigest())
def save(self, *args, **kwargs):
if self.name == "":
self.name = self.username
super().save(*args, **kwargs)
class Meta:
db_table = table_prefix + "system_users"
@@ -118,6 +138,27 @@ class Dept(CoreModel):
help_text="上级部门",
)
@classmethod
def _recursion(cls, instance, parent, result):
new_instance = getattr(instance, parent, None)
res = []
data = getattr(instance, result, None)
if data:
res.append(data)
if new_instance:
array = cls._recursion(new_instance, parent, result)
res += array
return res
@classmethod
def get_region_name(cls, obj):
"""
获取某个用户的递归所有部门名称
"""
dept_name_all = cls._recursion(obj, "parent", "name")
dept_name_all.reverse()
return "/".join(dept_name_all)
@classmethod
def recursion_all_dept(cls, dept_id: int, dept_all_list=None, dept_list=None):
"""
@@ -137,6 +178,23 @@ class Dept(CoreModel):
cls.recursion_all_dept(ele.get("id"), dept_all_list, dept_list)
return list(set(dept_list))
@classmethod
def recursion_all_parent_dept(cls, dept_id: int, dept_list=None):
"""
递归获取部门的所有上级部门
:param dept_id: 需要获取的id
:param dept_list: 递归list
:return:
"""
if dept_list is None:
dept_list = [dept_id]
current_dept = Dept.objects.filter(id=dept_id).values('parent').first()
if current_dept and current_dept.get('parent'):
parent_id = current_dept.get('parent')
dept_list.append(parent_id)
cls.recursion_all_parent_dept(parent_id, dept_list)
return list(set(dept_list))
class Meta:
db_table = table_prefix + "system_dept"
verbose_name = "部门表"
@@ -404,6 +462,18 @@ class FileList(CoreModel):
mime_type = models.CharField(max_length=100, blank=True, verbose_name="Mime类型", help_text="Mime类型")
size = models.CharField(max_length=36, blank=True, verbose_name="文件大小", help_text="文件大小")
md5sum = models.CharField(max_length=36, blank=True, verbose_name="文件md5", help_text="文件md5")
UPLOAD_METHOD_CHOIDES = (
(0, '默认上传'),
(1, '文件选择器上传'),
)
upload_method = models.SmallIntegerField(default=0, blank=True, null=True, choices=UPLOAD_METHOD_CHOIDES, verbose_name='上传方式', help_text='上传方式')
FILE_TYPE_CHOIDES = (
(0, '图片'),
(1, '视频'),
(2, '音频'),
(3, '其他'),
)
file_type = models.SmallIntegerField(default=3, choices=FILE_TYPE_CHOIDES, blank=True, null=True, verbose_name='文件类型', help_text='文件类型')
def save(self, *args, **kwargs):
if not self.md5sum: # file is new
@@ -485,7 +555,7 @@ class SystemConfig(CoreModel):
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)
key = models.CharField(max_length=100, 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="启用状态")
@@ -594,3 +664,41 @@ class MessageCenterTargetUser(CoreModel):
db_table = table_prefix + "message_center_target_user"
verbose_name = "消息中心目标用户表"
verbose_name_plural = verbose_name
def media_file_name_downloadcenter(instance:'DownloadCenter', filename):
h = instance.md5sum
basename, ext = os.path.splitext(filename)
return PurePosixPath("files", "dlct", h[:1], h[1:2], basename + '-' + str(time()).replace('.', '') + ext.lower())
class DownloadCenter(CoreModel):
TASK_STATUS_CHOICES = [
(0, '任务已创建'),
(1, '任务进行中'),
(2, '任务完成'),
(3, '任务失败'),
]
task_name = models.CharField(max_length=255, verbose_name="任务名称", help_text="任务名称")
task_status = models.SmallIntegerField(default=0, choices=TASK_STATUS_CHOICES, verbose_name='是否可下载', help_text='是否可下载')
file_name = models.CharField(max_length=255, null=True, blank=True, verbose_name="文件名", help_text="文件名")
url = models.FileField(upload_to=media_file_name_downloadcenter, null=True, blank=True)
size = models.BigIntegerField(default=0, verbose_name="文件大小", help_text="文件大小")
md5sum = models.CharField(max_length=36, null=True, blank=True, verbose_name="文件md5", help_text="文件md5")
def save(self, *args, **kwargs):
if self.url:
if not self.md5sum: # file is new
md5 = hashlib.md5()
for chunk in self.url.chunks():
md5.update(chunk)
self.md5sum = md5.hexdigest()
if not self.size:
self.size = self.url.size
super(DownloadCenter, self).save(*args, **kwargs)
class Meta:
db_table = table_prefix + "download_center"
verbose_name = "下载中心"
verbose_name_plural = verbose_name
ordering = ("-create_datetime",)

View File

@@ -0,0 +1,27 @@
import time
from django.db.models.signals import post_save, post_delete
from django.dispatch import Signal, receiver
from django.core.cache import cache
from dvadmin.system.models import MessageCenterTargetUser
# 初始化信号
pre_init_complete = Signal()
detail_init_complete = Signal()
post_init_complete = Signal()
# 租户初始化信号
pre_tenants_init_complete = Signal()
detail_tenants_init_complete = Signal()
post_tenants_init_complete = Signal()
post_tenants_all_init_complete = Signal()
# 租户创建完成信号
tenants_create_complete = Signal()
# 全局变量用于标记最后修改时间
last_db_change_time = time.time()
@receiver(post_save, sender=MessageCenterTargetUser)
@receiver(post_delete, sender=MessageCenterTargetUser)
def update_last_change_time(sender, **kwargs):
cache.set('last_db_change_time', time.time(), timeout=None) # 设置永不超时的键值对

View File

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

View File

@@ -18,6 +18,7 @@ from dvadmin.system.views.role_menu_button_permission import RoleMenuButtonPermi
from dvadmin.system.views.system_config import SystemConfigViewSet
from dvadmin.system.views.user import UserViewSet
from dvadmin.system.views.menu_field import MenuFieldViewSet
from dvadmin.system.views.download_center import DownloadCenterViewSet
system_url = routers.SimpleRouter()
system_url.register(r'menu', MenuViewSet)
@@ -35,6 +36,8 @@ system_url.register(r'message_center', MessageCenterViewSet)
system_url.register(r'role_menu_button_permission', RoleMenuButtonPermissionViewSet)
system_url.register(r'role_menu_permission', RoleMenuPermissionViewSet)
system_url.register(r'column', MenuFieldViewSet)
system_url.register(r'login_log', LoginLogViewSet)
system_url.register(r'download_center', DownloadCenterViewSet)
urlpatterns = [
@@ -44,9 +47,9 @@ urlpatterns = [
path('system_config/get_association_table/', SystemConfigViewSet.as_view({'get': 'get_association_table'})),
path('system_config/get_table_data/<int:pk>/', SystemConfigViewSet.as_view({'get': 'get_table_data'})),
path('system_config/get_relation_info/', SystemConfigViewSet.as_view({'get': 'get_relation_info'})),
path('login_log/', LoginLogViewSet.as_view({'get': 'list'})),
path('login_log/<int:pk>/', LoginLogViewSet.as_view({'get': 'retrieve'})),
path('dept_lazy_tree/', DeptViewSet.as_view({'get': 'dept_lazy_tree'})),
# path('login_log/', LoginLogViewSet.as_view({'get': 'list'})),
# path('login_log/<int:pk>/', LoginLogViewSet.as_view({'get': 'retrieve'})),
# path('dept_lazy_tree/', DeptViewSet.as_view({'get': 'dept_lazy_tree'})),
path('clause/privacy.html', PrivacyView.as_view()),
path('clause/terms_service.html', TermsServiceView.as_view()),
]

View File

View File

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

View File

@@ -10,6 +10,7 @@ from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import Dept, RoleMenuButtonPermission, Users
from dvadmin.utils.filters import DataLevelPermissionsFilter
from dvadmin.utils.json_response import DetailResponse, SuccessResponse, ErrorResponse
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
@@ -124,33 +125,7 @@ class DeptViewSet(CustomModelViewSet):
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_all_dept(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=[])
@action(methods=["GET"], detail=False, permission_classes=[IsAuthenticated])
def all_dept(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
data = queryset.filter(status=True).order_by('sort').values('name', 'id', 'parent')

View File

@@ -0,0 +1,54 @@
from rest_framework import serializers
from django.conf import settings
from django_filters.rest_framework import FilterSet, CharFilter
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
from dvadmin.system.models import DownloadCenter
class DownloadCenterSerializer(CustomModelSerializer):
url = serializers.SerializerMethodField(read_only=True)
def get_url(self, instance):
if self.request.query_params.get('prefix'):
if settings.ENVIRONMENT in ['local']:
prefix = 'http://127.0.0.1:8000'
elif settings.ENVIRONMENT in ['test']:
prefix = 'http://{host}/api'.format(host=self.request.get_host())
else:
prefix = 'https://{host}/api'.format(host=self.request.get_host())
return (f'{prefix}/media/{str(instance.url)}')
return f'media/{str(instance.url)}'
class Meta:
model = DownloadCenter
fields = "__all__"
read_only_fields = ["id"]
class DownloadCenterFilterSet(FilterSet):
task_name = CharFilter(field_name='task_name', lookup_expr='icontains')
file_name = CharFilter(field_name='file_name', lookup_expr='icontains')
class Meta:
model = DownloadCenter
fields = ['task_status', 'task_name', 'file_name']
class DownloadCenterViewSet(CustomModelViewSet):
queryset = DownloadCenter.objects.all()
serializer_class = DownloadCenterSerializer
filter_class = DownloadCenterFilterSet
permission_classes = []
extra_filter_class = []
def get_queryset(self):
# 判断是否是 Swagger 文档生成阶段,防止报错
if getattr(self, 'swagger_fake_view', False):
return self.queryset.model.objects.none()
# 正常请求下的逻辑
if self.request.user.is_superuser:
return super().get_queryset()
return super().get_queryset().filter(creator=self.request.user)

View File

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

View File

@@ -4,11 +4,15 @@ from datetime import datetime, timedelta
from captcha.views import CaptchaStore, captcha_image
from django.contrib import auth
from django.contrib.auth import login
from django.contrib.auth.hashers import check_password, make_password
from django.db.models import Q
from django.shortcuts import redirect
from django.utils.translation import gettext_lazy as _
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework import serializers
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenObtainPairView
@@ -83,31 +87,50 @@ class LoginSerializer(TokenObtainPairSerializer):
else:
self.image_code and self.image_code.delete()
raise CustomValidationError("图片验证码错误")
user = Users.objects.get(username=attrs['username'])
try:
user = Users.objects.get(
Q(username=attrs['username']) | Q(email=attrs['username']) | Q(mobile=attrs['username']))
except Users.DoesNotExist:
raise CustomValidationError("您登录的账号不存在")
except Users.MultipleObjectsReturned:
raise CustomValidationError("您登录的账号存在多个,请联系管理员检查登录账号唯一性")
if not user.is_active:
raise CustomValidationError("账号被锁定")
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}
raise CustomValidationError("账号被锁定,联系管理员解锁")
try:
# 必须重置用户名为username,否则使用邮箱手机号登录会提示密码错误
attrs['username'] = user.username
data = super().validate(attrs)
data["username"] = self.user.username
data["name"] = self.user.name
data["userId"] = self.user.id
data["avatar"] = self.user.avatar
data['user_type'] = self.user.user_type
data['pwd_change_count'] = self.user.pwd_change_count
dept = getattr(self.user, 'dept', None)
if dept:
data['dept_info'] = {
'dept_id': dept.id,
'dept_name': dept.name,
}
role = getattr(self.user, 'role', None)
if role:
data['role_info'] = role.values('id', 'name', 'key')
request = self.context.get("request")
request.user = self.user
# 记录登录日志
save_login_log(request=request)
user.login_error_count = 0
user.save()
return {"code": 2000, "msg": "请求成功", "data": data}
except Exception as e:
user.login_error_count += 1
if user.login_error_count >= 5:
user.is_active = False
user.save()
raise CustomValidationError("账号已被锁定,联系管理员解锁")
user.save()
count = 5 - user.login_error_count
raise CustomValidationError(f"账号/密码错误;重试{count}次后将被锁定~")
class LoginView(TokenObtainPairView):

View File

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

View File

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

View File

@@ -10,12 +10,14 @@ 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.system.models import MenuButton, RoleMenuButtonPermission, Menu
from dvadmin.utils.json_response import DetailResponse, SuccessResponse
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
class MenuButtonSerializer(CustomModelSerializer):
"""
菜单按钮-序列化器
@@ -80,4 +82,27 @@ class MenuButtonViewSet(CustomModelViewSet):
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)
return DetailResponse(data=queryset)
@action(methods=['post'], detail=False, permission_classes=[IsAuthenticated])
def batch_create(self, request, *args, **kwargs):
"""
批量创建菜单“增删改查查”权限
创建的数据来源于菜单,需要规范创建菜单参数
value菜单的component_name:method
api:菜单的web_path增加'/api'前缀并根据method增加{id}
"""
menu_obj = Menu.objects.filter(id=request.data['menu']).first()
result_list = [
{'menu': menu_obj.id, 'name': '新增', 'value': f'{menu_obj.component_name}:Create', 'api': f'/api/{menu_obj.component_name}/', 'method': 1},
{'menu': menu_obj.id, 'name': '删除', 'value': f'{menu_obj.component_name}:Delete', 'api': f'/api/{menu_obj.component_name}/{{id}}/', 'method': 3},
{'menu': menu_obj.id, 'name': '编辑', 'value': f'{menu_obj.component_name}:Update', 'api': f'/api/{menu_obj.component_name}/{{id}}/', 'method': 2},
{'menu': menu_obj.id, 'name': '查询', 'value': f'{menu_obj.component_name}:Search', 'api': f'/api/{menu_obj.component_name}/', 'method': 0},
{'menu': menu_obj.id, 'name': '详情', 'value': f'{menu_obj.component_name}:Retrieve', 'api': f'/api/{menu_obj.component_name}/{{id}}/', 'method': 0},
{'menu': menu_obj.id, 'name': '复制', 'value': f'{menu_obj.component_name}:Copy', 'api': f'/api/{menu_obj.component_name}/', 'method': 1},
{'menu': menu_obj.id, 'name': '导入', 'value': f'{menu_obj.component_name}:Import', 'api': f'/api/{menu_obj.component_name}/import_data/', 'method': 1},
{'menu': menu_obj.id, 'name': '导出', 'value': f'{menu_obj.component_name}:Export', 'api': f'/api{menu_obj.component_name}/export_data/', 'method': 1},]
serializer = self.get_serializer(data=result_list, many=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return SuccessResponse(serializer.data, msg="批量创建成功")

View File

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

View File

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

View File

@@ -6,33 +6,31 @@
@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, FieldPermission, \
MenuField
from dvadmin.system.views.menu import MenuSerializer
from dvadmin.utils.json_response import DetailResponse, ErrorResponse
from dvadmin.system.models import RoleMenuButtonPermission, Menu, Dept, MenuButton, RoleMenuPermission, \
MenuField, FieldPermission
from dvadmin.utils.json_response import DetailResponse
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
class RoleMenuButtonPermissionSerializer(CustomModelSerializer):
"""
菜单按钮-序列化
角色-菜单-按钮-权限 查询序列化
"""
class Meta:
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)
@@ -43,107 +41,108 @@ class RoleMenuButtonPermissionCreateUpdateSerializer(CustomModelSerializer):
read_only_fields = ["id"]
class RoleButtonPermissionSerializer(CustomModelSerializer):
class RoleMenuSerializer(CustomModelSerializer):
"""
角色按钮权限
角色-菜单 序列化
"""
isCheck = serializers.SerializerMethodField()
data_range = serializers.SerializerMethodField()
def get_isCheck(self, instance):
params = self.request.query_params
data = self.request.data
return RoleMenuPermission.objects.filter(
menu_id=instance.id,
role_id=params.get('roleId', data.get('roleId')),
).exists()
class Meta:
model = Menu
fields = ["id", "name", "parent", "is_catalog", "isCheck"]
class RoleMenuButtonSerializer(CustomModelSerializer):
"""
角色-菜单-按钮 序列化
"""
isCheck = serializers.SerializerMethodField()
data_range = serializers.SerializerMethodField()
role_menu_btn_perm_id = serializers.SerializerMethodField()
dept = serializers.SerializerMethodField()
def get_isCheck(self, instance):
params = self.request.query_params
data = self.request.data
return RoleMenuButtonPermission.objects.filter(
menu_button__id=instance['id'],
role__id=params.get('role'),
menu_button_id=instance.id,
role_id=params.get('roleId', data.get('roleId')),
).exists()
def get_data_range(self, instance):
params = self.request.query_params
obj = RoleMenuButtonPermission.objects.filter(
menu_button__id=instance['id'],
role__id=params.get('role'),
).first()
obj = self.get_role_menu_btn_prem(instance)
if obj is None:
return None
return obj.data_range
def get_role_menu_btn_perm_id(self, instance):
obj = self.get_role_menu_btn_prem(instance)
if obj is None:
return None
return obj.id
def get_dept(self, instance):
obj = self.get_role_menu_btn_prem(instance)
if obj is None:
return None
return obj.dept.all().values_list('id', flat=True)
def get_role_menu_btn_prem(self, instance):
params = self.request.query_params
data = self.request.data
obj = RoleMenuButtonPermission.objects.filter(
menu_button_id=instance.id,
role_id=params.get('roleId', data.get('roleId')),
).first()
return obj
class Meta:
model = MenuButton
fields = ['id','name','value','isCheck','data_range']
fields = ['id', 'menu', 'name', 'isCheck', 'data_range', 'role_menu_btn_perm_id', 'dept']
class RoleFieldPermissionSerializer(CustomModelSerializer):
class Meta:
model = FieldPermission
fields = "__all__"
class RoleMenuFieldSerializer(CustomModelSerializer):
"""
角色-菜单-字段 序列化
"""
is_query = serializers.SerializerMethodField()
is_create = serializers.SerializerMethodField()
is_update = serializers.SerializerMethodField()
def get_is_query(self, instance):
params = self.request.query_params
queryset = instance.menu_field.filter(role=params.get('role')).first()
queryset = instance.menu_field.filter(role=params.get('roleId')).first()
if queryset:
return queryset.is_query
return False
def get_is_create(self, instance):
params = self.request.query_params
queryset = instance.menu_field.filter(role=params.get('role')).first()
queryset = instance.menu_field.filter(role=params.get('roleId')).first()
if queryset:
return queryset.is_create
return False
def get_is_update(self, instance):
params = self.request.query_params
queryset = instance.menu_field.filter(role=params.get('role')).first()
queryset = instance.menu_field.filter(role=params.get('roleId')).first()
if queryset:
return queryset.is_update
return False
class Meta:
model = MenuField
fields = ['id','field_name','title','is_query','is_create','is_update']
fields = ['id', 'field_name', 'title', 'is_query', 'is_create', 'is_update']
class RoleMenuPermissionSerializer(CustomModelSerializer):
"""
菜单和按钮权限
"""
name = serializers.SerializerMethodField()
isCheck = serializers.SerializerMethodField()
btns = serializers.SerializerMethodField()
columns = serializers.SerializerMethodField()
def get_name(self, instance):
parent_list = Menu.get_all_parent(instance['id'])
names = [d["name"] for d in parent_list]
return "/".join(names)
def get_isCheck(self, instance):
params = self.request.query_params
return RoleMenuPermission.objects.filter(
menu__id=instance['id'],
role__id=params.get('role'),
).exists()
def get_btns(self, instance):
btn_list = MenuButton.objects.filter(menu__id=instance['id']).values('id', 'name', 'value')
serializer = RoleButtonPermissionSerializer(btn_list,many=True,request=self.request)
return serializer.data
def get_columns(self, instance):
col_list = MenuField.objects.filter(menu=instance['id'])
serializer = RoleMenuFieldSerializer(col_list,many=True,request=self.request)
return serializer.data
class Meta:
model = Menu
fields = ['id','name','isCheck','btns','columns']
class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
"""
菜单按钮接口
@@ -160,199 +159,110 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
extra_filter_class = []
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
def get_role_premission(self, request):
def get_role_menu(self, request):
"""
角色授权获取:
:param request: role
:return: menu,btns,columns
获取 角色-菜单
:param request:
:return:
"""
menu_queryset = Menu.objects.all()
serializer = RoleMenuSerializer(menu_queryset, many=True, request=request)
return DetailResponse(data=serializer.data)
@action(methods=['PUT'], detail=False, permission_classes=[IsAuthenticated])
def set_role_menu(self, request):
"""
设置 角色-菜单
:param request:
:return:
"""
data = request.data
roleId = data.get('roleId')
menuId = data.get('menuId')
isCheck = data.get('isCheck')
if isCheck:
# 添加权限:创建关联记录
instance = RoleMenuPermission.objects.create(role_id=roleId, menu_id=menuId)
else:
# 删除权限:移除关联记录
RoleMenuPermission.objects.filter(role_id=roleId, menu_id=menuId).delete()
menu_instance = Menu.objects.get(id=menuId)
serializer = RoleMenuSerializer(menu_instance, request=request)
return DetailResponse(data=serializer.data, msg="更新成功")
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
def get_role_menu_btn_field(self, request):
"""
获取 角色-菜单-按钮-列字段
:param request:
:return:
"""
params = request.query_params
role = params.get('role',None)
if role is None:
return ErrorResponse(msg="未获取到角色信息")
is_superuser = request.user.is_superuser
# if is_superuser:
# queryset = Menu.objects.filter(status=1,is_catalog=False).values('name', 'id').all()
# else:
# role_id = request.user.role.values_list('id', flat=True)
# menu_list = RoleMenuPermission.objects.filter(role__in=role_id).values_list('id',flat=True)
# queryset = Menu.objects.filter(status=1, is_catalog=False,id__in=menu_list).values('name', 'id').all()
# serializer = RoleMenuPermissionSerializer(queryset,many=True,request=request)
# data = serializer.data
# return DetailResponse(data=data)
data = []
if is_superuser:
queryset = Menu.objects.filter(status=1,is_catalog=False).values('name', 'id').all()
else:
role_id = request.user.role.values_list('id', flat=True)
menu_list = RoleMenuPermission.objects.filter(role__in=role_id).values_list('id',flat=True)
queryset = Menu.objects.filter(status=1, is_catalog=False,id__in=menu_list).values('name', 'id')
for item in queryset:
parent_list = Menu.get_all_parent(item['id'])
names = [d["name"] for d in parent_list]
completeName = "/".join(names)
isCheck = RoleMenuPermission.objects.filter(
menu__id=item['id'],
role__id=role,
).exists()
mbCheck = RoleMenuButtonPermission.objects.filter(
menu_button=OuterRef("pk"),
role__id=role,
)
btns = MenuButton.objects.filter(
menu__id=item['id'],
).annotate(isCheck=Exists(mbCheck)).values('id', 'name', 'value', 'isCheck',
data_range=F('menu_button_permission__data_range'))
dicts = {
'name': completeName,
'id': item['id'],
'isCheck': isCheck,
'btns': btns,
}
data.append(dicts)
return DetailResponse(data=data)
menuId = params.get('menuId', None)
menu_btn_queryset = MenuButton.objects.filter(menu_id=menuId)
menu_btn_serializer = RoleMenuButtonSerializer(menu_btn_queryset, many=True, request=request)
menu_field_queryset = MenuField.objects.filter(menu_id=menuId)
menu_field_serializer = RoleMenuFieldSerializer(menu_field_queryset, many=True, request=request)
return DetailResponse(data={'menu_btn': menu_btn_serializer.data, 'menu_field': menu_field_serializer.data})
@action(methods=['PUT'], detail=True, permission_classes=[IsAuthenticated])
def set_role_premission(self,request,pk):
def set_role_menu_field(self, request, pk):
"""
角色菜单和按钮及按钮范围授权:
:param request:
:param pk: role
:return:
设置 角色-菜单-列字段
"""
body = request.data
RoleMenuPermission.objects.filter(role=pk).delete()
RoleMenuButtonPermission.objects.filter(role=pk).delete()
for menu in body:
if menu.get('isCheck'):
menu_parent = Menu.get_all_parent(menu.get('id'))
role_menu_permission_list = []
for d in menu_parent:
role_menu_permission_list.append(RoleMenuPermission(role_id=pk, menu_id=d["id"]))
RoleMenuPermission.objects.bulk_create(role_menu_permission_list)
# RoleMenuPermission.objects.create(role_id=pk, menu_id=menu.get('id'))
for btn in menu.get('btns'):
if btn.get('isCheck'):
data_range = btn.get('data_range',0) or 0
instance = RoleMenuButtonPermission.objects.create(role_id=pk, menu_button_id=btn.get('id'),data_range=data_range)
instance.dept.set(btn.get('dept',[]))
for col in menu.get('columns'):
FieldPermission.objects.update_or_create(role_id=pk,field_id=col.get('id'),is_query=col.get('is_query'),is_create=col.get('is_create'),is_update=col.get('is_update'))
return DetailResponse(msg="授权成功")
data = request.data
for col in data:
FieldPermission.objects.update_or_create(
role_id=pk, field_id=col.get('id'),
defaults={
'is_create': col.get('is_create'),
'is_update': col.get('is_update'),
'is_query': col.get('is_query'),
})
return DetailResponse(data=[], msg="更新成功")
@action(methods=['PUT'], detail=False, permission_classes=[IsAuthenticated])
def set_role_menu_btn(self, request):
"""
设置 角色-菜单-按钮
"""
data = request.data
isCheck = data.get('isCheck', None)
roleId = data.get('roleId', None)
btnId = data.get('btnId', None)
data_range = data.get('data_range', None) or 0 # 默认仅本人权限
dept = data.get('dept', None) or [] # 默认空部门
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
def role_menu_get_button(self, request):
"""
当前用户角色和菜单获取可下拉选项的按钮:角色授权页面使用
:param request:
:return:
"""
if params := request.query_params:
if menu_id := params.get('menu', None):
is_superuser = request.user.is_superuser
if is_superuser:
queryset = MenuButton.objects.filter(menu=menu_id).values('id', 'name')
else:
role_list = request.user.role.values_list('id', flat=True)
queryset = RoleMenuButtonPermission.objects.filter(
role__in=role_list, menu_button__menu=menu_id
).values(btn_id=F('menu_button__id'), name=F('menu_button__name'))
return DetailResponse(data=queryset)
return ErrorResponse(msg="参数错误")
@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)
if isCheck:
# 添加权限:创建关联记录
instance = RoleMenuButtonPermission.objects.create(role_id=roleId,
menu_button_id=btnId,
data_range=data_range)
# 自定义部门权限
if data_range == 4 and dept:
instance.dept.set(dept)
else:
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="参数错误")
# 删除权限:移除关联记录
RoleMenuButtonPermission.objects.filter(role_id=roleId, menu_button_id=btnId).delete()
menu_btn_instance = MenuButton.objects.get(id=btnId)
serializer = RoleMenuButtonSerializer(menu_btn_instance, request=request)
return DetailResponse(data=serializer.data, msg="更新成功")
@action(methods=['PUT'], detail=False, permission_classes=[IsAuthenticated])
def set_role_menu_btn_data_range(self, request):
"""
设置 角色-菜单-按钮-权限
"""
data = request.data
instance = RoleMenuButtonPermission.objects.get(id=data.get('role_menu_btn_perm_id'))
instance.data_range = data.get('data_range')
instance.dept.add(*data.get('dept'))
if not data.get('dept'):
instance.dept.clear()
instance.save()
serializer = RoleMenuButtonPermissionSerializer(instance, request=request)
return DetailResponse(data=serializer.data, msg="更新成功")
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
def role_to_dept_all(self, request):
@@ -361,72 +271,20 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
:param request:
:return:
"""
params = request.query_params
is_superuser = request.user.is_superuser
if is_superuser:
queryset = Dept.objects.values('id', 'name', 'parent')
else:
if not params:
return ErrorResponse(msg="参数错误")
menu_button = params.get('menu_button')
if menu_button is None:
return ErrorResponse(msg="参数错误")
role_list = request.user.role.values_list('id', flat=True)
queryset = RoleMenuButtonPermission.objects.filter(role__in=role_list, menu_button=None).values(
dept_id=F('dept__id'),
name=F('dept__name'),
parent=F('dept__parent')
)
return DetailResponse(data=queryset)
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
def menu_to_button(self, request):
"""
根据所选择菜单获取已配置的按钮/接口权限:角色授权页面使用
:param request:
:return:
"""
params = request.query_params
menu_id = params.get('menu', None)
if menu_id is None:
return ErrorResponse(msg="未获取到参数")
is_superuser = request.user.is_superuser
if is_superuser:
queryset = RoleMenuButtonPermission.objects.filter(menu_button__menu=menu_id).values(
'id',
'data_range',
'menu_button',
'menu_button__name',
'menu_button__value'
)
return DetailResponse(data=queryset)
else:
if params:
# 当前登录用户的角色
role_list = request.user.role.values_list('id', flat=True)
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="未获取到参数")
menu_button_id = params.get('menu_button')
# 当前登录用户角色可以分配的自定义部门权限
dept_checked_disabled = RoleMenuButtonPermission.objects.filter(
role_id__in=role_list, menu_button_id=menu_button_id
).values_list('dept', flat=True)
dept_list = Dept.objects.values('id', 'name', 'parent')
@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)
data = []
for dept in dept_list:
dept["disabled"] = False if is_superuser else dept["id"] not in dept_checked_disabled
data.append(dept)
return DetailResponse(data=data)

View File

@@ -90,6 +90,8 @@ class UserCreateSerializer(CustomModelSerializer):
data = super().save(**kwargs)
data.dept_belong_id = data.dept_id
data.save()
if not self.validated_data.get('manage_dept', None):
data.manage_dept.add(data.dept_id)
data.post.set(self.initial_data.get("post", []))
return data
@@ -115,19 +117,20 @@ class UserUpdateSerializer(CustomModelSerializer):
],
)
# 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 validate_is_active(self, value):
"""
更改激活状态
"""
if value:
self.initial_data["login_error_count"] = 0
return value
def save(self, **kwargs):
data = super().save(**kwargs)
data.dept_belong_id = data.dept_id
data.save()
if not self.validated_data.get('manage_dept', None):
data.manage_dept.add(data.dept_id)
data.post.set(self.initial_data.get("post", []))
return data
@@ -287,6 +290,7 @@ class UserViewSet(CustomModelViewSet):
"dept": user.dept_id,
"is_superuser": user.is_superuser,
"role": user.role.values_list('id', flat=True),
"pwd_change_count":user.pwd_change_count
}
if hasattr(connection, 'tenant'):
result['tenant_id'] = connection.tenant and connection.tenant.id
@@ -326,22 +330,47 @@ class UserViewSet(CustomModelViewSet):
return ErrorResponse(msg="参数不能为空")
if new_pwd != new_pwd2:
return ErrorResponse(msg="两次密码不匹配")
verify_password = check_password(old_pwd, self.request.user.password)
verify_password = check_password(old_pwd, request.user.password)
if not verify_password:
verify_password = check_password(hashlib.md5(old_pwd.encode(encoding='UTF-8')).hexdigest(), self.request.user.password)
old_pwd_md5 = hashlib.md5(old_pwd.encode(encoding='UTF-8')).hexdigest()
verify_password = check_password(str(old_pwd_md5), request.user.password)
# 创建用户时、自定义密码无法修改问题
if not verify_password:
old_pwd_md5 = hashlib.md5(old_pwd_md5.encode(encoding='UTF-8')).hexdigest()
verify_password = check_password(str(old_pwd_md5), request.user.password)
if verify_password:
# request.user.password = make_password(hashlib.md5(new_pwd.encode(encoding='UTF-8')).hexdigest())
request.user.password = make_password(hashlib.md5(new_pwd.encode(encoding='UTF-8')).hexdigest())
request.user.pwd_change_count += 1
request.user.save()
return DetailResponse(data=None, msg="修改成功")
else:
return ErrorResponse(msg="旧密码不正确")
@action(methods=["post"], detail=False, permission_classes=[IsAuthenticated])
def login_change_password(self, request, *args, **kwargs):
"""初次登录进行密码修改"""
data = request.data
new_pwd = data.get("password")
new_pwd2 = data.get("password_regain")
if new_pwd != new_pwd2:
return ErrorResponse(msg="两次密码不匹配")
else:
request.user.password = make_password(new_pwd)
request.user.pwd_change_count += 1
request.user.save()
return DetailResponse(data=None, msg="修改成功")
@action(methods=["PUT"], detail=True, permission_classes=[IsAuthenticated])
def reset_to_default_password(self, request, *args, **kwargs):
def reset_to_default_password(self, request,pk):
"""恢复默认密码"""
instance = Users.objects.filter(id=kwargs.get("pk")).first()
if not self.request.user.is_superuser:
return ErrorResponse(msg="只允许超级管理员对其进行密码重置")
instance = Users.objects.filter(id=pk).first()
if instance:
instance.set_password(dispatch.get_system_config_values("base.default_password"))
default_password = dispatch.get_system_config_values("base.default_password")
md5_pwd = hashlib.md5(default_password.encode(encoding='UTF-8')).hexdigest()
instance.password = make_password(md5_pwd)
instance.save()
return DetailResponse(data=None, msg="密码重置成功")
else:

View File

View File

@@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
import oss2
from rest_framework.exceptions import ValidationError
from application import dispatch
# 进度条
# 当无法确定待上传的数据长度时total_bytes的值为None。
def percentage(consumed_bytes, total_bytes):
if total_bytes:
rate = int(100 * (float(consumed_bytes) / float(total_bytes)))
print('\r{0}% '.format(rate), end='')
def ali_oss_upload(file, file_name):
"""
阿里云OSS上传
"""
try:
file.seek(0)
file_read = file.read()
except Exception as e:
file_read = file
if not file:
raise ValidationError('请上传文件')
# 转存到oss
path_prefix = dispatch.get_system_config_values("file_storage.aliyun_path")
if not path_prefix.endswith('/'):
path_prefix = path_prefix + '/'
if path_prefix.startswith('/'):
path_prefix = path_prefix[1:]
base_fil_name = f'{path_prefix}{file_name}'
# 获取OSS配置
# 获取的AccessKey
access_key_id = dispatch.get_system_config_values("file_storage.aliyun_access_key")
access_key_secret = dispatch.get_system_config_values("file_storage.aliyun_access_secret")
auth = oss2.Auth(access_key_id, access_key_secret)
# 这个是需要用特定的地址,不同地域的服务器地址不同,不要弄错了
# 参考官网给的地址配置https://www.alibabacloud.com/help/zh/object-storage-service/latest/regions-and-endpoints#concept-zt4-cvy-5db
endpoint = dispatch.get_system_config_values("file_storage.aliyun_endpoint")
bucket_name = dispatch.get_system_config_values("file_storage.aliyun_bucket")
if bucket_name.endswith(endpoint):
bucket_name = bucket_name.replace(f'.{endpoint}', '')
# 你的项目名称类似于不同的项目上传的图片前缀url不同
bucket = oss2.Bucket(auth, endpoint, bucket_name) # 项目名称
# 生成外网访问的文件路径
aliyun_cdn_url = dispatch.get_system_config_values("file_storage.aliyun_cdn_url")
if aliyun_cdn_url:
if aliyun_cdn_url.endswith('/'):
aliyun_cdn_url = aliyun_cdn_url[1:]
file_path = f"{aliyun_cdn_url}/{base_fil_name}"
else:
file_path = f"https://{bucket_name}.{endpoint}/{base_fil_name}"
# 这个是阿里提供的SDK方法
res = bucket.put_object(base_fil_name, file_read, progress_callback=percentage)
# 如果上传状态是200 代表成功 返回文件外网访问路径
if res.status == 200:
return file_path
else:
return None

View File

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

View File

@@ -15,15 +15,16 @@ import six
from django.db import models
from django.db.models import Q, F
from django.db.models.constants import LOOKUP_SEP
from django_filters import utils, FilterSet
from django_filters.constants import ALL_FIELDS
from django_filters.filters import CharFilter, DateTimeFromToRangeFilter
from django_filters.rest_framework import DjangoFilterBackend
from django_filters.utils import get_model_field
from django_filters.utils import get_model_field, translate_validation, deprecate
from rest_framework.request import Request
from rest_framework.filters import BaseFilterBackend
from django_filters.conf import settings
from dvadmin.system.models import Dept, ApiWhiteList, RoleMenuButtonPermission
from dvadmin.utils.models import CoreModel
from dvadmin.system.models import Dept, ApiWhiteList, RoleMenuButtonPermission, MenuButton, Users
from util.currency import recursion_down_fast
class CoreModelFilterBankend(BaseFilterBackend):
"""
@@ -33,15 +34,15 @@ class CoreModelFilterBankend(BaseFilterBackend):
create_datetime_after = request.query_params.get('create_datetime_after', None)
create_datetime_before = request.query_params.get('create_datetime_before', None)
update_datetime_after = request.query_params.get('update_datetime_after', None)
update_datetime_before = request.query_params.get('update_datetime_after', None)
update_datetime_before = request.query_params.get('update_datetime_before', None)
if any([create_datetime_after, create_datetime_before, update_datetime_after, update_datetime_before]):
create_filter = Q()
if create_datetime_after and create_datetime_before:
create_filter &= Q(create_datetime__gte=create_datetime_after) & Q(create_datetime__lte=create_datetime_before)
create_filter &= Q(create_datetime__gte=create_datetime_after) & Q(create_datetime__lte=f'{create_datetime_before} 23:59:59')
elif create_datetime_after:
create_filter &= Q(create_datetime__gte=create_datetime_after)
elif create_datetime_before:
create_filter &= Q(create_datetime__lte=create_datetime_before)
create_filter &= Q(create_datetime__lte=f'{create_datetime_before} 23:59:59')
# 更新时间范围过滤条件
update_filter = Q()
@@ -149,13 +150,16 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
if _pk: # 判断是否是单例查询
re_api = re.sub(_pk,'{id}', api)
role_id_list = request.user.role.values_list('id', flat=True)
role_permission_list=RoleMenuButtonPermission.objects.filter(
role__in=role_id_list,
role__status=1,
menu_button__api=re_api,
menu_button__method=method).values(
'data_range'
)
# 修复权限获取bug
menu_button_ids = MenuButton.objects.filter(api=re_api,method=method).values_list('id', flat=True)
role_permission_list = []
if menu_button_ids:
role_permission_list=RoleMenuButtonPermission.objects.filter(
role__in=role_id_list,
role__status=1,
menu_button_id__in=menu_button_ids).values(
'data_range'
)
dataScope_list = [] # 权限范围列表
for ele in role_permission_list:
# 判断用户是否为超级管理员角色/如果拥有[全部数据权限]则返回所有数据
@@ -197,6 +201,86 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
return queryset.filter(dept_belong_id__in=list(set(dept_list)))
class DataLevelPermissionsSubFilter(DataLevelPermissionsFilter):
"""数据级权限过滤的子过滤器过滤管理部门字段manage_dept"""
def _extracted_from_filter_queryset_33(self, request:Request, queryset, api, method):
u:Users = request.user
if u.is_superuser:
return queryset
# (0, "仅本人数据权限"),
# (1, "本部门及以下数据权限"),
# (2, "本部门数据权限"),
# (3, "全部数据权限"),
# (4, "自定数据权限")
re_api = api
_pk = request.parser_context["kwargs"].get('pk')
if _pk: # 判断是否是单例查询
re_api = re.sub(_pk,'{id}', api)
role_id_list = request.user.role.values_list('id', flat=True)
menu_button_ids = MenuButton.objects.filter(api=re_api,method=method).values_list('id', flat=True)
role_permission_list = []
if menu_button_ids:
role_permission_list=RoleMenuButtonPermission.objects.filter(
role__in=role_id_list,
role__status=1,
menu_button_id__in=menu_button_ids).values(
'data_range'
)
dataScope_list = [] # 权限范围列表
for ele in role_permission_list:
# 判断用户是否为超级管理员角色/如果拥有[全部数据权限]则返回所有数据
if ele.get("data_range") == 3:
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=u.dept_id
)
dept_list = []
# 5. 自定数据权限 获取部门,根据部门过滤
for ele in dataScope_list:
if ele == 1:
dept_list.append(u.dept_id)
dept_list.extend(
get_dept(
u.dept_id,
)
)
elif ele == 2:
dept_list.append(u.dept_id)
elif ele == 4:
dept_ids = RoleMenuButtonPermission.objects.filter(
role__in=role_id_list,
role__status=1,
data_range=4).values_list(
'dept__id',flat=True
)
dept_list.extend(
dept_ids
)
# 自己部门 交 管理部门
if u.manage_dept.exists(): # 兼容旧数据
for dept in u.manage_dept.all():
dept_list.extend(recursion_down_fast(dept, 'parent', 'id'))
else:
dept_list = recursion_down_fast(u.dept, 'parent', 'id')
dept_list = set(recursion_down_fast(u.dept)) & set(dept_list)
# 自己创建的数据要能看到
# 应对归属a管b、c等情况如果自己创建数据则是a不显式指定自己的数据就查不到
if queryset.model._meta.model_name == 'dept':
return queryset.filter(Q(id__in=dept_list) | Q(creator=u))
return queryset.filter(Q(dept_belong_id__in=dept_list) | Q(creator=u))
class DataLevelPermissionMargeFilter(DataLevelPermissionsFilter):
def _extracted_from_filter_queryset_33(self, request, queryset, api, method):
queryset = super()._extracted_from_filter_queryset_33(request, queryset, api, method)
return DataLevelPermissionsSubFilter._extracted_from_filter_queryset_33(self, request, queryset, api, method)
class CustomDjangoFilterBackend(DjangoFilterBackend):
lookup_prefixes = {
"^": "istartswith",
@@ -237,14 +321,14 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
# TODO: remove assertion in 2.1
if filterset_class is None and hasattr(view, "filter_class"):
utils.deprecate(
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(
deprecate(
"`%s.filter_fields` attribute should be renamed `filterset_fields`." % view.__class__.__name__
)
self.filter_fields = getattr(view, "filter_fields", None)
@@ -340,7 +424,7 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
from timezone_field import TimeZoneField
# 不进行 过滤的model 类
if isinstance(field, (models.JSONField, TimeZoneField)):
if isinstance(field, (models.JSONField, TimeZoneField, models.FileField)):
continue
# warn if the field doesn't exist.
if field is None:
@@ -424,5 +508,5 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
return queryset
if not filterset.is_valid() and self.raise_exception:
raise utils.translate_validation(filterset.errors)
raise translate_validation(filterset.errors)
return filterset.qs

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
from rest_framework.exceptions import ValidationError
from application import dispatch
from qcloud_cos import CosConfig
from qcloud_cos import CosS3Client
# 进度条
# 当无法确定待上传的数据长度时total_bytes的值为None。
def percentage(consumed_bytes, total_bytes):
if total_bytes:
rate = int(100 * (float(consumed_bytes) / float(total_bytes)))
print('\r{0}% '.format(rate), end='')
def tencent_cos_upload(file, file_name):
try:
file.seek(0)
file_read = file.read()
except Exception as e:
file_read = file
if not file:
raise ValidationError('请上传文件')
# 生成文件名
path_prefix = dispatch.get_system_config_values("file_storage.tencent_path")
if not path_prefix.endswith('/'):
path_prefix = path_prefix + '/'
if path_prefix.startswith('/'):
path_prefix = path_prefix[1:]
base_fil_name = f'{path_prefix}{file_name}'
# 获取cos配置
# 1. 设置用户属性, 包括 secret_id, secret_key, region等。Appid 已在 CosConfig 中移除,请在参数 Bucket 中带上 Appid。Bucket 由 BucketName-Appid 组成
secret_id = dispatch.get_system_config_values("file_storage.tencent_secret_id") # 用户的 SecretId建议使用子账号密钥授权遵循最小权限指引降低使用风险。子账号密钥获取可参见 https://cloud.tencent.com/document/product/598/37140
secret_key = dispatch.get_system_config_values("file_storage.tencent_secret_key") # 用户的 SecretKey建议使用子账号密钥授权遵循最小权限指引降低使用风险。子账号密钥获取可参见 https://cloud.tencent.com/document/product/598/37140
region = dispatch.get_system_config_values("file_storage.tencent_region") # 替换为用户的 region已创建桶归属的 region 可以在控制台查看https://console.cloud.tencent.com/cos5/bucket # COS 支持的所有 region 列表参见https://cloud.tencent.com/document/product/436/6224
bucket = dispatch.get_system_config_values("file_storage.tencent_bucket") # 要访问的桶名称
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key)
client = CosS3Client(config)
# 访问地址
base_file_url = f'https://{bucket}.cos.{region}.myqcloud.com'
# 生成外网访问的文件路径
if base_file_url.endswith('/'):
file_path = base_file_url + base_fil_name
else:
file_path = f'{base_file_url}/{base_fil_name}'
# 这个是阿里提供的SDK方法 bucket是调用的4.1中配置的变量名
try:
response = client.put_object(
Bucket=bucket,
Body=file_read,
Key=base_fil_name,
EnableMD5=False
)
return file_path
except:
return None

View File

@@ -6,6 +6,8 @@
@Created on: 2021/6/1 001 22:57
@Remark: 自定义视图集
"""
import copy
from django.db import transaction
from django_filters import DateTimeFromToRangeFilter
from django_filters.rest_framework import FilterSet
@@ -14,7 +16,7 @@ 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, CoreModelFilterBankend
from dvadmin.utils.filters import CoreModelFilterBankend, DataLevelPermissionMargeFilter
from dvadmin.utils.import_export_mixin import ExportSerializerMixin, ImportSerializerMixin
from dvadmin.utils.json_response import SuccessResponse, ErrorResponse, DetailResponse
from dvadmin.utils.permission import CustomPermission
@@ -39,7 +41,7 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi
update_serializer_class = None
filter_fields = '__all__'
search_fields = ()
extra_filter_class = [CoreModelFilterBankend,DataLevelPermissionsFilter]
extra_filter_class = [CoreModelFilterBankend,DataLevelPermissionMargeFilter]
permission_classes = [CustomPermission]
import_field_dict = {}
export_field_label = {}
@@ -67,12 +69,14 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi
kwargs.setdefault('context', self.get_serializer_context())
# 全部以可见字段为准
can_see = self.get_menu_field(serializer_class)
# 排除掉序列化器级的字段
# sub_set = set(serializer_class._declared_fields.keys()) - set(can_see)
# for field in sub_set:
# serializer_class._declared_fields.pop(field)
# 排除掉序列化器级的字段(排除字段权限中未授权的字段)
# if not self.request.user.is_superuser:
# serializer_class.Meta.fields = can_see
# exclude_set = set(serializer_class._declared_fields.keys()) - set(can_see)
# for field in exclude_set:
# serializer_class._declared_fields.pop(field)
# meta = copy.deepcopy(serializer_class.Meta)
# meta.fields = list(can_see)
# serializer_class.Meta = meta
# 在分页器中使用
self.request.permission_fields = can_see
if isinstance(self.request.data, list):
@@ -83,15 +87,17 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi
def get_menu_field(self, serializer_class):
"""获取字段权限"""
finded = False
for model in get_custom_app_models():
if model['object'] is serializer_class.Meta.model:
finded = True
break
if finded is False:
if not any(model['object'] is serializer_class.Meta.model for model in get_custom_app_models()):
return []
return MenuField.objects.filter(model=model['model']
).values('field_name', 'title')
# 匿名用户没有角色
ret = FieldPermission.objects.filter(field__model=serializer_class.Meta.model.__name__)
if hasattr(self.request.user, 'role'):
roles = self.request.user.role.values_list('id', flat=True)
ret = ret.filter(is_query=True, role__in=roles)
return ret.values_list('field__field_name', flat=True)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data, request=request)
@@ -131,8 +137,7 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi
instance.delete()
return DetailResponse(data=[], msg="删除成功")
keys = openapi.Schema(description='主键列表', type=openapi.TYPE_ARRAY, items=openapi.TYPE_STRING)
keys = openapi.Schema(description='主键列表', type=openapi.TYPE_ARRAY, items=openapi.Schema(type=openapi.TYPE_STRING))
@swagger_auto_schema(request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
required=['keys'],
@@ -147,3 +152,13 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi
return SuccessResponse(data=[], msg="删除成功")
else:
return ErrorResponse(msg="未获取到keys字段")
@action(methods=['post'], detail=False)
def get_by_ids(self, request):
"""通过IDS列表获取数据"""
ids = request.data.get('ids', [])
if ids and ids != ['']:
queryset = self.get_queryset().filter(id__in=ids)
serializer = self.get_serializer(queryset, many=True)
return DetailResponse(data=serializer.data)
return DetailResponse(data=None)

17
backend/main.py Normal file
View File

@@ -0,0 +1,17 @@
import multiprocessing
import os
import sys
root_path = os.getcwd()
sys.path.append(root_path)
import uvicorn
from application.settings import LOGGING
if __name__ == '__main__':
multiprocessing.freeze_support()
workers = 4
if os.sys.platform.startswith('win'):
# Windows操作系统
workers = None
uvicorn.run("application.asgi:application", reload=False, host="0.0.0.0", port=8000, workers=workers,
log_config=LOGGING)

View File

@@ -1,31 +1,33 @@
Django==4.2.7
Django==4.2.14
django-comment-migrate==0.1.7
django-cors-headers==4.3.0
django-filter==23.3
django-cors-headers==4.4.0
django-filter==24.2
django-ranged-response==0.2.0
djangorestframework==3.14.0
django-restql==0.15.3
django-simple-captcha==0.5.20
django-timezone-field==6.0.1
djangorestframework-simplejwt==5.3.0
djangorestframework==3.15.2
django-restql==0.15.4
django-simple-captcha==0.6.0
django-timezone-field==7.0
djangorestframework_simplejwt==5.4.0
drf-yasg==1.21.7
mysqlclient==2.2.0
pypinyin==0.49.0
pypinyin==0.51.0
ua-parser==0.18.0
pyparsing==3.1.1
openpyxl==3.1.2
requests==2.31.0
typing-extensions==4.8.0
tzlocal==5.1
channels==3.0.5
channels-redis==4.1.0
websockets==11.0.3
pyparsing==3.1.2
openpyxl==3.1.5
requests==2.32.4
typing-extensions==4.12.2
tzlocal==5.2
channels==4.1.0
channels-redis==4.2.0
user-agents==2.2.0
six==1.16.0
whitenoise==6.6.0
whitenoise==6.7.0
psycopg2==2.9.9
uvicorn==0.23.2
gunicorn==21.2.0
gevent==23.9.1
Pillow==10.1.0
dvadmin-celery==1.0.5
uvicorn==0.30.3
gunicorn==23.0.0
gevent==24.2.1
Pillow==10.4.0
pyinstaller==6.9.0
dvadmin3-celery==3.1.6
oss2==2.19.1
cos-python-sdk-v5==1.9.37

View File

@@ -0,0 +1,123 @@
Bitstream Vera Fonts Copyright
The fonts have a generous copyright, allowing derivative works (as
long as "Bitstream" or "Vera" are not in the names), and full
redistribution (so long as they are not *sold* by themselves). They
can be be bundled, redistributed and sold with any software.
The fonts are distributed under the following copyright:
Copyright
=========
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream
Vera is a trademark of Bitstream, Inc.
Permission is hereby granted, free of charge, to any person obtaining
a copy of the fonts accompanying this license ("Fonts") and associated
documentation files (the "Font Software"), to reproduce and distribute
the Font Software, including without limitation the rights to use,
copy, merge, publish, distribute, and/or sell copies of the Font
Software, and to permit persons to whom the Font Software is furnished
to do so, subject to the following conditions:
The above copyright and trademark notices and this permission notice
shall be included in all copies of one or more of the Font Software
typefaces.
The Font Software may be modified, altered, or added to, and in
particular the designs of glyphs or characters in the Fonts may be
modified and additional glyphs or characters may be added to the
Fonts, only if the fonts are renamed to names not containing either
the words "Bitstream" or the word "Vera".
This License becomes null and void to the extent applicable to Fonts
or Font Software that has been modified and is distributed under the
"Bitstream Vera" names.
The Font Software may be sold as part of a larger software package but
no copy of one or more of the Font Software typefaces may be sold by
itself.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL,
OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT
SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
Except as contained in this notice, the names of Gnome, the Gnome
Foundation, and Bitstream Inc., shall not be used in advertising or
otherwise to promote the sale, use or other dealings in this Font
Software without prior written authorization from the Gnome Foundation
or Bitstream Inc., respectively. For further information, contact:
fonts at gnome dot org.
Copyright FAQ
=============
1. I don't understand the resale restriction... What gives?
Bitstream is giving away these fonts, but wishes to ensure its
competitors can't just drop the fonts as is into a font sale system
and sell them as is. It seems fair that if Bitstream can't make money
from the Bitstream Vera fonts, their competitors should not be able to
do so either. You can sell the fonts as part of any software package,
however.
2. I want to package these fonts separately for distribution and
sale as part of a larger software package or system. Can I do so?
Yes. A RPM or Debian package is a "larger software package" to begin
with, and you aren't selling them independently by themselves.
See 1. above.
3. Are derivative works allowed?
Yes!
4. Can I change or add to the font(s)?
Yes, but you must change the name(s) of the font(s).
5. Under what terms are derivative works allowed?
You must change the name(s) of the fonts. This is to ensure the
quality of the fonts, both to protect Bitstream and Gnome. We want to
ensure that if an application has opened a font specifically of these
names, it gets what it expects (though of course, using fontconfig,
substitutions could still could have occurred during font
opening). You must include the Bitstream copyright. Additional
copyrights can be added, as per copyright law. Happy Font Hacking!
6. If I have improvements for Bitstream Vera, is it possible they might get
adopted in future versions?
Yes. The contract between the Gnome Foundation and Bitstream has
provisions for working with Bitstream to ensure quality additions to
the Bitstream Vera font family. Please contact us if you have such
additions. Note, that in general, we will want such additions for the
entire family, not just a single font, and that you'll have to keep
both Gnome and Jim Lyles, Vera's designer, happy! To make sense to add
glyphs to the font, they must be stylistically in keeping with Vera's
design. Vera cannot become a "ransom note" font. Jim Lyles will be
providing a document describing the design elements used in Vera, as a
guide and aid for people interested in contributing to Vera.
7. I want to sell a software package that uses these fonts: Can I do so?
Sure. Bundle the fonts with your software and sell your software
with the fonts. That is the intent of the copyright.
8. If applications have built the names "Bitstream Vera" into them,
can I override this somehow to use fonts of my choosing?
This depends on exact details of the software. Most open source
systems and software (e.g., Gnome, KDE, etc.) are now converting to
use fontconfig (see www.fontconfig.org) to handle font configuration,
selection and substitution; it has provisions for overriding font
names and substituting alternatives. An example is provided by the
supplied local.conf file, which chooses the family Bitstream Vera for
"sans", "serif" and "monospace". Other software (e.g., the XFree86
core server) has other mechanisms for font substitution.

View File

@@ -0,0 +1,11 @@
Contained herin is the Bitstream Vera font family.
The Copyright information is found in the COPYRIGHT.TXT file (along
with being incorporated into the fonts themselves).
The releases notes are found in the file "RELEASENOTES.TXT".
We hope you enjoy Vera!
Bitstream, Inc.
The Gnome Project

Binary file not shown.

View File

@@ -0,0 +1,18 @@
Information about external resources
The following files are taken from external resources or trees.
Files: insQ.js
insQ.min.js
License: MIT
Copyright: Zbyszek Tenerowicz
Eryk Napierała <eryk.piast@gmail.com>
Askar Yusupov <devex.soft@gmail.com>
Dan Dascalescu <ddascalescu+github@gmail.com>
Source: https://github.com/naugtur/insertionQuery v1.0.3
Files: immutable.js
immutable.min.js
License: MIT
Copyright: 2014-present, Facebook, Inc
Source: https://github.com/immutable-js/immutable-js/releases/tag/v3.8.2

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,163 @@
var insertionQ = (function () {
"use strict";
var sequence = 100,
isAnimationSupported = false,
animation_string = 'animationName',
keyframeprefix = '',
domPrefixes = 'Webkit Moz O ms Khtml'.split(' '),
pfx = '',
elm = document.createElement('div'),
options = {
strictlyNew: true,
timeout: 20
};
if (elm.style.animationName) {
isAnimationSupported = true;
}
if (isAnimationSupported === false) {
for (var i = 0; i < domPrefixes.length; i++) {
if (elm.style[domPrefixes[i] + 'AnimationName'] !== undefined) {
pfx = domPrefixes[i];
animation_string = pfx + 'AnimationName';
keyframeprefix = '-' + pfx.toLowerCase() + '-';
isAnimationSupported = true;
break;
}
}
}
function listen(selector, callback) {
var styleAnimation, animationName = 'insQ_' + (sequence++);
var eventHandler = function (event) {
if (event.animationName === animationName || event[animation_string] === animationName) {
if (!isTagged(event.target)) {
callback(event.target);
}
}
};
styleAnimation = document.createElement('style');
styleAnimation.innerHTML = '@' + keyframeprefix + 'keyframes ' + animationName + ' { from { outline: 1px solid transparent } to { outline: 0px solid transparent } }' +
"\n" + selector + ' { animation-duration: 0.001s; animation-name: ' + animationName + '; ' +
keyframeprefix + 'animation-duration: 0.001s; ' + keyframeprefix + 'animation-name: ' + animationName + '; ' +
' } ';
document.head.appendChild(styleAnimation);
var bindAnimationLater = setTimeout(function () {
document.addEventListener('animationstart', eventHandler, false);
document.addEventListener('MSAnimationStart', eventHandler, false);
document.addEventListener('webkitAnimationStart', eventHandler, false);
//event support is not consistent with DOM prefixes
}, options.timeout); //starts listening later to skip elements found on startup. this might need tweaking
return {
destroy: function () {
clearTimeout(bindAnimationLater);
if (styleAnimation) {
document.head.removeChild(styleAnimation);
styleAnimation = null;
}
document.removeEventListener('animationstart', eventHandler);
document.removeEventListener('MSAnimationStart', eventHandler);
document.removeEventListener('webkitAnimationStart', eventHandler);
}
};
}
function tag(el) {
el.QinsQ = true; //bug in V8 causes memory leaks when weird characters are used as field names. I don't want to risk leaking DOM trees so the key is not '-+-' anymore
}
function isTagged(el) {
return (options.strictlyNew && (el.QinsQ === true));
}
function topmostUntaggedParent(el) {
if (isTagged(el.parentNode)) {
return el;
} else {
return topmostUntaggedParent(el.parentNode);
}
}
function tagAll(e) {
tag(e);
e = e.firstChild;
for (; e; e = e.nextSibling) {
if (e !== undefined && e.nodeType === 1) {
tagAll(e);
}
}
}
//aggregates multiple insertion events into a common parent
function catchInsertions(selector, callback) {
var insertions = [];
//throttle summary
var sumUp = (function () {
var to;
return function () {
clearTimeout(to);
to = setTimeout(function () {
insertions.forEach(tagAll);
callback(insertions);
insertions = [];
}, 10);
};
})();
return listen(selector, function (el) {
if (isTagged(el)) {
return;
}
tag(el);
var myparent = topmostUntaggedParent(el);
if (insertions.indexOf(myparent) < 0) {
insertions.push(myparent);
}
sumUp();
});
}
//insQ function
var exports = function (selector) {
if (isAnimationSupported && selector.match(/[^{}]/)) {
if (options.strictlyNew) {
tagAll(document.body); //prevents from catching things on show
}
return {
every: function (callback) {
return listen(selector, callback);
},
summary: function (callback) {
return catchInsertions(selector, callback);
}
};
} else {
return false;
}
};
//allows overriding defaults
exports.config = function (opt) {
for (var o in opt) {
if (opt.hasOwnProperty(o)) {
options[o] = opt[o];
}
}
};
return exports;
})();
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
module.exports = insertionQ;
}

Binary file not shown.

Binary file not shown.

View File

@@ -1,3 +1,9 @@
// redoc-init.js
// https://github.com/axnsan12/drf-yasg
// Copyright 2017 - 2021, Cristian V. <cristi@cvjd.me>
// This file is licensed under the BSD 3-Clause License.
// License text available at https://opensource.org/licenses/BSD-3-Clause
"use strict";
var currentPath = window.location.protocol + "//" + window.location.host + window.location.pathname;

Binary file not shown.

View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015, Rebilly, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -0,0 +1,23 @@
<html>
<head>
<script type="text/javascript" charset="utf-8">
// Map of projects that support handling 404s.
var supportedProjects = {
'ReDoc': 'https://redocly.github.io/redoc'
};
var project = window.location.pathname.split('/')[1];
// Always fallback to rebilly.com redirect.
var loc = 'https://www.rebilly.com/';
if (supportedProjects.hasOwnProperty(project)) {
if (typeof supportedProjects[project] === 'string') {
loc = supportedProjects[project];
}
}
window.location.href = loc;
</script>
</head>
<body></body>
</html>

Binary file not shown.

View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015-present, Rebilly, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Binary file not shown.

View File

@@ -0,0 +1,2 @@
swagger-ui
Copyright 2020-2021 SmartBear Software Inc.

View File

@@ -0,0 +1,16 @@
html {
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 0;
background: #fafafa;
}

Binary file not shown.

Binary file not shown.

View File

@@ -4,8 +4,6 @@
<title>Swagger UI: OAuth2 Redirect</title>
</head>
<body>
</body>
</html>
<script>
'use strict';
function run () {
@@ -15,31 +13,32 @@
var isValid, qp, arr;
if (/code|token|error/.test(window.location.hash)) {
qp = window.location.hash.substring(1);
qp = window.location.hash.substring(1).replace('?', '&');
} else {
qp = location.search.substring(1);
}
arr = qp.split("&")
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
arr = qp.split("&");
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value)
return key === "" ? value : decodeURIComponent(value);
}
) : {}
) : {};
isValid = qp.state === sentState
isValid = qp.state === sentState;
if ((
oauth2.auth.schema.get("flow") === "accessCode"||
oauth2.auth.schema.get("flow") === "authorizationCode"
oauth2.auth.schema.get("flow") === "accessCode" ||
oauth2.auth.schema.get("flow") === "authorizationCode" ||
oauth2.auth.schema.get("flow") === "authorization_code"
) && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
});
}
@@ -48,7 +47,7 @@
oauth2.auth.code = qp.code;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
let oauthErrorMsg
let oauthErrorMsg;
if (qp.error) {
oauthErrorMsg = "["+qp.error+"]: " +
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
@@ -59,7 +58,7 @@
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server."
});
}
} else {
@@ -68,7 +67,13 @@
window.close();
}
window.addEventListener('DOMContentLoaded', function () {
run();
});
if (document.readyState !== 'loading') {
run();
} else {
document.addEventListener('DOMContentLoaded', function () {
run();
});
}
</script>
</body>
</html>

View File

@@ -0,0 +1,20 @@
window.onload = function() {
//<editor-fold desc="Changeable Configuration Block">
// the following lines will be replaced by docker/configurator, when it runs in a docker-container
window.ui = SwaggerUIBundle({
url: "https://petstore.swagger.io/v2/swagger.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
});
//</editor-fold>
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More