431 Commits
Author SHA1 Message Date
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-开发-李强 82a16545b4 Accept Merge Request #5: (develop -> master)
1. 新增: 大数据选择器组件
2. 新增: docker默认一键启动参数
3. 新增: 集成celery和Redis
4. 新增: 封装时间范围的搜索,示例在登录日志页面
5. 优化: 角色授权中的获取授权列表接口
6. 优化: 角色授权中字段权限显示判断
7. 优化: 个人中心修改密码成功后,强制退出登录状态
8. 优化: getBaseURL函数
9. 修复: 页面缓存无效问题
10. 修复: 登录页背景logo设置无效问题
11. 修复: 全局过滤器导致过滤无效问题
12. 修复: 当dept_belong_id为null时,触发接口报错
2024-03-12 22:45:36 +08:00
猿小天 39638e2e6a 功能变化: 优化个人中心修改密码后强制退出登录状态 2024-02-28 15:27:03 +08:00
猿小天 d3e5f258e5 修复BUG: 修复页面缓存问题 2024-02-25 16:30:49 +08:00
猿小天 77a27cba14 Merge remote-tracking branch 'origin/develop' into develop 2024-02-25 15:03:26 +08:00
猿小天 26bbf67da8 新功能: 角色授权字段权限判断 2024-02-25 15:03:07 +08:00
李强 d4f754976e Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	backend/application/settings.py
2024-02-21 17:49:38 +08:00
猿小天 85805e0d4b 功能变化: 优化角色授权中的获取授权列表 2024-02-16 22:41:15 +08:00
猿小天 8a646e5ef7 新功能: 新增大数据选择组件 2024-02-16 20:13:20 +08:00
猿小天 71eec9cfbd 修复BUG:
1.消息中心不加载目标数据问题;
2.地区数据加载问题
2024-01-18 22:12:55 +08:00
猿小天 9bd79b9dc6 修复BUG:
1.角色管理没有分页问题;
2.自定义指令引用问题
2024-01-18 20:25:02 +08:00
李强 e210b7dd17 Merge remote-tracking branch 'origin/develop' into develop 2024-01-12 00:28:56 +08:00
李强 4d00661163 feat: 优化getBaseURL函数 2024-01-12 00:28:52 +08:00
H0nGzA1 6798fca362 fix: 修复登录页背景logo设置 2024-01-04 19:49:40 +08:00
猿小天 6f5bbb045d 修复BUG: 全局过滤器导致的过滤无效 2024-01-04 16:53:12 +08:00
猿小天 bbf3018b20 Merge remote-tracking branch 'origin/develop' into develop 2024-01-03 23:23:24 +08:00
猿小天 5510a18280 修复BUG: 全局过滤器导致的过滤无效 2024-01-03 23:23:13 +08:00
李强 97737c3ef1 feat: 更新docker默认一键启动参数 2024-01-02 23:17:39 +08:00
李强 0b554f3669 feat: 默认集成celery与redis 2024-01-02 22:10:57 +08:00
猿小天 63453450ad 修复BUG: 当dept_belong_id为null时,触发接口报错 2024-01-02 14:39:16 +08:00
猿小天 fea6ebd98e Merge remote-tracking branch 'origin/develop' into develop 2024-01-02 13:50:52 +08:00
猿小天 d4e43e28e8 新功能: 封装时间范围的搜索,示例在登录日志页面 2024-01-02 13:50:43 +08:00
dvadmin dffe47e79c !13 正式发布v3.0.1版本
Merge pull request !13 from dvadmin/develop
2024-01-01 15:23:20 +00:00
dvadmin-开发-李强 9973688675 Accept Merge Request #4: (develop -> master)
Merge Request: 正式发布v3.0.1版本

Created By: @dvadmin-开发-李强
Accepted By: @dvadmin-开发-李强
URL: https://dvadmin-private.coding.net/p/code/d/dvadmin3/git/merge/4?initial=true
2024-01-01 22:56:43 +08:00
李强 98a5bbd60a feat: 文档更新 2024-01-01 22:41:01 +08:00
李强 ac7e8a7764 feat: CustomModelSerializer序列化器中dept_belong_id 添加默认值 2024-01-01 22:31:43 +08:00
猿小天 26ed59d1ff 功能变化: 修复部分bug 2024-01-01 15:30:48 +08:00
猿小天 369157fa4f 修复BUG: 当部门没有返回顶级部门时,部门接口为空情况 2024-01-01 15:00:17 +08:00
猿小天 8961733025 功能变化: 修复部分bug 2024-01-01 14:47:53 +08:00
猿小天 577b88332f 功能变化: 菜单授权返回包含父级的完整名称 2023-12-28 01:25:58 +08:00
猿小天 411f065bfc 功能变化: 菜单授权返回包含父级的完整名称 2023-12-28 00:41:07 +08:00
猿小天 8d37d929b2 功能变化: 菜单按钮排序 2023-12-27 23:35:28 +08:00
猿小天 c0d6cd54c3 Merge remote-tracking branch 'origin/develop' into develop 2023-12-27 23:29:22 +08:00
猿小天 ae8366f097 功能变化: 部门允许选择父级 2023-12-27 23:29:15 +08:00
猿小天 4baf5da0ff 新功能: 框架外路由 2023-12-27 23:22:25 +08:00
猿小天 c00f5f2beb 新功能: 框架外路由 2023-12-27 23:22:05 +08:00
李强 5143afdd85 feat: 列权限初始化 2023-12-27 21:26:21 +08:00
李强 b4153d1848 feat: 优化commonCrudConfig 2023-12-27 19:14:46 +08:00
猿小天 d2c0c54080 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	web/src/utils/commonCrud.ts
2023-12-27 19:10:53 +08:00
猿小天 7f184b2a9a 新功能:
1.部门显示组件
2023-12-27 19:08:55 +08:00
李强 87f9784445 feat: 优化commonCrudConfig顺序 2023-12-26 15:39:04 +08:00
李强 e900cc2280 feat: 优化所有类型的table居中显示 2023-12-26 15:38:09 +08:00
李强 4a26257a61 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	web/src/settings.ts
2023-12-26 15:37:08 +08:00
李强 06476c182e feat: 优化所有类型的table居中显示 2023-12-26 15:36:26 +08:00
猿小天 6146e6edb6 修复BUG:
1.新增用户时,默认密码
2023-12-26 12:46:19 +08:00
猿小天 85f5ff1935 修复BUG:
1.允许table表头被选中
2023-12-26 11:50:52 +08:00
猿小天 86e33e44e1 功能变化: 将网络字体改为本地字体 2023-12-26 11:33:37 +08:00
猿小天 217f3c8e14 Merge remote-tracking branch 'origin/develop' into develop 2023-12-26 10:49:44 +08:00
猿小天 d0feaa23e5 功能变化: 修改baseUrl函数 2023-12-26 10:49:09 +08:00
李强 fa67a27908 feat: 删除log打印 2023-12-26 09:51:45 +08:00
李强 56145dfe61 feat: 删除log打印 2023-12-26 09:50:49 +08:00
李强 3def205e6e Merge remote-tracking branch 'origin/develop' into develop 2023-12-26 09:48:06 +08:00
李强 873400bcce feat: 新增 placeholder 的默认值 2023-12-26 09:47:59 +08:00
猿小天 0c1cf1218b 新功能: 1. 引入fa 图标,兼容dvadmin2的图标 2023-12-25 20:35:33 +08:00
猿小天 d8e5ad9057 新功能: 加入隐私和条款文件 2023-12-25 20:13:05 +08:00
猿小天 3639cad58c 新功能: 1. 封装所有的更新时间、更新数据、部门等,类似dvadmin2的,一键加入 2023-12-25 20:02:43 +08:00
猿小天 c99eb1a9bc 修复BUG:
1.系统配置没有在刷新页面的时候获取;
2.登录页面网站标题等信息更新名字不刷新;
3.列权限排序,且需可以搜索
2023-12-25 18:48:11 +08:00
猿小天 7ae15022c0 新功能:
1.菜单新增框架外显示字段
2023-12-24 23:05:00 +08:00
猿小天 68b3d479ca 新功能:
1.菜单新增是否固定配置;
2.菜单新增框外显示配置
2023-12-24 12:09:15 +08:00
猿小天 2826cafa75 修复BUG:
1.验证码输入错误不刷新问题
2023-12-24 11:04:28 +08:00
猿小天 ba4a580bb0 修复BUG:
1.修复更新提示,拒绝后仍然弹出问题
2023-12-20 23:56:09 +08:00
猿小天 f322d38ab8 Merge remote-tracking branch 'origin/develop' into develop 2023-12-20 23:45:49 +08:00
猿小天 7d315a7d28 修复BUG:
1.文件上传返回值缺少问题
2023-12-20 23:44:45 +08:00
猿小天 916071814a 修复BUG:
1.接口报错后,继续通过的情况
2023-12-20 23:43:32 +08:00
李强 acf113e5e6 feat: 升级中心弹窗bug 2023-12-19 01:06:18 +08:00
李强 083eef9df1 Merge branch 'master' into develop 2023-12-19 00:54:05 +08:00
李强 e8c64d6d11 feat: 新增存活检测中间件 2023-12-19 00:50:42 +08:00
李强 d1abc58b40 feat: add __init__.py 2023-12-19 00:34:14 +08:00
李强 0c8ce0ce27 feat: 更新celery.py 添加重试机制 2023-12-18 18:14:18 +08:00
猿小天 23d320434e Merge remote-tracking branch 'origin/develop' into develop 2023-12-06 18:28:09 +08:00
猿小天 14907e140d 修复BUG:
1.修复加入列权限后没有分页问题
2023-12-06 18:28:00 +08:00
猿小天 1132e04080 修复BUG:
1.修复加入列权限后没有分页问题
2023-12-06 18:15:18 +08:00
猿小天 39fee0875d 修复BUG:
1.修复加入列权限后没有分页问题
2023-12-06 18:00:21 +08:00
符锦辉 5898ae85de chore: 后端降低channels版本为3 2023-12-05 17:56:43 +08:00
猿小天 172a867bad 修复BUG:
1.部门信息递归获取子部门
2023-12-04 23:57:40 +08:00
猿小天 5a308695fd 修复BUG:
1.个人中心密码修改,未对密码进行md5加密
2023-12-04 23:53:22 +08:00
猿小天 0c5c3c4ecd 修复BUG:
1.修复字段权限不显示序号的问题
2023-12-04 18:19:58 +08:00
猿小天 9a8241023b 修复BUG:
1.个人中心密码修改,未对密码进行md5加密
2023-12-03 23:28:53 +08:00
猿小天 27be35616b 修复BUG:
1.菜单管理中状态在编辑时显示不正确的问题
2023-12-03 23:24:32 +08:00
猿小天 e6f0a5e0ea 修复BUG:
1.修复角色相关代码中存在is_admin引起的错误
2023-12-03 23:07:18 +08:00
猿小天 cbebd8305c 修复BUG:
1.自定义权限bug
2023-12-03 23:04:26 +08:00
猿小天 66b68523f5 功能变化:
1.优化字典配置的表格显示;
2023-12-03 22:51:58 +08:00
猿小天 95b14e5a42 功能变化:
1.优化角色管理中的列权限;
2023-12-03 22:51:42 +08:00
猿小天 583b172a37 功能变化:
1.优化对字段权限的处理
2023-12-03 22:42:37 +08:00
猿小天 2227564c6f 功能变化:
1.优化对字段权限的处理
2023-12-03 15:19:42 +08:00
猿小天 5e3ecfc0a6 功能变化:
1.优化对字段权限的处理
2023-12-03 15:03:18 +08:00
猿小天 d467915f97 Merge remote-tracking branch 'origin/develop' into develop 2023-12-01 17:31:47 +08:00
猿小天 197714e845 修复BUG:
1.修复settings中上传文件的bug;
2.优化字典展示
2023-12-01 17:31:37 +08:00
dvadmin fd3adeed57 !9 正式发布v3.0.0版本
Merge pull request !9 from dvadmin/develop
2023-11-30 15:59:22 +00:00
dvadmin-开发-李强 bc15ed15ca Accept Merge Request #2: (develop -> master)
Merge Request: 正式发布v3.0.0版本

Created By: @dvadmin-开发-李强
Accepted By: @dvadmin-开发-李强
URL: https://dvadmin-private.coding.net/p/code/d/dvadmin3/git/merge/2
2023-11-30 22:13:42 +08:00
李强 349986f14f 正式发布v3.0.0版本 2023-11-30 22:02:49 +08:00
李强 fb4bba9000 feat: 样式优化 2023-11-30 21:37:43 +08:00
李强 e0d46871c4 update README.md 2023-11-30 21:33:32 +08:00
李强 7691481006 feat: 优化页面样式 2023-11-30 21:31:55 +08:00
李强 120c737de7 feat: 优化获取所有项目下的app里的models配置项 2023-11-30 20:23:52 +08:00
猿小天 5729aeb521 refactor: 上传最新的英文README 2023-11-30 16:13:49 +08:00
猿小天 205cfcca2e refactor: 上传最新的README 2023-11-30 15:57:42 +08:00
猿小天 0fdc108ebf Merge remote-tracking branch 'origin/develop' into develop 2023-11-28 10:29:52 +08:00
猿小天 064cbee8a2 1.更新系统配置初始化文件 2023-11-28 10:29:29 +08:00
李强 a2d4d6e07e feat: 初始化菜单问题 2023-11-28 10:29:05 +08:00
猿小天 d721405ee4 1.更新列权限初始化文件 2023-11-26 11:43:43 +08:00
猿小天 0650c95745 1.更新列权限初始化文件 2023-11-26 11:37:21 +08:00
猿小天 f291885d57 1.更新按钮权限初始化文件 2023-11-26 11:03:26 +08:00
猿小天 4b77fb903c refactor: 检测目前所有菜单权限 2023-11-24 15:37:47 +08:00
李强 fca82c093a feat: 优化deep 2023-11-24 15:25:41 +08:00
猿小天 49300fb17e Merge remote-tracking branch 'origin/develop' into develop 2023-11-24 15:13:23 +08:00
猿小天 c763333024 refactor: 检测目前所有菜单权限 2023-11-24 15:12:46 +08:00
李强 42e6c7b600 feat: websocket优化 2023-11-23 18:55:25 +08:00
李强 6172dea399 feat: 优化商业授权标志 2023-11-23 16:33:19 +08:00
李强 2baba8e36f feat: 版本升级弹窗 2023-11-23 16:19:35 +08:00
李强 0131d31808 feat: 优化docker 2023-11-22 21:58:10 +08:00
李强 dd2dcd4ad1 feat: 优化docker 2023-11-22 17:52:39 +08:00
猿小天 fb8b0a5ac6 refactor: 重构权限管理
1.更新字段管理
2023-11-21 21:21:58 +08:00
猿小天 6045312f7e 1.修改菜单初始化文件 2023-11-21 19:22:00 +08:00
猿小天 4641d9c774 Merge remote-tracking branch 'origin/develop' into develop 2023-11-21 17:51:31 +08:00
猿小天 c04b33ed31 refactor: 重构权限管理
1.更新字段管理
2023-11-21 17:51:19 +08:00
李强 0286ca003f feat: 更新.gitignore 2023-11-21 16:01:42 +08:00
猿小天 54e4d23cf7 refactor: 重构权限管理
1.更新字段管理
2023-11-21 14:21:08 +08:00
猿小天 645f43c887 refactor: 重构权限管理
1.更新字段管理
2023-11-20 19:03:14 +08:00
猿小天 4ac8ed7627 refactor: 重构权限管理
1.更新字段管理
2023-11-20 17:19:20 +08:00
猿小天 94ad6b1bae refactor: 重构权限管理
1.更新字段管理
2023-11-20 17:02:37 +08:00
china_ahhui 96748da99d refactor: 优化配置文件的app配置 2023-11-13 18:27:10 +08:00
china_ahhui b74a196c94 chore: 修改后端的忽略文件 2023-11-13 17:45:49 +08:00
H0nGzA1 dd98b98775 feat: 更新fast-crud版本 2023-10-27 16:05:06 +08:00
李强 8e107d9944 feat: 优化默认头像显示 2023-10-19 16:55:19 +08:00
猿小天 7159253c6b Merge remote-tracking branch 'origin/master' 2023-10-19 16:10:15 +08:00
猿小天 075d457dc7 fix: 修复列管理的问题 2023-10-19 16:10:05 +08:00
李强 564e9a112b feat: 更新requirements.txt 2023-10-19 16:01:59 +08:00
李强 3b84773a7d feat: 更新requirements.txt 2023-10-19 16:01:16 +08:00
李强 996d644d9b 删除 .DS_Store 2023-10-19 15:36:07 +08:00
china_ahhui 97f7bfd3f0 chore(用户管理): 添加了直接重置密码的约束,只允许超管可以操作 2023-10-10 17:15:40 +08:00
china_ahhui d1fdbd7e51 Merge branches 'master' and 'master' of https://e.coding.net/dvadmin/dvadmin3/dvadmin3 2023-10-10 17:12:50 +08:00
china_ahhui 802ee7f50e fix(设置): 修复env的引入bug 2023-10-10 17:12:33 +08:00
sheng ddf9fcd8e8 feat(部门管理): 查询框多行模式添加 2023-10-10 16:11:49 +08:00
sheng 6f15c2f669 feat(部门管理): 重设密码添加 2023-10-10 15:44:13 +08:00
李强 084d1c0c3e 删除lock文件 2023-10-03 23:26:15 +08:00
李强 6ded198206 Merge branch 'master' of e.coding.net:dvadmin/dvadmin3/dvadmin3
# Conflicts:
#	README.md
#	backend/conf/env.example.py
#	backend/docker_start.sh
#	backend/requirements.txt
#	docker_env/README.md
#	docker_env/mysql/launch.sh
#	docker_env/web/Dockerfile
#	docker_env/web/DockerfileBuild
#	web/.env.development
#	web/package.json
2023-10-03 23:22:43 +08:00
李强 e549d33a48 更新启动方式 2023-10-03 23:11:51 +08:00
china_ahhui cbba5a60f4 fix(用户管理): 修复在部门显示子级的情况下搜索失效的bug 2023-10-03 23:09:55 +08:00
china_ahhui 773b56a5e6 style: 修改忽略文件 2023-10-03 23:09:55 +08:00
H0nGzA1 fb29929f55 perf: 优化首页登录 2023-10-03 23:09:55 +08:00
H0nGzA1 0772a605bf perf: 登录验证码自动刷新 2023-10-03 23:09:55 +08:00
H0nGzA1 496c453615 fix: transition组件热重载显示页面空白bug 2023-10-03 23:09:55 +08:00
sheng 58b59c2ed0 部门管理隐藏统计图表添加 2023-10-03 23:09:55 +08:00
sheng 94815f4b20 列权限添加 2023-10-03 23:09:55 +08:00
猿小天 ad6ec5ca58 1.修改字段权限判断是否为超级管理员 2023-10-03 23:09:51 +08:00
猿小天 4bcaf8d45f 1.新增字段权限接口 2023-10-03 23:09:51 +08:00
猿小天 be5db33a55 1.修复单例查询时,报404问题; 2023-10-03 23:09:51 +08:00
猿小天 cfc5f8597a 1.字段权限配置完成; 2023-10-03 23:09:51 +08:00
猿小天 209e2ecced 1.菜单和按钮权限范围授权完成; 2023-10-03 23:09:51 +08:00
猿小天 4b948fe5c3 1.菜单和按钮权限范围授权完成; 2023-10-03 23:09:51 +08:00
猿小天 ea335922b2 1.菜单和按钮基本授权完成;
2. todo:
a.按钮数据权限;
b.字段权限
2023-10-03 23:09:51 +08:00
符锦辉 d290836f4f 更新文件 init_menu.json 2023-10-03 23:09:36 +08:00
符锦辉 9a426dbd72 更新文件 routing.py 2023-10-03 23:09:36 +08:00
符锦辉 a0b6e57a3c 更新文件 urls.py 2023-10-03 23:09:36 +08:00
符锦辉 5a89cdf889 删除 device 2023-10-03 23:09:36 +08:00
ahhui b6b970f7a2 feat: 物联网相关代码 2023-10-03 23:09:36 +08:00
ahhui 2722631276 test: 列权限测试 2023-10-03 23:09:36 +08:00
ahhui 478aea32f1 chore: 更新依赖文件 2023-10-03 23:09:36 +08:00
sheng cf55897552 chore: 设备管理去除 2023-10-03 23:09:36 +08:00
sheng 8020da03e2 feat( 角色管理 ): 新权限配置页面完成 2023-10-03 23:09:36 +08:00
ahhui c646563d26 chore: 更新初始化文件 2023-10-03 23:09:36 +08:00
sheng 43a4572a71 feat( 角色管理 ): 权限配置重写 2023-10-03 23:09:36 +08:00
ahhui 1582353279 fix: 修复部门和菜单新增父级下的第一个子级菜单时sort找不到的bug 2023-10-03 23:09:36 +08:00
sheng 1df2101658 feat(设备管理): 文件添加 2023-10-03 23:09:36 +08:00
ahhui 0679198d0f perf: 优化列权限新增代码 2023-10-03 23:09:36 +08:00
sheng e3c478a263 feat(列管理): 样式修改 2023-10-03 23:09:36 +08:00
ahhui 07cf81c9fe chore: 更改jwt有效期 2023-10-03 23:09:36 +08:00
ahhui 1b98fbc982 feat: 新增列权限配置功能
新增列权限配置,优化代码结构,更新初始化文件
2023-10-03 23:09:36 +08:00
ahhui 85040c808f chore: 更新初始化文件 2023-10-03 23:09:36 +08:00
ahhui 00b2cac7a5 chore: 更新初始化文件 2023-10-03 23:09:36 +08:00
ahhui 7c27f95353 refactor: 重构配置文件
所有项目的app都放在env.py里的CUSTOM_APPS列表里注册,为了支持按照项目app查找model
2023-10-03 23:09:36 +08:00
ahhui d59df6db7c perf: 优化部分代码结构 2023-10-03 23:08:41 +08:00
ahhui 34c55cfd7e fix(菜单): 修复新增菜单时排序错误的bug 2023-10-03 23:08:41 +08:00
sheng 191e57dcd9 feat(列管理): 列管理页面添加 2023-10-03 23:08:41 +08:00
ahhui 9be50863d9 chore(优化): 优化代码结构 2023-10-03 23:08:41 +08:00
ahhui 9c218a856c feat(部门): 添加部门头信息查询以及递归的头信息查询,优化部分代码 2023-10-03 23:08:41 +08:00
ahhui b4ebb14843 fix(用户): 重写user视图集的list视图,支持递归查询 2023-10-03 23:08:41 +08:00
sheng 17d80b0b00 revert(角色管理): 回滚角色管理代码 2023-10-03 23:08:41 +08:00
sheng 01394c6904 feat(部门管理): 部门详情与统计的添加 2023-10-03 23:08:41 +08:00
ahhui 59b1b54da1 fix(部门): 修复部门排序bug
修复添加新部门后排序值依旧是1导致前端上移下移部门时不生效的bug,添加部门上移下移的权限校验
2023-10-03 23:08:41 +08:00
sheng 8084ca7b0b feat(部门管理): 部门信息布局完成 2023-10-03 23:08:41 +08:00
sheng 427b6f8478 perf(菜单管理,部门管理): 编辑时父级菜单懒加载的修改 2023-10-03 23:08:37 +08:00
sheng 41a9888441 feat(菜单管理): 新建编辑完成 2023-10-03 23:08:37 +08:00
sheng 5139d37007 feat(菜单管理): 父级菜单改为树形结构 2023-10-03 23:08:37 +08:00
ahhui 44bceb6e22 chore: 更新初始化用户数据 2023-10-03 23:08:37 +08:00
sheng c2f8c43905 refactor(菜单管理管理): 组件抽离 2023-10-03 23:08:37 +08:00
ahhui 9f479e476d perf: 微调验证码配置 2023-10-03 23:08:37 +08:00
ahhui f5a7b157f9 build: 增加pillow依赖 2023-10-03 23:08:37 +08:00
sheng 7110d10432 feat(部门管理): 新增编辑实现 2023-10-03 23:08:37 +08:00
sheng 05f4e2970d feat(部门管理): 上移下移实现 2023-10-03 23:08:33 +08:00
H0nGzA1 5353a27187 feat: 后端-部门管理-上移下移排序功能&新增部门人数字段 2023-10-03 23:08:33 +08:00
H0nGzA1 f4ff5d87af fix: 移动菜单子级问题 2023-10-03 23:08:33 +08:00
sheng ceac44813f fix(菜单管理): 上移下移修改 2023-10-03 23:08:33 +08:00
H0nGzA1 a490ced370 feat: 菜单管理-上下移动功能 2023-10-03 23:07:30 +08:00
sheng 20188261c5 refactor: 菜单管理新增重构 2023-10-03 23:06:26 +08:00
sheng 15ef01aed9 feat: 部门管理新增用户列表 2023-10-03 23:06:26 +08:00
sheng 1dea6ed29c refactor: 部门新增与编辑 2023-10-03 23:06:22 +08:00
sheng 2bad36c982 feat:部门管理重构 2023-10-03 23:05:59 +08:00
sheng 941dbbc783 feat: 菜单管理菜单列表上移下移添加 2023-10-03 23:05:59 +08:00
sheng 14881707e2 页面美化:菜单管理页面优化 2023-10-03 23:05:53 +08:00
sheng d60c795ea2 页面美化:菜单管理树的美化 2023-10-03 23:05:42 +08:00
sheng 9cf5d13f1c 页面美化:登陆页面优化 2023-10-03 23:05:32 +08:00
china_ahhui e1618359d7 fix(用户管理): 修复在部门显示子级的情况下搜索失效的bug 2023-09-19 11:45:37 +08:00
china_ahhui abe7972ab4 style: 修改忽略文件 2023-09-19 10:20:19 +08:00
H0nGzA1 0709e72ae1 perf: 优化首页登录 2023-09-17 22:25:55 +08:00
H0nGzA1 2e676d64bb perf: 登录验证码自动刷新 2023-09-17 19:42:50 +08:00
H0nGzA1 98e974b55e fix: transition组件热重载显示页面空白bug 2023-09-16 02:00:22 +08:00
sheng e7f78fbae0 部门管理隐藏统计图表添加 2023-09-11 14:58:34 +08:00
sheng 111edb3f4a 列权限添加 2023-09-11 12:02:33 +08:00
猿小天 86f539a193 1.修改字段权限判断是否为超级管理员 2023-09-11 11:33:09 +08:00
李强 3b9dc7c171 更新dockerfile 2023-09-09 22:38:41 +08:00
猿小天 8175c620ed 1.新增字段权限接口 2023-09-09 10:42:55 +08:00
猿小天 de5884d28d 1.修复单例查询时,报404问题; 2023-09-07 23:24:21 +08:00
猿小天 e63a7cab6f 1.字段权限配置完成; 2023-08-19 22:06:58 +08:00
猿小天 cdfdf0a483 1.菜单和按钮权限范围授权完成; 2023-08-18 00:42:26 +08:00
猿小天 515f720a59 1.菜单和按钮权限范围授权完成; 2023-08-18 00:39:14 +08:00
猿小天 5524446dbc 1.菜单和按钮基本授权完成;
2. todo:
a.按钮数据权限;
b.字段权限
2023-08-15 23:55:46 +08:00
sheng 9c32c7f60b Merge branch 'master' of https://e.coding.net/dvadmin/dvadmin3/dvadmin3 2023-08-11 18:03:05 +08:00
sheng 6eed2814f8 chore: 设备管理去除 2023-08-11 18:02:59 +08:00
符锦辉 f03e61ce63 更新文件 init_menu.json 2023-08-11 18:01:22 +08:00
符锦辉 5450102275 更新文件 routing.py 2023-08-11 17:44:27 +08:00
符锦辉 689ce4b43b 更新文件 urls.py 2023-08-11 17:41:59 +08:00
符锦辉 65af240757 删除 device 2023-08-11 17:41:25 +08:00
ahhui f61025c6db feat: 物联网相关代码 2023-08-08 17:40:28 +08:00
ahhui 516f0ac675 test: 列权限测试 2023-08-08 16:31:55 +08:00
ahhui b1b5ae5fcd chore: 更新依赖文件 2023-08-08 16:31:18 +08:00
sheng 9a847d078f feat( 角色管理 ): 新权限配置页面完成 2023-08-08 16:16:32 +08:00
sheng dcac5c759a Merge branch 'master' of https://e.coding.net/dvadmin/dvadmin3/dvadmin3 2023-08-07 18:26:14 +08:00
sheng 13999142aa feat( 角色管理 ): 权限配置重写 2023-08-07 18:26:08 +08:00
ahhui cd6318c6fd chore: 更新初始化文件 2023-08-07 16:31:06 +08:00
sheng 3c58bcf467 Merge branch 'master' of https://e.coding.net/dvadmin/dvadmin3/dvadmin3 2023-08-07 16:22:25 +08:00
sheng b9bdf878de feat(设备管理): 文件添加 2023-08-07 16:22:17 +08:00
ahhui db989671c0 Merge remote-tracking branch 'origin/master' 2023-08-07 14:50:40 +08:00
ahhui f67869d428 fix: 修复部门和菜单新增父级下的第一个子级菜单时sort找不到的bug 2023-08-07 14:50:34 +08:00
sheng 78c693718b Merge branch 'master' of https://e.coding.net/dvadmin/dvadmin3/dvadmin3 2023-08-07 13:42:13 +08:00
sheng 653483e5de feat(列管理): 样式修改 2023-08-07 13:42:03 +08:00
ahhui 89ba768932 Merge remote-tracking branch 'origin/master' 2023-08-04 18:26:50 +08:00
ahhui e4326b6d4e perf: 优化列权限新增代码 2023-08-04 18:26:44 +08:00
sheng 97020ba4e8 Merge branch 'master' of https://e.coding.net/dvadmin/dvadmin3/dvadmin3 2023-08-04 18:13:09 +08:00
sheng 945db3344c feat(列管理): 列管理页面添加 2023-08-04 18:13:04 +08:00
ahhui 19df23ce5f chore: 更改jwt有效期 2023-08-04 18:12:58 +08:00
ahhui 97c76b6afe feat: 新增列权限配置功能
新增列权限配置,优化代码结构,更新初始化文件
2023-08-04 18:10:51 +08:00
ahhui 6597826fdd chore: 更新初始化文件 2023-08-04 12:01:12 +08:00
ahhui ee419e282e chore: 更新初始化文件 2023-08-04 11:55:34 +08:00
ahhui 3d12d7a5c5 Merge remote-tracking branch 'origin/master' 2023-08-03 18:04:43 +08:00
ahhui b2aa22ce84 refactor: 重构配置文件
所有项目的app都放在env.py里的CUSTOM_APPS列表里注册,为了支持按照项目app查找model
2023-08-03 18:04:34 +08:00
ahhui 06d7d718e1 perf: 优化部分代码结构 2023-08-03 18:02:55 +08:00
ahhui 6067dfac2f fix(菜单): 修复新增菜单时排序错误的bug 2023-08-03 18:01:49 +08:00
sheng 637e45ef83 Merge branch 'master' of https://e.coding.net/dvadmin/dvadmin3/dvadmin3 2023-08-03 17:39:09 +08:00
sheng 202f67460e revert(角色管理): 回滚角色管理代码 2023-08-03 17:39:01 +08:00
ahhui 7da23ab6a6 chore(优化): 优化代码结构 2023-08-03 10:44:57 +08:00
ahhui be51124bb6 feat(部门): 添加部门头信息查询以及递归的头信息查询,优化部分代码 2023-08-02 18:21:14 +08:00
ahhui 7e7637f10b Merge remote-tracking branch 'origin/master' 2023-08-02 18:19:59 +08:00
ahhui dbb94fb9ff fix(用户): 重写user视图集的list视图,支持递归查询 2023-08-02 18:19:52 +08:00
sheng fea61e35ef feat(部门管理): 部门详情与统计的添加 2023-08-02 17:40:57 +08:00
sheng 8ecfc1143b Merge branch 'master' of https://e.coding.net/dvadmin/dvadmin3/dvadmin3 2023-08-01 18:29:41 +08:00
sheng 7c72cc74df feat(部门管理): 部门信息布局完成 2023-08-01 18:29:31 +08:00
ahhui 12f5125207 Merge remote-tracking branch 'origin/master' 2023-08-01 15:25:48 +08:00
ahhui 770c5f73c0 fix(部门): 修复部门排序bug
修复添加新部门后排序值依旧是1导致前端上移下移部门时不生效的bug,添加部门上移下移的权限校验
2023-08-01 15:25:42 +08:00
sheng 98b81a789d perf(菜单管理,部门管理): 编辑时父级菜单懒加载的修改 2023-08-01 15:13:50 +08:00
sheng c13a5f0dda feat(菜单管理): 新建编辑完成 2023-08-01 13:50:29 +08:00
sheng a524137e18 feat(菜单管理): 父级菜单改为树形结构 2023-07-31 17:58:52 +08:00
sheng 2c9d8766e3 Merge branch 'master' of https://e.coding.net/dvadmin/dvadmin3/dvadmin3 2023-07-31 17:06:38 +08:00
sheng 8a0f18eab1 refactor(菜单管理管理): 组件抽离 2023-07-31 17:05:15 +08:00
ahhui d8511eeb7b Merge remote-tracking branch 'origin/master' 2023-07-31 16:43:42 +08:00
ahhui 52a91f1703 chore: 更新初始化用户数据 2023-07-31 16:43:37 +08:00
sheng 14395621c0 Merge branch 'master' of https://e.coding.net/dvadmin/dvadmin3/dvadmin3 2023-07-31 14:49:18 +08:00
sheng c4e2451ac8 feat(部门管理): 新增编辑实现 2023-07-31 14:49:12 +08:00
ahhui 936928e854 perf: 微调验证码配置 2023-07-31 14:45:09 +08:00
ahhui 619ebeacf1 build: 增加pillow依赖 2023-07-31 14:43:59 +08:00
sheng b95679a986 feat(部门管理): 上移下移实现 2023-07-31 10:33:57 +08:00
sheng 8e930c6a10 Merge branch 'master' of https://e.coding.net/dvadmin/dvadmin3/dvadmin3 2023-07-31 10:23:55 +08:00
sheng d2811a1f87 fix(菜单管理): 上移下移修改 2023-07-31 10:23:46 +08:00
H0nGzA1 7ac5c86ed3 feat: 后端-部门管理-上移下移排序功能&新增部门人数字段 2023-07-30 22:43:30 +08:00
H0nGzA1 eea5cdadfe Merge remote-tracking branch 'origin/master' 2023-07-28 18:12:06 +08:00
H0nGzA1 4e5f9ce946 fix: 移动菜单子级问题 2023-07-28 18:11:57 +08:00
sheng e790882ab6 Merge remote-tracking branch 'origin/master' 2023-07-28 17:24:06 +08:00
sheng 0f86f5c6fc refactor: 菜单管理新增重构 2023-07-28 17:09:41 +08:00
H0nGzA1 4018113161 Merge remote-tracking branch 'origin/master' 2023-07-28 16:14:13 +08:00
H0nGzA1 252b6a8328 feat: 菜单管理-上下移动功能 2023-07-28 16:13:33 +08:00
sheng c26abef364 feat: 部门管理新增用户列表 2023-07-28 14:37:42 +08:00
sheng a40dc9afb1 refactor: 部门新增与编辑 2023-07-28 11:16:07 +08:00
sheng 9114c6bf81 feat:部门管理重构 2023-07-27 18:31:28 +08:00
sheng 7b0a6bc2be feat: 菜单管理菜单列表上移下移添加 2023-07-26 18:12:15 +08:00
sheng e4ca462153 页面美化:菜单管理页面优化 2023-07-26 16:55:22 +08:00
sheng 840323ff07 页面美化:菜单管理树的美化 2023-07-26 13:35:47 +08:00
H0nGzA1 ed0dca7346 Merge remote-tracking branch 'origin/dev' into dev
# Conflicts:
#	web/yarn.lock
2023-07-25 18:33:45 +08:00
H0nGzA1 2b63966ee2 feat: update fast-crud 2023-07-25 18:33:11 +08:00
sheng 55a2f73420 页面美化:登陆页面优化 2023-07-25 16:45:47 +08:00
sheng 7ccbcebe77 init 2023-07-25 15:25:57 +08:00
李一民 8f57a240db Initial Commit 2023-07-25 14:53:35 +08:00
猿小天 1ccfd619f3 fix: 查询时空字符串问题 2023-07-12 21:34:57 +08:00
猿小天 dcdc7bbe3b fix: 权限问题,文件管理页面,查询时TF问题
1.权限编辑提交时,值不变问题;
2.恢复文件管理页面;
3.查询时T/F后台报错问题;
2023-07-12 21:18:49 +08:00
猿小天 90557ea039 fix: 部门懒加载
修复部门懒加载报data_range错误
2023-06-10 22:39:58 +08:00
猿小天 a462e15643 refactor: ♻️ 授权页面重构
重构授权页面
2023-06-09 20:35:06 +08:00
猿小天 6a24683769 refactor: ♻️ 授权页面重构
重构授权页面
2023-06-08 13:20:31 +08:00
猿小天 9fa1775b77 Merge remote-tracking branch 'origin/dev' into dev 2023-06-08 13:17:18 +08:00
猿小天 16605779ed refactor: ♻️ 授权页面重构
重构授权页面
2023-06-08 13:17:10 +08:00
unknown 186658858d Merge branch 'dev' of https://gitee.com/huge-dream/django-vue3-admin into dev 2023-06-07 17:20:38 +08:00
unknown 996cb1447d fix: 🐛 url false拼接bug修复 2023-06-07 17:20:31 +08:00
猿小天 e8b84ad71f refactor: ♻️ 授权页面重构
重构授权页面
2023-06-06 20:57:07 +08:00
猿小天 e7065ab89e Merge remote-tracking branch 'origin/dev' into dev 2023-06-06 20:53:08 +08:00
猿小天 e962ca6559 refactor: ♻️ 授权页面重构
重构授权页面
2023-06-06 20:52:25 +08:00
unknown 4bdc256311 perf: 💄 部门列表和菜单列表字体修改,switch颜色修改 2023-06-06 17:52:47 +08:00
猿小天 f7b94e496c refactor: ♻️ 授权页面重构
重构授权页面
2023-06-05 22:21:19 +08:00
李强 8c0a04ce61 perf: 🐛 用户登录后,每次都会变化密码加密值 2023-05-28 13:28:36 +08:00
李强 31f20a766f perf: 🐛 filter_fields使用exact无效,使用icontains等报错
fix: https://gitee.com/liqianglog/django-vue-admin/issues/I6QZW5
2023-05-28 13:24:38 +08:00
李强 65a34a683c 配置变化: 验证码接口bug 2023-05-26 20:07:36 +08:00
H0nGzA1 66effde1d9 perf: 用户-重设密码 2023-05-18 00:15:39 +08:00
H0nGzA1 f4f958d2e9 perf: 优化细节 2023-05-17 23:48:59 +08:00
H0nGzA1 d10127eafc feat: keepAlive优化 2023-05-17 23:43:05 +08:00
H0nGzA1 60035767ad feat: keepAlive功能 2023-05-17 23:34:41 +08:00
H0nGzA1 adc522c180 fix: 字典管理-配置:无搜索框问题(https://gitee.com/huge-dream/django-vue3-admin/issues/I6ZGNK) 2023-05-17 21:37:17 +08:00
H0nGzA1 17b67e43c9 refactor: 前端布局 菜单,部门:重构 2023-05-17 21:25:59 +08:00
猿小天 3cccfaa7f4 feat(文件管理): 文件管理修改 2023-05-16 00:26:49 +08:00
猿小天 ce4d410e53 feat(文件管理): 文件管理修改 2023-05-16 00:24:22 +08:00
猿小天 7b0341b527 feat(文件管理): 文件管理修改 2023-05-15 22:11:40 +08:00
猿小天 31f7175186 feat(文件管理): 文件管理修改 2023-05-15 22:11:12 +08:00
猿小天 2c22ecbac5 feat: 🎉 导入导出
用户管理加入导入导出
2023-05-13 00:36:16 +08:00
猿小天 ad59bc68aa fix: 🐛 菜单管理
1.修复菜单管理问题;
2023-05-09 00:14:29 +08:00
猿小天 8d7d775164 fix: 🐛 菜单管理和字典管理
1.修复菜单管理问题;
2.修复字典管理问题;
2023-05-09 00:05:41 +08:00
H0nGzA1 9c2dea9db5 ci: 🐳 web env文件线上环境地址配置 2023-04-29 01:03:57 +08:00
H0nGzA1 8c31bda3da ci: 🐳 docker环境 nginx代理地址修改 2023-04-29 01:02:20 +08:00
H0nGzA1 7f7d88fe5b feat: 新增支持新版租户功能 2023-04-29 00:55:23 +08:00
猿小天 19e18a7b6f feat(全局配置): 🔧 字典配置和文件上传配置 2023-04-28 18:19:29 +08:00
猿小天 cd6cd775e7 fix(个人中心): 🐛 性别选项和字典对应问题 2023-04-28 17:48:16 +08:00
猿小天 111ca9554c fix(登录): 🐛 自动登录导致登录错误问题 2023-04-28 17:34:05 +08:00
猿小天 c9fddbd4a7 fix(角色授权): 🐛 权限删除失败问题 2023-04-28 12:02:11 +08:00
猿小天 d3b057f75a fix(系统配置): 🐛 系统配置中文件上传,内容添加,分组添加
1.文件上传;
2.内容添加;
3.添加完成后刷新
2023-04-28 11:54:50 +08:00
猿小天 096f5919af fix: 🐛 系统配置中switch的文件 2023-04-28 11:05:56 +08:00
H0nGzA1 25cc2c83c1 feat: 全局crud的ElNotification提示,ElNotification的封装 2023-04-21 20:49:48 +08:00
H0nGzA1 054bcf3eea build: ⬆️ 更新依赖:fast-crud版本 2023-04-21 15:07:52 +08:00
H0nGzA1 9c24e61123 fix: 🐛 修复菜单管理的添加权限按钮bug 2023-04-21 14:37:02 +08:00
H0nGzA1 cc36ff3d8f Merge branch 'master' into dev 2023-04-21 14:30:12 +08:00
H0nGzA1 93e74dbaa2 Merge remote-tracking branch 'origin/master' 2023-04-21 14:29:53 +08:00
H0nGzA1 5ab3ad4ed9 fix: 🐛 get查询params参数问题,导致分页不成功 2023-04-21 14:27:03 +08:00
李强 9468abd4a2 新功能: 取消loguru日志,使用简单Django日志 2023-04-17 23:24:43 +08:00
H0nGzA1 1080af6413 Merge remote-tracking branch 'origin/dev' into dev 2023-04-14 23:09:15 +08:00
H0nGzA1 c4c9a81ac8 fix: 🐛 部门删除bug 2023-04-13 02:24:31 +08:00
H0nGzA1 d1223dddd3 feat: 用户管理细节优化 2023-04-12 02:13:57 +08:00
H0nGzA1 3dd68e5d21 feat: 账号锁定功能 2023-04-12 01:58:34 +08:00
H0nGzA1 0668cb087f fix: 🐛 修复添加用户bug 2023-04-12 00:25:36 +08:00
H0nGzA1 38494edea5 fix: 🐛 头像更新问题 2023-04-11 22:43:33 +08:00
猿小天 2f04f22904 fix(文件上传): 🐛 文件上传 2023-04-11 19:09:12 +08:00
H0nGzA1 0ccf2e3725 fix: 🐛 头像上传,文件上传问题 2023-04-11 18:40:20 +08:00
H0nGzA1 68cd42246d Merge remote-tracking branch 'origin/dev' into dev 2023-04-10 17:59:42 +08:00
H0nGzA1 dc6ddda4ab refactor: ♻️ 重构部门管理前端 2023-04-10 17:59:30 +08:00
猿小天 08b56ee663 refactor(所有页面): ♻️ 所有页面的按钮权限配置 2023-04-10 16:45:49 +08:00
猿小天 82d7193dcb refactor(所有页面): ♻️ 所有页面的表格列宽优化 2023-04-10 09:45:25 +08:00
H0nGzA1 4b609ed7e4 Merge remote-tracking branch 'origin/dev' into dev 2023-04-10 00:04:00 +08:00
H0nGzA1 61066b8caa refactor: ♻️ 重构菜单管理前端 2023-04-10 00:03:17 +08:00
猿小天 160c376f81 fix(登录): 登录优化 2023-04-09 23:09:35 +08:00
猿小天 b96c220f1d refactor(登录页面): ♻️ 登录优化 2023-04-09 23:08:30 +08:00
猿小天 42884685ff refactor(用户管理): ♻️ 用户管理优化 2023-04-09 22:26:26 +08:00
H0nGzA1 58e611cb22 feat: 前端自动注册插件功能 2023-03-31 00:25:23 +08:00
H0nGzA1 0b2fa1e92c refactor: ♻️ 更新优化精简fast-crud结构 2023-03-30 21:42:45 +08:00
H0nGzA1 125dfd4dd5 feat: 更新fast-crud版本以及依赖 2023-03-30 18:21:38 +08:00
H0nGzA1 af7bfcd215 feat: 所有菜单页面状态改为可编辑开关 2023-03-30 18:12:38 +08:00
H0nGzA1 6917786864 feat: 所有菜单页面样式优化~ 2023-03-30 16:43:57 +08:00
H0nGzA1 cd60b3d9be refactor: ♻️ 菜单管理重构完成 2023-03-30 15:37:27 +08:00
H0nGzA1 cfae363f53 feat: 精简化初始化菜单json文件 2023-03-29 18:29:19 +08:00
H0nGzA1 4f8686b35e feat: 登录页面,自动刷新验证码 2023-03-28 21:21:05 +08:00
H0nGzA1 1407a25b6a feat: 登录页面回车登录功能 2023-03-28 20:35:35 +08:00
H0nGzA1 7c78c7e53f refactor: ♻️ 菜单管理重构完成 2023-03-28 20:09:49 +08:00
H0nGzA1 f1da0507dd Merge remote-tracking branch 'origin/dev' into dev 2023-03-24 18:46:42 +08:00
H0nGzA1 df74c792f6 refactor: ♻️ 用户管理重构 2023-03-24 18:45:58 +08:00
raymond 4c697ef9d4 Merge remote-tracking branch 'origin/dev' into dev 2023-03-24 16:42:12 +08:00
raymond ab1d1c87e2 feat: 支持通过python manage.py createsuperuser创建管理员用户 2023-03-24 16:41:22 +08:00
H0nGzA1 b6b3b4a39a feat: 🚀 更新nginx配置文件 2023-03-24 15:17:43 +08:00
H0nGzA1 581e4cd92f feat: 🔧 tailwind css 配置 2023-03-23 21:10:07 +08:00
H0nGzA1 fb76d4271b Merge remote-tracking branch 'origin/dev' into dev 2023-03-23 18:13:49 +08:00
victory 4f176cf88a Merge branch 'dev' of gitee.com:huge-dream/django-vue3-admin into dev 2023-03-22 22:39:22 +08:00
victory af5f3b7137 feat:添加对url中排序参数的解析 2023-03-22 22:39:19 +08:00
victory b03037b866 fix:优化角色管理用户管理和接口白名单的页面显示 2023-03-22 22:39:08 +08:00
386 changed files with 24524 additions and 10303 deletions
Vendored
BIN
View File
Binary file not shown.
+2 -1
View File
@@ -3,4 +3,5 @@
.idea
.history/
.vscode/
.vscode/
web/package-lock.json
+10
View File
@@ -0,0 +1,10 @@
# Django-Vue3-Admin 更新日志
## 正式发布v3.0.0版本
### 1.新增:列权限管理与授权;
### 2.新增:代码新版本发布后,进行升级提醒;
### 3.优化:角色管理中按钮权限的操作;
### 4.优化:websocket 连接状态显示;
### 5.优化:初始化获取系统配置与字典配置,进行动态渲染登录页面;
### 6.修复:登录页面中系统配置不生效问题;
### 7.其他优化
+106 -127
View File
@@ -1,196 +1,175 @@
# 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)
[![img](https://img.shields.io/badge/license-MIT-blue.svg)](https://gitee.com/huge-dream/django-vue3-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/huge-dream/django-vue3-admin/badge/star.svg?theme=dark)](https://gitee.com/huge-dream/django-vue3-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)
[preview](https://demo.dvadmin.com) | [Official website document](https://www.django-vue-admin.com) | [qq group](https://qm.qq.com/cgi-bin/qm/qr?k=fOdnHhC8DJlRHGYSnyhoB8P5rgogA6Vs&jump_from=webapi) | [community](https://bbs.django-vue-admin.com) | [plugins market](https://bbs.django-vue-admin.com/plugMarket.html) | [Github](https://github.com/liqianglog/django-vue-admin)
💡 **「About」**
It is a completely open-source rapid development platform, provided free for personal use and authorized for group use.
Django-Vue3-Admin is a comprehensive basic development platform based on the RBAC (Role-Based Access Control) model for permission control, with column-level granularity. It follows a frontend-backend separation architecture, with Django and Django Rest Framework used for the backend, and Vue3, Composition API, TypeScript, Vite, and Element Plus used for the frontend.
## framework introduction
💡 **「关于」**
💡 [django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin.git) Is a set of all open source rapid development platform, no reservation for individuals and enterprises free use.
我们是一群热爱代码的青年,在这个炙热的时代下,我们希望静下心来通过Code带来一点我们的色彩和颜色
* 🧑‍🤝‍🧑Front-end adoption Vue3+TS+pinia+fastcrud
* 👭The backend uses the Python language Django framework as well as the powerful[Django REST Framework](https://pypi.org/project/djangorestframework)。
* 👫Permission authentication use[Django REST Framework SimpleJWT](https://pypi.org/project/djangorestframework-simplejwt)Supports the multi-terminal authentication system.
* 👬Support loading dynamic permission menu, multi - way easy permission control.
* 👬Enhanced Column Permission Control, with granularity down to each column.
* 💏Special thanks: [vue-next-admin](https://lyt-top.gitee.io/vue-next-admin-doc-preview/).
* 💡Special thanks:[jetbrains](https://www.jetbrains.com/) To provide a free IntelliJ IDEA license for this open source project.
因为热爱,所以拥抱未来
## Online experience
## 平台简介
👩‍👧‍👦👩‍👧‍👦 demo address:[https://demo.dvadmin.com](https://demo.dvadmin.com)
💡 [django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin.git) 是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。
* demo accountsuperadmin
* demo passwordadmin123456
👩‍👦‍👦docs:[https://django-vue-admin.com](https://django-vue-admin.com)
* 🧑‍🤝‍🧑前端采用 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 授权。
## communication
* Communication community:[click here](https://bbs.django-vue-admin.com)👩‍👦‍👦
* plugins market:[click here](https://bbs.django-vue-admin.com/plugMarket.html)👩‍👦‍👦
## 在线体验
## source code url:
👩‍👧‍👦演示地址[http://demo.django-vue-admin.com](http://demo.django-vue-admin.com)
gitee(Main push)[https://gitee.com/huge-dream/django-vue3-admin](https://gitee.com/huge-dream/django-vue3-admin)👩‍👦‍👦
- 账号:superadmin
github[https://github.com/huge-dream/django-vue3-admin](https://github.com/huge-dream/django-vue3-admin)👩‍👦‍👦
- 密码:admin123456
## core function
👩‍👦‍👦文档地址:[https://django-vue-admin.com](https://django-vue-admin.com)
1. 👨‍⚕️Menu Management: Configure system menus, operation permissions, button permission flags, backend interface permissions, etc.
2. 🧑‍⚕️Department Management: Configure system organizational structure (company, department, role).
3. 👩‍⚕️Role Management: Role menu permission assignment, data permission assignment, set role-based data scope permissions by department.
4. 🧑‍🎓Button Permission Control: Authorize role-specific button permissions and interface permissions, enabling authorization of data scope for each interface.
5. 🧑‍🎓Field Column Permission Control: Authorize page field display permissions, specifically for the display permissions of a certain column.
6. 👨‍🎓User Management: Users are system operators, and this function is mainly used for system user configuration.
7. 👬API Whitelist: Configure interfaces that do not require permission verification.
8. 🧑‍🔧Dictionary Management: Maintain frequently used and relatively fixed data in the system.
9. 🧑‍🔧Region Management: Manage provinces, cities, counties, and districts.
10. 📁File Management: Unified management of all files, images, etc., on the platform.
11. 🗓️Operation Logs: Record and query logs for normal system operations and exceptional system information.
12. 🔌[Plugin Market](https://bbs.django-vue-admin.com/plugMarket.html): Applications and plugins developed based on the Django-Vue-Admin framework.
## plugins market 🔌
Updating...
## 交流
## Repository Branch Explanation 💈
Main Branch: master (stable version)
Development Branch: develop
- 交流社区:[戳我](https://bbs.django-vue-admin.com)👩‍👦‍👦
## before start project you need:
- 插件市场:[戳我](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)
- 二维码
<img src='https://images.gitee.com/uploads/images/2022/0530/233203_5fb11883_5074988.jpeg' width='200'>
## 源码地址
gitee地址(主推)[https://gitee.com/liqianglog/django-vue-admin](https://gitee.com/liqianglog/django-vue-admin)👩‍👦‍👦
github地址:[https://github.com/liqianglog/django-vue-admin](https://github.com/liqianglog/django-vue-admin)👩‍👦‍👦
## 内置功能
1. 👨‍⚕️菜单管理:配置系统菜单,操作权限,按钮权限标识、后端接口权限等。
2. 🧑‍⚕️部门管理:配置系统组织机构(公司、部门、角色)。
3. 👩‍⚕️角色管理:角色菜单权限分配、数据权限分配、设置角色按部门进行数据范围权限划分。
4. 🧑‍🎓权限权限:授权角色的权限范围。
5. 👨‍🎓用户管理:用户是系统操作者,该功能主要完成系统用户配置。
6. 👬接口白名单:配置不需要进行权限校验的接口。
7. 🧑‍🔧字典管理:对系统中经常使用的一些较为固定的数据进行维护。
8. 🧑‍🔧地区管理:对省市县区域进行管理。
9. 📁附件管理:对平台上所有文件、图片等进行统一管理。
10. 🗓️操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
11. 🔌[插件市场 ](https://bbs.django-vue-admin.com/plugMarket.html):基于Django-Vue-Admin框架开发的应用和插件。
## 插件市场 🔌
- Celery异步任务:[dvadmin-celery](https://gitee.com/huge-dream/dvadmin-celery)
- 升级中心后端:[dvadmin-upgrade-center](https://gitee.com/huge-dream/dvadmin-upgrade-center)
- 升级中心前端:[dvadmin-upgrade-center-web](https://gitee.com/huge-dream/dvadmin-upgrade-center-web)
## 准备工作
~~~
Python >= 3.8.0 (推荐3.8+版本)
nodejs >= 14.0 (推荐最新)
Mysql >= 5.7.0 (可选,默认数据库sqlite3,推荐8.0版本)
Redis(可选,最新版)
Python >= 3.11.0 (Minimum version 3.9+)
Node.js >= 16.0
Mysql >= 8.0 (Optional, default database: SQLite3, supports 5.7+, recommended version: 8.0)
Redis (Optional, latest version)
~~~
## 前端
## frontend
```bash
# 克隆项目
git clone https://gitee.com/liqianglog/django-vue-admin.git
# clone code
git clone https://gitee.com/huge-dream/django-vue3-admin.git
# 进入项目目录
# enter code dir
cd web
# 安装依赖
npm install --registry=https://registry.npm.taobao.org
# install dependence
npm install yarn
yarn install --registry=https://registry.npm.taobao.org
# 启动服务
npm run dev
# 浏览器访问 http://localhost:8080
# .env.development 文件中可配置启动端口等参数
# 构建生产环境
# npm run build
# Start service
yarn run dev
# Visit http://localhost:8080 in your browser
# Parameters such as boot port can be configured in the #.env.development file
# Build the production environment
# yarn run build
```
## 后端💈
## backend💈
~~~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
或使用 daphne :
daphne -b 0.0.0.0 -p 8000 application.asgi:application
1. enter code dir cd backend
2. copy ./conf/env.example.py to ./conf dirrename as env.py
3. in env.py configure database information
mysql database recommended version: 8.0
mysql database character set: utf8mb4
4. install pip dependence
pip3 install -r requirements.txt
5. Execute the migration command:
python3 manage.py makemigrations
python3 manage.py migrate
6. Initialization data
python3 manage.py init
7. Initialize provincial, municipal and county data:
python3 manage.py init_area
8. start backend
python3 manage.py runserver 0.0.0.0:8000
or uvicorn :
uvicorn application.asgi:application --port 8000 --host 0.0.0.0 --workers 8
~~~
### 访问项目
### visit backend swagger
- 访问地址[http://localhost:8080](http://localhost:8080) (默认为此地址,如有修改请按照配置文件)
- 账号`superadmin` 密码`admin123456`
* visit url[http://localhost:8080](http://localhost:8080) (The default address is this one. If you want to change it, follow the configuration file)
* account`superadmin` password`admin123456`
### docker-compose 运行
### docker-compose
~~~shell
# 先安装docker-compose (自行百度安装),执行此命令等待安装,如有使用celery插件请打开docker-compose.yml中celery 部分注释
docker-compose up -d
# 初始化后端数据(第一次执行即可)
docker exec -ti dvadmin-django bash
# Initialize backend data (first execution only)
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
frontend urlhttp://127.0.0.1:8080
backend urlhttp://127.0.0.1:8080/api
# Change 127.0.0.1 to your own public ip address on the server
account`superadmin` password`admin123456`
# docker-compose 停止
# docker-compose stop
docker-compose down
# docker-compose 重启
# docker-compose restart
docker-compose restart
# docker-compose 启动时重新进行 build
# docker-compose on start build
docker-compose up -d --build
~~~
## Demo screenshot✅
![image-01](https://foruda.gitee.com/images/1701348994587355489/1bc749e7_5074988.png)
## 演示图✅
![image-02](https://foruda.gitee.com/images/1701349037811908960/80d361db_5074988.png)
![image-01](https://images.gitee.com/uploads/images/2022/0530/234137_b58c8f98_5074988.png)
![image-03](https://foruda.gitee.com/images/1701349224478845203/954f0a7b_5074988.png)
![image-02](https://images.gitee.com/uploads/images/2022/0530/234240_39834603_5074988.png)
![image-04](https://foruda.gitee.com/images/1701349248928658877/64926724_5074988.png)
![image-03](https://images.gitee.com/uploads/images/2022/0530/234339_35e728a0_5074988.png)
![image-05](https://foruda.gitee.com/images/1701349259068943299/1306ba40_5074988.png)
![image-04](https://images.gitee.com/uploads/images/2022/0530/234426_957036b0_5074988.png)
![image-06](https://foruda.gitee.com/images/1701349294894429495/e3b3a8cf_5074988.png)
![image-05](https://images.gitee.com/uploads/images/2022/0530/234458_898be492_5074988.png)
![image-07](https://foruda.gitee.com/images/1701350432536247561/3b26685e_5074988.png)
![image-06](https://images.gitee.com/uploads/images/2022/0530/234521_35b40076_5074988.png)
![image-08](https://foruda.gitee.com/images/1701350455264771992/b364c57f_5074988.png)
![image-07](https://images.gitee.com/uploads/images/2022/0530/234615_c2325639_5074988.png)
![image-09](https://foruda.gitee.com/images/1701350479266000753/e4e4f7c5_5074988.png)
![image-08](https://images.gitee.com/uploads/images/2022/0530/234639_1ed6cc93_5074988.png)
![image-09](https://images.gitee.com/uploads/images/2022/0530/234815_cea2c53f_5074988.png)
![image-10](https://images.gitee.com/uploads/images/2022/0530/234840_5f3e5f53_5074988.png)
![image-10](https://foruda.gitee.com/images/1701350501421625746/f8dd215e_5074988.png)
+214
View File
@@ -0,0 +1,214 @@
# 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)
- 二维码
<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)
+2 -1
View File
@@ -91,10 +91,11 @@ ENV/
**/migrations/*.py
!**/migrations/__init__.py
*.pyc
conf/
conf/*
!conf/env.example.py
db.sqlite3
media/
__pypackages__/
package-lock.json
gunicorn.pid
!plugins/__init__.py
+22
View File
@@ -1,9 +1,11 @@
import functools
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
from django.conf import settings
from celery import platforms
# 租户模式
if "django_tenants" in settings.INSTALLED_APPS:
from tenant_schemas_celery.app import CeleryApp as TenantAwareCeleryApp
@@ -16,3 +18,23 @@ else:
app.config_from_object('django.conf:settings')
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
platforms.C_FORCE_ROOT = True
def retry_base_task_error():
"""
celery 失败重试装饰器
:return:
"""
def wraps(func):
@app.task(bind=True, retry_delay=180, max_retries=3)
@functools.wraps(func)
def wrapper(self, *args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as exc:
raise self.retry(exc=exc)
return wrapper
return wraps
+1 -2
View File
@@ -3,6 +3,5 @@ from django.urls import path
from application.websocketConfig import MegCenter
websocket_urlpatterns = [
path('ws/<str:service_uid>/', MegCenter.as_asgi()), #consumers.DvadminWebSocket 是该路由的消费者
path('ws/<str:service_uid>/', MegCenter.as_asgi()), # consumers.DvadminWebSocket 是该路由的消费者
]
+70 -48
View File
@@ -13,6 +13,7 @@ https://docs.djangoproject.com/en/4.1/ref/settings/
import os
import sys
from pathlib import Path
from datetime import timedelta
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
@@ -42,7 +43,8 @@ sys.path.insert(0, os.path.join(PLUGINS_PATH))
DEBUG = locals().get("DEBUG", True)
ALLOWED_HOSTS = locals().get("ALLOWED_HOSTS", ["*"])
# Application definition
# 列权限需要排除的App应用
COLUMN_EXCLUDE_APPS = ['channels', 'captcha'] + locals().get("COLUMN_EXCLUDE_APPS", [])
INSTALLED_APPS = [
"django.contrib.auth",
@@ -54,13 +56,14 @@ INSTALLED_APPS = [
"rest_framework",
"django_filters",
"corsheaders", # 注册跨域app
"dvadmin.system",
"drf_yasg",
"captcha",
'channels',
"channels",
"dvadmin.system",
]
MIDDLEWARE = [
"dvadmin.utils.middleware.HealthCheckMiddleware",
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
@@ -152,6 +155,11 @@ STATICFILES_DIRS = [
MEDIA_ROOT = "media" # 项目下的目录
MEDIA_URL = "/media/" # 跟STATIC_URL类似,指定用户可以通过这个url找到文件
#添加以下代码以后就不用写{% load staticfiles %},可以直接引用
STATICFILES_FINDERS = (
"django.contrib.staticfiles.finders.FileSystemFinder",
"django.contrib.staticfiles.finders.AppDirectoriesFinder"
)
# 收集静态文件,必须将 MEDIA_ROOT,STATICFILES_DIRS先注释
# python manage.py collectstatic
# STATIC_ROOT=os.path.join(BASE_DIR,'static')
@@ -165,9 +173,9 @@ CORS_ORIGIN_ALLOW_ALL = True
# 允许cookie
CORS_ALLOW_CREDENTIALS = True # 指明在跨域访问中,后端是否支持对cookie的操作
# ================================================= #
# ===================================================== #
# ********************* channels配置 ******************* #
# ================================================= #
# ===================================================== #
ASGI_APPLICATION = 'application.asgi.application'
CHANNEL_LAYERS = {
"default": {
@@ -187,69 +195,83 @@ CHANNEL_LAYERS = {
# ================================================= #
# ********************* 日志配置 ******************* #
# ================================================= #
# log 配置部分BEGIN #
# # log 配置部分BEGIN #
SERVER_LOGS_FILE = os.path.join(BASE_DIR, "logs", "server.log")
ERROR_LOGS_FILE = os.path.join(BASE_DIR, "logs", "error.log")
LOGS_FILE = os.path.join(BASE_DIR, "logs")
if not os.path.exists(os.path.join(BASE_DIR, "logs")):
os.makedirs(os.path.join(BASE_DIR, "logs"))
# 格式:[2020-04-22 23:33:01][micoservice.apps.ready():16] [INFO] 这是一条日志:
# 格式:[日期][模块.函数名称():行号] [级别] 信息
STANDARD_LOG_FORMAT = (
"[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s"
)
CONSOLE_LOG_FORMAT = (
"[%(asctime)s][%(name)s.%(funcName)s():%(lineno)d] [%(levelname)s] %(message)s"
)
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"servers": {
"format": "%(message)s",
"standard": {"format": STANDARD_LOG_FORMAT},
"console": {
"format": CONSOLE_LOG_FORMAT,
"datefmt": "%Y-%m-%d %H:%M:%S",
}
},
"file": {
"format": CONSOLE_LOG_FORMAT,
"datefmt": "%Y-%m-%d %H:%M:%S",
},
},
"handlers": {
"servers": {
"logging_levels": ["info", "error", "warning"],
"class": "dvadmin.utils.log.InterceptTimedRotatingFileHandler", # 这个路径看你本地放在哪里(下面的log文件)
"filename": os.path.join(LOGS_FILE, "server.log"),
"when": "D",
"interval": 1,
"file": {
"level": "INFO",
"class": "logging.handlers.RotatingFileHandler",
"filename": SERVER_LOGS_FILE,
"maxBytes": 1024 * 1024 * 100, # 100 MB
"backupCount": 1,
"formatter": "servers",
"backupCount": 5, # 最多备份5个
"formatter": "standard",
"encoding": "utf-8",
}
},
"error": {
"level": "ERROR",
"class": "logging.handlers.RotatingFileHandler",
"filename": ERROR_LOGS_FILE,
"maxBytes": 1024 * 1024 * 100, # 100 MB
"backupCount": 3, # 最多备份3个
"formatter": "standard",
"encoding": "utf-8",
},
"console": {
"level": "INFO",
"class": "logging.StreamHandler",
"formatter": "console",
},
},
"loggers": {
# default日志
'django': {
'handlers': ['servers'],
'propagate': False,
'level': "INFO"
"": {
"handlers": ["console", "error", "file"],
"level": "INFO",
},
'': {
'handlers': ['servers'],
'propagate': False,
'level': "ERROR"
},
'celery': {
'handlers': ['servers'],
'propagate': False,
'level': "INFO"
"django": {
"handlers": ["console", "error", "file"],
"level": "INFO",
"propagate": False,
},
'django.db.backends': {
'handlers': ['servers'],
'handlers': ["console", "error", "file"],
'propagate': False,
'level': "INFO"
},
'django.request': {
'handlers': ['servers'],
'propagate': False,
'level': "DEBUG"
},
"uvicorn.error": {
"level": "INFO",
"handlers": ["servers"],
"handlers": ["console", "error", "file"],
},
"uvicorn.access": {
"handlers": ["servers"],
"level": "INFO",
"propagate": False
"handlers": ["console", "error", "file"],
"level": "INFO"
},
},
}
@@ -289,11 +311,9 @@ AUTHENTICATION_BACKENDS = ["dvadmin.utils.backends.CustomBackend"]
# ================================================= #
# ****************** simplejwt配置 ***************** #
# ================================================= #
from datetime import timedelta
SIMPLE_JWT = {
# token有效时长
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=120),
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=1440),
# token刷新后的有效时间
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),
# 设置前缀
@@ -329,11 +349,11 @@ SWAGGER_SETTINGS = {
# ================================================= #
# **************** 验证码配置 ******************* #
# ================================================= #
CAPTCHA_IMAGE_SIZE = (160, 60) # 设置 captcha 图片大小
CAPTCHA_IMAGE_SIZE = (160, 46) # 设置 captcha 图片大小
CAPTCHA_LENGTH = 4 # 字符个数
CAPTCHA_TIMEOUT = 1 # 超时(minutes)
CAPTCHA_OUTPUT_FORMAT = "%(image)s %(text_field)s %(hidden_field)s "
CAPTCHA_FONT_SIZE = 40 # 字体大小
CAPTCHA_FONT_SIZE = 36 # 字体大小
CAPTCHA_FOREGROUND_COLOR = "#64DAAA" # 前景色
CAPTCHA_BACKGROUND_COLOR = "#F5F7F4" # 背景色
CAPTCHA_NOISE_FUNCTIONS = (
@@ -388,5 +408,7 @@ PLUGINS_URL_PATTERNS = []
# from dvadmin_third.settings import * # 第三方用户管理
# from dvadmin_ak_sk.settings import * # 秘钥管理管理
# from dvadmin_tenants.settings import * # 租户管理
#from dvadmin_social_auth.settings import *
#from dvadmin_uniapp.settings import *
# ...
# ********** 一键导入插件配置结束 **********
+34
View File
@@ -30,6 +30,7 @@ from dvadmin.system.views.login import (
CaptchaView,
ApiLogin,
LogoutView,
LoginTokenView
)
from dvadmin.system.views.system_config import InitSettingsViewSet
from dvadmin.utils.swagger import CustomOpenAPISchemaGenerator
@@ -52,6 +53,33 @@ schema_view = get_schema_view(
permission_classes=(permissions.AllowAny,),
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 = (
[
@@ -81,6 +109,12 @@ urlpatterns = (
path("api/init/dictionary/", InitDictionaryViewSet.as_view()),
path("api/init/settings/", InitSettingsViewSet.as_view()),
path("apiLogin/", ApiLogin.as_view()),
# 仅用于开发,上线需关闭
path("api/token/", LoginTokenView.as_view()),
# 前端页面映射
path('web/', web_view, name='web_view'),
path('web/<path:filename>', serve_web_files, name='serve_web_files'),
]
+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+ static(settings.STATIC_URL, document_root=settings.STATIC_URL)
+11 -9
View File
@@ -73,7 +73,7 @@ class DvadminWebSocket(AsyncJsonWebsocketConsumer):
unread_count = await _get_message_unread(self.user_id)
if unread_count == 0:
# 发送连接成功
await self.send_json(set_message('system', 'SYSTEM', '连接成功'))
await self.send_json(set_message('system', 'SYSTEM', '您已上线'))
else:
await self.send_json(
set_message('system', 'SYSTEM', "请查看您的未读消息~",
@@ -122,7 +122,8 @@ class MessageCreateSerializer(CustomModelSerializer):
fields = "__all__"
read_only_fields = ["id"]
def websocket_push(user_id,message):
def websocket_push(user_id, message):
username = "user_" + str(user_id)
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
@@ -133,8 +134,9 @@ def websocket_push(user_id,message):
}
)
def create_message_push(title: str, content: str, target_type: int=0, target_user: list=[], target_dept=None, target_role=None,
message: dict = {'contentType': 'INFO', 'content': '测试~'}, request= Request):
def create_message_push(title: str, content: str, target_type: int = 0, target_user: list = None, target_dept=None,
target_role=None, message: dict = None, request=Request):
if message is None:
message = {"contentType": "INFO", "content": None}
if target_role is None:
@@ -145,11 +147,11 @@ def create_message_push(title: str, content: str, target_type: int=0, target_use
"title": title,
"content": content,
"target_type": target_type,
"target_user":target_user,
"target_dept":target_dept,
"target_role":target_role
"target_user": target_user,
"target_dept": target_dept,
"target_role": target_role
}
message_center_instance = MessageCreateSerializer(data=data,request=request)
message_center_instance = MessageCreateSerializer(data=data, request=request)
message_center_instance.is_valid(raise_exception=True)
message_center_instance.save()
users = target_user or []
@@ -176,6 +178,6 @@ def create_message_push(title: str, content: str, target_type: int=0, target_use
username,
{
"type": "push.message",
"json": {**message,'unread':unread_count}
"json": {**message, 'unread': unread_count}
}
)
+13 -12
View File
@@ -7,30 +7,32 @@ from application.settings import BASE_DIR
# ================================================= #
# 数据库 ENGINE ,默认演示使用 sqlite3 数据库,正式环境建议使用 mysql 数据库
# sqlite3 设置
DATABASE_ENGINE = "django.db.backends.sqlite3"
DATABASE_NAME = os.path.join(BASE_DIR, "db.sqlite3")
# DATABASE_ENGINE = "django.db.backends.sqlite3"
# DATABASE_NAME = os.path.join(BASE_DIR, "db.sqlite3")
# 使用mysql时,改为此配置
# DATABASE_ENGINE = "django.db.backends.mysql"
# DATABASE_NAME = 'django-vue-admin' # mysql 时使用
DATABASE_ENGINE = "django.db.backends.mysql"
DATABASE_NAME = 'django-vue3-admin' # mysql 时使用
# 数据库地址 改为自己数据库地址
DATABASE_HOST = "127.0.0.1"
DATABASE_HOST = '127.0.0.1'
# # 数据库端口
DATABASE_PORT = 3306
# # 数据库用户名
DATABASE_USER = "root"
# # 数据库密码
DATABASE_PASSWORD = "123456"
DATABASE_PASSWORD = 'DVADMIN3'
# 表前缀
TABLE_PREFIX = "dvadmin_"
# ================================================= #
# ******** redis配置,无redis 可不进行配置 ******** #
# ================================================= #
# REDIS_PASSWORD = ''
# REDIS_HOST = '127.0.0.1'
# REDIS_URL = f'redis://:{REDIS_PASSWORD or ""}@{REDIS_HOST}:6380'
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'
# ================================================= #
# ****************** 功能 启停 ******************* #
# ================================================= #
@@ -44,6 +46,5 @@ LOGIN_NO_CAPTCHA_AUTH = True
# ================================================= #
ALLOWED_HOSTS = ["*"]
# daphne启动命令
#daphne application.asgi:application -b 0.0.0.0 -p 8000
# 列权限中排除App应用
COLUMN_EXCLUDE_APPS = []
+1 -1
View File
@@ -4,7 +4,7 @@ import os
exclude = ["venv"] # 需要排除的文件目录
for root, dirs, files in os.walk('.'):
dirs[:] = [d for d in set(dirs) - set(exclude)]
dirs[:] = list(set(dirs) - set(exclude))
if 'migrations' in dirs:
dir = dirs[dirs.index('migrations')]
for root_j, dirs_j, files_j in os.walk(os.path.join(root, dir)):
+1 -1
View File
@@ -2,4 +2,4 @@
# python manage.py makemigrations
# python manage.py migrate
# python manage.py init -y
gunicorn -c gunicorn_conf.py application.asgi:application
uvicorn application.asgi:application --port 8000 --host 0.0.0.0 --workers 4
@@ -5,9 +5,13 @@ from rest_framework import serializers
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
import django
django.setup()
from dvadmin.system.models import Role, Dept, Users, Menu, MenuButton, ApiWhiteList, Dictionary, SystemConfig, \
RoleMenuPermission, RoleMenuButtonPermission
from dvadmin.system.models import (
Role, Dept, Users, Menu, MenuButton,
ApiWhiteList, Dictionary, SystemConfig,
RoleMenuPermission, RoleMenuButtonPermission, MenuField
)
from dvadmin.utils.serializers import CustomModelSerializer
@@ -50,6 +54,16 @@ class MenuButtonInitSerializer(CustomModelSerializer):
read_only_fields = ["id"]
class MenuFieldInitSerializer(CustomModelSerializer):
"""
初始化列权限-序列化器
"""
class Meta:
model = MenuField
fields = ['id', 'menu', 'field_name', 'title', 'model']
read_only_fields = ["id"]
class MenuInitSerializer(CustomModelSerializer):
"""
@@ -58,6 +72,7 @@ class MenuInitSerializer(CustomModelSerializer):
name = serializers.CharField(required=False)
children = serializers.SerializerMethodField()
menu_button = serializers.SerializerMethodField()
menu_field = serializers.SerializerMethodField()
def get_children(self, obj: Menu):
data = []
@@ -74,10 +89,18 @@ class MenuInitSerializer(CustomModelSerializer):
data = list(instance.values('name', 'value', 'api', 'method'))
return data
def get_menu_field(self, obj: Menu):
data = []
instance = obj.menufield_set.order_by('field_name')
if instance:
data = list(instance.values('field_name', 'title', 'model'))
return data
def save(self, **kwargs):
instance = super().save(**kwargs)
children = self.initial_data.get('children')
menu_button = self.initial_data.get('menu_button')
menu_field = self.initial_data.get('menu_field')
# 菜单表
if children:
for menu_data in children:
@@ -106,12 +129,25 @@ class MenuInitSerializer(CustomModelSerializer):
serializer = MenuButtonInitSerializer(instance_obj, data=menu_button_data, request=self.request)
serializer.is_valid(raise_exception=True)
serializer.save()
# 列权限
if menu_field:
for field_data in menu_field:
field_data['menu'] = instance.id
filter_data = {
'menu': field_data['menu'],
'field_name': field_data['field_name'],
'model': field_data['model']
}
instance_obj = MenuField.objects.filter(**filter_data).first()
serializer = MenuFieldInitSerializer(instance_obj, data=field_data, request=self.request)
serializer.is_valid(raise_exception=True)
serializer.save()
return instance
class Meta:
model = Menu
fields = ['name', 'icon', 'sort', 'is_link', 'is_catalog', 'web_path', 'component', 'component_name', 'status',
'cache', 'visible', 'parent', 'children', 'menu_button', 'creator', 'dept_belong_id']
'cache', 'visible', 'parent', 'children', 'menu_button', 'menu_field', 'creator', 'dept_belong_id']
extra_kwargs = {
'creator': {'write_only': True},
'dept_belong_id': {'write_only': True}
@@ -126,7 +162,7 @@ class RoleInitSerializer(CustomModelSerializer):
class Meta:
model = Role
fields = ['name', 'key', 'sort', 'status', 'admin',
fields = ['name', 'key', 'sort', 'status',
'creator', 'dept_belong_id']
read_only_fields = ["id"]
extra_kwargs = {
@@ -139,22 +175,24 @@ 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(max_length=100, required=True)
menu__web_path = serializers.CharField(max_length=100, required=True)
menu__component_name = serializers.CharField(max_length=100, required=True, allow_blank=True)
def create(self, validated_data):
init_data = self.initial_data
validated_data.pop('menu_component_name')
validated_data.pop('role_key')
role_id = Role.objects.filter(key=init_data['role_key']).first()
menu_id = Menu.objects.filter(component_name=init_data['menu_component_name']).first()
validated_data.pop('menu__web_path')
validated_data.pop('menu__component_name')
validated_data.pop('role__key')
role_id = Role.objects.filter(key=init_data['role__key']).first()
menu_id = Menu.objects.filter(web_path=init_data['menu__web_path'], component_name=init_data['menu__component_name']).first()
validated_data['role'] = role_id
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},
@@ -168,25 +206,30 @@ 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(max_length=100, required=True)
menu_button__value = serializers.CharField(max_length=100, required=True)
data_range = serializers.CharField(max_length=100, required=False)
def create(self, validated_data):
init_data = self.initial_data
validated_data.pop('menu_button_value')
validated_data.pop('role_key')
role_id = Role.objects.filter(key=init_data['role_key']).first()
menu_button_id = MenuButton.objects.filter(value=init_data['menu_button_value']).first()
validated_data.pop('menu_button__value')
validated_data.pop('role__key')
role_id = Role.objects.filter(key=init_data['role__key']).first()
menu_button_id = MenuButton.objects.filter(value=init_data['menu_button__value']).first()
validated_data['role'] = role_id
validated_data['menu_button'] = menu_button_id
instance = super().create(validated_data)
instance.dept.set([])
return instance
def save(self, **kwargs):
if self.instance and 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},
@@ -196,7 +239,6 @@ class RoleMenuButtonInitSerializer(CustomModelSerializer):
}
class ApiWhiteListInitSerializer(CustomModelSerializer):
"""
初始化获取数信息(用于生成初始化json文件)
File diff suppressed because it is too large Load Diff
@@ -4,7 +4,6 @@
"key": "admin",
"sort": 1,
"status": true,
"admin": true,
"remark": null
},
{
@@ -12,7 +11,6 @@
"key": "public",
"sort": 2,
"status": true,
"admin": true,
"remark": null
}
]
@@ -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
}
]
@@ -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"
}
]
@@ -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": "开启验证码",
@@ -65,6 +93,20 @@
"placeholder": null,
"setting": null,
"children": [
{
"parent": 1,
"title": "网站标题",
"key": "site_title",
"value": "Dvadmin",
"sort": 1,
"status": true,
"data_options": null,
"form_item_type": 0,
"rule": [],
"placeholder": "请输入网站标题",
"setting": null,
"children": []
},
{
"parent": 1,
"title": "网站名称",
@@ -116,7 +158,7 @@
"parent": 1,
"title": "版权信息",
"key": "copyright",
"value": "2021-2022 django-vue-admin.com 版权所有",
"value": "2021-2024 django-vue-admin.com 版权所有",
"sort": 4,
"status": true,
"data_options": null,
@@ -168,7 +210,7 @@
"parent": 1,
"title": "隐私链接",
"key": "privacy_url",
"value": "#",
"value": "/api/system/clause/privacy.html",
"sort": 7,
"status": true,
"data_options": null,
@@ -182,7 +224,7 @@
"parent": 1,
"title": "条款链接",
"key": "clause_url",
"value": "#",
"value": "/api/system/clause/terms_service.html",
"sort": 8,
"status": true,
"data_options": null,
@@ -29,6 +29,7 @@
"gender": 1,
"user_type": 0,
"role": [],
"dept_key": "dvadmin",
"first_name": "",
"last_name": "",
"is_staff": true,
@@ -8,9 +8,11 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "application.settings")
django.setup()
from dvadmin.utils.core_initialize import CoreInitialize
from dvadmin.system.fixtures.initSerializer import UsersInitSerializer, DeptInitSerializer, RoleInitSerializer, \
MenuInitSerializer, ApiWhiteListInitSerializer, DictionaryInitSerializer, SystemConfigInitSerializer, \
RoleMenuInitSerializer, RoleMenuButtonInitSerializer
from dvadmin.system.fixtures.initSerializer import (
UsersInitSerializer, DeptInitSerializer, RoleInitSerializer,
MenuInitSerializer, ApiWhiteListInitSerializer, DictionaryInitSerializer,
SystemConfigInitSerializer, RoleMenuInitSerializer, RoleMenuButtonInitSerializer
)
class Initialize(CoreInitialize):
@@ -43,14 +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):
"""
@@ -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"""
+63 -14
View File
@@ -5,12 +5,7 @@ from django.contrib.auth.models import AbstractUser, UserManager
from django.db import models
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from application import dispatch
from dvadmin.utils.models import CoreModel, table_prefix
STATUS_CHOICES = (
(0, "禁用"),
(1, "启用"),
)
from dvadmin.utils.models import CoreModel, table_prefix, get_custom_app_models
class Role(CoreModel):
@@ -18,7 +13,6 @@ class Role(CoreModel):
key = models.CharField(max_length=64, unique=True, verbose_name="权限字符", help_text="权限字符")
sort = models.IntegerField(default=1, verbose_name="角色顺序", help_text="角色顺序")
status = models.BooleanField(default=True, verbose_name="角色状态", help_text="角色状态")
admin = models.BooleanField(default=False, verbose_name="是否为admin", help_text="是否为admin")
class Meta:
db_table = table_prefix + "system_role"
@@ -76,6 +70,7 @@ class Users(CoreModel, AbstractUser):
blank=True,
help_text="关联部门",
)
login_error_count = models.IntegerField(default=0, verbose_name="登录错误次数", help_text="登录错误次数")
objects = CustomUserManager()
def set_password(self, raw_password):
@@ -107,8 +102,7 @@ class Post(CoreModel):
class Dept(CoreModel):
name = models.CharField(max_length=64, verbose_name="部门名称", help_text="部门名称")
key = models.CharField(max_length=64, unique=True, null=True, blank=True, verbose_name="关联字符",
help_text="关联字符")
key = models.CharField(max_length=64, unique=True, null=True, blank=True, verbose_name="关联字符", help_text="关联字符")
sort = models.IntegerField(default=1, verbose_name="显示排序", help_text="显示排序")
owner = models.CharField(max_length=32, verbose_name="负责人", null=True, blank=True, help_text="负责人")
phone = models.CharField(max_length=32, verbose_name="联系电话", null=True, blank=True, help_text="联系电话")
@@ -169,6 +163,7 @@ class Menu(CoreModel):
(1, ""),
)
is_link = models.BooleanField(default=False, verbose_name="是否外链", help_text="是否外链")
link_url = models.CharField(max_length=255, verbose_name="链接地址", null=True, blank=True, help_text="链接地址")
is_catalog = models.BooleanField(default=False, verbose_name="是否目录", help_text="是否目录")
web_path = models.CharField(max_length=128, verbose_name="路由地址", null=True, blank=True, help_text="路由地址")
component = models.CharField(max_length=128, verbose_name="组件地址", null=True, blank=True, help_text="组件地址")
@@ -178,13 +173,59 @@ class Menu(CoreModel):
cache = models.BooleanField(default=False, blank=True, verbose_name="是否页面缓存", help_text="是否页面缓存")
visible = models.BooleanField(default=True, blank=True, verbose_name="侧边栏中是否显示",
help_text="侧边栏中是否显示")
is_iframe = models.BooleanField(default=False, blank=True, verbose_name="框架外显示", help_text="框架外显示")
is_affix = models.BooleanField(default=False, blank=True, verbose_name="是否固定", help_text="是否固定")
@classmethod
def get_all_parent(cls, id: int, all_list=None, nodes=None):
"""
递归获取给定ID的所有层级
:param id: 参数ID
:param all_list: 所有列表
:param nodes: 递归列表
:return: nodes
"""
if not all_list:
all_list = Menu.objects.values("id", "name", "parent")
if nodes is None:
nodes = []
for ele in all_list:
if ele.get("id") == id:
parent_id = ele.get("parent")
if parent_id is not None:
cls.get_all_parent(parent_id, all_list, nodes)
nodes.append(ele)
return nodes
class Meta:
db_table = table_prefix + "system_menu"
verbose_name = "菜单表"
verbose_name_plural = verbose_name
ordering = ("sort",)
class MenuField(CoreModel):
model = models.CharField(max_length=64, verbose_name='表名')
menu = models.ForeignKey(to='Menu', on_delete=models.CASCADE, verbose_name='菜单', db_constraint=False)
field_name = models.CharField(max_length=64, verbose_name='模型表字段名')
title = models.CharField(max_length=64, verbose_name='字段显示名')
class Meta:
db_table = table_prefix + "system_menu_field"
verbose_name = "菜单字段表"
verbose_name_plural = verbose_name
ordering = ("id",)
class FieldPermission(CoreModel):
role = models.ForeignKey(to='Role', on_delete=models.CASCADE, verbose_name='角色', db_constraint=False)
field = models.ForeignKey(to='MenuField', on_delete=models.CASCADE,related_name='menu_field', verbose_name='字段', db_constraint=False)
is_query = models.BooleanField(default=1, verbose_name='是否可查询')
is_create = models.BooleanField(default=1, verbose_name='是否可创建')
is_update = models.BooleanField(default=1, verbose_name='是否可更新')
class Meta:
db_table = table_prefix + "system_field_permission"
verbose_name = "字段权限表"
verbose_name_plural = verbose_name
ordering = ("id",)
class MenuButton(CoreModel):
menu = models.ForeignKey(
@@ -236,7 +277,7 @@ class RoleMenuPermission(CoreModel):
db_table = table_prefix + "role_menu_permission"
verbose_name = "角色菜单权限表"
verbose_name_plural = verbose_name
ordering = ("-create_datetime",)
# ordering = ("-create_datetime",)
class RoleMenuButtonPermission(CoreModel):
@@ -289,8 +330,7 @@ class Dictionary(CoreModel):
(7, "images"),
)
label = models.CharField(max_length=100, blank=True, null=True, verbose_name="字典名称", help_text="字典名称")
value = models.CharField(max_length=200, blank=True, null=True, verbose_name="字典编号",
help_text="字典编号/实际值")
value = models.CharField(max_length=200, blank=True, null=True, verbose_name="字典编号", help_text="字典编号/实际值")
parent = models.ForeignKey(
to="self",
related_name="sublist",
@@ -359,7 +399,11 @@ def media_file_name(instance, filename):
class FileList(CoreModel):
name = models.CharField(max_length=200, null=True, blank=True, verbose_name="名称", help_text="名称")
url = models.FileField(upload_to=media_file_name)
url = models.FileField(upload_to=media_file_name, null=True, blank=True,)
file_url = models.CharField(max_length=255, blank=True, verbose_name="文件地址", help_text="文件地址")
engine = models.CharField(max_length=100, default='local', blank=True, verbose_name="引擎", help_text="引擎")
mime_type = models.CharField(max_length=100, blank=True, verbose_name="Mime类型", help_text="Mime类型")
size = models.CharField(max_length=36, blank=True, verbose_name="文件大小", help_text="文件大小")
md5sum = models.CharField(max_length=36, blank=True, verbose_name="文件md5", help_text="文件md5")
def save(self, *args, **kwargs):
@@ -368,6 +412,11 @@ class FileList(CoreModel):
for chunk in self.url.chunks():
md5.update(chunk)
self.md5sum = md5.hexdigest()
if not self.size:
self.size = self.url.size
if not self.file_url:
url = media_file_name(self, self.name)
self.file_url = f'media/{url}'
super(FileList, self).save(*args, **kwargs)
class Meta:
@@ -437,7 +486,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="启用状态")
+55
View File
@@ -1 +1,56 @@
from functools import wraps
from django.db.models import Func, F, OuterRef, Exists
from django.test import TestCase
import django
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "application.settings")
django.setup()
from dvadmin.system.models import Menu, RoleMenuPermission, RoleMenuButtonPermission, MenuButton
import time
def timing_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
run_time = end_time - start_time
print(f"{func.__name__} ran in {run_time:.6f} seconds")
return result
return wrapper
@timing_decorator
def getMenu():
data = []
queryset = Menu.objects.filter(status=1, is_catalog=False).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=1,
).exists()
mbCheck = RoleMenuButtonPermission.objects.filter(
menu_button = OuterRef("pk"),
role__id=1,
)
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'))
# print(b)
dicts = {
'name': completeName,
'id': item['id'],
'isCheck': isCheck,
'btns':btns
}
print(dicts)
data.append(dicts)
# print(data)
if __name__ == '__main__':
getMenu()
+6 -3
View File
@@ -3,6 +3,7 @@ from rest_framework import routers
from dvadmin.system.views.api_white_list import ApiWhiteListViewSet
from dvadmin.system.views.area import AreaViewSet
from dvadmin.system.views.clause import PrivacyView, TermsServiceView
from dvadmin.system.views.dept import DeptViewSet
from dvadmin.system.views.dictionary import DictionaryViewSet
from dvadmin.system.views.file_list import FileViewSet
@@ -16,6 +17,7 @@ from dvadmin.system.views.role_menu import RoleMenuPermissionViewSet
from dvadmin.system.views.role_menu_button_permission import RoleMenuButtonPermissionViewSet
from dvadmin.system.views.system_config import SystemConfigViewSet
from dvadmin.system.views.user import UserViewSet
from dvadmin.system.views.menu_field import MenuFieldViewSet
system_url = routers.SimpleRouter()
system_url.register(r'menu', MenuViewSet)
@@ -29,11 +31,10 @@ system_url.register(r'area', AreaViewSet)
system_url.register(r'file', FileViewSet)
system_url.register(r'api_white_list', ApiWhiteListViewSet)
system_url.register(r'system_config', SystemConfigViewSet)
system_url.register(r'message_center',MessageCenterViewSet)
system_url.register(r'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)
urlpatterns = [
@@ -46,5 +47,7 @@ urlpatterns = [
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()),
]
urlpatterns += system_url.urls
+3 -6
View File
@@ -60,12 +60,9 @@ class AreaViewSet(CustomModelViewSet):
del params['page']
if limit:
del params['limit']
if params:
if pcode:
queryset = self.queryset.filter(enable=True, pcode=pcode)
else:
queryset = self.queryset.filter(enable=True)
if params and pcode:
queryset = self.queryset.filter(enable=True, pcode=pcode)
else:
queryset = self.queryset.filter(enable=True, pcode__isnull=True)
queryset = self.queryset.filter(enable=True)
return queryset
+23
View File
@@ -0,0 +1,23 @@
from rest_framework.views import APIView
from django.shortcuts import render
class PrivacyView(APIView):
"""
后台隐私政策
"""
permission_classes = []
def get(self, request, *args, **kwargs):
return render(request, 'privacy.html')
class TermsServiceView(APIView):
"""
后台服务条款
"""
permission_classes = []
def get(self, request, *args, **kwargs):
return render(request, 'terms_service.html')
+101 -20
View File
@@ -9,9 +9,8 @@ from rest_framework import serializers
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import Dept
from dvadmin.utils.json_response import DetailResponse, SuccessResponse
from dvadmin.utils.permission import AnonymousUserPermission
from dvadmin.system.models import Dept, RoleMenuButtonPermission, Users
from dvadmin.utils.json_response import DetailResponse, SuccessResponse, ErrorResponse
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
@@ -25,6 +24,11 @@ class DeptSerializer(CustomModelSerializer):
has_children = serializers.SerializerMethodField()
hasChild = serializers.SerializerMethodField()
dept_user_count = serializers.SerializerMethodField()
def get_dept_user_count(self, obj: Dept):
return Users.objects.filter(dept=obj).count()
def get_hasChild(self, instance):
hasChild = Dept.objects.filter(parent=instance.id)
if hasChild:
@@ -56,18 +60,18 @@ class DeptImportSerializer(CustomModelSerializer):
read_only_fields = ["id"]
class DeptCreateUpdateSerializer(CustomModelSerializer):
"""
部门管理 创建/更新时的列化器
"""
def create(self, validated_data):
value = validated_data.get('parent',None)
value = validated_data.get('parent', None)
if value is None:
validated_data['parent'] = self.request.user.dept
dept_obj = Dept.objects.filter(parent=self.request.user.dept).order_by('-sort').first()
last_sort = dept_obj.sort if dept_obj else 0
validated_data['sort'] = last_sort + 1
instance = super().create(validated_data)
instance.dept_belong_id = instance.id
instance.save()
@@ -111,13 +115,10 @@ class DeptViewSet(CustomModelViewSet):
del params['page']
if limit:
del params['limit']
if params:
if parent:
queryset = self.queryset.filter(status=True, parent=parent)
else:
queryset = self.queryset.filter(status=True)
if params and parent:
queryset = self.queryset.filter(status=True, parent=parent)
else:
queryset = self.queryset.filter(status=True, parent__isnull=True)
queryset = self.queryset.filter(status=True)
queryset = self.filter_queryset(queryset)
serializer = DeptSerializer(queryset, many=True, request=request)
data = serializer.data
@@ -130,27 +131,107 @@ class DeptViewSet(CustomModelViewSet):
if is_superuser:
queryset = Dept.objects.values('id', 'name', 'parent')
else:
data_range = request.user.role.values_list('data_range', flat=True)
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]:
if item in [0, 2]:
dept_list = [user_dept_id]
elif item == 1:
dept_list = Dept.recursion_dept_info(dept_id=user_dept_id)
dept_list = Dept.recursion_all_dept(dept_id=user_dept_id)
elif item == 3:
dept_list = Dept.objects.values_list('id',flat=True)
dept_list = Dept.objects.values_list('id', flat=True)
elif item == 4:
dept_list = request.user.role.values_list('dept',flat=True)
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], extra_filter_class=[])
def all_dept(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
data = queryset.filter(status=True).order_by('sort').values('name', 'id', 'parent')
return DetailResponse(data=data, msg="获取成功")
@action(methods=['POST'], detail=False, permission_classes=[IsAuthenticated])
def move_up(self, request):
"""部门上移"""
dept_id = request.data.get('dept_id')
try:
dept = Dept.objects.get(id=dept_id)
except Dept.DoesNotExist:
return ErrorResponse(msg="部门不存在")
previous_menu = Dept.objects.filter(sort__lt=dept.sort, parent=dept.parent).order_by('-sort').first()
if previous_menu:
previous_menu.sort, dept.sort = dept.sort, previous_menu.sort
previous_menu.save()
dept.save()
return SuccessResponse(data=[], msg="上移成功")
@action(methods=['POST'], detail=False, permission_classes=[IsAuthenticated])
def move_down(self, request):
"""部门下移"""
dept_id = request.data['dept_id']
try:
dept = Dept.objects.get(id=dept_id)
except Dept.DoesNotExist:
return ErrorResponse(msg="部门不存在")
next_menu = Dept.objects.filter(sort__gt=dept.sort, parent=dept.parent).order_by('sort').first()
if next_menu:
next_menu.sort, dept.sort = dept.sort, next_menu.sort
next_menu.save()
dept.save()
return SuccessResponse(data=[], msg="下移成功")
@action(methods=['GET'], detail=False, permission_classes=[])
def dept_info(self, request):
"""部门信息"""
def inner(did, li):
sub = Dept.objects.filter(parent_id=did)
if not sub.exists():
return li
for i in sub:
li.append(i.pk)
inner(i, li)
return li
dept_id = request.query_params.get('dept_id')
show_all = request.query_params.get('show_all')
if dept_id is None:
return ErrorResponse(msg="部门不存在")
if not show_all:
show_all = 0
if int(show_all): # 递归当前部门下的所有部门,查询用户
all_did = [dept_id]
inner(dept_id, all_did)
users = Users.objects.filter(dept_id__in=all_did)
else:
if dept_id != '':
users = Users.objects.filter(dept_id=dept_id)
else:
users = Users.objects.none()
dept_obj = Dept.objects.get(id=dept_id) if dept_id != '' else None
sub_dept = Dept.objects.filter(parent_id=dept_obj.pk) if dept_id != '' else []
data = {
'dept_name': dept_obj and dept_obj.name,
'dept_user': users.count(),
'owner': dept_obj and dept_obj.owner,
'description': dept_obj and dept_obj.description,
'gender': {
'male': users.filter(gender=1).count(),
'female': users.filter(gender=2).count(),
'unknown': users.filter(gender=0).count(),
},
'sub_dept_map': []
}
for dept in sub_dept:
all_did = [dept.pk]
inner(dept.pk, all_did)
sub_data = {
'name': dept.name,
'count': Users.objects.filter(dept_id__in=all_did).count()
}
data['sub_dept_map'].append(sub_data)
return SuccessResponse(data)
+25 -8
View File
@@ -34,6 +34,19 @@ class DictionaryCreateUpdateSerializer(CustomModelSerializer):
"""
字典管理 创建/更新时的列化器
"""
value = serializers.CharField(max_length=100)
def validate_value(self, value):
"""
在父级的字典编号验证重复性
"""
initial_data = self.initial_data
parent = initial_data.get('parent',None)
if parent is None:
unique = Dictionary.objects.filter(value=value).exists()
if unique:
raise serializers.ValidationError("字典编号不能重复")
return value
class Meta:
model = Dictionary
@@ -51,20 +64,24 @@ class DictionaryViewSet(CustomModelViewSet):
"""
queryset = Dictionary.objects.all()
serializer_class = DictionarySerializer
create_serializer_class = DictionaryCreateUpdateSerializer
extra_filter_class = []
search_fields = ['label']
def get_queryset(self):
params = self.request.query_params
parent = params.get('parent', None)
if params:
if parent:
queryset = self.queryset.filter(status=1, parent=parent)
if self.action =='list':
params = self.request.query_params
parent = params.get('parent', None)
if params:
if parent:
queryset = self.queryset.filter(parent=parent)
else:
queryset = self.queryset.filter(parent__isnull=True)
else:
queryset = self.queryset.filter(status=1, parent__isnull=True)
queryset = self.queryset.filter(parent__isnull=True)
return queryset
else:
queryset = self.queryset.filter(status=1, parent__isnull=True)
return queryset
return self.queryset
class InitDictionaryViewSet(APIView):
+48 -4
View File
@@ -1,5 +1,12 @@
import hashlib
import mimetypes
from rest_framework import serializers
from rest_framework.decorators import action
from application import dispatch
from dvadmin.system.models import FileList
from dvadmin.utils.json_response import DetailResponse
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
@@ -8,15 +15,52 @@ class FileSerializer(CustomModelSerializer):
url = serializers.SerializerMethodField(read_only=True)
def get_url(self, instance):
return f'media/{str(instance.url)}'
# return 'media/' + str(instance.url)
return instance.file_url or (f'media/{str(instance.url)}')
class Meta:
model = FileList
fields = "__all__"
def create(self, validated_data):
validated_data['name'] = str(self.initial_data.get('file'))
validated_data['url'] = self.initial_data.get('file')
file_engine = dispatch.get_system_config_values("fileStorageConfig.file_engine") or 'local'
file_backup = dispatch.get_system_config_values("fileStorageConfig.file_backup")
file = self.initial_data.get('file')
file_size = file.size
validated_data['name'] = str(file)
validated_data['size'] = file_size
md5 = hashlib.md5()
for chunk in file.chunks():
md5.update(chunk)
validated_data['md5sum'] = md5.hexdigest()
validated_data['engine'] = file_engine
validated_data['mime_type'] = file.content_type
if file_backup:
validated_data['url'] = file
if file_engine == 'oss':
from dvadmin_cloud_storage.views.aliyun import ali_oss_upload
file_path = ali_oss_upload(file)
if file_path:
validated_data['file_url'] = file_path
else:
raise ValueError("上传失败")
elif file_engine == 'cos':
from dvadmin_cloud_storage.views.tencent import tencent_cos_upload
file_path = tencent_cos_upload(file)
if file_path:
validated_data['file_url'] = file_path
else:
raise ValueError("上传失败")
else:
validated_data['url'] = file
# 审计字段
try:
request_user = self.request.user
validated_data['dept_belong_id'] = request_user.dept.id
validated_data['creator'] = request_user.id
validated_data['modifier'] = request_user.id
except:
pass
return super().create(validated_data)
@@ -32,4 +76,4 @@ class FileViewSet(CustomModelViewSet):
queryset = FileList.objects.all()
serializer_class = FileSerializer
filter_fields = ['name', ]
permission_classes = []
permission_classes = []
+31 -21
View File
@@ -86,28 +86,38 @@ class LoginSerializer(TokenObtainPairSerializer):
user = Users.objects.get(username=attrs['username'])
if not user.is_active:
raise CustomValidationError("账号被锁定")
raise CustomValidationError("账号被锁定,联系管理员解锁")
try:
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,
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}
}
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
raise CustomValidationError("账号已被锁定,联系管理员解锁")
user.save()
count = 5 - user.login_error_count
raise CustomValidationError(f"账号/密码错误;重试{count}次后将被锁定~")
class LoginView(TokenObtainPairView):
+70 -44
View File
@@ -9,8 +9,9 @@
from rest_framework import serializers
from rest_framework.decorators import action
from dvadmin.system.models import Menu, MenuButton, RoleMenuPermission
from dvadmin.utils.json_response import SuccessResponse
from dvadmin.system.models import Menu, RoleMenuPermission
from dvadmin.system.views.menu_button import MenuButtonSerializer
from dvadmin.utils.json_response import SuccessResponse, ErrorResponse
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
@@ -23,7 +24,8 @@ class MenuSerializer(CustomModelSerializer):
hasChild = serializers.SerializerMethodField()
def get_menuPermission(self, instance):
queryset = instance.menuPermission.order_by('-name').values_list('name', flat=True)
queryset = instance.menuPermission.order_by('-name').values('id', 'name', 'value')
# MenuButtonSerializer(instance.menuPermission.all(), many=True)
if queryset:
return queryset
else:
@@ -47,6 +49,12 @@ class MenuCreateSerializer(CustomModelSerializer):
"""
name = serializers.CharField(required=False)
def create(self, validated_data):
menu_obj = Menu.objects.filter(parent_id=validated_data.get('parent', None)).order_by('-sort').first()
last_sort = menu_obj.sort if menu_obj else 0
validated_data['sort'] = last_sort + 1
return super().create(validated_data)
class Meta:
model = Menu
fields = "__all__"
@@ -63,8 +71,8 @@ class WebRouterSerializer(CustomModelSerializer):
class Meta:
model = Menu
fields = (
'id', 'parent', 'icon', 'sort', 'path', 'name', 'title', 'is_link', 'is_catalog', 'web_path', 'component',
'component_name', 'cache', 'visible', 'status')
'id', 'parent', 'icon', 'sort', 'path', 'name', 'title', 'is_link','link_url', 'is_catalog', 'web_path', 'component',
'component_name', 'cache', 'visible','is_iframe','is_affix', 'status')
read_only_fields = ["id"]
@@ -84,45 +92,6 @@ class MenuViewSet(CustomModelViewSet):
search_fields = ['name', 'status']
filter_fields = ['parent', 'name', 'status', 'is_link', 'visible', 'cache', 'is_catalog']
# extra_filter_class = []
@action(methods=['GET'], detail=False, permission_classes=[])
def web_router(self, request):
"""用于前端获取当前角色的路由"""
user = request.user
queryset = self.queryset.filter(status=1)
if not user.is_superuser:
role_list = user.role.values_list('id', flat=True)
menu_list = RoleMenuPermission.objects.filter(role__in=role_list).values_list('menu_id')
queryset = Menu.objects.filter(id__in=menu_list)
serializer = WebRouterSerializer(queryset, many=True, request=request)
data = serializer.data
return SuccessResponse(data=data, total=len(data), msg="获取成功")
@action(methods=['GET'], detail=False, permission_classes=[])
def get_all_menu(self, request):
"""用于菜单管理获取所有的菜单"""
user = request.user
queryset = self.queryset.all()
if not user.is_superuser:
role_list = user.role.values_list('id', flat=True)
menu_list = RoleMenuPermission.objects.filter(role__in=role_list).values_list('menu_id')
queryset = Menu.objects.filter(id__in=menu_list)
serializer = WebRouterSerializer(queryset, many=True, request=request)
data = serializer.data
return SuccessResponse(data=data, total=len(data), msg="获取成功")
@action(methods=['POST'], detail=False, permission_classes=[])
def drag_menu(self, request):
"""前端拖拽菜单之后重写parent"""
menu_id = request.data['menu_id']
parent_id = request.data['parent_id']
menu = Menu.objects.get(id=menu_id)
menu.parent_id = parent_id
menu.save()
return SuccessResponse(data=[], msg="拖拽菜单成功")
def list(self, request):
"""懒加载"""
request.query_params._mutable = True
@@ -145,3 +114,60 @@ class MenuViewSet(CustomModelViewSet):
serializer = MenuSerializer(queryset, many=True, request=request)
data = serializer.data
return SuccessResponse(data=data)
@action(methods=['GET'], detail=False, permission_classes=[])
def web_router(self, request):
"""用于前端获取当前角色的路由"""
user = request.user
if user.is_superuser:
queryset = self.queryset.filter(status=1)
else:
role_list = user.role.values_list('id', flat=True)
menu_list = RoleMenuPermission.objects.filter(role__in=role_list).values_list('menu_id', flat=True)
queryset = Menu.objects.filter(id__in=menu_list)
serializer = WebRouterSerializer(queryset, many=True, request=request)
data = serializer.data
return SuccessResponse(data=data, total=len(data), msg="获取成功")
@action(methods=['GET'], detail=False, permission_classes=[])
def get_all_menu(self, request):
"""用于菜单管理获取所有的菜单"""
user = request.user
queryset = self.queryset.all()
if not user.is_superuser:
role_list = user.role.values_list('id', flat=True)
menu_list = RoleMenuPermission.objects.filter(role__in=role_list).values_list('menu_id')
queryset = Menu.objects.filter(id__in=menu_list)
serializer = WebRouterSerializer(queryset, many=True, request=request)
data = serializer.data
return SuccessResponse(data=data, total=len(data), msg="获取成功")
@action(methods=['POST'], detail=False, permission_classes=[])
def move_up(self, request):
"""菜单上移"""
menu_id = request.data.get('menu_id')
try:
menu = Menu.objects.get(id=menu_id)
except Menu.DoesNotExist:
return ErrorResponse(msg="菜单不存在")
previous_menu = Menu.objects.filter(sort__lt=menu.sort, parent=menu.parent).order_by('-sort').first()
if previous_menu:
previous_menu.sort, menu.sort = menu.sort, previous_menu.sort
previous_menu.save()
menu.save()
return SuccessResponse(data=[], msg="上移成功")
@action(methods=['POST'], detail=False, permission_classes=[])
def move_down(self, request):
"""菜单下移"""
menu_id = request.data['menu_id']
try:
menu = Menu.objects.get(id=menu_id)
except Menu.DoesNotExist:
return ErrorResponse(msg="菜单不存在")
next_menu = Menu.objects.filter(sort__gt=menu.sort, parent=menu.parent).order_by('sort').first()
if next_menu:
next_menu.sort, menu.sort = menu.sort, next_menu.sort
next_menu.save()
menu.save()
return SuccessResponse(data=[], msg="下移成功")
+42 -6
View File
@@ -11,7 +11,7 @@ from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import MenuButton, RoleMenuButtonPermission
from dvadmin.utils.json_response import DetailResponse
from dvadmin.utils.json_response import DetailResponse, SuccessResponse
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
@@ -23,7 +23,7 @@ class MenuButtonSerializer(CustomModelSerializer):
class Meta:
model = MenuButton
fields = ['id', 'name', 'value', 'api', 'method']
fields = ['id', 'name', 'value', 'api', 'method','menu']
read_only_fields = ["id"]
@@ -49,12 +49,24 @@ class MenuButtonViewSet(CustomModelViewSet):
retrieve:单例
destroy:删除
"""
queryset = MenuButton.objects.all()
queryset = MenuButton.objects.order_by('create_datetime')
serializer_class = MenuButtonSerializer
create_serializer_class = MenuButtonCreateUpdateSerializer
update_serializer_class = MenuButtonCreateUpdateSerializer
extra_filter_class = []
def list(self, request, *args, **kwargs):
"""
重写list方法
:param request:
:param args:
:param kwargs:
:return:
"""
queryset = self.filter_queryset(self.get_queryset()).order_by('name')
serializer = self.get_serializer(queryset, many=True, request=request)
return SuccessResponse(serializer.data,msg="获取成功")
@action(methods=['get'],detail=False,permission_classes=[IsAuthenticated])
def menu_button_all_permission(self,request):
"""
@@ -63,10 +75,34 @@ class MenuButtonViewSet(CustomModelViewSet):
:return:
"""
is_superuser = request.user.is_superuser
is_admin = request.user.role.values_list('admin', flat=True)
if is_superuser or True in is_admin:
if is_superuser:
queryset = MenuButton.objects.values_list('value',flat=True)
else:
role_id = request.user.role.values_list('id', flat=True)
queryset = RoleMenuButtonPermission.objects.filter(role__in=role_id).values_list('menu_button__value',flat=True).distinct()
return DetailResponse(data=queryset)
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.web_path}/',
'method': 1},
{'menu': menu_obj.id, 'name': '删除', 'value': f'{menu_obj.component_name}:Delete', 'api': f'/api{menu_obj.web_path}/{{id}}/',
'method': 3},
{'menu': menu_obj.id, 'name': '修改', 'value': f'{menu_obj.component_name}:Update', 'api': f'/api{menu_obj.web_path}/{{id}}/',
'method': 2},
{'menu': menu_obj.id, 'name': '查询', 'value': f'{menu_obj.component_name}:Search', 'api': f'/api{menu_obj.web_path}/',
'method': 0},
{'menu': menu_obj.id, 'name': '详情', 'value': f'{menu_obj.component_name}:Retrieve', 'api': f'/api{menu_obj.web_path}/{{id}}/',
'method': 0}]
serializer = self.get_serializer(data=result_list, many=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return SuccessResponse(serializer.data, msg="批量创建成功")
@@ -0,0 +1,89 @@
# -*- coding: utf-8 -*-
from django.apps import apps
from rest_framework import serializers
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import Role, MenuField
from dvadmin.utils.models import get_custom_app_models
from dvadmin.utils.viewset import CustomModelViewSet
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.json_response import DetailResponse, ErrorResponse, SuccessResponse
class MenuFieldSerializer(CustomModelSerializer):
"""
列权限序列化器
"""
class Meta:
model = MenuField
fields = '__all__'
read_only_fields = ['id']
class MenuFieldViewSet(CustomModelViewSet):
"""
列权限视图集
"""
queryset = MenuField.objects.order_by('-model')
serializer_class = MenuFieldSerializer
def list(self, request, *args, **kwargs):
menu = request.query_params.get('menu')
if not menu:
return SuccessResponse([])
queryset = self.filter_queryset(self.get_queryset().filter(menu=menu))
serializer = self.get_serializer(queryset, many=True, request=request)
return SuccessResponse(data=serializer.data, msg="获取成功")
def create(self, request, *args, **kwargs):
payload = request.data
for model in apps.get_models():
if payload.get('model') == model.__name__:
break
else:
return ErrorResponse(msg='模型表不存在')
if MenuField.objects.filter(menu=payload.get('menu'),model=model.__name__, field_name=payload.get('field_name')).exists():
return ErrorResponse(msg='%s 字段权限已有,不可重复创建' % payload.get('title'))
return super().create(request, *args, **kwargs)
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
def get_models(self, request):
"""获取所有项目app下的model"""
res = []
for model in get_custom_app_models():
res.append({
'app': model['app'],
'title': model['verbose'],
'key': model['model']
})
return DetailResponse(res)
@action(methods=['POST'], detail=False, permission_classes=[IsAuthenticated])
def auto_match_fields(self, request):
"""自动匹配已有的字段"""
menu_id = request.data.get('menu')
model_name = request.data.get('model')
if not menu_id or not model_name:
return ErrorResponse( msg='参数错误')
for model in get_custom_app_models():
if model['model'] != model_name:
continue
for field in model['fields']:
if MenuField.objects.filter(
menu_id=menu_id, model=model_name, field_name=field['name']
).exists():
continue
data = {
'menu': menu_id,
'model': model_name,
'field_name': field['name'],
'title': str(field['title']),
}
serializer = self.get_serializer(data=data, request=request)
serializer.is_valid(raise_exception=True)
serializer.save()
return SuccessResponse(msg='匹配成功')
+34 -14
View File
@@ -15,6 +15,7 @@ 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.serializers import CustomModelSerializer
from dvadmin.utils.validator import CustomUniqueValidator
@@ -32,9 +33,6 @@ class RoleSerializer(CustomModelSerializer):
read_only_fields = ["id"]
class RoleCreateUpdateSerializer(CustomModelSerializer):
"""
角色管理 创建/更新时的列化器
@@ -49,19 +47,19 @@ class RoleCreateUpdateSerializer(CustomModelSerializer):
def validate(self, attrs: dict):
return super().validate(attrs)
def save(self, **kwargs):
is_superuser = self.request.user.is_superuser
if not is_superuser:
self.validated_data.pop('admin')
data = super().save(**kwargs)
return data
# def save(self, **kwargs):
# is_superuser = self.request.user.is_superuser
# if not is_superuser:
# self.validated_data.pop('admin')
# data = super().save(**kwargs)
# return data
class Meta:
model = Role
fields = '__all__'
class MenuPermissonSerializer(CustomModelSerializer):
class MenuPermissionSerializer(CustomModelSerializer):
"""
菜单的按钮权限
"""
@@ -72,9 +70,9 @@ class MenuPermissonSerializer(CustomModelSerializer):
if is_superuser:
queryset = MenuButton.objects.filter(menu__id=instance.id)
else:
menu_permission_id_list = self.request.user.role.values_list('permission',flat=True)
queryset = MenuButton.objects.filter(id__in=menu_permission_id_list,menu__id=instance.id)
serializer = MenuButtonSerializer(queryset,many=True, read_only=True)
menu_permission_id_list = self.request.user.role.values_list('permission', flat=True)
queryset = MenuButton.objects.filter(id__in=menu_permission_id_list, menu__id=instance.id)
serializer = MenuButtonSerializer(queryset, many=True, read_only=True)
return serializer.data
class Meta:
@@ -82,7 +80,29 @@ class MenuPermissonSerializer(CustomModelSerializer):
fields = ['id', 'parent', 'name', 'menuPermission']
class RoleViewSet(CustomModelViewSet,FastCrudMixin):
class MenuButtonPermissionSerializer(CustomModelSerializer):
"""
菜单和按钮权限
"""
isCheck = serializers.SerializerMethodField()
def get_isCheck(self, instance):
is_superuser = self.request.user.is_superuser
if is_superuser:
return True
else:
return MenuButton.objects.filter(
menu__id=instance.id,
role__id__in=self.request.user.role.values_list('id', flat=True),
).exists()
class Meta:
model = Menu
fields = '__all__'
class RoleViewSet(CustomModelViewSet, FastCrudMixin,FieldPermissionMixin):
"""
角色管理接口
list:查询
+8 -1
View File
@@ -72,7 +72,14 @@ class RoleMenuPermissionViewSet(CustomModelViewSet):
menu_list = body.get('menu',None)
if menu_list is None:
return ErrorResponse(msg="未获取到菜单参数")
data = [{"role":role_id,"menu":item} for item in menu_list]
obj_list = RoleMenuPermission.objects.filter(role__id=role_id).values_list('menu__id',flat=True)
old_set = set(obj_list)
new_set = set(menu_list)
#need_update = old_set.intersection(new_set) # 需要更新的
need_del = old_set.difference(new_set) # 需要删除的
need_add = new_set.difference(old_set) # 需要新增的
RoleMenuPermission.objects.filter(role__id=role_id,menu__in=list(need_del)).delete()
data = [{"role": role_id, "menu": item} for item in list(need_add)]
serializer = RoleMenuPermissionSerializer(data=data,many=True,request=request)
if serializer.is_valid(raise_exception=True):
serializer.save()
@@ -6,11 +6,14 @@
@Created on: 2021/6/3 003 0:30
@Remark: 菜单按钮管理
"""
from django.db.models import F
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
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.utils.serializers import CustomModelSerializer
from dvadmin.utils.viewset import CustomModelViewSet
@@ -27,20 +30,12 @@ class RoleMenuButtonPermissionSerializer(CustomModelSerializer):
read_only_fields = ["id"]
class RoleMenuButtonPermissionInitSerializer(CustomModelSerializer):
"""
初始化菜单按钮-序列化器
"""
class Meta:
model = RoleMenuButtonPermission
fields = "__all__"
read_only_fields = ["id"]
class RoleMenuButtonPermissionCreateUpdateSerializer(CustomModelSerializer):
"""
初始化菜单按钮-序列化器
"""
menu_button__name = serializers.CharField(source='menu_button.name', read_only=True)
menu_button__value = serializers.CharField(source='menu_button.value', read_only=True)
class Meta:
model = RoleMenuButtonPermission
@@ -48,6 +43,120 @@ class RoleMenuButtonPermissionCreateUpdateSerializer(CustomModelSerializer):
read_only_fields = ["id"]
class RoleButtonPermissionSerializer(CustomModelSerializer):
"""
角色按钮权限
"""
isCheck = serializers.SerializerMethodField()
data_range = serializers.SerializerMethodField()
def get_isCheck(self, instance):
params = self.request.query_params
return RoleMenuButtonPermission.objects.filter(
menu_button__id=instance['id'],
role__id=params.get('role'),
).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()
if obj is None:
return None
return obj.data_range
class Meta:
model = MenuButton
fields = ['id', 'name', 'value', 'isCheck', 'data_range']
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()
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()
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()
if queryset:
return queryset.is_update
return False
class Meta:
model = MenuField
fields = ['id', 'field_name', 'title', 'is_query', 'is_create', 'is_update']
class RoleMenuSerializer(CustomModelSerializer):
menus = serializers.SerializerMethodField()
def get_menus(self, instance):
menu_list = Menu.objects.filter(parent=instance['id']).values('id', 'name')
serializer = RoleMenuPermissionSerializer(menu_list, many=True, request=self.request)
return serializer.data
class Meta:
model = Menu
fields = ['id', 'name', 'menus']
class RoleMenuPermissionSerializer(CustomModelSerializer):
"""
菜单和按钮权限
"""
# name = serializers.SerializerMethodField()
isCheck = serializers.SerializerMethodField()
btns = serializers.SerializerMethodField()
columns = serializers.SerializerMethodField()
# def get_name(self, instance):
# parent_list = Menu.get_all_parent(instance['id'])
# names = [d["name"] for d in parent_list]
# return "/".join(names)
def get_isCheck(self, instance):
params = self.request.query_params
return RoleMenuPermission.objects.filter(
menu__id=instance['id'],
role__id=params.get('role'),
).exists()
def get_btns(self, instance):
btn_list = MenuButton.objects.filter(menu__id=instance['id']).values('id', 'name', 'value')
serializer = RoleButtonPermissionSerializer(btn_list, many=True, request=self.request)
return serializer.data
def get_columns(self, instance):
col_list = MenuField.objects.filter(menu=instance['id'])
serializer = RoleMenuFieldSerializer(col_list, many=True, request=self.request)
return serializer.data
class Meta:
model = Menu
fields = ['id', 'name', 'isCheck', 'btns', 'columns']
class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
"""
菜单按钮接口
@@ -64,38 +173,111 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
extra_filter_class = []
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
def role_get_menu(self, request):
"""根据当前用户的角色返回角色拥有的菜单"""
def get_role_premission(self, request):
"""
角色授权获取:
:param request: role
:return: menu,btns,columns
"""
params = request.query_params
role = params.get('role', None)
if role is None:
return ErrorResponse(msg="未获取到角色信息")
is_superuser = request.user.is_superuser
is_admin = request.user.role.values_list('admin', flat=True)
if is_superuser or True in is_admin:
queryset = Menu.objects.filter(status=1).values('id','name','parent','is_catalog')
if is_superuser:
queryset = Menu.objects.filter(status=1, is_catalog=True).values('name', 'id').all()
else:
role_id = request.user.role.values_list('id',flat=True)
queryset = RoleMenuButtonPermission.objects.filter(role__in=role_id).values(id=F('menu__id'),name=F('menu__name'),parent=F('menu__parent'),is_catalog=F('menu__is_catalog'))
return DetailResponse(data=queryset)
role_id = request.user.role.values_list('id', flat=True)
menu_list = RoleMenuPermission.objects.filter(role__in=role_id).values_list('id', flat=True)
queryset = Menu.objects.filter(status=1, is_catalog=True, id__in=menu_list).values('name', 'id').all()
serializer = RoleMenuSerializer(queryset, many=True, request=request)
data = serializer.data
return DetailResponse(data=data)
# data = []
# if is_superuser:
# queryset = Menu.objects.filter(status=1, is_catalog=False).values('name', 'id').all()
# else:
# role_id = request.user.role.values_list('id', flat=True)
# menu_list = RoleMenuPermission.objects.filter(role__in=role_id).values_list('id', flat=True)
# queryset = Menu.objects.filter(status=1, is_catalog=False, id__in=menu_list).values('name', 'id')
# for item in queryset:
# parent_list = Menu.get_all_parent(item['id'])
# names = [d["name"] for d in parent_list]
# completeName = "/".join(names)
# isCheck = RoleMenuPermission.objects.filter(
# menu__id=item['id'],
# role__id=role,
# ).exists()
# mbCheck = RoleMenuButtonPermission.objects.filter(
# menu_button=OuterRef("pk"),
# role__id=role,
# )
# btns = MenuButton.objects.filter(
# menu__id=item['id'],
# ).annotate(isCheck=Exists(mbCheck)).values('id', 'name', 'value', 'isCheck',
# data_range=F('menu_button_permission__data_range'))
# dicts = {
# 'name': completeName,
# 'id': item['id'],
# 'isCheck': isCheck,
# 'btns': btns,
#
# }
# data.append(dicts)
# return DetailResponse(data=data)
@action(methods=['PUT'], detail=True, permission_classes=[IsAuthenticated])
def set_role_premission(self, request, pk):
"""
对角色的菜单和按钮及按钮范围授权:
:param request:
:param pk: role
:return:
"""
body = request.data
RoleMenuPermission.objects.filter(role=pk).delete()
RoleMenuButtonPermission.objects.filter(role=pk).delete()
for item in body:
for menu in item["menus"]:
if menu.get('isCheck'):
menu_parent = Menu.get_all_parent(menu.get('id'))
role_menu_permission_list = []
for d in menu_parent:
role_menu_permission_list.append(RoleMenuPermission(role_id=pk, menu_id=d["id"]))
RoleMenuPermission.objects.bulk_create(role_menu_permission_list)
# RoleMenuPermission.objects.create(role_id=pk, menu_id=menu.get('id'))
for btn in menu.get('btns'):
if btn.get('isCheck'):
data_range = btn.get('data_range', 0) or 0
instance = RoleMenuButtonPermission.objects.create(role_id=pk, menu_button_id=btn.get('id'),
data_range=data_range)
instance.dept.set(btn.get('dept', []))
for col in menu.get('columns'):
FieldPermission.objects.update_or_create(role_id=pk, field_id=col.get('id'),
defaults={
'is_query': col.get('is_query'),
'is_create': col.get('is_create'),
'is_update': col.get('is_update')
})
return DetailResponse(msg="授权成功")
@action(methods=['GET'], detail=False, permission_classes=[IsAuthenticated])
def role_menu_get_button(self,request):
def role_menu_get_button(self, request):
"""
当前用户角色和菜单获取可下拉选项的按钮:角色授权页面使用
:param request:
:return:
"""
params = request.query_params
if params:
menu_id = params.get('menu',None)
if menu_id:
if params := request.query_params:
if menu_id := params.get('menu', None):
is_superuser = request.user.is_superuser
is_admin = request.user.role.values_list('admin', flat=True)
if is_superuser or True in is_admin:
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(
id=F('menu_button__id'),
name=F('menu_button__name')
)
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="参数错误")
@@ -133,12 +315,12 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
return DetailResponse(data=data)
else:
data = []
role_id = request.user.role.id
params = request.query_params
if params:
menu_button_id = params.get('menu_button', None)
if menu_button_id:
role_queryset = RoleMenuButtonPermission.objects.filter(role=role_id,menu_button=menu_button_id).values_list('data_range',flat=True)
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:
@@ -199,46 +381,70 @@ class RoleMenuButtonPermissionViewSet(CustomModelViewSet):
"""
params = request.query_params
is_superuser = request.user.is_superuser
is_admin = request.user.role.values_list('admin', flat=True)
if is_superuser or True in is_admin:
queryset = Dept.objects.values('id','name','parent')
return DetailResponse(data=queryset)
if is_superuser:
queryset = Dept.objects.values('id', 'name', 'parent')
else:
if params:
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(
id=F('dept__id'),
name=F('dept__name'),
parent=F('dept__parent')
)
return DetailResponse(data=queryset)
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):
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
def menu_to_button(self, request):
"""
根据所选择菜单获取已配置的按钮/接口权限:角色授权页面使用
:param request:
:return:
"""
params = request.query_params
if params:
menu_id = params.get('menu',None)
if menu_id is None:
return ErrorResponse(msg="未获取到参数")
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(
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',
'menu_button__name',
'menu_button__value'
)
return DetailResponse(data=queryset)
return ErrorResponse(msg="未获取到参数")
else:
if params:
role_id = params.get('role', None)
if role_id is None:
return ErrorResponse(msg="未获取到参数")
queryset = RoleMenuButtonPermission.objects.filter(role=role_id, menu_button__menu=menu_id).values(
'id',
'data_range',
'menu_button',
'menu_button__name',
'menu_button__value'
)
return DetailResponse(data=queryset)
return ErrorResponse(msg="未获取到参数")
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
def role_to_menu(self, request):
"""
获取角色对应的按钮权限
:param request:
:return:
"""
params = request.query_params
role_id = params.get('role', None)
if role_id is None:
return ErrorResponse(msg="未获取到参数")
queryset = RoleMenuPermission.objects.filter(role_id=role_id).values_list('menu_id', flat=True).distinct()
return DetailResponse(data=queryset)
+74 -25
View File
@@ -1,15 +1,16 @@
import hashlib
from django.contrib.auth.hashers import make_password
from django.contrib.auth.hashers import make_password, check_password
from django_restql.fields import DynamicSerializerMethodField
from rest_framework import serializers
from rest_framework.decorators import action, permission_classes
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from django.db import connection
from django.db.models import Q
from application import dispatch
from dvadmin.system.models import Users, Role, Dept
from dvadmin.system.views.role import RoleSerializer
from dvadmin.utils.json_response import ErrorResponse, DetailResponse
from dvadmin.utils.json_response import ErrorResponse, DetailResponse, SuccessResponse
from dvadmin.utils.serializers import CustomModelSerializer
from dvadmin.utils.validator import CustomUniqueValidator
from dvadmin.utils.viewset import CustomModelViewSet
@@ -23,7 +24,7 @@ def recursion(instance, parent, result):
res.append(data)
if new_instance:
array = recursion(new_instance, parent, result)
res += (array)
res += array
return res
@@ -114,14 +115,14 @@ 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):
"""
更改激活状态
"""
print(111, value)
if value:
self.initial_data["login_error_count"] = 0
return value
def save(self, **kwargs):
data = super().save(**kwargs)
@@ -196,10 +197,12 @@ class ExportUserProfileSerializer(CustomModelSerializer):
class UserProfileImportSerializer(CustomModelSerializer):
password = serializers.CharField(read_only=True, required=False)
def save(self, **kwargs):
data = super().save(**kwargs)
password = hashlib.new(
"md5", str(self.initial_data.get("password", "")).encode(encoding="UTF-8")
"md5", str(self.initial_data.get("password", "admin123456")).encode(encoding="UTF-8")
).hexdigest()
data.set_password(password)
data.save()
@@ -231,7 +234,7 @@ class UserViewSet(CustomModelViewSet):
create_serializer_class = UserCreateSerializer
update_serializer_class = UserUpdateSerializer
filter_fields = ["name", "username", "gender", "is_active", "dept", "user_type"]
search_fields = ["username", "name", "gender", "dept__name", "role__name"]
search_fields = ["username", "name", "dept__name", "role__name"]
# 导出
export_field_label = {
"username": "用户账号",
@@ -264,7 +267,6 @@ class UserViewSet(CustomModelViewSet):
"data": {"启用": True, "禁用": False},
}
},
"password": "登录密码",
"dept": {"title": "部门", "choices": {"queryset": Dept.objects.filter(status=True), "values_name": "name"}},
"role": {"title": "角色", "choices": {"queryset": Role.objects.filter(status=True), "values_name": "name"}},
}
@@ -313,34 +315,39 @@ class UserViewSet(CustomModelViewSet):
serializer.save()
return DetailResponse(data=None, msg="修改成功")
@action(methods=["PUT"], detail=True, permission_classes=[IsAuthenticated])
@action(methods=["PUT"], detail=False, permission_classes=[IsAuthenticated])
def change_password(self, request, *args, **kwargs):
"""密码修改"""
data = request.data
old_pwd = data.get("oldPassword")
print(old_pwd)
new_pwd = data.get("newPassword")
new_pwd2 = data.get("newPassword2")
if old_pwd is None or new_pwd is None or new_pwd2 is None:
return ErrorResponse(msg="参数不能为空")
if new_pwd != new_pwd2:
return ErrorResponse(msg="两次密码不匹配")
check_password = request.user.check_password(old_pwd)
if not check_password:
check_password = request.user.check_password(hashlib.md5(old_pwd.encode(encoding='UTF-8')).hexdigest())
if check_password:
new_pwd = hashlib.md5(new_pwd.encode(encoding='UTF-8')).hexdigest()
request.user.password = make_password(new_pwd)
verify_password = check_password(old_pwd, request.user.password)
if not verify_password:
old_pwd_md5 = hashlib.md5(old_pwd.encode(encoding='UTF-8')).hexdigest()
verify_password = check_password(str(old_pwd_md5), request.user.password)
if verify_password:
request.user.password = make_password(hashlib.md5(new_pwd.encode(encoding='UTF-8')).hexdigest())
request.user.save()
return DetailResponse(data=None, msg="修改成功")
else:
return ErrorResponse(msg="旧密码不正确")
@action(methods=["PUT"], detail=True, permission_classes=[IsAuthenticated])
def reset_to_default_password(self, request, *args, **kwargs):
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:
@@ -351,6 +358,8 @@ class UserViewSet(CustomModelViewSet):
"""
密码重置
"""
if not self.request.user.is_superuser:
return ErrorResponse(msg="只允许超级管理员对其进行密码重置")
instance = Users.objects.filter(id=pk).first()
data = request.data
new_pwd = data.get("newPassword")
@@ -364,3 +373,43 @@ class UserViewSet(CustomModelViewSet):
return DetailResponse(data=None, msg="修改成功")
else:
return ErrorResponse(msg="未获取到用户")
def list(self, request, *args, **kwargs):
dept_id = request.query_params.get('dept')
show_all = request.query_params.get('show_all')
if not dept_id:
dept_id = ''
if not show_all:
show_all = 0
if int(show_all):
all_did = [dept_id]
def inner(did):
sub = Dept.objects.filter(parent_id=did)
if not sub.exists():
return
for i in sub:
all_did.append(i.pk)
inner(i)
if dept_id != '':
inner(dept_id)
searchs = [
Q(**{f+'__icontains':i})
for f in self.search_fields
] if (i:=request.query_params.get('search')) else []
q_obj = []
if searchs:
q = searchs[0]
for i in searchs[1:]:
q |= i
q_obj.append(Q(q))
queryset = Users.objects.filter(*q_obj, dept_id__in=all_did)
else:
queryset = self.filter_queryset(self.get_queryset())
else:
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True, request=request)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True, request=request)
return SuccessResponse(data=serializer.data, msg="获取成功")
View File
+13 -7
View File
@@ -3,8 +3,11 @@ import logging
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.hashers import check_password
from django.utils import timezone
from dvadmin.utils.validator import CustomValidationError
logger = logging.getLogger(__name__)
UserModel = get_user_model()
@@ -24,10 +27,13 @@ class CustomBackend(ModelBackend):
except UserModel.DoesNotExist:
UserModel().set_password(password)
else:
check_password = user.check_password(password)
if not check_password:
check_password = user.check_password(hashlib.md5(password.encode(encoding='UTF-8')).hexdigest())
if check_password and self.user_can_authenticate(user):
user.last_login = timezone.now()
user.save()
return user
verify_password = check_password(password, user.password)
if not verify_password:
password = hashlib.md5(password.encode(encoding='UTF-8')).hexdigest()
verify_password = check_password(password, user.password)
if verify_password:
if self.user_can_authenticate(user):
user.last_login = timezone.now()
user.save()
return user
raise CustomValidationError("当前用户已被禁用,请联系管理员!")
+38
View File
@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
from django.db.models import F
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from dvadmin.system.models import FieldPermission, MenuField
from dvadmin.utils.json_response import DetailResponse
from dvadmin.utils.models import get_custom_app_models
class FieldPermissionMixin:
@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 []
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
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)
+204 -98
View File
@@ -12,16 +12,49 @@ from collections import OrderedDict
from functools import reduce
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
from django_filters.filters import CharFilter
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 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
class CoreModelFilterBankend(BaseFilterBackend):
"""
自定义时间范围过滤器
"""
def filter_queryset(self, request, queryset, view):
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)
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)
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)
# 更新时间范围过滤条件
update_filter = Q()
if update_datetime_after and update_datetime_before:
update_filter &= Q(update_datetime__gte=update_datetime_after) & Q(update_datetime__lte=update_datetime_before)
elif update_datetime_after:
update_filter &= Q(update_datetime__gte=update_datetime_after)
elif update_datetime_before:
update_filter &= Q(update_datetime__lte=update_datetime_before)
# 结合两个时间范围过滤条件
queryset = queryset.filter(create_filter & update_filter)
return queryset
return queryset
def get_dept(dept_id: int, dept_all_list=None, dept_list=None):
"""
@@ -75,7 +108,7 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
if item.get("permission__api")
]
for item in api_white_list:
new_api = api + ":" + str(method)
new_api = f"{api}:{method}"
matchObj = re.match(item, new_api, re.M | re.I)
if matchObj is None:
continue
@@ -86,74 +119,83 @@ class DataLevelPermissionsFilter(BaseFilterBackend):
如果不是超级管理员,则进入下一步权限判断
"""
if request.user.is_superuser == 0:
# 0. 获取用户的部门id,没有部门则返回空
user_dept_id = getattr(request.user, "dept_id", None)
if not user_dept_id:
return queryset.none()
# 1. 判断过滤的数据是否有创建人所在部门 "dept_belong_id" 字段
if not getattr(queryset.model, "dept_belong_id", None):
return queryset
# 2. 如果用户没有关联角色则返回本部门数据
if not hasattr(request.user, "role"):
return queryset.filter(dept_belong_id=user_dept_id)
# 3. 根据所有角色 获取所有权限范围
# (0, "仅本人数据权限"),
# (1, "本部门及以下数据权限"),
# (2, "本部门数据权限"),
# (3, "全部数据权限"),
# (4, "自定数据权限")
replace_str = re.compile('\d')
re_api = replace_str.sub('{id}', api)
role_id_list = request.user.role.values_list('id', flat=True)
role_permission_list=RoleMenuButtonPermission.objects.filter(
role__in=role_id_list,
role__status=1,
menu_button__api=re_api,
menu_button__method=method).values(
'data_range',
role_admin=F('role__admin')
)
dataScope_list = [] # 权限范围列表
for ele in role_permission_list:
# 判断用户是否为超级管理员角色/如果拥有[全部数据权限]则返回所有数据
if 3 == ele.get("data_range") or ele.get("role_admin") == True:
return queryset
dataScope_list.append(ele.get("data_range"))
dataScope_list = list(set(dataScope_list))
# 4. 只为仅本人数据权限时只返回过滤本人数据,并且部门为自己本部门(考虑到用户会变部门,只能看当前用户所在的部门数据)
if 0 in dataScope_list:
return queryset.filter(
creator=request.user, dept_belong_id=user_dept_id
)
# 5. 自定数据权限 获取部门,根据部门过滤
dept_list = []
for ele in dataScope_list:
if ele == 4:
dept_list.extend(
request.user.role.filter(status=1).values_list(
"dept__id", flat=True
)
)
elif ele == 2:
dept_list.append(user_dept_id)
elif ele == 1:
dept_list.append(user_dept_id)
dept_list.extend(
get_dept(
user_dept_id,
)
)
if queryset.model._meta.model_name == 'dept':
return queryset.filter(id__in=list(set(dept_list)))
return queryset.filter(dept_belong_id__in=list(set(dept_list)))
return self._extracted_from_filter_queryset_33(request, queryset, api, method)
else:
return queryset
# TODO Rename this here and in `filter_queryset`
def _extracted_from_filter_queryset_33(self, request, queryset, api, method):
# 0. 获取用户的部门id,没有部门则返回空
user_dept_id = getattr(request.user, "dept_id", None)
if not user_dept_id:
return queryset.none()
# 1. 判断过滤的数据是否有创建人所在部门 "dept_belong_id" 字段
if not getattr(queryset.model, "dept_belong_id", None):
return queryset
# 2. 如果用户没有关联角色则返回本部门数据
if not hasattr(request.user, "role"):
return queryset.filter(dept_belong_id=user_dept_id)
# 3. 根据所有角色 获取所有权限范围
# (0, "仅本人数据权限"),
# (1, "本部门及以下数据权限"),
# (2, "本部门数据权限"),
# (3, "全部数据权限"),
# (4, "自定数据权限")
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)
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'
)
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=user_dept_id
)
# 5. 自定数据权限 获取部门,根据部门过滤
dept_list = []
for ele in dataScope_list:
if ele == 1:
dept_list.append(user_dept_id)
dept_list.extend(
get_dept(
user_dept_id,
)
)
elif ele == 2:
dept_list.append(user_dept_id)
elif ele == 4:
dept_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 queryset.model._meta.model_name == 'dept':
return queryset.filter(id__in=list(set(dept_list)))
return queryset.filter(dept_belong_id__in=list(set(dept_list)))
class CustomDjangoFilterBackend(DjangoFilterBackend):
lookup_prefixes = {
@@ -163,19 +205,24 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
"$": "iregex",
"~": "icontains",
}
filter_fields = "__all__"
def construct_search(self, field_name):
def construct_search(self, field_name, lookup_expr=None):
lookup = self.lookup_prefixes.get(field_name[0])
if lookup:
field_name = field_name[1:]
else:
lookup = "icontains"
return LOOKUP_SEP.join([field_name, lookup])
lookup = lookup_expr
if lookup:
if field_name.endswith(lookup):
return field_name
return LOOKUP_SEP.join([field_name, lookup])
return field_name
def find_filter_lookups(self, orm_lookups, search_term_key):
for lookup in orm_lookups:
# if lookup.find(search_term_key) >= 0:
new_lookup = lookup.split("__")[0]
new_lookup = LOOKUP_SEP.join(lookup.split(LOOKUP_SEP)[:-1]) if len(lookup.split(LOOKUP_SEP)) > 1 else lookup
# 修复条件搜索错误 bug
if new_lookup == search_term_key:
return lookup
@@ -191,18 +238,22 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
# TODO: remove assertion in 2.1
if filterset_class is None and hasattr(view, "filter_class"):
utils.deprecate(
"`%s.filter_class` attribute should be renamed `filterset_class`."
% view.__class__.__name__
"`%s.filter_class` attribute should be renamed `filterset_class`." % view.__class__.__name__
)
filterset_class = getattr(view, "filter_class", None)
# TODO: remove assertion in 2.1
if filterset_fields is None and hasattr(view, "filter_fields"):
utils.deprecate(
"`%s.filter_fields` attribute should be renamed `filterset_fields`."
% view.__class__.__name__
"`%s.filter_fields` attribute should be renamed `filterset_fields`." % view.__class__.__name__
)
filterset_fields = getattr(view, "filter_fields", None)
self.filter_fields = getattr(view, "filter_fields", None)
if isinstance(self.filter_fields, (list, tuple)):
filterset_fields = [
field[1:] if field[0] in self.lookup_prefixes.keys() else field for field in self.filter_fields
]
else:
filterset_fields = self.filter_fields
if filterset_class:
filterset_model = filterset_class._meta.model
@@ -222,6 +273,51 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
MetaBase = getattr(self.filterset_base, "Meta", object)
class AutoFilterSet(self.filterset_base):
@classmethod
def get_all_model_fields(cls, model):
opts = model._meta
return [
f.name
for f in sorted(opts.fields + opts.many_to_many)
if (f.name == "id")
or not isinstance(f, models.AutoField)
and not (getattr(f.remote_field, "parent_link", False))
]
@classmethod
def get_fields(cls):
"""
Resolve the 'fields' argument that should be used for generating filters on the
filterset. This is 'Meta.fields' sans the fields in 'Meta.exclude'.
"""
model = cls._meta.model
fields = cls._meta.fields
exclude = cls._meta.exclude
assert not (fields is None and exclude is None), (
"Setting 'Meta.model' without either 'Meta.fields' or 'Meta.exclude' "
"has been deprecated since 0.15.0 and is now disallowed. Add an explicit "
"'Meta.fields' or 'Meta.exclude' to the %s class." % cls.__name__
)
# Setting exclude with no fields implies all other fields.
if exclude is not None and fields is None:
fields = ALL_FIELDS
# Resolve ALL_FIELDS into all fields for the filterset's model.
if fields == ALL_FIELDS:
fields = cls.get_all_model_fields(model)
# Remove excluded fields
exclude = exclude or []
if not isinstance(fields, dict):
fields = [(f, [settings.DEFAULT_LOOKUP_EXPR]) for f in fields if f not in exclude]
else:
fields = [(f, lookups) for f, lookups in fields.items() if f not in exclude]
return OrderedDict(fields)
@classmethod
def get_filters(cls):
"""
@@ -249,7 +345,13 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
# warn if the field doesn't exist.
if field is None:
undefined.append(field_name)
# 更新默认字符串搜索为模糊搜索
if (
isinstance(field, (models.CharField))
and filterset_fields == "__all__"
and lookups == ["exact"]
):
lookups = ["icontains"]
for lookup_expr in lookups:
filter_name = cls.get_filter_name(field_name, lookup_expr)
@@ -259,20 +361,15 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
continue
if field is not None:
filters[filter_name] = cls.filter_for_field(
field, field_name, lookup_expr
)
filters[filter_name] = cls.filter_for_field(field, field_name, lookup_expr)
# Allow Meta.fields to contain declared filters *only* when a list/tuple
if isinstance(cls._meta.fields, (list, tuple)):
undefined = [
f for f in undefined if f not in cls.declared_filters
]
undefined = [f for f in undefined if f not in cls.declared_filters]
if undefined:
raise TypeError(
"'Meta.fields' must not contain non-model field names: %s"
% ", ".join(undefined)
"'Meta.fields' must not contain non-model field names: %s" % ", ".join(undefined)
)
# Add in declared filters. This is necessary since we don't enforce adding
@@ -294,22 +391,31 @@ class CustomDjangoFilterBackend(DjangoFilterBackend):
return queryset
if filterset.__class__.__name__ == "AutoFilterSet":
queryset = filterset.queryset
orm_lookups = []
for search_field in filterset.filters:
if isinstance(filterset.filters[search_field], CharFilter):
orm_lookups.append(
self.construct_search(six.text_type(search_field))
)
else:
orm_lookups.append(search_field)
filter_fields = filterset.filters if self.filter_fields == "__all__" else self.filter_fields
orm_lookup_dict = dict(
zip(
[field for field in filter_fields],
[filterset.filters[lookup].lookup_expr for lookup in filterset.filters.keys()],
)
)
orm_lookups = [
self.construct_search(lookup, lookup_expr) for lookup, lookup_expr in orm_lookup_dict.items()
]
# print(orm_lookups)
conditions = []
queries = []
for search_term_key in filterset.data.keys():
orm_lookup = self.find_filter_lookups(orm_lookups, search_term_key)
if not orm_lookup:
if not orm_lookup or filterset.data.get(search_term_key) == '':
continue
query = Q(**{orm_lookup: filterset.data[search_term_key]})
queries.append(query)
filterset_data_len = len(filterset.data.getlist(search_term_key))
if filterset_data_len == 1:
query = Q(**{orm_lookup: filterset.data[search_term_key]})
queries.append(query)
elif filterset_data_len == 2:
orm_lookup += '__range'
query = Q(**{orm_lookup: filterset.data.getlist(search_term_key)})
queries.append(query)
if len(queries) > 0:
conditions.append(reduce(operator.and_, queries))
queryset = queryset.filter(reduce(operator.and_, conditions))
-125
View File
@@ -1,125 +0,0 @@
import logging
import os.path
import sys
from django.db import connection
from loguru import logger
from logging.handlers import RotatingFileHandler
# 1.🎖️先声明一个类继承logging.Handler(制作一件品如的衣服)
from application.dispatch import is_tenants_mode
class InterceptTimedRotatingFileHandler(RotatingFileHandler, logging.Filter):
"""
自定义反射时间回滚日志记录器
缺少命名空间
"""
def __init__(self, filename, when='d', interval=1, backupCount=5, encoding="utf-8", delay=False, utc=False,
maxBytes=1024 * 1024 * 100, atTime=None, logging_levels="all", format=None):
super(InterceptTimedRotatingFileHandler, self).__init__(filename)
filename = os.path.abspath(filename)
# 定义默认格式
if not format:
format = "<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <green>{extra[ip]}:{extra[port]}</green> | <level>{level: <8}</level>| <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>"
when = when.lower()
# 2.🎖️需要本地用不同的文件名做为不同日志的筛选器
logger.configure(
handlers=[
dict(sink=sys.stderr, format=format),
],
)
self.logger_ = logger.bind(sime=filename, ip="-", port="-", username="张三")
self.filename = filename
key_map = {
'h': 'hour',
'w': 'week',
's': 'second',
'm': 'minute',
'd': 'day',
}
# 根据输入文件格式及时间回滚设立文件名称
rotation = f"{maxBytes / 1024 / 1024}MB"
retention = "%d %ss" % (backupCount, key_map[when])
time_format = "{time:%Y-%m-%d_%H-%M-%S}"
if when == "s":
time_format = "{time:%Y-%m-%d_%H-%M-%S}"
elif when == "m":
time_format = "{time:%Y-%m-%d_%H-%M}"
elif when == "h":
time_format = "{time:%Y-%m-%d_%H}"
elif when == "d":
time_format = "{time:%Y-%m-%d}"
elif when == "w":
time_format = "{time:%Y-%m-%d}"
level_keys = ["info"]
# 3.🎖️构建一个筛选器
levels = {
"debug": lambda x: "DEBUG" == x['level'].name.upper() and x['extra'].get('sime') == filename,
"error": lambda x: "ERROR" == x['level'].name.upper() and x['extra'].get('sime') == filename,
"info": lambda x: "INFO" == x['level'].name.upper() and x['extra'].get('sime') == filename,
"warning": lambda x: "WARNING" == x['level'].name.upper() and x['extra'].get('sime') == filename
}
# 4. 🎖️根据输出构建筛选器
if isinstance(logging_levels, str):
if logging_levels.lower() == "all":
level_keys = levels.keys()
elif logging_levels.lower() in levels:
level_keys = [logging_levels]
elif isinstance(logging_levels, (list, tuple)):
level_keys = logging_levels
for k, f in {_: levels[_] for _ in level_keys}.items():
# 5.🎖️为防止重复添加sink,而重复写入日志,需要判断是否已经装载了对应sink,防止其使用秘技:反复横跳。
filename_fmt = filename.replace(".log", "_%s_%s.log" % (time_format, k))
# noinspection PyUnresolvedReferences,PyProtectedMember
file_key = {_._name: han_id for han_id, _ in self.logger_._core.handlers.items()}
filename_fmt_key = "'{}'".format(filename_fmt)
if filename_fmt_key in file_key:
continue
# self.logger_.remove(file_key[filename_fmt_key])
self.logger_.add(
filename_fmt,
format=format,
retention=retention,
encoding=encoding,
level=self.level,
rotation=rotation,
compression="zip", # 日志归档自行压缩文件
delay=delay,
enqueue=True,
backtrace=True,
filter=f
)
def emit(self, record):
try:
level = self.logger_.level(record.levelname).name
except ValueError:
level = record.levelno
frame, depth = logging.currentframe(), 2
# 6.🎖️把当前帧的栈深度回到发生异常的堆栈深度,不然就是当前帧发生异常而无法回溯
while frame.f_code.co_filename == logging.__file__:
frame = frame.f_back
depth += 1
# 设置自定义属性
port = "-"
ip = "-"
details = frame.f_locals.get('details', None)
msg = self.format(record)
bind = {}
if details and details.get('client'):
ip, port = details.get('client').split(':')
if is_tenants_mode():
bind["schema_name"] = connection.tenant.schema_name
bind["domain_url"] = getattr(connection.tenant, 'domain_url', None)
bind["ip"] = ip
bind["port"] = port
self.logger_ \
.opt(depth=depth, exception=record.exc_info, colors=True) \
.bind(**bind) \
.log(level, msg)
+57
View File
@@ -2,9 +2,11 @@
日志 django中间件
"""
import json
import logging
from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from django.http import HttpResponse, HttpResponseServerError
from django.utils.deprecation import MiddlewareMixin
from dvadmin.system.models import OperationLog
@@ -87,3 +89,58 @@ class ApiLoggingMiddleware(MiddlewareMixin):
if self.methods == 'ALL' or request.method in self.methods:
self.__handle_response(request, response)
return response
logger = logging.getLogger("healthz")
class HealthCheckMiddleware(object):
"""
存活检查中间件
"""
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
if request.method == "GET":
if request.path == "/readiness":
return self.readiness(request)
elif request.path == "/healthz":
return self.healthz(request)
return self.get_response(request)
def healthz(self, request):
"""
Returns that the server is alive.
"""
return HttpResponse("OK")
def readiness(self, request):
# Connect to each database and do a generic standard SQL query
# that doesn't write any data and doesn't depend on any tables
# being present.
try:
from django.db import connections
for name in connections:
cursor = connections[name].cursor()
cursor.execute("SELECT 1;")
row = cursor.fetchone()
if row is None:
return HttpResponseServerError("db: invalid response")
except Exception as e:
logger.exception(e)
return HttpResponseServerError("db: cannot connect to database.")
# Call get_stats() to connect to each memcached instance and get it's stats.
# This can effectively check if each is online.
try:
from django.core.cache import caches
from django.core.cache.backends.memcached import BaseMemcachedCache
for cache in caches.all():
if isinstance(cache, BaseMemcachedCache):
stats = cache._cache.get_stats()
if len(stats) != len(cache._servers):
return HttpResponseServerError("cache: cannot connect to cache.")
except Exception as e:
logger.exception(e)
return HttpResponseServerError("cache: cannot connect to cache.")
return HttpResponse("OK")
+60 -12
View File
@@ -6,22 +6,21 @@
@Created on: 2021/5/31 031 22:08
@Remark: 公共基础model类
"""
import uuid
from importlib import import_module
from django.apps import apps
from django.db import models
from django.db.models import QuerySet
from django.conf import settings
from application import settings
table_prefix = settings.TABLE_PREFIX # 数据库表名前缀
class SoftDeleteQuerySet(QuerySet):
class SoftDeleteQuerySet(models.QuerySet):
pass
class SoftDeleteManager(models.Manager):
"""支持软删除"""
@@ -40,7 +39,7 @@ class SoftDeleteManager(models.Manager):
return SoftDeleteQuerySet(self.model, using=self._db).exclude(is_deleted=False)
return SoftDeleteQuerySet(self.model).exclude(is_deleted=True)
def get_by_natural_key(self,name):
def get_by_natural_key(self, name):
return SoftDeleteQuerySet(self.model).get(username=name)
@@ -73,10 +72,13 @@ class CoreModel(models.Model):
id = models.BigAutoField(primary_key=True, help_text="Id", verbose_name="Id")
description = models.CharField(max_length=255, verbose_name="描述", null=True, blank=True, help_text="描述")
creator = models.ForeignKey(to=settings.AUTH_USER_MODEL, related_query_name='creator_query', null=True,
verbose_name='创建人', help_text="创建人", on_delete=models.SET_NULL, db_constraint=False)
verbose_name='创建人', help_text="创建人", on_delete=models.SET_NULL,
db_constraint=False)
modifier = models.CharField(max_length=255, null=True, blank=True, help_text="修改人", verbose_name="修改人")
dept_belong_id = models.CharField(max_length=255, help_text="数据归属部门", null=True, blank=True, verbose_name="数据归属部门")
update_datetime = models.DateTimeField(auto_now=True, null=True, blank=True, help_text="修改时间", verbose_name="修改时间")
dept_belong_id = models.CharField(max_length=255, help_text="数据归属部门", null=True, blank=True,
verbose_name="数据归属部门")
update_datetime = models.DateTimeField(auto_now=True, null=True, blank=True, help_text="修改时间",
verbose_name="修改时间")
create_datetime = models.DateTimeField(auto_now_add=True, null=True, blank=True, help_text="创建时间",
verbose_name="创建时间")
@@ -86,8 +88,6 @@ class CoreModel(models.Model):
verbose_name_plural = verbose_name
def get_all_models_objects(model_name=None):
"""
获取所有 models 对象
@@ -111,4 +111,52 @@ def get_all_models_objects(model_name=None):
settings.ALL_MODELS_OBJECTS.setdefault(item.__name__, {"table": table, "object": item})
if model_name:
return settings.ALL_MODELS_OBJECTS[model_name] or {}
return settings.ALL_MODELS_OBJECTS or {}
return settings.ALL_MODELS_OBJECTS or {}
def get_model_from_app(app_name):
"""获取模型里的字段"""
model_module = import_module(app_name + '.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)
]
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
})
return model_list
def get_custom_app_models(app_name=None):
"""
获取所有项目下的app里的models
"""
if app_name:
return get_model_from_app(app_name)
all_apps = apps.get_app_configs()
res = []
for app in all_apps:
if app.name.startswith('django'):
continue
if app.name in settings.COLUMN_EXCLUDE_APPS:
continue
try:
all_models = get_model_from_app(app.name)
if all_models:
for model in all_models:
res.append(model)
except Exception as e:
pass
return res
+8 -10
View File
@@ -40,13 +40,11 @@ class CustomPagination(PageNumberPagination):
try:
self.page = paginator.page(page_number)
except InvalidPage as exc:
# msg = self.invalid_page_message.format(
# page_number=page_number, message=str(exc)
# )
# raise NotFound(msg)
empty = False
pass
if paginator.num_pages > 1 and self.template is not None:
# The browsable API should display pagination controls.
@@ -58,15 +56,15 @@ class CustomPagination(PageNumberPagination):
self.page = []
return list(self.page)
def get_paginated_response(self, data):
code = 2000
msg = 'success'
page =int(self.get_page_number(self.request, paginator)) or 1
total=self.page.paginator.count if self.page else 0
limit= int(self.get_page_size(self.request)) or 10
is_next= self.page.has_next()
is_previous= self.page.has_previous()
data=data
page = int(self.get_page_number(self.request, paginator)) or 1
total = self.page.paginator.count if self.page else 0
limit = int(self.get_page_size(self.request)) or 10
is_next = self.page.has_next() if self.page else False
is_previous = self.page.has_previous() if self.page else False
if not data:
code = 2000
@@ -78,8 +76,8 @@ class CustomPagination(PageNumberPagination):
('msg', msg),
('page', page),
('limit', limit),
('total',total),
('is_next',is_next),
('total', total),
('is_next', is_next),
('is_previous', is_previous),
('data', data)
]))
+1
View File
@@ -26,6 +26,7 @@ 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"):
+28 -4
View File
@@ -6,18 +6,20 @@
@Created on: 2021/6/1 001 22:57
@Remark: 自定义视图集
"""
import uuid
from django.db import transaction
from django_filters import DateTimeFromToRangeFilter
from django_filters.rest_framework import FilterSet
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework.decorators import action
from rest_framework.viewsets import ModelViewSet
from dvadmin.utils.filters import DataLevelPermissionsFilter
from dvadmin.utils.filters import DataLevelPermissionsFilter, CoreModelFilterBankend
from dvadmin.utils.import_export_mixin import ExportSerializerMixin, ImportSerializerMixin
from dvadmin.utils.json_response import SuccessResponse, ErrorResponse, DetailResponse
from dvadmin.utils.permission import CustomPermission
from dvadmin.utils.models import get_custom_app_models, CoreModel
from dvadmin.system.models import FieldPermission, MenuField
from django_restql.mixins import QueryArgumentsMixin
@@ -37,7 +39,7 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi
update_serializer_class = None
filter_fields = '__all__'
search_fields = ()
extra_filter_class = [DataLevelPermissionsFilter]
extra_filter_class = [CoreModelFilterBankend,DataLevelPermissionsFilter]
permission_classes = [CustomPermission]
import_field_dict = {}
export_field_label = {}
@@ -63,12 +65,34 @@ class CustomModelViewSet(ModelViewSet, ImportSerializerMixin, ExportSerializerMi
def get_serializer(self, *args, **kwargs):
serializer_class = self.get_serializer_class()
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
# 在分页器中使用
self.request.permission_fields = can_see
if isinstance(self.request.data, list):
with transaction.atomic():
return serializer_class(many=True, *args, **kwargs)
else:
return serializer_class(*args, **kwargs)
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:
return []
return MenuField.objects.filter(model=model['model']
).values('field_name', 'title')
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data, request=request)
serializer.is_valid(raise_exception=True)
View File
+13
View File
@@ -0,0 +1,13 @@
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()
uvicorn.run("application.asgi:application", reload=False, host="0.0.0.0", port=8000, workers=4,
log_config=LOGGING)
+26 -25
View File
@@ -1,31 +1,32 @@
Django==4.1.5
Django==4.2.7
django-comment-migrate==0.1.7
django-cors-headers==3.13.0
django-filter==21.1
django-cors-headers==4.3.0
django-filter==23.3
django-ranged-response==0.2.0
djangorestframework==3.14.0
django-restql==0.15.3
django-simple-captcha==0.5.17
django-timezone-field==5.0
djangorestframework-simplejwt==5.2.2
drf-yasg==1.21.4
mysqlclient==2.1.1
pypinyin==0.48.0
ua-parser==0.16.1
pyparsing==3.0.9
openpyxl==3.0.10
requests==2.28.2
loguru==0.6.0
typing-extensions==4.4.0
smmap==5.0.0
tzlocal==4.1
channels==4.0.0
channels-redis==4.0.0
websockets==10.4
django-simple-captcha==0.5.20
django-timezone-field==6.0.1
djangorestframework-simplejwt==5.3.0
drf-yasg==1.21.7
mysqlclient==2.2.0
pypinyin==0.49.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
user-agents==2.2.0
six==1.16.0
whitenoise==6.3.0
psycopg2==2.9.5
uvicorn==0.20.0
gunicorn==20.1.0
gevent==22.10.2
whitenoise==6.6.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
pyinstaller==6.8.0
+123
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.
+11
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.
+34
View File
@@ -0,0 +1,34 @@
html,body{width:100%;overflow-x: hidden;margin: 0;padding: 0;font-size:1rem;font-family: "PingFang SC","SF Pro SC","SF Pro Text","SF Pro Icons","Helvetica Neue","Helvetica","Arial",sans-serif;
-webkit-font-smoothing: antialiased;}
a{text-decoration:none;}
ul,li{list-style:none;padding:0;margin:0;}
.bg{background-repeat:no-repeat;background-position:center center;background-size:100%;}
.center{margin:0 auto;position:relative;}
.abs_center{position:absolute;left: 50%;transform: translateX(-50%);-webkit-transform:translateX(-50%);}
/*滚动条统一样式*/
.scrollbar{overflow-x:hidden;scrollbar-track-color:none;}
.scrollbar::-webkit-scrollbar{width:8px;background-color:transparent;} /*滚动条整体部分*/
.scrollbar::-webkit-scrollbar-track{border-radius:4px;-webkit-box-shadow: inset 0 0 0 rgba(0,0,0,0);background-color:transparent;display:none;} /* 滚动条的轨道*/
.scrollbar::-webkit-scrollbar-track-piece{background-color:transparent;display:none;}/* 滚动条的内层轨道*/
.scrollbar::-webkit-scrollbar-thumb{border-radius:4px;background: #313131;}/*滚动条里面的小方块,能向上向下移动(或往左往右移动,取决于是垂直滚动条还是水平滚动条)*/
h3,h4,h5{ font-weight: bold;}
h3{ font-size: .4rem; color:#000;}
h4{ font-size: .35rem; color: #000; margin:.2rem 0 0 0;}
p{ line-height: .5rem; margin:.2rem 0;word-break: break-all;text-align:justify;}
.app .main{width:90%;overflow:hidden;font-size:.30rem;box-sizing: border-box; margin: 0 auto; color: #484d57;}
.app ul { padding-left: .1rem; font-size: .26rem;}
.app ul p{ line-height: .4rem;}
.app .mainbg{position:absolute;left:50%;transform:translateX(-50%);background-repeat:no-repeat;background-size:100%;}
.app .poster{width:100%;height:31.85rem;background-image:url(../images/poster_bg.jpg);}
.pc .main{font-size:.16rem;}
+18
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.
+163
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.
+6
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.
+22
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.
@@ -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>
+22
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.
@@ -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.
@@ -0,0 +1,2 @@
swagger-ui
Copyright 2020-2021 SmartBear Software Inc.
@@ -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.
@@ -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>
@@ -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
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