新的提交信息03171806
This commit is contained in:
commit
c1d6b4602e
31
LICENSE
Normal file
31
LICENSE
Normal file
@ -0,0 +1,31 @@
|
||||
Django-Vue-Lyadmin 专业版 授权许可协议 1.0
|
||||
|
||||
用户购买授权时(以下称为“授权者”),即意味着同意以下协议:
|
||||
|
||||
1、授权者可将本产品用于任意「符合国家法律法规」的应用平台,禁止用于黄赌毒等危害国家安全与稳定的网站、应用等。
|
||||
|
||||
2、本产品购买后可用于开发商业项目,不限制域名和项目数量。
|
||||
|
||||
3、授权者需保证不传播产品源码,不得直接对本产品(或简单包装成同类产品)进行二次转售或发布。无论有意或无意,我们有权利收回产品授权及更新权限。
|
||||
|
||||
4、本产品的源码(区别于django-vue-lyadmin基础版的源码),在未经我们许可下,不可以用于任何形式的开源项目,否则我们有权利收回产品授权及更新权限。
|
||||
|
||||
5、因使用本产品所产生的损害及风险,包括但不限于个人损害、商业赢利丧失、贸易中断、商业信息丢失、违反国家法律法规等或任何其它经济损失,需由授权者自行承担,我们不承担任何责任。
|
||||
|
||||
6、授权者享有反映和提出意见的优先权,相关意见将被我们作为首要考虑。
|
||||
|
||||
7、授权者购买的授权,可以用与个人或自己公司的项目中进行二次开发使用,也可用于第三方,禁止简单开发包装后二次售卖场景。
|
||||
|
||||
8、授权者可对源码进行修改和扩展,但需保留源码中相关本作者的文件头部作者声明。
|
||||
|
||||
9、订阅有效期内收到的源代码可以永久使用,超过订阅有效期只是不再推送包含最新功能的新版代码。
|
||||
|
||||
10、Django-Vue-Lyadmin专业版为非开源项目,获取到源代码的授权者可自由修改源码供自身开发使用,也可以分发build构建后的库代码,但不可分发Django-Vue-Lyadmin专业版源代码,本作者保留Django-Vue-Lyadmin专业版的原始著作权。
|
||||
|
||||
11、由于软件属于虚拟物品,因此一旦售出不支持退货退款。
|
||||
|
||||
12、本协议约定的任何条款部分或完全无效的,不影响其他条款的效力。
|
||||
|
||||
13、如果授权者违反了本协议的任一条款,本授权协议将自动终止,授权者行为损害我们或其他任意第三方权利的,我们保留通过法律手段追究责任的权利。
|
||||
|
||||
14、本协议及本协议任何条款内容的最终解释权归django-vue-lyadmin及本作者所有。
|
||||
260
README.md
Normal file
260
README.md
Normal file
@ -0,0 +1,260 @@
|
||||
说明:本系统为django-vue-lyadmin专业版,专业版与基础版大体框架一样,只是功能有增加,因此文档可以参考基础版
|
||||
|
||||
注意:使用专业版开发时,请使用superadmin/123456超级管理员登录系统,方便测试和调试(避免菜单权限问题)
|
||||
|
||||
# Django-Vue-Lyadmin
|
||||
|
||||
[](https://python.org/) [](https://docs.djangoproject.com/zh-hans/4.0/) [](https://nodejs.org/zh-cn/) [](https://gitee.com/lybbn/django-vue-lyadmin)
|
||||
|
||||
[QQ群聊](https://jq.qq.com/?_wv=1027&k=StAkGqk5) | [在线体验(admin/123456)](http://django-vue3-lyadmin.lybbn.cn) | [开发文档](https://gitee.com/lybbn/django-vue-lyadmin/wikis/pages?sort_id=5264002&doc_id=2214316) | [在线课程](https://gitee.com/lybbn/django-vue-lyadmin/wikis/pages?sort_id=5476409&doc_id=2214316) | [捐赠](https://gitee.com/lybbn/django-vue-lyadmin/wikis/pages?sort_id=5264497&doc_id=2214316)
|
||||
|
||||
## slogon
|
||||
|
||||
前端frontend:做一个专业前端能用的框架,后台人员也能面向配置的、能改得动的CRUD
|
||||
后端backend :强大的功能集合,让你开箱即用,成为初学者的领航员
|
||||
|
||||
## 平台简介
|
||||
|
||||
django-vue-lyadmin 是一套python django web前后端分离的管理后台快速开发平台(内置简易商城模块),去繁从简、还你一个干净的后台管理系统
|
||||
|
||||
* 前端采用Vue3(elementplus 2.3.14 支持暗黑主题)(vue2版本请访问分支django-vue2-lyadmin)
|
||||
* 前端支持面向配置的CRUD和自定义页面的CRUD双开发模式
|
||||
* DashBoard: 数据分析查看
|
||||
* 计划任务(定时任务,运维能力),django-celery-beat 定时任务
|
||||
* 服务器监控面板(运维能力),支持windows和linux服务器的实时服务器资源状态监控
|
||||
* 终端服务webssh(运维能力),支持基于channels的websocket与xterm的webssh(实现websocket的simple-jwt认证,并实现请求方法和接口地址的权限控制)
|
||||
* 后端采用Python语言Django框架
|
||||
* 权限认证使用JWT(djangorestframework-simplejwt),支持多终端认证系统
|
||||
* 接口采用(drf)djangorestframework,支持后台一键关闭前端API访问功能
|
||||
* 支持加载动态权限菜单,内置常用模块,多方式轻松权限控制,支持单用户登录(踢掉上一个)
|
||||
* 支持支付宝、微信支付、微信登录、阿里云短信、腾讯云短信等
|
||||
* 新增商城模块:商品管理、订单管理、财务统计、支付接口(微信支付(app端、小程序端)、支付宝(app端)供参考)....
|
||||
* 适合刚入门或苦于寻找django web快速开发框架的小伙伴们
|
||||
|
||||
特别鸣谢:本平台后端权限设计模式,部分逻辑参考[django-vue-admin-pro](https://gitee.com/dvadmin/django-vue-admin-pro)
|
||||
|
||||
## 在线体验
|
||||
|
||||
演示地址:[http://django-vue3-lyadmin.lybbn.cn](http://django-vue3-lyadmin.lybbn.cn) 账号:admin 密码:123456
|
||||
|
||||
Eleunipy在线模板系统:[https://eleunipy.lybbn.cn/](https://eleunipy.lybbn.cn/) 账号/密码: 自行注册使用
|
||||
|
||||
eleunipy系统是结合
|
||||
[django-vue-lyadmin](https://gitee.com/lybbn/django-vue-lyadmin)
|
||||
[unielepy](https://gitee.com/lybbn/unielepy)
|
||||
在全栈开发中,能让开发者挑选模板/组件/源码实现低代码、避免重复造轮子快速完成项目,模板持续更新中...
|
||||
|
||||
## 文档地址
|
||||
|
||||
文档地址:文档在本项目的wiki中会持续更新,也可以通过官网访问:[www.lybbn.cn](http://www.lybbn.cn)
|
||||
|
||||
说明:django-vue3-lyadmin 项目功能已合并至django-vue-lyadmin项目下,如果仅需要简约功能框架可访问: [django-vue3-lyadmin](https://gitee.com/lybbn/django-vue3-lyadmin)
|
||||
|
||||
补充:如果想找到1.x版本(vue2)标准模块(不带商城功能)的,可前往 [正式版v1.0.20](https://gitee.com/lybbn/django-vue-lyadmin/releases/v1.0.20) 版本进行下载
|
||||
|
||||
## 交流
|
||||
- 开发者WX号:laoyanyj
|
||||
|
||||
- QQ群号:755277564 <a target="_blank" href="https://jq.qq.com/?_wv=1027&k=oPz6bqmL"><img border="0" src="//pub.idqqimg.com/wpa/images/group.png" alt="django-vue-lyadmin交流01群" title="django-vue-lyadmin交流01群"></a>
|
||||
- 二维码:
|
||||
|
||||
<img src='https://gitee.com/lybbn/django-vue-lyadmin/raw/master/frontend/src/assets/img/qq.jpg' width='200'>
|
||||
|
||||
## 源码地址
|
||||
|
||||
gitee地址(主推):https://gitee.com/lybbn/django-vue-lyadmin
|
||||
|
||||
## 内置功能
|
||||
|
||||
01. DashBoard: 数据分析查看
|
||||
02. CRUD: 面向配置的crud功能
|
||||
03. 计划任务(定时任务,运维能力),django-celery-beat 定时任务
|
||||
04. 服务器监控面板(运维能力),支持windows和linux服务器的实时服务器资源状态监控
|
||||
05. 终端服务webssh(运维能力),支持基于channels的websocket与xterm的webssh(实现websocket的simple-jwt认证,并实现请求方法和接口地址的权限控制)
|
||||
06. 部门管理:配置系统组织机构(公司、部门、角色),树结构展现支持数据权限。
|
||||
07. 菜单管理:配置系统菜单,操作权限,按钮权限标识、后端接口权限等。
|
||||
08. 角色管理:角色菜单权限、数据权限、设置角色按部门进行数据范围权限划分。
|
||||
09. 权限管理:授权角色的权限范围。
|
||||
10. 地区管理:国内省市区管理。
|
||||
11. 管理员管理:主要管理系统管理员账号。
|
||||
12. 用户管理:主要管理前端用户。
|
||||
13. 个人中心:主要设置登录系统的个人昵称、密码等账号信息。
|
||||
14. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
|
||||
15. 平台设置:系统设置如字典参数、轮播图
|
||||
16. 意见反馈:配合前端接口收集用户的反馈信息
|
||||
17. 商品管理:支持多规格、单规格添加商品、提供对应的支付接口和前端商品详情接口供参考
|
||||
18. 订单管理:主要为商品订单的管理有发货、统计......
|
||||
19. 财务统计:平台订单等财务统计
|
||||
20. 其他功能:内置微信登录、小程序登录、短信登录、密码登录、微信企业到零钱、微信支付、支付宝支付、极光推送等API。
|
||||
|
||||
## django-vue-lyadmin项目启动视频讲解
|
||||
|
||||
[](https://v.kuaishouapp.com/s/VIJPdIx6)
|
||||
|
||||
## lyadmin后端
|
||||
|
||||
~~~bash
|
||||
1. 进入项目目录
|
||||
|
||||
2. 在 config.py 中配置数据库信息
|
||||
mysql数据库版本建议:8.0(django4.2版本开始要求mysql8.x及以上)
|
||||
mysql数据库字符集:utf8mb4(mysql8.x排序规则选择:utf8mb4_0900_ai_ci、mysql5.7.x选择utf8mb4_general_ci)
|
||||
mysql数据库对应的表关于事务处理的确保是:innodb引擎(能回滚)
|
||||
|
||||
3. 设置数据库隔离级别(悲观锁、乐观锁)
|
||||
全局设置mysql数据库隔离级别为READ-COMMITTED(临时生效,重启就没了):SET GLOBAL tx_isolation='READ-COMMITTED';
|
||||
全局设置mysql数据库隔离级别为READ-COMMITTED(永久有效):修改配置文件my.cnf 的[mysqld]中增加 transaction-isolation=Read-Committed
|
||||
当数据库当前会话的隔离级别:set tx_isolation='READ-COMMITTED';
|
||||
查询当前会话的数据库隔离级别:select @@tx_isolation;
|
||||
查询数据库mysql的隔离级别:select @@global.tx_isolation;
|
||||
|
||||
4. 设置数据库不区分大小写:修改配置文件my.cnf 的[mysqld]中增加 lower_case_table_names = 1
|
||||
|
||||
5. 安装依赖环境
|
||||
pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
|
||||
|
||||
6. 数据初始化使用sql脚本直接导入
|
||||
sql脚本位置:backend/lyadmin_pro.sql
|
||||
|
||||
7. 执行迁移命令:(防止代码有更新但未进行数据同步问题)
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
|
||||
8. 启动项目(初始账号:superadmin 密码:123456)
|
||||
python manage.py runserver 127.0.0.1:8000
|
||||
或使用 daphne (使用【终端服务】的需要使用此asgi方式部署来支持websocket):
|
||||
daphne -b 0.0.0.0 -p 8000 --proxy-headers application.asgi:application
|
||||
|
||||
使用celery【计划任务】需要额外启动celery 和 beat(调度器)
|
||||
mac/linux:
|
||||
celery -A application worker -B -l info
|
||||
windows:(需要安装: pip install eventlet)
|
||||
celery -A application worker -P eventlet -l info
|
||||
celery -A application beat -l info
|
||||
|
||||
9. 线上linux部署启动项目(需修改gunicorn_restart.sh中gunicorn命令安装目录)
|
||||
sh gunicorn_restart.sh
|
||||
~~~
|
||||
|
||||
#### docker-compose 部署
|
||||
|
||||
~~~bash
|
||||
1、先安装docker环境
|
||||
2、pip install docker-compose 安装docker-compose
|
||||
3、切换到项目根目录运行 docker-compose build 创建环境
|
||||
4、docker-compose up -d 后台的方式启动docker环境
|
||||
5、初始化django后端数据(第一次执行即可)
|
||||
docker exec -ti django-vue-lyadmin_django bash
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
python manage.py init
|
||||
exit
|
||||
6、其他docker-compose命令
|
||||
# docker-compose 停止
|
||||
docker-compose down
|
||||
# docker-compose 重启
|
||||
docker-compose restart
|
||||
# docker-compose 启动时重新进行 build
|
||||
docker-compose up -d --build
|
||||
7、说明:默认docker端口mysql:3306\redis:6379\前端:8080\后台:8000
|
||||
如果端口冲突会造成启动docker失败情况
|
||||
~~~
|
||||
|
||||
## 其他说明
|
||||
|
||||
1、使用本项目记得要更改application-->settings-->SECRET_KEY
|
||||
~~~bash
|
||||
可以运行python manage.py shell
|
||||
from django.core.management import utils
|
||||
utils.get_random_secret_key()
|
||||
获取生成的新SECRET_KEY替换原来的老KEY
|
||||
~~~
|
||||
|
||||
## lyadmin前端
|
||||
|
||||
#### 介绍
|
||||
|
||||
django-vue-lyadmin 是一套前后端分离的前端后台管理框架,是适配 django-vue-lyadmin 的 python django 后台管理项目的专属框架,基于原生 vue 开发,灵活自定义,可发挥空间大
|
||||
|
||||
#### 软件架构
|
||||
|
||||
|
||||
```
|
||||
1、vue3
|
||||
2、elementplus
|
||||
3、pinia
|
||||
4、富文本编辑器采用tinymce
|
||||
```
|
||||
|
||||
#### 安装教程
|
||||
|
||||
|
||||
```
|
||||
cd frontend
|
||||
npm install --registry=https://registry.npmmirror.com
|
||||
```
|
||||
|
||||
|
||||
#### 使用说明
|
||||
|
||||
调试开发直接运行(development):
|
||||
|
||||
```
|
||||
npm start
|
||||
```
|
||||
|
||||
|
||||
#### 打包
|
||||
|
||||
线上部署(production):
|
||||
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
打包后静态文件在 dist 目录中
|
||||
|
||||
## 线上部署注意事项
|
||||
|
||||
~~~bash
|
||||
1、前端打包前修改frontend\src\api\url里面的线上服务器ip或域名
|
||||
2、前端打包的dist里面的静态文件放到backend\frontend\目录
|
||||
3、运行python manage.py collectstatic收集静态文件到django
|
||||
~~~
|
||||
|
||||
## 演示图
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## 捐赠该项目
|
||||
|
||||
开源不易,可使用支付宝、微信扫下面二维码打赏支持。您的支持是我不断创作的动力!!!
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="https://gitee.com/lybbn/django-vue-lyadmin/raw/master/frontend/src/assets/img/alipay.jpg" height="300" width="400"/></td>
|
||||
<td><img src="https://gitee.com/lybbn/django-vue-lyadmin/raw/master/frontend/src/assets/img/wechat.jpg" height="300" width="400"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
16
backend/.gitignore
vendored
Normal file
16
backend/.gitignore
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
.idea
|
||||
logs
|
||||
# virtualenv
|
||||
/venv/
|
||||
*.log
|
||||
*.pot
|
||||
*.py[cod]
|
||||
__pycache__
|
||||
.env
|
||||
.venv
|
||||
#项目日志和上传资源目录
|
||||
/logs/
|
||||
#/media/
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
62
backend/README.md
Normal file
62
backend/README.md
Normal file
@ -0,0 +1,62 @@
|
||||
|
||||
## lyadmin后端 专业版
|
||||
|
||||
注意:本后端python版本请选择3.9及以上
|
||||
|
||||
~~~bash
|
||||
1. 进入项目目录
|
||||
|
||||
2. 在 config.py 中配置数据库信息
|
||||
mysql数据库版本建议:8.0(django4.2版本开始要求mysql8.x及以上)
|
||||
mysql数据库字符集:utf8mb4(mysql8.x排序规则选择:utf8mb4_0900_ai_ci、mysql5.7.x选择utf8mb4_general_ci)
|
||||
mysql数据库对应的表关于事务处理的确保是:innodb引擎(能回滚)
|
||||
|
||||
3. 设置数据库隔离级别(悲观锁、乐观锁)
|
||||
全局设置mysql数据库隔离级别为READ-COMMITTED(临时生效,重启就没了):SET GLOBAL tx_isolation='READ-COMMITTED';
|
||||
全局设置mysql数据库隔离级别为READ-COMMITTED(永久有效):修改配置文件my.cnf 的[mysqld]中增加 transaction-isolation=Read-Committed
|
||||
当数据库当前会话的隔离级别:set tx_isolation='READ-COMMITTED';
|
||||
查询当前会话的数据库隔离级别:select @@tx_isolation;
|
||||
查询数据库mysql的隔离级别:select @@global.tx_isolation;
|
||||
|
||||
4. 设置数据库不区分大小写:修改配置文件my.cnf 的[mysqld]中增加 lower_case_table_names = 1
|
||||
|
||||
5. 安装依赖环境
|
||||
pip install -r requirements_linux.txt -i https://mirrors.aliyun.com/pypi/simple/
|
||||
|
||||
6. 数据初始化使用sql脚本直接导入(本sql脚本使用的是SQL_Front5.3工具配合mysql5.7数据库导出的)
|
||||
sql脚本位置:backend/lyadmin_pro.sql
|
||||
|
||||
7. 执行迁移命令:(防止代码有更新但未进行数据同步问题)
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
|
||||
8. 启动项目(初始账号:superadmin 密码:123456)(本地开发)
|
||||
python manage.py runserver 127.0.0.1:8000
|
||||
或使用 daphne (使用【终端服务】的需要使用此asgi方式部署来支持websocket):
|
||||
daphne -b 0.0.0.0 -p 8000 --proxy-headers application.asgi:application
|
||||
|
||||
使用celery【计划任务】需要额外启动celery 和 beat(调度器)
|
||||
mac/linux:
|
||||
celery -A application worker -B -l info
|
||||
windows:(需要安装: pip install eventlet)
|
||||
celery -A application worker -P eventlet -l info
|
||||
celery -A application beat -l info
|
||||
|
||||
9. 线上linux部署启动项目(需修改gunicorn_restart.sh中gunicorn命令安装目录)
|
||||
sh gunicorn_restart.sh
|
||||
~~~
|
||||
|
||||
## 其他说明
|
||||
|
||||
1、使用本项目记得要更改application-->settings-->SECRET_KEY
|
||||
~~~bash
|
||||
可以运行python manage.py shell
|
||||
from django.core.management import utils
|
||||
utils.get_random_secret_key()
|
||||
获取生成的新SECRET_KEY替换原来的老KEY
|
||||
~~~
|
||||
2、本项目可使用最新的python版本开发、注意最新django版本安全问题,请及时更新安全稳定版本
|
||||
|
||||
python3.11.x 在windows中需要Microsoft Visual C++ >=14.0. 下载地址: https://visualstudio.microsoft.com/visual-cpp-build-tools/
|
||||
以上安装完还报错则访问如下地址:直接下载对应python和windows版本的twisted_iocpsupport如:twisted_iocpsupport‑1.0.2‑cp311‑cp311‑win32.whl直接pip install xxx.whl即可
|
||||
https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
|
||||
4
backend/application/__init__.py
Normal file
4
backend/application/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
#Django连接MySQL时默认使用MySQLdb驱动,但MySQLdb不支持Python3,因此这里将MySQL驱动设置为pymysql
|
||||
import pymysql
|
||||
|
||||
pymysql.install_as_MySQLdb()
|
||||
32
backend/application/asgi.py
Normal file
32
backend/application/asgi.py
Normal file
@ -0,0 +1,32 @@
|
||||
"""
|
||||
ASGI config for application project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
import django
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
|
||||
django.setup()
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
from utils.middleware import JwtAuthMiddleware
|
||||
from channels.routing import ProtocolTypeRouter, URLRouter
|
||||
from apps.lywebsocket.routing import websocket_urlpatterns
|
||||
|
||||
|
||||
# application = get_asgi_application()
|
||||
|
||||
application = ProtocolTypeRouter({
|
||||
"http": get_asgi_application(),# 也可以不需要此项,普通的HTTP请求不需要我们手动在这里添加,框架会自动加载
|
||||
"websocket": JwtAuthMiddleware(
|
||||
# 多个url合并一起使用,多个子路由列表相加:a+b
|
||||
URLRouter(
|
||||
websocket_urlpatterns
|
||||
)
|
||||
),
|
||||
})
|
||||
38
backend/application/celery.py
Normal file
38
backend/application/celery.py
Normal file
@ -0,0 +1,38 @@
|
||||
#!/bin/python
|
||||
#coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | system: django-vue-lyadmin
|
||||
# +-------------------------------------------------------------------
|
||||
# | Author: lybbn
|
||||
# +-------------------------------------------------------------------
|
||||
# | QQ: 1042594286
|
||||
# +-------------------------------------------------------------------
|
||||
|
||||
# ------------------------------
|
||||
# celery
|
||||
# ------------------------------
|
||||
# 官网
|
||||
# https://docs.celeryproject.org/en/stable/index.html
|
||||
# https://django-celery-beat.readthedocs.io/en/latest/
|
||||
# http://django-celery-results.readthedocs.io/
|
||||
# ------------------------------
|
||||
|
||||
import os
|
||||
from django.conf import settings
|
||||
from celery import platforms
|
||||
from celery import Celery
|
||||
|
||||
# Set the default Django settings module for the 'celery' program.
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
|
||||
|
||||
app = Celery("application")
|
||||
|
||||
# Using a string here means the worker doesn't have to serialize
|
||||
# the configuration object to child processes.
|
||||
# - namespace='CELERY' means all celery-related configuration keys
|
||||
# should have a `CELERY_` prefix.
|
||||
app.config_from_object('django.conf:settings',namespace='CELERY')
|
||||
|
||||
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
|
||||
|
||||
platforms.C_FORCE_ROOT = True
|
||||
566
backend/application/settings.py
Normal file
566
backend/application/settings.py
Normal file
@ -0,0 +1,566 @@
|
||||
"""
|
||||
Django settings for application project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 4.1.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.1/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/4.1/ref/settings/
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
|
||||
sys.path.insert(1, os.path.join(BASE_DIR, 'extra_apps'))
|
||||
|
||||
# ================================================= #
|
||||
# ******************** 动态配置 ******************** #
|
||||
# ================================================= #
|
||||
|
||||
from config import *
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'django-insecure-n=x1q3sl^3va9tb&ty$p1b+kob@eb%wh0bn&8yj&!bp05201314'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = locals().get("DEBUG", True)
|
||||
|
||||
ALLOWED_HOSTS = ["*"]
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
#other app
|
||||
# 'channels',#websocket
|
||||
'django_comment_migrate',
|
||||
'rest_framework',
|
||||
'django_filters',
|
||||
'corsheaders',#允许跨域
|
||||
'drf_yasg',#在线接口文档
|
||||
'captcha',#验证码
|
||||
'django_celery_results',
|
||||
'django_celery_beat',#计划任务
|
||||
#myown app
|
||||
'mysystem',
|
||||
#custom app
|
||||
'apps.lymessages',
|
||||
'apps.address',
|
||||
'apps.oauth',
|
||||
'apps.logins',
|
||||
'apps.lyusers',
|
||||
'apps.platformsettings',
|
||||
'apps.mall',
|
||||
'apps.lymonitor',
|
||||
'apps.lywebsocket',
|
||||
'apps.lycrontab',
|
||||
'apps.lyautocode',
|
||||
'apps.lyFormBuilder',
|
||||
'apps.lyTiktokUnion',
|
||||
'apps.lyworkflow',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'corsheaders.middleware.CorsMiddleware',#跨域中间件
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'utils.middleware.ApiLoggingMiddleware',#自定义日志中间件
|
||||
]
|
||||
|
||||
|
||||
ROOT_URLCONF = 'application.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [os.path.join(BASE_DIR, 'frontend')]#放置前端页面的地方
|
||||
,
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
ASGI_APPLICATION = 'application.asgi.application'
|
||||
WSGI_APPLICATION = 'application.wsgi.application'
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
|
||||
#sqlite3
|
||||
# DATABASES = {
|
||||
# 'default': {
|
||||
# 'ENGINE': 'django.db.backends.sqlite3',
|
||||
# 'NAME': BASE_DIR / 'db.sqlite3',
|
||||
# }
|
||||
# }
|
||||
#mysql
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': DATABASE_ENGINE,
|
||||
'NAME': DATABASE_NAME,
|
||||
'USER': DATABASE_USER,
|
||||
'PASSWORD': DATABASE_PASSWORD,
|
||||
'HOST': DATABASE_HOST,
|
||||
'PORT': DATABASE_PORT,
|
||||
'CONN_MAX_AGE':DATABASE_CONN_MAX_AGE,
|
||||
'OPTIONS': {
|
||||
'charset':DATABASE_CHARSET,
|
||||
'init_command': 'SET default_storage_engine=INNODB', #innodb才支持事务
|
||||
}
|
||||
}
|
||||
}
|
||||
AUTH_USER_MODEL = 'mysystem.Users'
|
||||
USERNAME_FIELD = 'username'
|
||||
ALL_MODELS_OBJECTS = [] # 所有app models 对象
|
||||
|
||||
# 配置redis缓存
|
||||
CACHES = {
|
||||
'default': {
|
||||
'BACKEND': 'django_redis.cache.RedisCache', # 缓存后端 Redis
|
||||
# 连接Redis数据库(服务器地址)
|
||||
# 一主带多从(可以配置多个Redis,写走第一台,读走其他的机器)
|
||||
'LOCATION': [
|
||||
f'{REDIS_URL}/0',
|
||||
],
|
||||
'KEY_PREFIX': 'lybbn', # 项目名当做文件前缀
|
||||
'OPTIONS': {
|
||||
'CLIENT_CLASS': 'django_redis.client.DefaultClient', # 连接选项(默认,不改)
|
||||
'CONNECTION_POOL_KWARGS': {
|
||||
'max_connections': 512, # 连接池的连接(最大连接)
|
||||
},
|
||||
}
|
||||
},
|
||||
'session': { #缓存session
|
||||
'BACKEND': 'django_redis.cache.RedisCache', # 缓存后端 Redis
|
||||
# 连接Redis数据库(服务器地址)
|
||||
# 一主带多从(可以配置多个Redis,写走第一台,读走其他的机器)
|
||||
'LOCATION': [
|
||||
f'{REDIS_URL}/1',
|
||||
],
|
||||
'OPTIONS': {
|
||||
'CLIENT_CLASS': 'django_redis.client.DefaultClient', # 连接选项(默认,不改)
|
||||
}
|
||||
},
|
||||
'verify_codes': { #缓存短信验证码
|
||||
'BACKEND': 'django_redis.cache.RedisCache', # 缓存后端 Redis
|
||||
# 连接Redis数据库(服务器地址)
|
||||
# 一主带多从(可以配置多个Redis,写走第一台,读走其他的机器)
|
||||
'LOCATION': [
|
||||
f'{REDIS_URL}/2',
|
||||
],
|
||||
'OPTIONS': {
|
||||
'CLIENT_CLASS': 'django_redis.client.DefaultClient', # 连接选项(默认,不改)
|
||||
}
|
||||
},
|
||||
"carts": { #登陆过的用户购物车的存储
|
||||
"BACKEND": "django_redis.cache.RedisCache",
|
||||
'LOCATION': [
|
||||
f'{REDIS_URL}/3',
|
||||
],
|
||||
"OPTIONS": {
|
||||
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
||||
'CONNECTION_POOL_KWARGS': {'decode_responses': True}, # 添加这一行,防止取出的值带有b'' bytes
|
||||
},
|
||||
},
|
||||
"authapi": { # 接口安全校验(验证接口重复第二次访问会拒绝)
|
||||
'BACKEND': 'django_redis.cache.RedisCache', # 缓存后端 Redis
|
||||
# 连接Redis数据库(服务器地址)
|
||||
# 一主带多从(可以配置多个Redis,写走第一台,读走其他的机器)
|
||||
'LOCATION': [
|
||||
f'{REDIS_URL}/4',
|
||||
],
|
||||
'OPTIONS': {
|
||||
'CLIENT_CLASS': 'django_redis.client.DefaultClient', # 连接选项(默认,不改)
|
||||
}
|
||||
},
|
||||
"singletoken": { # jwt单用户登录(确保一个账户只有一个地点登录,后一个会顶掉前一个)
|
||||
'BACKEND': 'django_redis.cache.RedisCache', # 缓存后端 Redis
|
||||
# 连接Redis数据库(服务器地址)
|
||||
# 一主带多从(可以配置多个Redis,写走第一台,读走其他的机器)
|
||||
'LOCATION': [
|
||||
f'{REDIS_URL}/5',
|
||||
],
|
||||
'OPTIONS': {
|
||||
'CLIENT_CLASS': 'django_redis.client.DefaultClient', # 连接选项(默认,不改)
|
||||
'CONNECTION_POOL_KWARGS': {'decode_responses': True}, # 添加这一行,防止取出的值带有b'' bytes
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
REDIS_TIMEOUT = 7 * 24 * 60 * 60
|
||||
CUBES_REDIS_TIMEOUT = 60 * 60
|
||||
NEVER_REDIS_TIMEOUT = 365 * 24 * 60 * 60
|
||||
|
||||
CHANNEL_LAYERS = {
|
||||
'default': {
|
||||
"BACKEND": "channels.layers.InMemoryChannelLayer" # 默认用内存
|
||||
},
|
||||
}
|
||||
# # 配置channels_redis,windows redis运行后报错:aioredis.errors.ReplyError: ERR unknown command 'BZPOPMIN',请注释以下配置或升级redis到5.0及以上
|
||||
# CHANNEL_LAYERS = {
|
||||
# 'default': {
|
||||
# 'BACKEND': 'channels_redis.core.RedisChannelLayer',
|
||||
# 'CONFIG': {
|
||||
# "hosts": ["redis://localhost:6379/5"],
|
||||
# },
|
||||
# },
|
||||
# }
|
||||
|
||||
# session使用的存储方式
|
||||
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
|
||||
# 指明使用哪一个库保存session数据
|
||||
SESSION_CACHE_ALIAS = "session"
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/3.2/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'zh-Hans'
|
||||
|
||||
TIME_ZONE = 'Asia/Shanghai'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = True
|
||||
|
||||
USE_TZ = False#设置为中国时间
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/3.2/howto/static-files/
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
# 设置django的静态文件目录
|
||||
# STATICFILES_DIRS = [
|
||||
# os.path.join(BASE_DIR, "static"),
|
||||
# ]
|
||||
FRONTEND_ROOT = os.path.join(BASE_DIR, "frontend")
|
||||
STATICFILES_DIRS = [
|
||||
os.path.join(FRONTEND_ROOT,"static"),
|
||||
os.path.join(FRONTEND_ROOT,"download-app","static"),
|
||||
os.path.join(FRONTEND_ROOT,"h5","static"),
|
||||
]
|
||||
# 收集静态文件,必须将 MEDIA_ROOT,STATICFILES_DIRS先注释
|
||||
# python manage.py collectstatic
|
||||
STATIC_ROOT=os.path.join(BASE_DIR,'static')
|
||||
|
||||
# 访问上传文件的url地址前缀
|
||||
if not os.path.exists(os.path.join(BASE_DIR, 'media')):
|
||||
os.makedirs(os.path.join(BASE_DIR, 'media'))
|
||||
|
||||
MEDIA_URL = "/media/"
|
||||
# 项目中存储上传文件的根目录
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
|
||||
|
||||
# ================================================= #
|
||||
# ******************** 自定义权限 ******************** #
|
||||
# ================================================= #
|
||||
|
||||
AUTHENTICATION_BACKENDS = ["utils.customBackend.CustomBackend"]
|
||||
|
||||
# ================================================= #
|
||||
# ******************* 跨域的配置 ******************* #
|
||||
# ================================================= #
|
||||
# 如果为True,则将不使用白名单,并且将接受所有来源。默认为False
|
||||
#允许跨域
|
||||
CORS_ORIGIN_ALLOW_ALL = True
|
||||
CORS_ALLOW_ALL_ORIGINS = True #新版 ACCESS_CONTROL_ALLOW_ORIGIN = '*' ,不能与CORS_ALLOW_CREDENTIALS一起使用
|
||||
# 允许cookie
|
||||
# CORS_ALLOW_CREDENTIALS = True # 指明在跨域访问中,后端是否支持对cookie的操作
|
||||
SECURE_CROSS_ORIGIN_OPENER_POLICY = 'None'
|
||||
|
||||
X_FRAME_OPTIONS = 'SAMEORIGIN'#SAMEORIGIN允许同源iframe嵌套、 DENY不允许iframe、ALLOW-FROM http://xxx.com指定uri嵌套、ALLOWALL 允许所有域名嵌套
|
||||
|
||||
# 允许的header头部字段
|
||||
# CORS_ALLOW_HEADERS = (
|
||||
# "accept",
|
||||
# "authorization",
|
||||
# "content-type",
|
||||
# "user-agent",
|
||||
# "x-csrftoken",
|
||||
# "x-requested-with",
|
||||
# "accept-encoding",
|
||||
# "lypage"
|
||||
# )
|
||||
|
||||
# ================================================= #
|
||||
# ********************* 日志配置 ******************* #
|
||||
# ================================================= #
|
||||
# log 配置部分BEGIN #
|
||||
SERVER_LOGS_FILE = os.path.join(BASE_DIR, 'logs', 'server.log')
|
||||
ERROR_LOGS_FILE = os.path.join(BASE_DIR, 'logs', 'error.log')
|
||||
if not os.path.exists(os.path.join(BASE_DIR, 'logs')):
|
||||
os.makedirs(os.path.join(BASE_DIR, 'logs'))
|
||||
|
||||
# 格式:[2023-12-28 20:45:03][basehttp.py:187:log_message] [INFO] 这是一条日志:
|
||||
# 格式:[日期][文件名:行号:函数] [级别] 信息
|
||||
STANDARD_LOG_FORMAT = '[%(asctime)s][%(filename)s:%(lineno)d:%(funcName)s] [%(levelname)s] %(message)s'
|
||||
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'formatters': {
|
||||
'standard': {
|
||||
'format': STANDARD_LOG_FORMAT,
|
||||
'datefmt': '%Y-%m-%d %H:%M:%S',
|
||||
},
|
||||
'console': {
|
||||
'format': STANDARD_LOG_FORMAT,
|
||||
'datefmt': '%Y-%m-%d %H:%M:%S',
|
||||
},
|
||||
'file': {
|
||||
'format': STANDARD_LOG_FORMAT,
|
||||
'datefmt': '%Y-%m-%d %H:%M:%S',
|
||||
},
|
||||
},
|
||||
'handlers': {
|
||||
'file': {
|
||||
'level': 'INFO',
|
||||
'class': 'logging.handlers.RotatingFileHandler',
|
||||
'filename': SERVER_LOGS_FILE,
|
||||
'maxBytes': 1024 * 1024 * 10, # 10 MB
|
||||
'backupCount': 10, # 最多备份10个
|
||||
'formatter': 'standard',
|
||||
'encoding': 'utf-8',
|
||||
},
|
||||
'error': {
|
||||
'level': 'ERROR',
|
||||
'class': 'logging.handlers.RotatingFileHandler',
|
||||
'filename': ERROR_LOGS_FILE,
|
||||
'maxBytes': 1024 * 1024 * 10, # 10 MB
|
||||
'backupCount': 10, # 最多备份10个
|
||||
'formatter': 'standard',
|
||||
'encoding': 'utf-8',
|
||||
},
|
||||
'console': {
|
||||
'level': 'INFO',
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'console',
|
||||
}
|
||||
},
|
||||
'loggers': {
|
||||
# default日志
|
||||
'': {
|
||||
'handlers': ['console', 'error', 'file'],
|
||||
'level': 'INFO',
|
||||
},
|
||||
'django': {
|
||||
'handlers': ['console', 'error', 'file'],
|
||||
'level': 'INFO',
|
||||
"propagate": False,# 不向上级 logger 传播
|
||||
},
|
||||
'scripts': {
|
||||
'handlers': ['console', 'error', 'file'],
|
||||
'level': 'INFO',
|
||||
},
|
||||
# 数据库相关日志
|
||||
'django.db.backends': {
|
||||
'handlers': ["console", "error", "file"],
|
||||
'propagate': False,
|
||||
'level': 'INFO',
|
||||
},
|
||||
"uvicorn.error": {
|
||||
"level": "INFO",
|
||||
"handlers": ["console", "error", "file"],
|
||||
},
|
||||
"uvicorn.access": {
|
||||
"handlers": ["console", "error", "file"],
|
||||
"level": "INFO"
|
||||
},
|
||||
}
|
||||
}
|
||||
# ================================================= #
|
||||
# *************** REST_FRAMEWORK配置 *************** #
|
||||
# ================================================= #
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DATETIME_FORMAT': "%Y-%m-%d %H:%M:%S", # 日期时间格式配置
|
||||
'DATE_FORMAT': "%Y-%m-%d",
|
||||
'DEFAULT_FILTER_BACKENDS': (
|
||||
'django_filters.rest_framework.DjangoFilterBackend',
|
||||
'rest_framework.filters.SearchFilter',
|
||||
'rest_framework.filters.OrderingFilter',
|
||||
|
||||
),
|
||||
'DEFAULT_PAGINATION_CLASS': 'utils.pagination.CustomPagination', # 自定义分页
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
'rest_framework_simplejwt.authentication.JWTAuthentication',
|
||||
# 'rest_framework_simplejwt.authentication.JWTTokenUserAuthentication',
|
||||
'rest_framework.authentication.SessionAuthentication',
|
||||
),
|
||||
#限速设置
|
||||
# 'DEFAULT_THROTTLE_CLASSES': (
|
||||
# 'rest_framework.throttling.AnonRateThrottle', #未登陆用户
|
||||
# 'rest_framework.throttling.UserRateThrottle' #登陆用户
|
||||
# ),
|
||||
# 'DEFAULT_THROTTLE_RATES': {
|
||||
# 'anon': '30/minute', #未登录用户每分钟可以请求30次,还可以设置'100/day',天数
|
||||
# 'user': '60/minute' #已登录用户每分钟可以请求60次
|
||||
# },
|
||||
'EXCEPTION_HANDLER': 'utils.exception.CustomExceptionHandler', # 自定义的异常处理
|
||||
# #线上部署正式环境,关闭web接口测试页面
|
||||
# 'DEFAULT_RENDERER_CLASSES':(
|
||||
# 'rest_framework.renderers.JSONRenderer',
|
||||
# ),
|
||||
}
|
||||
# ================================================= #
|
||||
# ****************** simplejwt配置 ***************** #
|
||||
# ================================================= #
|
||||
from datetime import timedelta
|
||||
|
||||
SIMPLE_JWT = {
|
||||
# token有效时长
|
||||
'ACCESS_TOKEN_LIFETIME': timedelta(days=7),
|
||||
# token刷新后的有效时间
|
||||
'REFRESH_TOKEN_LIFETIME': timedelta(days=15),
|
||||
# 设置header字段Authorization的值得前缀: JWT accesstoken字符串
|
||||
'AUTH_HEADER_TYPES': ('JWT',),
|
||||
'ROTATE_REFRESH_TOKENS': True
|
||||
}
|
||||
|
||||
# ====================================#
|
||||
# ****************swagger************#
|
||||
#====================================#
|
||||
SWAGGER_SETTINGS = {
|
||||
# 基础样式
|
||||
'SECURITY_DEFINITIONS': {
|
||||
"basic":{#用户名密码cookie验证
|
||||
'type': 'basic'
|
||||
},
|
||||
'JWT': {#通过jwt验证
|
||||
'type': 'apiKey',
|
||||
'name': 'Authorization',
|
||||
'in': 'header'
|
||||
},
|
||||
# 'Query': {#通过query中auth变量验证
|
||||
# 'type': 'apiKey',
|
||||
# 'name': 'auth',
|
||||
# 'in': 'query'
|
||||
# }
|
||||
},
|
||||
# 如果需要登录才能够查看接口文档, 登录的链接使用restframework自带的.
|
||||
'LOGIN_URL': 'rest_framework:login',
|
||||
'LOGOUT_URL': 'rest_framework:logout',
|
||||
# 'DOC_EXPANSION': None,
|
||||
# 'SHOW_REQUEST_HEADERS':True,
|
||||
# 'USE_SESSION_AUTH': True,
|
||||
# 'DOC_EXPANSION': 'list',
|
||||
# 接口文档中方法列表以首字母升序排列
|
||||
'APIS_SORTER': 'alpha',
|
||||
# 如果支持json提交, 则接口文档中包含json输入框
|
||||
'JSON_EDITOR': True,
|
||||
# 方法列表字母排序
|
||||
'OPERATIONS_SORTER': 'alpha',
|
||||
'VALIDATOR_URL': None,
|
||||
'AUTO_SCHEMA_TYPE': 1, # 分组根据url层级分,0、1 或 2 层
|
||||
'DEFAULT_AUTO_SCHEMA_CLASS': 'utils.swagger.CustomSwaggerAutoSchema',
|
||||
# 'DEFAULT_PARSER_CLASSES': (
|
||||
# 'rest_framework.parsers.FormParser',
|
||||
# 'rest_framework.parsers.MultiPartParser',
|
||||
# 'rest_framework.parsers.JSONParser',
|
||||
# ),
|
||||
}
|
||||
# ================================================= #
|
||||
# **************** 验证码配置 ******************* #
|
||||
# ================================================= #
|
||||
CAPTCHA_STATE = True
|
||||
CAPTCHA_IMAGE_SIZE = (160, 60) # 设置 captcha 图片大小
|
||||
CAPTCHA_LENGTH = 4 # 字符个数
|
||||
CAPTCHA_TIMEOUT = 1 # 超时(minutes)
|
||||
CAPTCHA_OUTPUT_FORMAT = '%(image)s %(text_field)s %(hidden_field)s '
|
||||
CAPTCHA_FONT_SIZE = 42 # 字体大小
|
||||
CAPTCHA_FOREGROUND_COLOR = '#296dff' # 前景色
|
||||
CAPTCHA_BACKGROUND_COLOR = '#FFFFFF' # 背景色
|
||||
CAPTCHA_NOISE_FUNCTIONS = (
|
||||
'captcha.helpers.noise_arcs', # 线
|
||||
# 'captcha.helpers.noise_dots', # 点
|
||||
)
|
||||
# CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.random_char_challenge' #字母验证码
|
||||
CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.math_challenge' # 加减乘除验证码
|
||||
# ================================================= #
|
||||
# ******************** celery配置 ******************** #
|
||||
# ================================================= #
|
||||
CELERY_TIMEZONE = 'Asia/Shanghai' # celery 时区问题
|
||||
CELERY_BROKER_URL = f'{REDIS_URL}/10' # Broker配置,使用Redis作为消息中间件(无密码)
|
||||
#CELERY_BROKER_URL = 'redis://lybbn:{}@127.0.0.1:6379/10'.format('123456') #lybbn 代表 账号(没有可省略) {} 存放密码 127.0.0.1连接的 ip 6379端口 10 redis库
|
||||
# CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/11' # 把任务结果存在了Redis
|
||||
CELERY_RESULT_BACKEND = 'django-db' # celery结果存储到数据库中django-db
|
||||
CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler' # Backend数据库
|
||||
CELERY_RESULT_PERSISTENT = True
|
||||
CELERY_RESULT_EXTENDED = True
|
||||
DJANGO_CELERY_BEAT_TZ_AWARE = False
|
||||
CELERY_ENABLE_UTC = False
|
||||
BROKER_TRANSPORT_OPTIONS = {'visibility_timeout': 3600}# 连接超时
|
||||
CELERY_TASK_SERIALIZER = 'json' # 任务序列化和反序列化使json
|
||||
CELERY_RESULT_SERIALIZER = 'json'
|
||||
#CELERYD_CONCURRENCY = 2 #并发worker数量
|
||||
CELERY_WORKER_CONCURRENCY = 2 # 并发数
|
||||
CELERYD_FORCE_EXECV = True #防止死锁,应确保为True
|
||||
CELERY_TASK_TIME_LIMIT = 60*30*5 # 限制celery任务执行时间,# 单个任务的运行时间限制,否则会被杀死
|
||||
CELERYD_MAX_TASKS_PER_CHILD = 100 #worker执行100个任务自动销毁,防止内存泄露
|
||||
CELERYD_TASK_SOFT_TIME_LIMIT = 6000 #单个任务的运行时间不超过此值(秒),否则会抛出(SoftTimeLimitExceeded)异常停止任务
|
||||
CELERY_DISABLE_RATE_LIMITS = True #即使任务设置了明确的速率限制,也禁用所有速率限制。
|
||||
CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True #去除celery6.0启动时warning警告,确保在启动时进行代理连接重试
|
||||
# ================================================= #
|
||||
# ******************** 其他配置 ******************** #
|
||||
# ================================================= #
|
||||
# DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
||||
API_LOG_ENABLE = True#全局控制日志记录
|
||||
# API_LOG_METHODS = 'ALL' # ['POST', 'DELETE']
|
||||
API_LOG_METHODS = ['POST', 'UPDATE', 'DELETE', 'PUT'] # ['POST', 'DELETE']
|
||||
#日志记录显示的请求模块中文名映射
|
||||
API_MODEL_MAP = {
|
||||
"/token/": "登录模块",
|
||||
"/api/token/": "登录模块",
|
||||
"/api/super/operate/":"前端API关闭开启",
|
||||
"/api/platformsettings/uploadplatformimg/":"图片上传",
|
||||
}
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
187
backend/application/urls.py
Normal file
187
backend/application/urls.py
Normal file
@ -0,0 +1,187 @@
|
||||
"""application URL Configuration
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/3.2/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.urls import path,include, re_path
|
||||
from django.views.static import serve
|
||||
from django.conf import settings
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.views import get_schema_view
|
||||
from rest_framework import permissions
|
||||
from django.views.generic.base import RedirectView
|
||||
from rest_framework_simplejwt.views import (
|
||||
TokenRefreshView,
|
||||
)
|
||||
|
||||
from mysystem.views.login import LoginView,CaptchaView
|
||||
from utils.swagger import CustomOpenAPISchemaGenerator
|
||||
|
||||
#前端接口view
|
||||
from apps.oauth.views import WeChatXCXLoginAPIView,XCXWeChatUserInfoUpdateAPIView,WeChatXCXMobileLoginAPIView,WeChatGZHLoginAPIView,WeChatGZHBindAPIView,GetXCXShareQrcodeView,TTXCXLoginAPIView,WeChatGZHH5LoginAPIView,CheckWeChatGZHH5APIView,GetWeChatGZHH5JSSDKTempSignAPIView
|
||||
from apps.address.views import *
|
||||
from apps.logins.views import MobilePassWordLoginView,UsernamePassWordLoginView,SendSmsCodeView,APPMobileSMSLoginView,ForgetPasswdResetView,RegisterView
|
||||
from apps.lyusers.views import SetUserNicknameView,ChangeAvatarView,uploadImagesView,DestroyUserView,GetUserinfoView
|
||||
from apps.lymessages.views import UserMessagesView,UserMessagesNoticeView,GetUnreadMessageNumView
|
||||
from apps.platformsettings.views import *
|
||||
from apps.mall.views import *
|
||||
#抖音精选联盟
|
||||
from apps.lyTiktokUnion.views.douyinMsgWebhook import DouyinAllianceDarenOrderWebhookView,DouyinAllianceDouKeOrderWebhookView
|
||||
from apps.lyTiktokUnion.views.doudianCallback import *
|
||||
from apps.lyTiktokUnion.views.dySystemAccountViews import DouyinSytemDarenCodeCallbackView
|
||||
|
||||
#字典信息数据获取
|
||||
from mysystem.views.dictionary import GetDictionaryInfoView,GetDictionaryAllView
|
||||
#app下载页
|
||||
from apps.lyusers.views import downloadapp,h5web
|
||||
#媒体文件流式响应
|
||||
from utils.streamingmedia_response import streamingmedia_serve
|
||||
#部署vue
|
||||
from django.views.generic import TemplateView
|
||||
#是否允许前端接口访问
|
||||
from utils.middleware import OperateAllowFrontendView
|
||||
|
||||
schema_view = get_schema_view(
|
||||
openapi.Info(
|
||||
title="django-vue-lyadmin-pro API",
|
||||
default_version='v1',
|
||||
# description="Test description",
|
||||
# terms_of_service="https://www.google.com/policies/terms/",
|
||||
# contact=openapi.Contact(email="contact@snippets.local"),
|
||||
# license=openapi.License(name="BSD License"),
|
||||
),
|
||||
# public 如果为False,则只包含当前用户可以访问的端点。True返回全部
|
||||
public=True,
|
||||
permission_classes=(permissions.AllowAny,),# 可以允许任何人查看该接口
|
||||
# permission_classes=(permissions.IsAuthenticated) # 只允许通过认证的查看该接口
|
||||
generator_class=CustomOpenAPISchemaGenerator,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
path('static/<path:path>', serve, {'document_root': settings.STATIC_ROOT},), # 处理静态文件
|
||||
# path('media/<path:path>', serve, {'document_root': settings.MEDIA_ROOT},), # 处理媒体文件
|
||||
path('media/<path:path>', streamingmedia_serve, {'document_root': settings.MEDIA_ROOT}, ), # 处理媒体文件
|
||||
# path('admin/', admin.site.urls),
|
||||
#api文档地址(正式上线需要注释掉)
|
||||
re_path(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
||||
re_path(r'^api/lyapi(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='api-schema-json'),
|
||||
path('lyapi/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
|
||||
path(r'lyredoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
|
||||
#管理后台的标准接口
|
||||
path('api/system/', include('mysystem.urls')),
|
||||
path('api/monitor/', include('lymonitor.urls')),
|
||||
path('api/terminal/', include('lywebsocket.urls')),
|
||||
path('api/token/', LoginView.as_view(), name='token_obtain_pair'),
|
||||
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
|
||||
path('api/captcha/', CaptchaView.as_view()),
|
||||
|
||||
#管理后台其他自定义接口
|
||||
path('api/platformsettings/', include('apps.platformsettings.urls')),
|
||||
path('api/address/', include('apps.address.urls')),
|
||||
path('api/messages/', include('apps.lymessages.urls')),
|
||||
path('api/users/', include('apps.lyusers.urls')),
|
||||
path('api/mall/', include('apps.mall.urls')),
|
||||
path('api/crontab/', include('apps.lycrontab.urls')),
|
||||
path('api/autocode/', include('apps.lyautocode.urls')),
|
||||
path('api/lyformbuilder/', include('apps.lyFormBuilder.urls')),
|
||||
path('api/lytiktokunion/', include('apps.lyTiktokUnion.urls')),
|
||||
path('api/workflow/', include('apps.lyworkflow.urls')),
|
||||
|
||||
# ========================================================================================= #
|
||||
# ********************************** 前端微服务API用户接口************************************ #
|
||||
# ========================================================================================= #
|
||||
#登录
|
||||
path('api/app/register/', RegisterView.as_view(), name='app端手机号注册'),
|
||||
path('api/h5/ttlogin/', TTXCXLoginAPIView.as_view(), name='字节跳动小程序登录认证'),
|
||||
path('api/h5/wxh5logincheck/', CheckWeChatGZHH5APIView.as_view(), name='微信服务器校验服务器'),
|
||||
path('api/h5/wxh5login/', WeChatGZHH5LoginAPIView.as_view(), name='微信公众号H5网页授权登录'),
|
||||
path('api/h5/wxh5sign/', GetWeChatGZHH5JSSDKTempSignAPIView.as_view(), name='微信公众号H5网页获取js sdk的临时签名信息'),
|
||||
path('api/app/login/', UsernamePassWordLoginView.as_view(), name='app端账号密码登录认证'),
|
||||
path('api/app/phonelogin/', MobilePassWordLoginView.as_view(), name='app端手机号密码登录认证'),
|
||||
path('api/app/wxlogin/', WeChatGZHLoginAPIView.as_view(), name='app端手机号微信登录认证'),
|
||||
path('api/app/wxbindlogin/', WeChatGZHBindAPIView.as_view(), name='app端手机号微信登录认证绑定微信'),
|
||||
path('api/app/sendsms/', SendSmsCodeView.as_view(), name='app端手机号发送短信验证码'),
|
||||
path('api/app/mobilelogin/', APPMobileSMSLoginView.as_view(), name='app端手机号短信登录认证'),
|
||||
path('api/xcx/login/', WeChatXCXLoginAPIView.as_view(), name='微信小程序登录认证'),
|
||||
path('api/xcx/mobilelogin/', WeChatXCXMobileLoginAPIView.as_view(), name='微信小程序手机号授权绑定登录认证'),
|
||||
path('api/app/destoryuser/', DestroyUserView.as_view(), name='app端用户注销账户'),
|
||||
|
||||
#用户信息
|
||||
path('api/app/getmyinfo/', GetUserinfoView.as_view(), name='app端获取用户信息'),
|
||||
path('api/app/restpassword/', ForgetPasswdResetView.as_view(), name='app端手机号重置密码'),
|
||||
path('api/app/setnickname/', SetUserNicknameView.as_view(), name='app端修改昵称'),
|
||||
path('api/app/changeavatar/', ChangeAvatarView.as_view(), name='app端修改头像'),
|
||||
path('api/app/uploadimage/', uploadImagesView.as_view(), name='app端上传图片'),
|
||||
path('api/xcx/updateUserinfo/', XCXWeChatUserInfoUpdateAPIView.as_view(), name='微信小程序更新用户信息'),
|
||||
path('api/xcx/getshareqrcode/', GetXCXShareQrcodeView.as_view(), name='微信小程序用户获取推广小程序二维码'),
|
||||
path('api/app/usermessages/', UserMessagesView.as_view(), name='app端获取系统消息和平台公告通知(包含已读,未读)/操作修改为已读/删除'),
|
||||
path('api/app/usermessagesnotice/', UserMessagesNoticeView.as_view(), name='app端获取平台公告列表'),
|
||||
path('api/app/getunreadmessagenums/', GetUnreadMessageNumView.as_view(), name='app端获取未读消息的数量'),
|
||||
path('api/app/feeckback/', APPUserLeavingMessageView.as_view(), name='app端意见反馈'),
|
||||
|
||||
#用户地址管理API
|
||||
path('api/app/getaddress/', GetAssressesListView.as_view(), name='app用户获取地址'),
|
||||
path('api/app/addeditaddress/', CreateUpdateAssressesView.as_view(), name='app用户新增编辑地址'),
|
||||
path('api/app/deladdress/', DeleteAssressesView.as_view(), name='app用户删除地址'),
|
||||
path('api/app/setdefaultaddress/', SetDefaultAssressesView.as_view(), name='app用户设置默认地址'),
|
||||
|
||||
#商城API
|
||||
path('api/app/getgoodstypelist/', GoodsTypeView.as_view(), name='app用户端商城-获取分类标签'),
|
||||
path('api/app/getgoodslist/', GoodsListView.as_view(), name='app用户端商城-获取商品列表'),
|
||||
path('api/app/getgoodsdetail/', GoodsDetailView.as_view(), name='app用户端商城-获取商品详情'),
|
||||
path('api/app/cartoperate/', CartsView.as_view(), name='app用户端商城-购物车操作'),
|
||||
path('api/app/cartselectall/', CartsSelectAllView.as_view(), name='app用户端商城-购物车全选\取消全选'),
|
||||
path('api/app/mycoupon/', MyCouponView.as_view(), name='app用户端-我的优惠券'),
|
||||
path('api/app/goodsordercancel/', GoodsOrderCancleView.as_view(), name='app用户端-取消商城订单'),
|
||||
path('api/app/goodsordercommit/', OrdersCommitView.as_view(), name='app用户端-商城订单生成'),
|
||||
path('api/app/goodsorderconfirmrev/', GoodsOrderConfirmReceiveView.as_view(), name='app用户端-商城订单生成'),
|
||||
path('api/app/goodsorderlist/', GoodsOrdersListView.as_view(), name='app用户端-商城订单列表'),
|
||||
path('api/app/goodsorderdetail/', GoodsOrdersDetailView.as_view(), name='app用户端-商城订单详情'),
|
||||
|
||||
#支付API
|
||||
path('api/app/payment/', PaymentView.as_view(), name='app端购买接口'),
|
||||
path('api/app/ali_notify/', alipay_notify.as_view(), name='支付宝异步通知回调接口'),
|
||||
path('api/app/wechatpay_notify/', wechatpay_notify.as_view(), name='微信支付异步通知回调接口'),
|
||||
|
||||
#抖音精选联盟
|
||||
# path('api/app/darenOrderWebhook/', DouyinAllianceDarenOrderWebhookView.as_view(), name='订阅达人订单webhook抖音回调'),
|
||||
# path('api/app/doukeOrderWebhook/', DouyinAllianceDouKeOrderWebhookView.as_view(), name='订阅抖客订单webhook抖音回调'),
|
||||
#抖店开放平台接口(精选联盟)
|
||||
# path('api/system/doudianUnionCodeCallback/', DoudianUnionCodeCallbackView.as_view(), name='系统获取团长自研应用授权信息code 回调'),
|
||||
# path('api/system/DoudianUnionDKLiveCodeCallback/', DoudianUnionDKLiveCodeCallbackView.as_view(), name='系统 联盟抖客分销 工具型应用授权信息code 回调'),
|
||||
#获取平台使用抖音达人/抖客授权信息
|
||||
# path('api/system/douyincodeCallback/', DouyinSytemDarenCodeCallbackView.as_view(), name='后台获取平台使用抖音达人授权信息 回调'),
|
||||
|
||||
#获取平台信息
|
||||
path('api/getsysconfigByKey/', GetSystemConfigSettingsByKeyView.as_view(), name='前端用户获取系统配置根据KEY获取'),
|
||||
path('api/getsysconfig/', GetSystemConfigSettingsView.as_view(), name='前端用户获取系统配置(组)'),
|
||||
path('api/getdictionary/', GetDictionaryInfoView.as_view(), name='获取字典数据信息'),
|
||||
path('api/getdictionaryall/', GetDictionaryAllView.as_view(), name='获取所有字典数据信息'),
|
||||
path('api/getothersettings/', GetOtherManageDetailView.as_view(), name='前端用户获取平台其他设置'),
|
||||
path('api/getrotationimgs/', GetLunboManageListView.as_view(), name='前端用户获取平台轮播图设置'),
|
||||
re_path(r'^api/areas/$', ProvinceAreasView.as_view(),name='省市区三级联动获取省'),
|
||||
re_path(r'^api/areas/(?P<pk>[1-9]\d*)/$', SubAreasView.as_view(),name='省市区三级联动获取市/区'),
|
||||
path('api/getallareaslist/', GetProvinceAreasListView.as_view(), name='递归获取所有省市区数据'),
|
||||
path('api/getaddressaccuracy/', GetAddressAccuracyView.as_view(), name='后台根据详细地址获取经纬度'),
|
||||
|
||||
#是否允许前端接口访问(临时操作,重启后无效)
|
||||
path('api/super/operate/', OperateAllowFrontendView.as_view(), name='超级管理员动态操作是否允许前端api接口访问'),
|
||||
|
||||
#集成部署后端管理页面
|
||||
path('h5/',h5web ,name='h5端页面'),
|
||||
path('downloadapp/',downloadapp ,name='前端APP下载页'),
|
||||
path('favicon.ico',RedirectView.as_view(url=r'static/favicon.ico')),
|
||||
path('', TemplateView.as_view(template_name="index.html"),name='后台管理默认页面'),
|
||||
]
|
||||
|
||||
16
backend/application/wsgi.py
Normal file
16
backend/application/wsgi.py
Normal file
@ -0,0 +1,16 @@
|
||||
"""
|
||||
WSGI config for application project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings')
|
||||
|
||||
application = get_wsgi_application()
|
||||
0
backend/apps/__init__.py
Normal file
0
backend/apps/__init__.py
Normal file
0
backend/apps/address/__init__.py
Normal file
0
backend/apps/address/__init__.py
Normal file
3
backend/apps/address/admin.py
Normal file
3
backend/apps/address/admin.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
backend/apps/address/apps.py
Normal file
6
backend/apps/address/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AddressConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.address'
|
||||
64
backend/apps/address/migrations/0001_initial.py
Normal file
64
backend/apps/address/migrations/0001_initial.py
Normal file
@ -0,0 +1,64 @@
|
||||
# Generated by Django 4.1.3 on 2023-11-13 21:52
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import utils.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Area',
|
||||
fields=[
|
||||
('id', models.CharField(default=utils.models.make_uuid, help_text='Id', max_length=100, primary_key=True, serialize=False, verbose_name='Id')),
|
||||
('modifier', models.CharField(blank=True, help_text='修改人', max_length=100, null=True, verbose_name='修改人')),
|
||||
('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=100, null=True, verbose_name='数据归属部门')),
|
||||
('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')),
|
||||
('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),
|
||||
('name', models.CharField(max_length=50, verbose_name='名称')),
|
||||
('citycode', models.CharField(blank=True, db_index=True, help_text='城市编码', max_length=20, null=True, verbose_name='城市编码')),
|
||||
('level', models.PositiveIntegerField(default=0, verbose_name='地区层级(1省份 2城市 3区县 4乡镇/街道级)')),
|
||||
('status', models.BooleanField(default=True, verbose_name='状态')),
|
||||
('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
|
||||
('parent', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='subs', to='address.area', verbose_name='上级行政区划')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '省市区',
|
||||
'verbose_name_plural': '省市区',
|
||||
'db_table': 'tb_areas',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Address',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('update_datetime', models.DateTimeField(auto_now=True, null=True, verbose_name='更新时间')),
|
||||
('create_datetime', models.DateTimeField(auto_now_add=True, null=True, verbose_name='创建时间')),
|
||||
('receiver', models.CharField(blank=True, max_length=20, null=True, verbose_name='收货人')),
|
||||
('province', models.CharField(blank=True, max_length=32, null=True, verbose_name='省')),
|
||||
('city', models.CharField(blank=True, max_length=32, null=True, verbose_name='市')),
|
||||
('district', models.CharField(blank=True, max_length=32, null=True, verbose_name='区')),
|
||||
('street', models.CharField(blank=True, max_length=32, null=True, verbose_name='街道')),
|
||||
('place', models.CharField(max_length=50, verbose_name='收货地址')),
|
||||
('mobile', models.CharField(max_length=11, verbose_name='手机号')),
|
||||
('longitude', models.FloatField(blank=True, null=True, verbose_name='经度')),
|
||||
('latitude', models.FloatField(blank=True, null=True, verbose_name='纬度')),
|
||||
('is_default', models.BooleanField(default=False, verbose_name='是否默认')),
|
||||
('is_deleted', models.BooleanField(default=False, verbose_name='逻辑删除')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='addresses', to=settings.AUTH_USER_MODEL, verbose_name='所属用户')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '用户地址',
|
||||
'verbose_name_plural': '用户地址',
|
||||
'db_table': 'tb_address',
|
||||
},
|
||||
),
|
||||
]
|
||||
18
backend/apps/address/migrations/0002_alter_area_id.py
Normal file
18
backend/apps/address/migrations/0002_alter_area_id.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.1.3 on 2023-12-06 21:58
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('address', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='area',
|
||||
name='id',
|
||||
field=models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id'),
|
||||
),
|
||||
]
|
||||
0
backend/apps/address/migrations/__init__.py
Normal file
0
backend/apps/address/migrations/__init__.py
Normal file
60
backend/apps/address/models.py
Normal file
60
backend/apps/address/models.py
Normal file
@ -0,0 +1,60 @@
|
||||
from django.db import models
|
||||
from utils.models import BaseModel,CoreModel
|
||||
from mysystem.models import Users
|
||||
# Create your models here.
|
||||
|
||||
# ================================================= #
|
||||
# ************** 全国省区市 model************** #
|
||||
# ================================================= #
|
||||
|
||||
class Area(CoreModel):
|
||||
"""省市区"""
|
||||
name = models.CharField(max_length=50, verbose_name='名称')
|
||||
parent = models.ForeignKey('self', on_delete=models.SET_NULL, related_name='subs', null=True, blank=True,db_constraint=False, verbose_name='上级行政区划')#外键链接自己
|
||||
citycode = models.CharField(max_length=20, verbose_name="城市编码", help_text="城市编码", db_index=True, null=True, blank=True)
|
||||
level = models.PositiveIntegerField(default=0,verbose_name="地区层级(1省份 2城市 3区县 4乡镇/街道级)")
|
||||
status = models.BooleanField(default=True,verbose_name="状态")
|
||||
#related_name='subs' ,意思为如果想找自己的子级,就可以通过area.subs找到自己下级所有的area区域,我们也可以这样调用获取市: area.area_set.all() ==> area.subs.all()
|
||||
#on_delete=models.SET_NULL: 如果省删掉了,省内其他的信息为 NULL
|
||||
class Meta:
|
||||
db_table = 'tb_areas'
|
||||
verbose_name = '省市区'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
# ================================================= #
|
||||
# ************** 用户地址model************** #
|
||||
# ================================================= #
|
||||
|
||||
class Address(BaseModel):
|
||||
"""用户地址"""
|
||||
user = models.ForeignKey(Users, on_delete=models.CASCADE, related_name='addresses', verbose_name='所属用户') # 一对多
|
||||
receiver = models.CharField(max_length=20,null=True, blank=True, verbose_name='收货人')
|
||||
province = models.CharField(max_length=32, null=True, blank=True,verbose_name='省')
|
||||
city = models.CharField(max_length=32,null=True, blank=True, verbose_name='市')
|
||||
district = models.CharField(max_length=32, null=True, blank=True,verbose_name='区')
|
||||
street = models.CharField(max_length=32, null=True, blank=True,verbose_name='街道')
|
||||
place = models.CharField(max_length=50, verbose_name='收货地址')#详细地址
|
||||
mobile = models.CharField(max_length=11, verbose_name='手机号')
|
||||
longitude = models.FloatField(verbose_name='经度',null=True, blank=True)
|
||||
latitude = models.FloatField(verbose_name='纬度',null=True, blank=True)
|
||||
is_default = models.BooleanField(default=False, verbose_name='是否默认')
|
||||
is_deleted = models.BooleanField(default=False, verbose_name='逻辑删除')#是否有效,是否显示
|
||||
|
||||
def get_region_format(self):
|
||||
"""省市区"""
|
||||
return "{self.province}{self.city}{self.district}".format(self=self)
|
||||
|
||||
class Meta:
|
||||
db_table = 'tb_address'
|
||||
verbose_name = '用户地址'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
|
||||
def __str__(self):
|
||||
if self.street:
|
||||
return self.get_region_format() + self.street + self.place
|
||||
else:
|
||||
return self.get_region_format() + self.place
|
||||
17
backend/apps/address/urls.py
Normal file
17
backend/apps/address/urls.py
Normal file
@ -0,0 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@Remark: 地区地址相关的路由文件
|
||||
"""
|
||||
from django.urls import path, re_path
|
||||
from rest_framework import routers
|
||||
|
||||
from apps.address.views import AreaViewSet
|
||||
|
||||
system_url = routers.SimpleRouter()
|
||||
system_url.register(r'area', AreaViewSet)
|
||||
|
||||
urlpatterns = [
|
||||
re_path('area_root/', AreaViewSet.as_view({'get': 'area_root'})),
|
||||
]
|
||||
urlpatterns += system_url.urls
|
||||
415
backend/apps/address/views.py
Normal file
415
backend/apps/address/views.py
Normal file
@ -0,0 +1,415 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
@Remark: 用户地址管理
|
||||
"""
|
||||
import re
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.views import APIView
|
||||
from apps.address.models import *
|
||||
from utils.jsonResponse import SuccessResponse,ErrorResponse
|
||||
from rest_framework_simplejwt.authentication import JWTAuthentication
|
||||
from utils.common import get_parameter_dic,REGEX_MOBILE
|
||||
from django.core.cache import cache
|
||||
from utils.locationanalysis import gettecentlnglat
|
||||
from utils.serializers import CustomModelSerializer
|
||||
from utils.viewset import CustomModelViewSet
|
||||
from rest_framework import serializers
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from django.db.models import Q,F
|
||||
|
||||
# ================================================= #
|
||||
# ************** 省市区后台管理 view ************** #
|
||||
# ================================================= #
|
||||
class AreaSerializer(CustomModelSerializer):
|
||||
"""
|
||||
地区-序列化器
|
||||
"""
|
||||
child_count = serializers.SerializerMethodField(read_only=True)
|
||||
|
||||
def get_child_count(self, instance: Area):
|
||||
return Area.objects.filter(parent=instance).count()
|
||||
|
||||
class Meta:
|
||||
model = Area
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
|
||||
class AreaCreateUpdateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
地区管理 创建/更新时的列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = Area
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class AreaViewSet(CustomModelViewSet):
|
||||
"""
|
||||
地区管理接口:
|
||||
list:查询
|
||||
create:新增
|
||||
update:修改
|
||||
retrieve:单例
|
||||
destroy:删除
|
||||
"""
|
||||
queryset = Area.objects.all().order_by('id')
|
||||
serializer_class = AreaSerializer
|
||||
filterset_fields = ['status','id','parent']
|
||||
search_fields = ('name',)
|
||||
|
||||
def area_root(self, request):
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
queryset = queryset.filter(parent__isnull=True).order_by('id')
|
||||
serializer = AreaSerializer(queryset, many=True)
|
||||
return SuccessResponse(data=serializer.data, msg="获取成功")
|
||||
|
||||
# ================================================= #
|
||||
# ************** 省市区查询自己接口管理 view ************** #
|
||||
# ================================================= #
|
||||
|
||||
class ProvinceAreasView(APIView):
|
||||
"""
|
||||
查询省数据
|
||||
get:
|
||||
查询省数据,补充缓存逻辑
|
||||
请求方式: GET /areas/
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
authentication_classes = [JWTAuthentication]
|
||||
|
||||
def get(self, request):
|
||||
#补充缓存逻辑
|
||||
province_list = cache.get('province_list')
|
||||
if not province_list:
|
||||
try:
|
||||
province_model_list = Area.objects.filter(parent__isnull=True).order_by('id')
|
||||
province_list = []
|
||||
for province_model in province_model_list:
|
||||
province_list.append({'id': province_model.id,'name': province_model.name})
|
||||
# 增加: 缓存省级数据
|
||||
cache.set('province_list', province_list, 3600)
|
||||
except Exception as e:
|
||||
return ErrorResponse(msg='省份数据错误')
|
||||
return SuccessResponse(data=province_list,msg='success')
|
||||
|
||||
class SubAreasView(APIView):
|
||||
"""
|
||||
查询市或区数据
|
||||
get:
|
||||
子级地区:市和区县
|
||||
请求方式: GET /areas/(?P<pk>[1-9]\d+)/
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
authentication_classes = [JWTAuthentication]
|
||||
|
||||
def get(self, request, pk):
|
||||
"""提供市或区地区数据
|
||||
1.查询市或区数据
|
||||
2.序列化市或区数据
|
||||
3.响应市或区数据
|
||||
4.补充缓存数据
|
||||
"""
|
||||
# 判断是否有缓存
|
||||
sub_data = cache.get('sub_area_' + pk)
|
||||
|
||||
if not sub_data:
|
||||
|
||||
# 1.查询市或区数据
|
||||
try:
|
||||
sub_model_list = Area.objects.filter(parent=pk).order_by('id')
|
||||
# 查询市或区的父级
|
||||
parent_model = Area.objects.get(id=pk)
|
||||
|
||||
|
||||
# 2.序列化市或区数据
|
||||
sub_list = []
|
||||
for sub_model in sub_model_list:
|
||||
sub_list.append({'id': sub_model.id, 'name': sub_model.name})
|
||||
|
||||
sub_data = {
|
||||
'id':parent_model.id, # pk
|
||||
'name':parent_model.name,
|
||||
'subs': sub_list
|
||||
}
|
||||
|
||||
# 缓存市或区数据
|
||||
cache.set('sub_area_' + pk, sub_data, 3600)
|
||||
except Exception as e:
|
||||
return ErrorResponse(msg='城市或区县数据错误')
|
||||
|
||||
# 3.响应市或区数据
|
||||
return SuccessResponse(data=sub_data,msg="success")
|
||||
|
||||
def CommentTree(datas):
|
||||
"""
|
||||
获取结构树
|
||||
:param datas:
|
||||
:return:
|
||||
"""
|
||||
lists=[]
|
||||
tree={}
|
||||
parent_id=''
|
||||
for s in datas:
|
||||
s['childlist'] = []
|
||||
tree[str(s['id'])]=s
|
||||
root=None
|
||||
for i in datas:
|
||||
if not i['pid']:#判断根
|
||||
root=tree[str(i['id'])]
|
||||
lists.append(root)#添加到列表
|
||||
if 'childlist' not in tree[str(i['id'])]:
|
||||
tree[str(i['id'])]['childlist'] = []
|
||||
else:
|
||||
parent_id=str(i['pid'])
|
||||
if 'childlist' not in tree[parent_id]:
|
||||
tree[parent_id]['childlist']=[]
|
||||
tree[parent_id]['childlist'].append(tree[str(i['id'])])
|
||||
return lists
|
||||
|
||||
class Area2Serializer(CustomModelSerializer):
|
||||
"""
|
||||
省市区 -序列化器
|
||||
"""
|
||||
pid = serializers.CharField(source="parent_id")
|
||||
|
||||
class Meta:
|
||||
model = Area
|
||||
read_only_fields = ["id"]
|
||||
fields = ['id','name','pid','level','citycode']
|
||||
# exclude = ['password']
|
||||
# extra_kwargs = {
|
||||
# 'post': {'required': False},
|
||||
# }
|
||||
|
||||
class GetProvinceAreasListView(APIView):
|
||||
"""
|
||||
递归获取所有省市区
|
||||
get:
|
||||
递归获取所有省市区
|
||||
id:区域编码,为空表示获取所有一级
|
||||
subdistrict:取值0-2(0:不返回下级行政区、1:返回下一级行政区、2:返回下两级行政区)
|
||||
"""
|
||||
permission_classes = []
|
||||
authentication_classes = []
|
||||
# permission_classes = [IsAuthenticated]
|
||||
# authentication_classes = [JWTAuthentication]
|
||||
|
||||
def get(self, request):
|
||||
reqData = get_parameter_dic(request)
|
||||
id = reqData.get('id',"")
|
||||
subdistrict = int(reqData.get('subdistrict',0))
|
||||
queryset = Area.objects.filter(status=True).order_by('id')
|
||||
if subdistrict == 0:
|
||||
if id:
|
||||
queryset = queryset.filter(id = id)
|
||||
else:
|
||||
queryset = queryset.filter(level=1)
|
||||
elif subdistrict == 1:
|
||||
if id:
|
||||
queryset = queryset.filter(Q(id = id)|Q(parent_id=id))
|
||||
else:
|
||||
queryset = queryset.filter(level__in=[1,2])
|
||||
else:
|
||||
if id:
|
||||
queryset = queryset.filter(Q(id = id)|Q(parent_id=id)|Q(parent__parent_id=id))
|
||||
queryset_ser = Area2Serializer(queryset,many=True)
|
||||
queryset_list = CommentTree(queryset_ser.data)
|
||||
return SuccessResponse(data=queryset_list, msg='success')
|
||||
# ================================================= #
|
||||
# ************** 根据详细地址获取经纬度信息 view ************** #
|
||||
# ================================================= #
|
||||
class GetAddressAccuracyView(APIView):
|
||||
"""
|
||||
get:
|
||||
根据详细地址信息获取经纬度
|
||||
【参数】:address 为要查询的详细地址
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
authentication_classes = [JWTAuthentication]
|
||||
|
||||
def get(self, request):
|
||||
address = get_parameter_dic(request)['address']
|
||||
if address is None:
|
||||
return ErrorResponse(msg="要查询的地址不能为空")
|
||||
data = gettecentlnglat(address)
|
||||
return SuccessResponse(data=data,msg="success")
|
||||
|
||||
|
||||
# ================================================= #
|
||||
# ************** 前端用户地址操作 view ************** #
|
||||
# ================================================= #
|
||||
class AddressSerializer(ModelSerializer):
|
||||
"""
|
||||
用户地址 简单序列化器
|
||||
"""
|
||||
|
||||
|
||||
class Meta:
|
||||
model = Address
|
||||
fields = "__all__"
|
||||
read_only_fields = ["id"]
|
||||
|
||||
def create(self, validated_data):#有外键的新增数据时要自定义create添加外键数据
|
||||
return Address.objects.create(user=self.context["user"], **validated_data)
|
||||
|
||||
class GetAssressesListView(APIView):
|
||||
"""
|
||||
用户查询地址列表/获取默认地址接口
|
||||
get:
|
||||
【参数】type=default 获取默认地址,不传type默认获取地址列表
|
||||
【参数】type=detail 获取单个地址详情,后面需跟上地址的id
|
||||
【参数】type=all 获取地址列表
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
authentication_classes = [JWTAuthentication]
|
||||
serializer_class = AddressSerializer
|
||||
|
||||
def get(self, request):
|
||||
type = get_parameter_dic(request)['type']
|
||||
user = request.user
|
||||
if type == "default":
|
||||
# queryset = Address.objects.filter(id=user.default_address_id).first()
|
||||
queryset = Address.objects.filter(user=user, is_deleted=False,is_default=True).first()
|
||||
serializer = self.serializer_class(queryset,many=False)
|
||||
return SuccessResponse(data=serializer.data, msg="success")
|
||||
elif type == "detail":
|
||||
id = get_parameter_dic(request)['id']
|
||||
if id is None:
|
||||
return ErrorResponse(msg="id不能为空")
|
||||
queryset = Address.objects.filter(id=id,user=user,is_deleted=False).first()
|
||||
serializer = self.serializer_class(queryset, many=False)
|
||||
return SuccessResponse(data=serializer.data, msg="success")
|
||||
else:
|
||||
queryset = Address.objects.filter(user=user, is_deleted=False)
|
||||
serializer = self.serializer_class(queryset,many=True)
|
||||
return SuccessResponse(data=serializer.data,msg="success")
|
||||
|
||||
class CreateUpdateAssressesView(APIView):
|
||||
"""
|
||||
用户地址新增/修改管理接口
|
||||
post:
|
||||
【参数】提交的参数名,与获取地址列表名保持一致
|
||||
receiver 收货人姓名
|
||||
province 省
|
||||
city 市
|
||||
district 区
|
||||
street 街道
|
||||
place 详细地址
|
||||
mobile 手机号
|
||||
参数中有id字段表示修改,没有id则表示新增
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
authentication_classes = [JWTAuthentication]
|
||||
serializer_class = AddressSerializer
|
||||
|
||||
def post(self, request):
|
||||
reqData = get_parameter_dic(request)
|
||||
mobile = reqData.get('mobile',None)
|
||||
if not re.match(REGEX_MOBILE, mobile):
|
||||
return ErrorResponse(msg="手机号不正确")
|
||||
user = request.user
|
||||
type = reqData.get('type','add')
|
||||
receiver = reqData.get('receiver',None)
|
||||
province = reqData.get('province',None)
|
||||
city = reqData.get('city',None)
|
||||
district = reqData.get('district',None)
|
||||
street = reqData.get('street',None)
|
||||
place = reqData.get('place',None)
|
||||
latitude=reqData.get('latitude',None)
|
||||
longitude=reqData.get('longitude',None)
|
||||
is_default = int(reqData.get('is_default',0))
|
||||
|
||||
if is_default not in [0,1]:
|
||||
return ErrorResponse(msg="is_default类型错误")
|
||||
|
||||
if type=="add":#新增
|
||||
queryset = Address.objects.filter(user=user,is_deleted=False,is_default=True)
|
||||
if queryset:
|
||||
if is_default:
|
||||
queryset.update(is_default=False)
|
||||
|
||||
myaddress = Address.objects.create(user=user,receiver=receiver,province=province,city=city,district=district,street=street,place=place,mobile=mobile,latitude=latitude,longitude=longitude,is_default=is_default)
|
||||
return SuccessResponse(data={'addressid':myaddress.id},msg='success')
|
||||
elif type=='edit':
|
||||
id = get_parameter_dic(request)['id']
|
||||
instance = Address.objects.filter(id=id,user=user).first()
|
||||
if instance:#有这个地址数据
|
||||
otheraddresslist = Address.objects.filter(user=user,is_deleted=False).exclude(id=id)
|
||||
if is_default: # 设置默认
|
||||
if otheraddresslist: # 取消其他地址的默认
|
||||
otheraddresslist.update(is_default=False)
|
||||
instance.is_default = True
|
||||
else: # 取消默认
|
||||
instance.is_default = False
|
||||
|
||||
instance.receiver = receiver
|
||||
instance.province = province
|
||||
instance.city = city
|
||||
instance.district = district
|
||||
instance.receiver = receiver
|
||||
instance.street = street
|
||||
instance.place = place
|
||||
instance.mobile = mobile
|
||||
instance.longitude = longitude
|
||||
instance.latitude = latitude
|
||||
instance.save()
|
||||
return SuccessResponse(msg='修改成功')
|
||||
else:
|
||||
return ErrorResponse(msg="修改失败")
|
||||
else:
|
||||
return ErrorResponse(msg="type类型错误")
|
||||
|
||||
class DeleteAssressesView(APIView):
|
||||
"""
|
||||
用户地址删除接口
|
||||
get:
|
||||
【参数】id:需要删除的地址id
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
authentication_classes = [JWTAuthentication]
|
||||
serializer_class = AddressSerializer
|
||||
|
||||
def get(self, request):
|
||||
id = get_parameter_dic(request)['id']
|
||||
if id is None:
|
||||
return ErrorResponse(msg="id不能为空")
|
||||
user = request.user
|
||||
queryset = Address.objects.filter(Q(id=id)&Q(user=user)&Q(is_deleted=False))
|
||||
if queryset:
|
||||
queryset.update(is_deleted=True)
|
||||
return SuccessResponse(msg='success')
|
||||
return ErrorResponse(msg="删除失败")
|
||||
|
||||
class SetDefaultAssressesView(APIView):
|
||||
"""
|
||||
用户设置默认地址接口
|
||||
post:
|
||||
【参数】id:设置默认的地址id,is_default:0非默认地址,1默认地址
|
||||
"""
|
||||
permission_classes = [IsAuthenticated]
|
||||
authentication_classes = [JWTAuthentication]
|
||||
serializer_class = AddressSerializer
|
||||
|
||||
def post(self, request):
|
||||
id = get_parameter_dic(request)['id']
|
||||
is_default = get_parameter_dic(request)['is_default']
|
||||
if id is None:
|
||||
return ErrorResponse(msg="id不能为空")
|
||||
user = request.user
|
||||
otheraddresslist = Address.objects.filter(user=user).exclude(id=id)
|
||||
currentaddress = Address.objects.filter(id=id,user=user).first()
|
||||
if not currentaddress:
|
||||
return ErrorResponse(msg="设置失败")
|
||||
if is_default:#设置默认
|
||||
currentaddress.is_default = True
|
||||
currentaddress.save()
|
||||
if otheraddresslist:#取消其他地址的默认
|
||||
otheraddresslist.update(is_default=False)
|
||||
return SuccessResponse(msg='success')
|
||||
else:#取消默认
|
||||
currentaddress.is_default = False
|
||||
currentaddress.save()
|
||||
return SuccessResponse(msg='success')
|
||||
0
backend/apps/logins/__init__.py
Normal file
0
backend/apps/logins/__init__.py
Normal file
3
backend/apps/logins/admin.py
Normal file
3
backend/apps/logins/admin.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
backend/apps/logins/apps.py
Normal file
6
backend/apps/logins/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class LoginsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.logins'
|
||||
0
backend/apps/logins/migrations/__init__.py
Normal file
0
backend/apps/logins/migrations/__init__.py
Normal file
3
backend/apps/logins/models.py
Normal file
3
backend/apps/logins/models.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
3
backend/apps/logins/tests.py
Normal file
3
backend/apps/logins/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
455
backend/apps/logins/views.py
Normal file
455
backend/apps/logins/views.py
Normal file
@ -0,0 +1,455 @@
|
||||
from rest_framework.views import APIView
|
||||
from mysystem.models import Users
|
||||
from utils.jsonResponse import SuccessResponse,ErrorResponse,DetailResponse
|
||||
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
|
||||
from rest_framework_simplejwt.views import TokenObtainPairView
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
from utils.common import REGEX_MOBILE,get_parameter_dic,ast_convert
|
||||
import re
|
||||
from random import choice
|
||||
from django.db.models import Q,F
|
||||
from django_redis import get_redis_connection
|
||||
from utils.yunpian import YunPian
|
||||
from utils.uniappsms import UniCloudSms
|
||||
from django.contrib.auth.hashers import make_password, check_password
|
||||
import uuid
|
||||
from config import ALIYUN_SMS_SIGN,ALIYUM_SMS_TEMPLATE
|
||||
from utils.aliyunsms import send_sms
|
||||
from utils.tencentsms import tencentsms
|
||||
import json
|
||||
# ================================================= #
|
||||
# ************** 前端用户登录 view ************** #
|
||||
# ================================================= #
|
||||
|
||||
"""
|
||||
手机号密码登录
|
||||
"""
|
||||
|
||||
class AppMoliePasswordLoginSerializer(TokenObtainPairSerializer):
|
||||
"""
|
||||
前端登录的序列化器:
|
||||
重写djangorestframework-simplejwt的序列化器
|
||||
"""
|
||||
|
||||
default_error_messages = {
|
||||
'no_active_account': _('该账号已被禁用,请联系管理员')
|
||||
}
|
||||
|
||||
def validate(self, attrs):
|
||||
mobile = attrs['username']
|
||||
# 验证手机号是否合法
|
||||
if not re.match(REGEX_MOBILE, mobile):
|
||||
result = {
|
||||
"code": 4000,
|
||||
"msg": "请输入正确的手机号",
|
||||
"data": None
|
||||
}
|
||||
return result
|
||||
password = attrs['password']
|
||||
user = Users.objects.filter(username=mobile, identity=2).first()
|
||||
if user and not user.is_active:
|
||||
result = {
|
||||
"code": 4000,
|
||||
"msg": "该账号已被禁用,请联系管理员",
|
||||
"data": None
|
||||
}
|
||||
return result
|
||||
if user and user.check_password(password): # check_password() 对明文进行加密,并验证
|
||||
# data = super().validate(attrs)
|
||||
data={}
|
||||
refresh = self.get_token(user)
|
||||
data['identity'] = ast_convert(user.identity)
|
||||
data['userId'] = user.id
|
||||
data['refresh'] = str(refresh)
|
||||
data['access'] = str(refresh.access_token)
|
||||
result = {
|
||||
"code": 2000,
|
||||
"msg": "登录成功",
|
||||
"data": {
|
||||
"page": 1,
|
||||
"limit": 1,
|
||||
"total": 1,
|
||||
"data": data
|
||||
},
|
||||
}
|
||||
else:
|
||||
result = {
|
||||
"code": 4000,
|
||||
"msg": "账号/密码不正确",
|
||||
"data": None
|
||||
}
|
||||
return result
|
||||
|
||||
|
||||
class APPMobilePasswordLoginView(TokenObtainPairView):
|
||||
"""
|
||||
手机号密码登录接口(此种方式传参需要formdata方式)
|
||||
"""
|
||||
serializer_class = AppMoliePasswordLoginSerializer
|
||||
permission_classes = []
|
||||
|
||||
class UsernamePassWordLoginView(APIView):
|
||||
"""
|
||||
post:
|
||||
账号密码登录
|
||||
【功能描述】账号密码登录</br>
|
||||
【参数说明】username 手机号</br>
|
||||
【参数说明】password 密码</br>
|
||||
"""
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
|
||||
def post(self,request):
|
||||
username = get_parameter_dic(request)['username']
|
||||
password = get_parameter_dic(request)['password']
|
||||
user = Users.objects.filter(username=username, identity=2).first()
|
||||
if user and not user.is_active:
|
||||
return ErrorResponse(msg="该账号已被禁用,请联系管理员")
|
||||
if user and user.check_password(password): # check_password() 对明文进行加密,并验证
|
||||
resdata = APPMobileSMSLoginSerializer.get_token(user)
|
||||
return DetailResponse(data=resdata, msg="登录成功")
|
||||
return ErrorResponse(msg="账号/密码错误")
|
||||
|
||||
class MobilePassWordLoginView(APIView):
|
||||
"""
|
||||
post:
|
||||
手机号密码登录
|
||||
【功能描述】手机号密码登录</br>
|
||||
【参数说明】mobile 手机号</br>
|
||||
【参数说明】password 密码</br>
|
||||
"""
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
|
||||
def post(self,request):
|
||||
username = get_parameter_dic(request)['mobile']
|
||||
password = get_parameter_dic(request)['password']
|
||||
# 验证手机号是否合法
|
||||
if not re.match(REGEX_MOBILE, username):
|
||||
return ErrorResponse(msg="请输入正确的手机号")
|
||||
user = Users.objects.filter(username=username, identity=2).first()
|
||||
if user and not user.is_active:
|
||||
return ErrorResponse(msg="该账号已被禁用,请联系管理员")
|
||||
if user and user.check_password(password): # check_password() 对明文进行加密,并验证
|
||||
resdata = APPMobileSMSLoginSerializer.get_token(user)
|
||||
return DetailResponse(data=resdata, msg="登录成功")
|
||||
return ErrorResponse(msg="账号/密码错误")
|
||||
|
||||
#发送短信序列化器
|
||||
class SmsSerializer(serializers.Serializer):
|
||||
mobile = serializers.CharField(max_length=11,required=True,help_text="手机号")
|
||||
smstype=serializers.CharField(max_length=10,required=True,help_text="请求类型:login/register/restpass/rebind")
|
||||
|
||||
# 函数名必须:validate + 验证字段名
|
||||
def validate_mobile(self, mobile):
|
||||
"""
|
||||
手机号码验证
|
||||
"""
|
||||
# 是否合法手机号
|
||||
if not re.match(REGEX_MOBILE, mobile):
|
||||
raise ValueError("手机号码非法")
|
||||
|
||||
if self.context['smstype']== "register":#用户注册
|
||||
# 是否已经注册
|
||||
if Users.objects.filter(mobile=mobile).count():
|
||||
raise ValueError("该手机号已注册")
|
||||
|
||||
if self.context['smstype']== "restpass" or self.context['smstype']== "login":#重置密码/用户登录
|
||||
#该手机号是否已注册
|
||||
if not Users.objects.filter(username=mobile,identity=2,is_active=True).count():
|
||||
raise ValueError("没有找到该用户")
|
||||
|
||||
if self.context['smstype']== "wxbind":#微信绑定
|
||||
#该手机号是否已注册
|
||||
if not Users.objects.filter(username=mobile,identity=2,is_active=True,oauthwxuser__isnull=True).count():
|
||||
raise ValueError("没有找到该用户或该用户已绑定微信")
|
||||
|
||||
if self.context['smstype'] == "rebind":#换绑手机号,前提用户已经登录
|
||||
#是否跟原来绑定过的号码一致
|
||||
mqueryset = Users.objects.filter(id = self.context['request'].user.id)
|
||||
if not mqueryset:
|
||||
raise ValueError("该用户不存在")
|
||||
if mqueryset[0].mobile == mobile:
|
||||
raise ValueError("请使用新的手机号绑定")
|
||||
if Users.objects.filter(mobile=mobile).count():
|
||||
raise ValueError("该手机号已被其他用户绑定")
|
||||
|
||||
return mobile
|
||||
|
||||
#发送短信验证码(不需要登录验证就可以调用)
|
||||
#注册,登录,忘记密码时使用
|
||||
class SendSmsCodeView(APIView):
|
||||
"""
|
||||
post:
|
||||
发送手机验证码
|
||||
【参数说明】使用"application/json"编码传输,参数如下</br>
|
||||
mobile 必要 手机号</br>
|
||||
smstype 必要 短信类型:register/restpass/login</br>
|
||||
"""
|
||||
authentication_classes = ()#不需要身份认证
|
||||
permission_classes = ()#不需要权限分配
|
||||
serializer_class = SmsSerializer
|
||||
|
||||
def generate_code(self):
|
||||
"""
|
||||
生成四位数字的验证码
|
||||
"""
|
||||
seeds = "1234567890"
|
||||
random_str = []
|
||||
for i in range(6):
|
||||
random_str.append(choice(seeds))
|
||||
|
||||
return "".join(random_str)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
||||
mobile = get_parameter_dic(request)['mobile']
|
||||
smstype = get_parameter_dic(request)['smstype']
|
||||
if smstype == "login" or smstype == "restpass" or smstype == "wxbind" or smstype == "register":
|
||||
# 创建序列化器
|
||||
serializer = SmsSerializer(data=request.data, context={"request": request, "smstype": smstype})
|
||||
# 验证是否有效
|
||||
serializer.is_valid(raise_exception=True)
|
||||
# 判断该手机号60s内是否已经发送过短信
|
||||
redis_conn = get_redis_connection('verify_codes')
|
||||
send_flag = redis_conn.get('send_flag_%s' % mobile)
|
||||
if send_flag: # 如果取到了标记,说明该手机号60s内发送过短信验证码
|
||||
return ErrorResponse(msg="请一分钟后再获取验证码")
|
||||
# 验证码过期时间
|
||||
codeexpire = 300 # 300秒,默认5分钟
|
||||
# 生成验证码
|
||||
code = self.generate_code()
|
||||
|
||||
# 云片网api短信接口调用-----------开始
|
||||
# yun_pian = YunPian(SMS_API_KEY)
|
||||
# sms_status = yun_pian.send_sms(code=code, mobile=mobile)
|
||||
#
|
||||
# if sms_status["code"] != 0:
|
||||
# mydata = {}
|
||||
# mydata["mobile"] = mobile
|
||||
# return Response(ly_api_res(400,mydata,sms_status["msg"]), status=status.HTTP_400_BAD_REQUEST)
|
||||
# else:
|
||||
# #存储短信验证码到redis
|
||||
# redis_conn.setex('sms_%s'%mobile,codeexpire,code)#默认300秒5分钟过期时间
|
||||
# #存储一个标记,表示此手机号已发送过短信,标记有效期为60s
|
||||
# redis_conn.setex('send_flag_%s'%mobile,60,1)
|
||||
# mydata = {}
|
||||
# mydata["mobile"] = mobile
|
||||
# return Response(ly_api_res(200,mydata,"短信验证码发送成功"), status=status.HTTP_200_OK)
|
||||
# 云片网api短信接口调用-----------结束
|
||||
# unicloud短信接口api调用-----------开始
|
||||
# unicloudsms = UniCloudSms()
|
||||
# sms_status = unicloudsms.send_sms(code=code, mobile=mobile,expminute=codeexpire)
|
||||
# #返回内容
|
||||
# #{"msg":"sendSms参数phone值不可为空"}
|
||||
# #{"code":0,"errCode":0,"success":true}
|
||||
# if 'code' in sms_status: #判断返回内容是否存在code的key,错误时不返回code
|
||||
# if sms_status["code"] == 0:
|
||||
# # 存储短信验证码到redis
|
||||
# redis_conn.setex('sms_%s' % mobile, codeexpire, code) # 默认300秒5分钟过期时间
|
||||
# # 存储一个标记,表示此手机号已发送过短信,标记有效期为60s
|
||||
# redis_conn.setex('send_flag_%s' % mobile, 60, 1)
|
||||
# mydata = {}
|
||||
# mydata["mobile"] = mobile
|
||||
# return SuccessResponse(data=mydata, msg="短信验证码发送成功")
|
||||
# else:
|
||||
# mydata = {}
|
||||
# mydata["mobile"] = mobile
|
||||
# return ErrorResponse(data=mydata, msg=sms_status['msg'])
|
||||
# else:
|
||||
# mydata = {}
|
||||
# mydata["mobile"] = mobile
|
||||
# return ErrorResponse(data=mydata, msg=sms_status['msg'])
|
||||
# unicloud短信接口api调用-----------结束
|
||||
# 阿里云短信-----------开始
|
||||
# __business_id = uuid.uuid1()
|
||||
# # 一个验证码发送的例子
|
||||
# params = '{\"code\":\"'+code+'\"}' # 模板参数
|
||||
# sms_status= send_sms(__business_id,mobile,ALIYUN_SMS_SIGN,ALIYUM_SMS_TEMPLATE,params)
|
||||
# # 返回内容byte类型
|
||||
# # {
|
||||
# # "Message": "OK",
|
||||
# # "RequestId": "F655A8D5-B967-440B-8683-DAD6FF8DE990",
|
||||
# # "Code": "OK",
|
||||
# # "BizId": "900619746936498440^0"
|
||||
# # }
|
||||
# sms_status_str = sms_status.decode()
|
||||
# sms_status_json = json.loads(sms_status)
|
||||
# if 'Code' in sms_status_str: # 判断返回内容是否存在code的key,错误时不返回code
|
||||
# if sms_status_json["Code"] == 'OK':
|
||||
# # 存储短信验证码到redis
|
||||
# redis_conn.setex('sms_%s' % mobile, codeexpire, code) # 默认300秒5分钟过期时间
|
||||
# # 存储一个标记,表示此手机号已发送过短信,标记有效期为60s
|
||||
# redis_conn.setex('send_flag_%s' % mobile, 60, 1)
|
||||
# mydata = {}
|
||||
# mydata["mobile"] = mobile
|
||||
# return SuccessResponse(data=mydata, msg="短信验证码发送成功")
|
||||
# else:
|
||||
# return ErrorResponse(data=sms_status_json, msg='发送失败')
|
||||
# else:
|
||||
# return ErrorResponse(data=sms_status_json, msg='发送失败')
|
||||
# 阿里云短信-----------结束
|
||||
# 腾讯云短信-----------开始
|
||||
sms_status_json = tencentsms("+" + str(86) + str(mobile), code)
|
||||
# sms_status_json = 'Ok'
|
||||
if 'Ok' in sms_status_json: #
|
||||
# 存储短信验证码到redis
|
||||
redis_conn.setex('sms_%s' % mobile, codeexpire, code) # 默认300秒5分钟过期时间
|
||||
# 存储一个标记,表示此手机号已发送过短信,标记有效期为60s
|
||||
redis_conn.setex('send_flag_%s' % mobile, 60, 1)
|
||||
mydata = {}
|
||||
mydata["mobile"] = mobile
|
||||
return SuccessResponse(data=mydata, msg="success")
|
||||
else:
|
||||
return ErrorResponse(data=sms_status_json, msg='error')
|
||||
# 腾讯云短信-----------结束
|
||||
|
||||
else:
|
||||
return ErrorResponse(msg="smstype短信验证码类型错误")
|
||||
|
||||
|
||||
|
||||
class APPMobileSMSLoginSerializer(TokenObtainPairSerializer):
|
||||
"""
|
||||
登录的序列化器:
|
||||
重写djangorestframework-simplejwt的序列化器
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def get_token(cls, user):
|
||||
refresh = super(APPMobileSMSLoginSerializer,cls).get_token(user)
|
||||
data = {}
|
||||
data['identity'] = ast_convert(user.identity)
|
||||
data['userId'] = user.id
|
||||
data['refresh'] = str(refresh)
|
||||
data['access'] = str(refresh.access_token)
|
||||
return data
|
||||
|
||||
class APPMobileSMSLoginView(APIView):
|
||||
"""
|
||||
post:
|
||||
手机号短信登录接口
|
||||
【功能描述】用户手机号短信验证码登录</br>
|
||||
【参数说明】mobile为手机号</br>
|
||||
【参数说明】code短信验证码</br>
|
||||
"""
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
|
||||
def post(self,request):
|
||||
mobile = get_parameter_dic(request)['mobile']
|
||||
code = get_parameter_dic(request)['code']
|
||||
# 验证手机号是否合法
|
||||
if not re.match(REGEX_MOBILE, mobile):
|
||||
return ErrorResponse(msg="请输入正确手机号")
|
||||
# 判断短信验证码是否正确
|
||||
redis_conn = get_redis_connection('verify_codes')
|
||||
send_flag = redis_conn.get('sms_%s' % mobile) # send_flag的值为bytes,需要转换成str ,send_flag.decode()
|
||||
if not send_flag: # 如果取不到标记,则说明验证码过期
|
||||
return ErrorResponse(msg="短信验证码已过期")
|
||||
else:
|
||||
if str(send_flag.decode()) != str(code):
|
||||
return ErrorResponse(msg="验证码错误")
|
||||
#开始登录
|
||||
user = Users.objects.filter(username=mobile,identity=2).first()
|
||||
if not user:
|
||||
return ErrorResponse(msg="用户不存在")
|
||||
if not user.is_active:
|
||||
return ErrorResponse(msg="该账号已被禁用,请联系管理员")
|
||||
resdata = APPMobileSMSLoginSerializer.get_token(user)
|
||||
redis_conn.delete('sms_%s' % mobile)
|
||||
return SuccessResponse(data=resdata, msg="登录成功")
|
||||
|
||||
|
||||
#用户忘记密码重置密码
|
||||
class ForgetPasswdResetView(APIView):
|
||||
'''
|
||||
post:
|
||||
【功能描述】重置用户密码</br>
|
||||
【参数说明】mobile为手机号</br>
|
||||
【参数说明】code短信验证码</br>
|
||||
【参数说明】password为密码</br>
|
||||
'''
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
||||
mobile = get_parameter_dic(request)['mobile']
|
||||
code = get_parameter_dic(request)['code']
|
||||
password = get_parameter_dic(request)['password']
|
||||
if len(password) < 6:
|
||||
return ErrorResponse(msg="密码长度至少6位")
|
||||
|
||||
# 验证手机号是否合法
|
||||
if not re.match(REGEX_MOBILE, mobile):
|
||||
return ErrorResponse(msg="请输入正确手机号")
|
||||
# 判断短信验证码是否正确
|
||||
redis_conn = get_redis_connection('verify_codes')
|
||||
send_flag = redis_conn.get('sms_%s'%mobile)#send_flag的值为bytes,需要转换成str ,,send_flag.decode()
|
||||
if not send_flag: # 如果取不到标记,则说明验证码过期
|
||||
return ErrorResponse(msg="短信验证码已过期")
|
||||
else:
|
||||
if str(send_flag.decode()) != str(code):
|
||||
return ErrorResponse(msg="验证码错误")
|
||||
#开始更换密码
|
||||
user = Users.objects.filter(username=mobile,identity=2).first()
|
||||
if not user:
|
||||
return ErrorResponse(msg="用户不存在")
|
||||
if not user.is_active:
|
||||
return ErrorResponse(msg="该账号已被禁用,请联系管理员")
|
||||
# 重置密码
|
||||
user.password = make_password(password)
|
||||
user.save()
|
||||
redis_conn.delete('sms_%s' % mobile)
|
||||
return SuccessResponse(msg="success")
|
||||
|
||||
class RegisterView(APIView):
|
||||
'''
|
||||
前端用户注册
|
||||
post:
|
||||
【功能描述】前端用户注册</br>
|
||||
【参数说明】mobile为手机号</br>
|
||||
【参数说明】code短信验证码</br>
|
||||
【参数说明】password为密码</br>
|
||||
【参数说明】password2为确认输入的密码</br>
|
||||
'''
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
mobile = get_parameter_dic(request)['mobile']
|
||||
code = get_parameter_dic(request)['code']
|
||||
password = get_parameter_dic(request)['password']
|
||||
password2 = get_parameter_dic(request)['password2']
|
||||
if mobile is None or code is None or password is None or password2 is None:
|
||||
return ErrorResponse(msg="提交的参数不能为空")
|
||||
|
||||
# 判断密码是否合法
|
||||
if len(password) < 6:
|
||||
return ErrorResponse(msg="密码长度至少6位")
|
||||
|
||||
if not re.match(r'^[a-zA_Z0-9]{6,20}$', password):
|
||||
return ErrorResponse(msg="密码格式不正确(大小写字母、数字组合)")
|
||||
|
||||
if password != password2:
|
||||
return ErrorResponse(msg="两次密码输入不一致")
|
||||
|
||||
# 验证手机号是否合法
|
||||
if not re.match(REGEX_MOBILE, mobile):
|
||||
return ErrorResponse(msg="请输入正确手机号")
|
||||
|
||||
# 判断短信验证码是否正确
|
||||
if not re.match(r'^\d{6}$', code):
|
||||
return ErrorResponse(msg="验证码格式错误")
|
||||
redis_conn = get_redis_connection('verify_codes')
|
||||
send_flag = redis_conn.get('sms_%s' % mobile) # send_flag的值为bytes,需要转换成str ,,send_flag.decode()
|
||||
if not send_flag: # 如果取不到标记,则说明验证码过期
|
||||
return ErrorResponse(msg="短信验证码已过期")
|
||||
else:
|
||||
if str(send_flag.decode()) != str(code):
|
||||
return ErrorResponse(msg="验证码错误")
|
||||
# 开始注册
|
||||
Users.objects.create(username=mobile,password=make_password(password),is_staff=False,identity=2)
|
||||
redis_conn.delete('sms_%s' % mobile)
|
||||
return SuccessResponse(msg="注册成功")
|
||||
0
backend/apps/lyFormBuilder/__init__.py
Normal file
0
backend/apps/lyFormBuilder/__init__.py
Normal file
5
backend/apps/lyFormBuilder/apps.py
Normal file
5
backend/apps/lyFormBuilder/apps.py
Normal file
@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
class LyformbuilderConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = 'apps.lyFormBuilder'
|
||||
67
backend/apps/lyFormBuilder/migrations/0001_initial.py
Normal file
67
backend/apps/lyFormBuilder/migrations/0001_initial.py
Normal file
@ -0,0 +1,67 @@
|
||||
# Generated by Django 4.1.3 on 2023-05-13 11:29
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import utils.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='lyFormBuilder',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('modifier', models.CharField(blank=True, help_text='修改人', max_length=100, null=True, verbose_name='修改人')),
|
||||
('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')),
|
||||
('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),
|
||||
('is_delete', models.BooleanField(default=False, help_text='是否逻辑删除', verbose_name='是否逻辑删除')),
|
||||
('verbose_name', models.CharField(help_text='表中文名称', max_length=30, verbose_name='表中文名称')),
|
||||
('class_name', models.CharField(help_text='模型类名', max_length=32, verbose_name='模型类名')),
|
||||
('db_table', models.CharField(help_text='表名', max_length=50, verbose_name='表名')),
|
||||
('formJson', models.TextField(blank=True, help_text='表单设计', null=True, verbose_name='表单设计')),
|
||||
('remark', models.CharField(blank=True, help_text='描述', max_length=100, null=True, verbose_name='描述')),
|
||||
('is_mount', models.BooleanField(default=False, help_text='是否生成挂载', verbose_name='是否生成挂载')),
|
||||
('file_name_old', models.CharField(blank=True, help_text='上一次生成文件名(通用)', max_length=100, null=True, verbose_name='上一次生成文件名(通用)')),
|
||||
('parent_menu', models.CharField(blank=True, help_text='上级菜单', max_length=50, null=True, verbose_name='上级菜单')),
|
||||
('menu_sort', models.PositiveSmallIntegerField(default=88, help_text='菜单排序', verbose_name='菜单排序')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '表单构建',
|
||||
'verbose_name_plural': '表单构建',
|
||||
'db_table': 'lyadmin_lyformbuilder',
|
||||
'ordering': ('-create_datetime',),
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='teacherManage',
|
||||
fields=[
|
||||
('id', models.CharField(default=utils.models.make_uuid, help_text='Id', max_length=100, primary_key=True, serialize=False, verbose_name='Id')),
|
||||
('modifier', models.CharField(blank=True, help_text='修改人', max_length=100, null=True, verbose_name='修改人')),
|
||||
('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=100, null=True, verbose_name='数据归属部门')),
|
||||
('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')),
|
||||
('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),
|
||||
('avatar', models.CharField(blank=True, max_length=100, null=True, verbose_name='头像')),
|
||||
('name', models.CharField(blank=True, max_length=50, null=True, verbose_name='姓名')),
|
||||
('phone', models.CharField(blank=True, max_length=100, null=True, verbose_name='手机号')),
|
||||
('subject', models.CharField(blank=True, max_length=50, null=True, verbose_name='科目')),
|
||||
('gender', models.IntegerField(blank=True, default=0, null=True, verbose_name='性别')),
|
||||
('birthday', models.DateField(blank=True, null=True, verbose_name='生日')),
|
||||
('workstatus', models.BooleanField(blank=True, default=True, null=True, verbose_name='在职状态')),
|
||||
('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '老师管理',
|
||||
'verbose_name_plural': '老师管理',
|
||||
'db_table': 'tb_teacherManage',
|
||||
'ordering': ['-create_datetime'],
|
||||
},
|
||||
),
|
||||
]
|
||||
35
backend/apps/lyFormBuilder/migrations/0002_initial.py
Normal file
35
backend/apps/lyFormBuilder/migrations/0002_initial.py
Normal file
@ -0,0 +1,35 @@
|
||||
# Generated by Django 4.1.3 on 2023-05-13 11:29
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('lyautocode', '0001_initial'),
|
||||
('lyFormBuilder', '0001_initial'),
|
||||
('mysystem', '0007_alter_dept_options'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='teachermanage',
|
||||
name='students',
|
||||
field=models.ManyToManyField(db_constraint=False, to='lyautocode.studentmanage', verbose_name='关联学生'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='lyformbuilder',
|
||||
name='creator',
|
||||
field=models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='lyformbuilder',
|
||||
name='menu',
|
||||
field=models.ForeignKey(blank=True, db_constraint=False, help_text='关联菜单', null=True, on_delete=django.db.models.deletion.SET_NULL, to='mysystem.menu', verbose_name='关联菜单'),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,17 @@
|
||||
# Generated by Django 4.1.3 on 2023-05-13 11:41
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('lyFormBuilder', '0002_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelTable(
|
||||
name='teachermanage',
|
||||
table='tb_teachermanage',
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.1.3 on 2023-12-06 21:58
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('lyFormBuilder', '0003_alter_teachermanage_table'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='teachermanage',
|
||||
name='id',
|
||||
field=models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id'),
|
||||
),
|
||||
]
|
||||
0
backend/apps/lyFormBuilder/migrations/__init__.py
Normal file
0
backend/apps/lyFormBuilder/migrations/__init__.py
Normal file
26
backend/apps/lyFormBuilder/models/__init__.py
Normal file
26
backend/apps/lyFormBuilder/models/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
import os
|
||||
import importlib
|
||||
from django.conf import settings
|
||||
|
||||
autoCodePath = os.path.join(settings.BASE_DIR, 'apps','lyFormBuilder','models')
|
||||
|
||||
def get_modules(package="."):
|
||||
"""
|
||||
获取包名下所有非__init__的模块名
|
||||
"""
|
||||
modules = []
|
||||
files = os.listdir(package)
|
||||
|
||||
for file in files:
|
||||
if not file.startswith("__"):
|
||||
name, ext = os.path.splitext(file)
|
||||
modules.append("." + name)
|
||||
|
||||
return modules
|
||||
|
||||
|
||||
modules = get_modules(autoCodePath)
|
||||
|
||||
# 将包下的所有模块,逐个导入
|
||||
for module in modules:
|
||||
importlib.import_module(module, 'apps.lyFormBuilder.models')
|
||||
25
backend/apps/lyFormBuilder/models/models_lyFormBuilder.py
Normal file
25
backend/apps/lyFormBuilder/models/models_lyFormBuilder.py
Normal file
@ -0,0 +1,25 @@
|
||||
from django.db import models
|
||||
from utils.models import CoreModel,BaseModel,SimpleCoreModel,table_prefix
|
||||
from mysystem.models import Menu
|
||||
|
||||
class lyFormBuilder(SimpleCoreModel):
|
||||
"""
|
||||
表单构建
|
||||
"""
|
||||
verbose_name = models.CharField(max_length=30, verbose_name="表中文名称", help_text="表中文名称")
|
||||
class_name = models.CharField(max_length=32, verbose_name="模型类名", help_text="模型类名")
|
||||
db_table = models.CharField(max_length=50, verbose_name="表名", help_text="表名")
|
||||
formJson = models.TextField(verbose_name="表单设计", help_text="表单设计", null=True, blank=True)
|
||||
remark = models.CharField(max_length=100, verbose_name="描述", null=True, blank=True, help_text="描述")
|
||||
is_mount = models.BooleanField(default=False, verbose_name="是否生成挂载", help_text="是否生成挂载")
|
||||
file_name_old = models.CharField(max_length=100, null=True, blank=True, verbose_name="上一次生成文件名(通用)", help_text="上一次生成文件名(通用)")#第一次创建默认为class_name通用名
|
||||
parent_menu = models.CharField(max_length=50, verbose_name="上级菜单", null=True, blank=True, help_text="上级菜单")
|
||||
menu_sort = models.PositiveSmallIntegerField(default=88, verbose_name="菜单排序", help_text="菜单排序")
|
||||
menu = models.ForeignKey(Menu, on_delete=models.SET_NULL, verbose_name="关联菜单", null=True, blank=True,db_constraint=False, help_text="关联菜单")
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "lyformbuilder"
|
||||
verbose_name = '表单构建'
|
||||
verbose_name_plural = verbose_name
|
||||
app_label = 'lyFormBuilder'
|
||||
ordering = ('-create_datetime',)
|
||||
25
backend/apps/lyFormBuilder/models/models_teacherManage.py
Normal file
25
backend/apps/lyFormBuilder/models/models_teacherManage.py
Normal file
@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#此文件请放在lyFormBuilder的models目录中
|
||||
from django.db import models
|
||||
from utils.models import CoreModel,BaseModel,SimpleCoreModel
|
||||
from apps.lyautocode.models.models_StudentManage import StudentManage
|
||||
|
||||
class teacherManage(CoreModel):
|
||||
"""
|
||||
老师管理
|
||||
"""
|
||||
avatar = models.CharField(verbose_name="头像", max_length=100, null=True,blank=True)
|
||||
name = models.CharField(verbose_name="姓名", max_length=50, null=True,blank=True)
|
||||
phone = models.CharField(verbose_name="手机号", max_length=100, null=True,blank=True)
|
||||
subject = models.CharField(verbose_name="科目", max_length=50, null=True,blank=True)
|
||||
gender = models.IntegerField(verbose_name="性别", null=True,blank=True,default=0)
|
||||
birthday = models.DateField(verbose_name="生日", null=True,blank=True)
|
||||
workstatus = models.BooleanField(verbose_name="在职状态", default=True, null=True,blank=True)
|
||||
students = models.ManyToManyField(StudentManage, db_constraint=False, verbose_name="关联学生")
|
||||
|
||||
class Meta:
|
||||
db_table = "tb_teachermanage"
|
||||
verbose_name = "老师管理"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ['-create_datetime']
|
||||
app_label = 'lyFormBuilder'#不放在lyFormBuilder的app中此配置项可去掉
|
||||
29
backend/apps/lyFormBuilder/urls/__init__.py
Normal file
29
backend/apps/lyFormBuilder/urls/__init__.py
Normal file
@ -0,0 +1,29 @@
|
||||
import os
|
||||
import importlib
|
||||
from django.conf import settings
|
||||
|
||||
autoCodePath = os.path.join(settings.BASE_DIR, 'apps','lyFormBuilder','urls')
|
||||
|
||||
def get_modules(package="."):
|
||||
"""
|
||||
获取包名下所有非__init__的模块名
|
||||
"""
|
||||
modules = []
|
||||
files = os.listdir(package)
|
||||
|
||||
for file in files:
|
||||
if not file.startswith("__"):
|
||||
name, ext = os.path.splitext(file)
|
||||
modules.append("." + name)
|
||||
|
||||
return modules
|
||||
|
||||
|
||||
modules = get_modules(autoCodePath)
|
||||
|
||||
urlpatterns = []
|
||||
|
||||
# 将包下的所有模块,逐个导入
|
||||
for module in modules:
|
||||
mod = importlib.import_module(module, 'apps.lyFormBuilder.urls')
|
||||
urlpatterns += mod.urlpatterns
|
||||
19
backend/apps/lyFormBuilder/urls/urls_lyFormBuilder.py
Normal file
19
backend/apps/lyFormBuilder/urls/urls_lyFormBuilder.py
Normal file
@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#路由文件
|
||||
|
||||
from django.urls import path, re_path
|
||||
from rest_framework import routers
|
||||
|
||||
from apps.lyFormBuilder.views.views_lyFormBuilder import lyFormBuilderViewSet
|
||||
|
||||
system_url = routers.SimpleRouter()
|
||||
system_url.register(r'lyformbuilder', lyFormBuilderViewSet)
|
||||
|
||||
urlpatterns = [
|
||||
path('lyformbuilder/previewcodejson/',lyFormBuilderViewSet.as_view({'post':'previewCodeFormJSON'}), name='预览'),
|
||||
path('lyformbuilder/generatemount/',lyFormBuilderViewSet.as_view({'post':'generateMount'}), name='同步文件'),
|
||||
path('lyformbuilder/syncdb/',lyFormBuilderViewSet.as_view({'post':'syncdb'}), name='同步数据库'),
|
||||
path('lyformbuilder/editMenu/',lyFormBuilderViewSet.as_view({'post':'editMenu'}), name='菜单配置'),
|
||||
]
|
||||
urlpatterns += system_url.urls
|
||||
15
backend/apps/lyFormBuilder/urls/urls_teacherManage.py
Normal file
15
backend/apps/lyFormBuilder/urls/urls_teacherManage.py
Normal file
@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#路由文件
|
||||
#此文件请放在lyFormBuilder的urls目录中
|
||||
|
||||
from django.urls import path, re_path
|
||||
from rest_framework import routers
|
||||
|
||||
from apps.lyFormBuilder.views.views_teacherManage import teacherManageViewSet
|
||||
|
||||
system_url = routers.SimpleRouter()
|
||||
system_url.register(r'teacherManage', teacherManageViewSet)
|
||||
|
||||
urlpatterns = []
|
||||
urlpatterns += system_url.urls
|
||||
26
backend/apps/lyFormBuilder/views/__init__.py
Normal file
26
backend/apps/lyFormBuilder/views/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
import os
|
||||
import importlib
|
||||
from django.conf import settings
|
||||
|
||||
autoCodePath = os.path.join(settings.BASE_DIR, 'apps','lyFormBuilder','views')
|
||||
|
||||
def get_modules(package="."):
|
||||
"""
|
||||
获取包名下所有非__init__的模块名
|
||||
"""
|
||||
modules = []
|
||||
files = os.listdir(package)
|
||||
|
||||
for file in files:
|
||||
if not file.startswith("__"):
|
||||
name, ext = os.path.splitext(file)
|
||||
modules.append("." + name)
|
||||
|
||||
return modules
|
||||
|
||||
|
||||
modules = get_modules(autoCodePath)
|
||||
|
||||
# 将包下的所有模块,逐个导入
|
||||
for module in modules:
|
||||
importlib.import_module(module, 'apps.lyFormBuilder.views')
|
||||
182
backend/apps/lyFormBuilder/views/views_lyFormBuilder.py
Normal file
182
backend/apps/lyFormBuilder/views/views_lyFormBuilder.py
Normal file
@ -0,0 +1,182 @@
|
||||
import json
|
||||
from apps.lyFormBuilder.models.models_lyFormBuilder import *
|
||||
from utils.jsonResponse import SuccessResponse,DetailResponse,ErrorResponse
|
||||
from utils.common import get_parameter_dic, ast_convert,starts_with_digit
|
||||
from utils.models import is_table_exists
|
||||
from rest_framework import serializers
|
||||
from utils.serializers import CustomModelSerializer
|
||||
from utils.viewset import CustomModelViewSet
|
||||
from utils.lyformbuilder.lygeneratecode import GenerateCode
|
||||
from django.db.models import Q
|
||||
from django.db import transaction
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class lyFormBuilderSerializer(CustomModelSerializer):
|
||||
"""
|
||||
表单构建 -序列化器
|
||||
"""
|
||||
|
||||
def to_representation(self, instance): # 序列化
|
||||
ret = super().to_representation(instance)
|
||||
ret['formJson'] = ast_convert(ret['formJson']) # 可以保存的修改字段值的方法
|
||||
return ret
|
||||
|
||||
|
||||
class Meta:
|
||||
model = lyFormBuilder
|
||||
read_only_fields = ["id"]
|
||||
fields = '__all__'
|
||||
|
||||
class lyFormBuilderCreateUpdateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
表单构建 -序列化器
|
||||
"""
|
||||
formJson = serializers.JSONField()
|
||||
|
||||
class Meta:
|
||||
model = lyFormBuilder
|
||||
read_only_fields = ["id"]
|
||||
fields = '__all__'
|
||||
|
||||
class lyFormBuilderViewSet(CustomModelViewSet):
|
||||
"""
|
||||
后台表单构建 接口:
|
||||
"""
|
||||
queryset = lyFormBuilder.objects.filter(is_delete=False).order_by('-create_datetime')
|
||||
serializer_class = lyFormBuilderSerializer
|
||||
create_serializer_class = lyFormBuilderCreateUpdateSerializer
|
||||
update_serializer_class = lyFormBuilderCreateUpdateSerializer
|
||||
search_fields = ('class_name','verbose_name','db_table')
|
||||
|
||||
# 重写delete方法,并改为逻辑删除
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
real_delete = get_parameter_dic(request).get('real_delete',None)
|
||||
instance = self.get_object_list()
|
||||
if not real_delete:
|
||||
for i in range(len(instance)):
|
||||
instance[i].is_delete = True
|
||||
model = self.get_serializer().Meta.model
|
||||
model.objects.bulk_update(instance, fields=['is_delete'])
|
||||
return SuccessResponse(data=[], msg="删除成功")
|
||||
else:
|
||||
for i in range(len(instance)):
|
||||
serializer = self.get_serializer(instance[i])
|
||||
object = serializer.data
|
||||
if instance[i].is_mount:
|
||||
menu_instance = None
|
||||
if instance[i].menu:
|
||||
menu_instance = instance[i].menu
|
||||
newObject = json.loads(json.dumps(object['formJson']))
|
||||
newObject['class_name'] = object['class_name']
|
||||
newObject['file_name_old'] = object['file_name_old']
|
||||
GenerateCode().generate(newObject, True, menu_instance,True)
|
||||
self.perform_destroy(instance)
|
||||
return DetailResponse(msg="删除成功")
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
params = request.data
|
||||
class_name = params.get('class_name',None)
|
||||
if starts_with_digit(class_name):
|
||||
return ErrorResponse(msg="类名【%s】不能以数字开头!!!"%class_name)
|
||||
db_table = params.get('db_table', None)
|
||||
if not all([class_name,db_table]):
|
||||
return ErrorResponse(msg="请先填写表单CRUD属性")
|
||||
if self.filter_queryset(self.get_queryset()).filter(Q(class_name=class_name)|Q(db_table=db_table)).exists():
|
||||
return ErrorResponse(msg="存在同名的类名或表名")
|
||||
if is_table_exists(db_table):
|
||||
return ErrorResponse(msg="存在同名的表名,请更改后再试")
|
||||
formJson = ast_convert(params.get('formJson', None))
|
||||
if not formJson or not len(formJson['widgetList'])>0:
|
||||
return ErrorResponse(msg="表单至少拥有一个组件")
|
||||
params['parent_menu'] = 35#系统工具
|
||||
params['file_name_old'] = class_name
|
||||
serializer = self.get_serializer(data=params, request=request)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
self.perform_create(serializer)
|
||||
return DetailResponse(data=serializer.data, msg="新增成功")
|
||||
|
||||
def editMenu(self,request):
|
||||
"""
|
||||
菜单配置
|
||||
"""
|
||||
params = request.data
|
||||
parent_menu = params.get('parent_menu',None)
|
||||
id = params.get('id',None)
|
||||
menu_sort = params.get('menu_sort',88)
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
instance = queryset.filter(id=id).first()
|
||||
if not instance:
|
||||
return ErrorResponse(msg="id error or no permission")
|
||||
instance.parent_menu = parent_menu
|
||||
instance.menu_sort = menu_sort
|
||||
instance.save()
|
||||
return DetailResponse(msg="更新成功")
|
||||
|
||||
def previewCodeFormJSON(self,request):
|
||||
"""
|
||||
根据前端传递form表单json数据预览生成代码
|
||||
"""
|
||||
formJson = ast_convert(get_parameter_dic(request).get('formJson',None))
|
||||
if not formJson:
|
||||
return ErrorResponse(msg="参数错误")
|
||||
if formJson['formConfig']['jsonVersion'] !=1:
|
||||
return ErrorResponse(msg="版本错误")
|
||||
if formJson['formConfig']['modelClassName'] == "" or formJson['formConfig']['modelDbTable'] == "" or formJson['formConfig']['modelVerboseName'] == "":
|
||||
return SuccessResponse(data=[],total=0)
|
||||
data = GenerateCode().generate(formJson)
|
||||
return SuccessResponse(data=data,total=len(data))
|
||||
|
||||
def generateMount(self,request):
|
||||
id = get_parameter_dic(request).get('id',None)
|
||||
instance = self.filter_queryset(self.get_queryset()).filter(id=id).first()
|
||||
if not instance:
|
||||
return ErrorResponse(msg="id error")
|
||||
serializer = self.get_serializer(instance)
|
||||
object = serializer.data
|
||||
|
||||
# vue的route name
|
||||
VueIndexName = 'lyFormBuilder%s' % object['class_name']
|
||||
try:
|
||||
# 在事务开始前创建保存点
|
||||
save_id = transaction.savepoint()
|
||||
# 以下代码会在事务中执行
|
||||
# 配置路由菜单
|
||||
if instance.menu:
|
||||
menu_instance = instance.menu
|
||||
Menu.objects.filter(id=menu_instance.id).update(name=object['verbose_name'], web_path=VueIndexName,sort=object['menu_sort'],parent_id=object['parent_menu'])
|
||||
else:
|
||||
menu_instance,created = Menu.objects.get_or_create(name=object['verbose_name'], sort=object['menu_sort'],web_path=VueIndexName, isautopm=1)
|
||||
if object['parent_menu']:
|
||||
menu_instance.parent_id = object['parent_menu']
|
||||
menu_instance.save()
|
||||
instance.menu = menu_instance
|
||||
instance.save()
|
||||
newObject = json.loads(json.dumps(object['formJson']))
|
||||
newObject['class_name'] = object['class_name']
|
||||
newObject['file_name_old'] = object['file_name_old']
|
||||
data = GenerateCode().generate(newObject, True, menu_instance)
|
||||
if not data:
|
||||
# 回滚到保存点
|
||||
transaction.savepoint_rollback(save_id)
|
||||
return ErrorResponse(msg="生成文件错误")
|
||||
instance.file_name_old = object['class_name']
|
||||
instance.is_mount = True
|
||||
instance.save()
|
||||
# 提交从保存点到当前状态的所有数据库事务操作
|
||||
transaction.savepoint_commit(save_id)
|
||||
return DetailResponse(msg="操作成功")
|
||||
except Exception as e:
|
||||
# 回滚到保存点
|
||||
transaction.savepoint_rollback(save_id)
|
||||
# 处理代码
|
||||
raise e
|
||||
|
||||
def syncdb(self,request):
|
||||
result = GenerateCode().syncdb()
|
||||
if result:
|
||||
return DetailResponse(msg="数据库同步成功")
|
||||
return ErrorResponse(msg="数据库同步失败,请查看后端IDE报错内容,或重启后端项目重试同步")
|
||||
|
||||
|
||||
|
||||
52
backend/apps/lyFormBuilder/views/views_teacherManage.py
Normal file
52
backend/apps/lyFormBuilder/views/views_teacherManage.py
Normal file
@ -0,0 +1,52 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#此文件请放在lyFormBuilder的views目录中
|
||||
import django_filters
|
||||
from rest_framework import serializers
|
||||
from utils.viewset import CustomModelViewSet
|
||||
from utils.serializers import CustomModelSerializer
|
||||
from utils.common import ast_convert
|
||||
from apps.lyFormBuilder.models.models_teacherManage import teacherManage
|
||||
|
||||
class teacherManageFilterSet(django_filters.rest_framework.FilterSet):
|
||||
"""
|
||||
老师管理 过滤器
|
||||
URL格式:http://127.0.0.1:8000/?beginAt=2020-12-02 12:00:00&endAt=2021-12-13 12:00:00
|
||||
field_name: 过滤字段名,一般应该对应模型中字段名
|
||||
lookup_expr: 查询时所要进行的操作,和ORM中运算符一致
|
||||
fields:指明过滤字段,可以是列表,也可以字典
|
||||
exclude = ['password'] 排除字段,不允许使用列表中字典进行过滤
|
||||
自定义字段名可以和模型中不一致,但一定要用参数field_name指明对应模型中的字段名
|
||||
"""
|
||||
#开始时间
|
||||
beginAt = django_filters.DateTimeFilter(field_name='create_datetime', lookup_expr='gte') # 指定过滤的字段
|
||||
#结束时间
|
||||
endAt = django_filters.DateTimeFilter(field_name='create_datetime', lookup_expr='lte')
|
||||
name = django_filters.CharFilter(field_name='name',lookup_expr='icontains')
|
||||
birthday_beginAt = django_filters.DateFilter(field_name='birthday', lookup_expr='gte')
|
||||
birthday_endAt = django_filters.DateFilter(field_name='birthday', lookup_expr='lte')
|
||||
|
||||
class Meta:
|
||||
model = teacherManage
|
||||
fields = ['beginAt', 'endAt', 'name', 'birthday_beginAt', 'birthday_endAt']
|
||||
|
||||
class teacherManageSerializer(CustomModelSerializer):
|
||||
"""
|
||||
teacherManage 序列化器
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = teacherManage
|
||||
read_only_fields = ["id"]
|
||||
fields = '__all__'
|
||||
|
||||
class teacherManageViewSet(CustomModelViewSet):
|
||||
"""
|
||||
teacherManage 接口
|
||||
"""
|
||||
queryset = teacherManage.objects.all()
|
||||
serializer_class = teacherManageSerializer
|
||||
filterset_class = teacherManageFilterSet
|
||||
|
||||
export_download_filename = "导出老师管理数据"
|
||||
export_field_dict = {'name': '姓名', 'phone': '手机号'}
|
||||
|
||||
0
backend/apps/lyTiktokUnion/__init__.py
Normal file
0
backend/apps/lyTiktokUnion/__init__.py
Normal file
5
backend/apps/lyTiktokUnion/apps.py
Normal file
5
backend/apps/lyTiktokUnion/apps.py
Normal file
@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
class LytiktokunionConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = 'apps.lyTiktokUnion'
|
||||
0
backend/apps/lyTiktokUnion/management/__init__.py
Normal file
0
backend/apps/lyTiktokUnion/management/__init__.py
Normal file
@ -0,0 +1,12 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from apps.lyTiktokUnion.views.dySystemAccountViews import keepDouDianTokenOnline
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
"""
|
||||
保持团长和抖客账号的token可用(每2个小时执行一次)
|
||||
"""
|
||||
class Command(BaseCommand):
|
||||
def handle(self, *args, **options):
|
||||
logger.info("开始执行保持团长和抖客账号的token可用任务")
|
||||
keepDouDianTokenOnline()
|
||||
@ -0,0 +1,18 @@
|
||||
from django.core.management.base import BaseCommand
|
||||
from utils.common import ast_convert
|
||||
from mysystem.models import Users
|
||||
import datetime
|
||||
from apps.lyTiktokUnion.tools.douyin import allianceColonelGoodsSync
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
"""
|
||||
同步团长活动商品同步(每天执行一次)
|
||||
"""
|
||||
class Command(BaseCommand):
|
||||
def handle(self, *args, **options):
|
||||
isok = allianceColonelGoodsSync()
|
||||
if isok:
|
||||
logger.info("自动同步团长活动商品同步成功(每天执行一次),执行结果:OK")
|
||||
logger.error("自动同步团长活动商品同步失败(每天执行一次),执行结果:FAIL")
|
||||
|
||||
229
backend/apps/lyTiktokUnion/migrations/0001_initial.py
Normal file
229
backend/apps/lyTiktokUnion/migrations/0001_initial.py
Normal file
@ -0,0 +1,229 @@
|
||||
# Generated by Django 4.1.3 on 2023-12-21 21:56
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='DyAwardTaskManage',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('update_datetime', models.DateTimeField(auto_now=True, null=True, verbose_name='更新时间')),
|
||||
('create_datetime', models.DateTimeField(auto_now_add=True, null=True, verbose_name='创建时间')),
|
||||
('task_id', models.CharField(db_index=True, max_length=40, verbose_name='任务ID')),
|
||||
('task_name', models.CharField(blank=True, db_index=True, max_length=100, null=True, verbose_name='任务名称')),
|
||||
('conver', models.CharField(blank=True, max_length=255, null=True, verbose_name='封面图')),
|
||||
('award_description', models.CharField(blank=True, max_length=255, null=True, verbose_name='奖励说明')),
|
||||
('task_other_requirement', models.CharField(blank=True, max_length=255, null=True, verbose_name='任务其他要求')),
|
||||
('apply_cnt', models.IntegerField(default=0, verbose_name='已领取人数')),
|
||||
('promote_cnt', models.IntegerField(default=0, verbose_name='已推广人数')),
|
||||
('gmv', models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='支付GMV')),
|
||||
('order_cnt', models.IntegerField(default=0, verbose_name='总订单量')),
|
||||
('can_apply', models.BooleanField(default=False, help_text='是否可报名', verbose_name='是否可报名')),
|
||||
('task_start_time', models.CharField(blank=True, max_length=100, null=True, verbose_name='任务开始时间')),
|
||||
('task_end_time', models.CharField(blank=True, max_length=100, null=True, verbose_name='任务结束时间')),
|
||||
('task_status', models.IntegerField(choices=[(1, '未发布'), (2, '审核中'), (3, '审核未通过'), (4, '未开始'), (5, '进行中'), (6, '已结束')], default=0, verbose_name='任务状态')),
|
||||
('product_id', models.CharField(db_index=True, default=None, max_length=40, verbose_name='商品ID')),
|
||||
('sort', models.PositiveSmallIntegerField(default=1, verbose_name='排序')),
|
||||
('status', models.BooleanField(default=True, help_text='状态', verbose_name='状态')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '赏金任务',
|
||||
'verbose_name_plural': '赏金任务',
|
||||
'db_table': 'dy_awardtask',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DyProductManage',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('update_datetime', models.DateTimeField(auto_now=True, null=True, verbose_name='更新时间')),
|
||||
('create_datetime', models.DateTimeField(auto_now_add=True, null=True, verbose_name='创建时间')),
|
||||
('product_id', models.CharField(db_index=True, max_length=40, verbose_name='商品ID')),
|
||||
('title', models.CharField(blank=True, db_index=True, max_length=100, null=True, verbose_name='商品标题')),
|
||||
('price', models.IntegerField(default=0, verbose_name='商品价格')),
|
||||
('sales', models.IntegerField(default=0, verbose_name='历史总销量')),
|
||||
('begin_time', models.DateField(blank=True, null=True, verbose_name='活动开始日期')),
|
||||
('end_time', models.DateField(blank=True, null=True, verbose_name='活动结束日期')),
|
||||
('coupon', models.IntegerField(default=0, verbose_name='优惠券个数')),
|
||||
('coupon_price', models.IntegerField(default=0, verbose_name='劵后价')),
|
||||
('cover', models.CharField(blank=True, max_length=255, null=True, verbose_name='商品封面')),
|
||||
('category_id', models.CharField(blank=True, max_length=20, null=True, verbose_name='行业分类ID')),
|
||||
('category_name', models.CharField(blank=True, max_length=30, null=True, verbose_name='行业分类名称')),
|
||||
('cos_ratio', models.FloatField(default=0, verbose_name='普通佣金比例')),
|
||||
('cos_fee', models.IntegerField(default=0, verbose_name='普通佣金金额')),
|
||||
('comment_score', models.FloatField(default=0, verbose_name='商品评分(5分制,保留一位小数)')),
|
||||
('special_cos_ratio', models.IntegerField(default=0, verbose_name='达人/团长/活动佣金比例')),
|
||||
('special_cos_fee', models.IntegerField(default=0, verbose_name='达人/团长/活动佣金金额')),
|
||||
('activity_id', models.CharField(blank=True, db_index=True, max_length=50, null=True, verbose_name='活动ID')),
|
||||
('imgs', models.TextField(blank=True, null=True, verbose_name='轮播图')),
|
||||
('detail_url', models.CharField(blank=True, max_length=1000, null=True, verbose_name='商品链接')),
|
||||
('detail_brief', models.TextField(blank=True, null=True, verbose_name='详情装修信息(商品详情图)')),
|
||||
('sharable', models.BooleanField(default=True, verbose_name='是否可分销')),
|
||||
('is_assured', models.BooleanField(default=False, verbose_name='是否提供安心购服务')),
|
||||
('has_sxt', models.BooleanField(default=False, verbose_name='是否具有短视频随心推资质')),
|
||||
('has_subsidy_tag', models.BooleanField(default=False, verbose_name='是否是超值购商品(百亿补贴)')),
|
||||
('has_supermarket_tag', models.BooleanField(default=False, verbose_name='是否抖音超市(次日达)商品')),
|
||||
('has_douin_goods_tag', models.BooleanField(default=False, verbose_name='是否有【抖in好物】标签')),
|
||||
('has_shop_brand_tag', models.BooleanField(default=False, verbose_name='是否有品牌旗舰店标签([品牌]黑标)')),
|
||||
('in_stock', models.BooleanField(default=True, verbose_name='是否有库存')),
|
||||
('shop_id', models.CharField(blank=True, db_index=True, max_length=40, null=True, verbose_name='店铺ID')),
|
||||
('logistics_info', models.CharField(blank=True, max_length=150, null=True, verbose_name='物流信息')),
|
||||
('sort', models.PositiveSmallIntegerField(default=1, help_text='显示顺序', verbose_name='排序')),
|
||||
('status', models.BooleanField(default=True, help_text='商品状态', verbose_name='商品状态')),
|
||||
('is_calc_award', models.BooleanField(default=False, verbose_name='是否计算(拉新、新人扶持)奖励')),
|
||||
('order_num', models.IntegerField(default=0, verbose_name='近30天商品总销售量')),
|
||||
('view_num', models.IntegerField(default=0, verbose_name='近30天商品总浏览量')),
|
||||
('kol_num', models.IntegerField(default=0, verbose_name='近30天推广总达人数')),
|
||||
('source', models.IntegerField(choices=[(0, '精选联盟'), (1, '团长活动')], default=0, verbose_name='商品接口来源')),
|
||||
('share_nums', models.IntegerField(default=0, verbose_name='前端总分享次数')),
|
||||
('sample_stock', models.IntegerField(default=0, verbose_name='申请领样库存')),
|
||||
('limit_d30_sales', models.IntegerField(default=0, verbose_name='领样要求:近30天橱窗销量')),
|
||||
('limit_fans_nums', models.IntegerField(default=0, verbose_name='领样要求:粉丝数')),
|
||||
('shop_score', models.FloatField(blank=True, default=0, null=True, verbose_name='店铺分')),
|
||||
('is_delete', models.BooleanField(default=False, help_text='是否逻辑删除', verbose_name='是否逻辑删除')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '商品管理',
|
||||
'verbose_name_plural': '商品管理',
|
||||
'db_table': 'dy_product_manage',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DyShopManage',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('update_datetime', models.DateTimeField(auto_now=True, null=True, verbose_name='更新时间')),
|
||||
('create_datetime', models.DateTimeField(auto_now_add=True, null=True, verbose_name='创建时间')),
|
||||
('shop_id', models.CharField(blank=True, db_index=True, max_length=40, null=True, unique=True, verbose_name='店铺ID')),
|
||||
('shop_name', models.CharField(blank=True, max_length=50, null=True, verbose_name='店铺名称')),
|
||||
('shop_avatar_url', models.CharField(blank=True, max_length=255, null=True, verbose_name='商家头像')),
|
||||
('shop_total_score', models.CharField(blank=True, max_length=255, null=True, verbose_name='商家评分(4种类型分数)')),
|
||||
('shop_score', models.FloatField(blank=True, default=0, null=True, verbose_name='商家体验分')),
|
||||
('shop_score_level', models.PositiveSmallIntegerField(default=0, verbose_name='商家体验分等级')),
|
||||
('sort', models.PositiveSmallIntegerField(default=1, verbose_name='排序')),
|
||||
('status', models.BooleanField(default=True, help_text='状态', verbose_name='状态')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '店铺管理',
|
||||
'verbose_name_plural': '店铺管理',
|
||||
'db_table': 'dy_shop_manage',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DYSystemAccount',
|
||||
fields=[
|
||||
('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')),
|
||||
('modifier', models.CharField(blank=True, help_text='修改人', max_length=100, null=True, verbose_name='修改人')),
|
||||
('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=100, null=True, verbose_name='数据归属部门')),
|
||||
('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')),
|
||||
('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),
|
||||
('avatar', models.CharField(blank=True, max_length=255, null=True, verbose_name='头像')),
|
||||
('nickname', models.CharField(blank=True, default='', help_text='用户昵称', max_length=100, null=True, verbose_name='用户昵称')),
|
||||
('open_id', models.CharField(blank=True, db_index=True, max_length=100, null=True, verbose_name='抖音open_id')),
|
||||
('fans_num', models.IntegerField(default=0, verbose_name='粉丝个数')),
|
||||
('buyin_id', models.CharField(blank=True, db_index=True, max_length=50, null=True, verbose_name='百应ID')),
|
||||
('pid', models.CharField(blank=True, db_index=True, max_length=255, null=True, verbose_name='抖客PID')),
|
||||
('status', models.BooleanField(default=True, verbose_name='启用状态')),
|
||||
('access_token', models.CharField(blank=True, max_length=255, null=True, verbose_name='授权access_token')),
|
||||
('access_token_expire', models.DateTimeField(blank=True, null=True, verbose_name='access_token过期时间')),
|
||||
('refresh_token', models.CharField(blank=True, max_length=255, null=True, verbose_name='授权refresh_token')),
|
||||
('refresh_token_expire', models.DateTimeField(blank=True, null=True, verbose_name='refresh_token过期时间')),
|
||||
('identity', models.IntegerField(choices=[(0, '-'), (1, '抖客'), (2, '团长'), (3, '达人')], default=0, verbose_name='用户类型')),
|
||||
('is_delete', models.BooleanField(default=False, verbose_name='是否逻辑删除')),
|
||||
('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '平台使用的达人/抖客/团长账号授权信息',
|
||||
'verbose_name_plural': '平台使用的达人/抖客/团长账号授权信息',
|
||||
'db_table': 'dy_system_account',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DyProductSaleData',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('update_datetime', models.DateTimeField(auto_now=True, null=True, verbose_name='更新时间')),
|
||||
('create_datetime', models.DateTimeField(auto_now_add=True, null=True, verbose_name='创建时间')),
|
||||
('product_id', models.CharField(db_index=True, max_length=40, verbose_name='商品ID')),
|
||||
('date', models.DateField(blank=True, null=True, verbose_name='统计日期')),
|
||||
('order_num', models.IntegerField(default=0, verbose_name='当日商品销售量明细')),
|
||||
('view_num', models.IntegerField(default=0, verbose_name='当日商品浏览量明细')),
|
||||
('kol_num', models.IntegerField(default=0, verbose_name='当日推广达人数明细')),
|
||||
('inner_product_id', models.ForeignKey(db_constraint=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='dtproductsd', to='lyTiktokUnion.dyproductmanage', verbose_name='内部关联商品ID')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '商品月售信息',
|
||||
'verbose_name_plural': '商品月售信息',
|
||||
'db_table': 'dy_product_saledata',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='dyproductmanage',
|
||||
name='dyshop',
|
||||
field=models.ForeignKey(db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='lyTiktokUnion.dyshopmanage', verbose_name='关联商家'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DyColonelActivityManage',
|
||||
fields=[
|
||||
('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')),
|
||||
('modifier', models.CharField(blank=True, help_text='修改人', max_length=100, null=True, verbose_name='修改人')),
|
||||
('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=100, null=True, verbose_name='数据归属部门')),
|
||||
('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')),
|
||||
('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),
|
||||
('activity_id', models.CharField(db_index=True, default='', max_length=50, verbose_name='活动ID')),
|
||||
('activity_name', models.CharField(blank=True, max_length=100, null=True, verbose_name='活动名称')),
|
||||
('activity_start_time', models.CharField(blank=True, max_length=100, null=True, verbose_name='任务开始时间')),
|
||||
('activity_end_time', models.CharField(blank=True, max_length=100, null=True, verbose_name='任务结束时间')),
|
||||
('status', models.IntegerField(default=0, help_text='状态', verbose_name='状态')),
|
||||
('colonel_buyin_id', models.CharField(default='', max_length=50, verbose_name='团长百应ID')),
|
||||
('institution_id', models.CharField(default='', max_length=50, verbose_name='机构ID')),
|
||||
('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '团长活动管理',
|
||||
'verbose_name_plural': '团长活动管理',
|
||||
'db_table': 'dy_colonel_activity',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DYBalanceRecord',
|
||||
fields=[
|
||||
('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')),
|
||||
('modifier', models.CharField(blank=True, help_text='修改人', max_length=100, null=True, verbose_name='修改人')),
|
||||
('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=100, null=True, verbose_name='数据归属部门')),
|
||||
('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')),
|
||||
('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),
|
||||
('order_no', models.CharField(max_length=64, unique=True, verbose_name='订单编号(内部)')),
|
||||
('balance', models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='剩余余额')),
|
||||
('money', models.DecimalField(decimal_places=2, default=0, max_digits=10, verbose_name='金额')),
|
||||
('trade_no', models.CharField(blank=True, max_length=200, null=True, verbose_name='交易订单号(外部)')),
|
||||
('type', models.IntegerField(choices=[(1, '提现'), (2, '充值')], default=1, help_text='类型', verbose_name='类型')),
|
||||
('paytype', models.IntegerField(choices=[(10, '微信'), (20, '支付宝'), (30, '银行卡')], default=10, help_text='提现方式', verbose_name='提现方式')),
|
||||
('audit_status', models.IntegerField(choices=[(10, '待审核'), (20, '已通过'), (30, '未通过')], default=10, verbose_name='审核状态')),
|
||||
('status', models.IntegerField(choices=[(10, '打款中'), (20, '已完成'), (30, '失败')], default=10, verbose_name='打款状态')),
|
||||
('audit_time', models.DateTimeField(blank=True, null=True, verbose_name='管理员审核时间')),
|
||||
('audit_remarks', models.CharField(blank=True, max_length=200, null=True, verbose_name='审核备注')),
|
||||
('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '用户余额记录',
|
||||
'verbose_name_plural': '用户余额记录',
|
||||
'db_table': 'dy_balance_recode',
|
||||
},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='dyproductmanage',
|
||||
unique_together={('product_id', 'source')},
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,80 @@
|
||||
# Generated by Django 4.1.3 on 2023-12-21 22:42
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('lyTiktokUnion', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='DyOrderManage',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('update_datetime', models.DateTimeField(auto_now=True, null=True, verbose_name='更新时间')),
|
||||
('create_datetime', models.DateTimeField(auto_now_add=True, null=True, verbose_name='创建时间')),
|
||||
('order_id', models.CharField(blank=True, max_length=100, null=True, unique=True, verbose_name='订单号')),
|
||||
('flow_point', models.CharField(blank=True, choices=[('PAY_SUCC', '支付完成'), ('REFUND', '退款'), ('SETTLE', '结算'), ('CONFIRM', '确认收货')], help_text='订单状态', max_length=20, null=True, verbose_name='订单状态')),
|
||||
('product_id', models.CharField(db_index=True, max_length=40, verbose_name='商品ID')),
|
||||
('product_name', models.CharField(blank=True, max_length=40, null=True, verbose_name='商品名称')),
|
||||
('product_img', models.CharField(blank=True, max_length=255, null=True, verbose_name='商品图片URL')),
|
||||
('app', models.CharField(blank=True, max_length=40, null=True, verbose_name='来源APP')),
|
||||
('author_account', models.CharField(blank=True, max_length=40, null=True, verbose_name='作者账号昵称')),
|
||||
('author_openid', models.CharField(blank=True, max_length=100, null=True, verbose_name='作者抖音open_id')),
|
||||
('author_short_id', models.CharField(blank=True, max_length=60, null=True, verbose_name='达人抖音号/火山号')),
|
||||
('shop_id', models.CharField(blank=True, max_length=60, null=True, verbose_name='商家ID')),
|
||||
('shop_name', models.CharField(blank=True, max_length=60, null=True, verbose_name='商家名称')),
|
||||
('pick_source_client_key', models.CharField(blank=True, max_length=80, null=True, verbose_name='选品App client_key')),
|
||||
('pick_extra', models.CharField(blank=True, max_length=60, null=True, verbose_name='选品来源自定义参数')),
|
||||
('pid', models.CharField(blank=True, max_length=160, null=True, verbose_name='分销PID')),
|
||||
('external_info', models.CharField(blank=True, max_length=100, null=True, verbose_name='分销外部参数')),
|
||||
('media_type_name', models.CharField(blank=True, max_length=160, null=True, verbose_name='分销类型')),
|
||||
('media_type', models.CharField(blank=True, choices=[('shop_list', '橱窗'), ('video', '视频'), ('live', '直播'), ('others', '其他')], help_text='带货体裁', max_length=20, null=True, verbose_name='带货体裁')),
|
||||
('item_num', models.IntegerField(default=1, help_text='商品数目', verbose_name='商品数目')),
|
||||
('total_pay_amount', models.IntegerField(default=0, help_text='订单支付金额', verbose_name='订单支付金额')),
|
||||
('commission_rate', models.IntegerField(default=0, help_text='达人佣金率', verbose_name='达人佣金率')),
|
||||
('update_time', models.DateTimeField(blank=True, null=True, verbose_name='更新时间[联盟侧订单更新时间]')),
|
||||
('pay_success_time', models.DateTimeField(blank=True, null=True, verbose_name='付款时间')),
|
||||
('settle_time', models.DateTimeField(blank=True, null=True, verbose_name='结算时间,结算前为空')),
|
||||
('refund_time', models.DateTimeField(blank=True, null=True, verbose_name='退款订单退款时间')),
|
||||
('pay_goods_amount', models.IntegerField(default=0, verbose_name='预估参与结算金额')),
|
||||
('settled_goods_amount', models.IntegerField(default=0, verbose_name='实际参与结算金额')),
|
||||
('estimated_commission', models.IntegerField(default=0, verbose_name='达人/渠道预估佣金收入')),
|
||||
('real_commission', models.IntegerField(default=0, verbose_name='达人实际佣金收入')),
|
||||
('order_type', models.IntegerField(choices=[(0, ''), (1, '橱窗订单'), (2, '分享赚订单'), (3, '0元购订单'), (4, '垫付领样')], default=0, help_text='订单类型', verbose_name='订单类型')),
|
||||
('from_user_id', models.CharField(blank=True, max_length=60, null=True, verbose_name='达人的openid')),
|
||||
('inner_user_id', models.CharField(blank=True, max_length=60, null=True, verbose_name='所属内部用户ID')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '抖音订单管理',
|
||||
'verbose_name_plural': '抖音订单管理',
|
||||
'db_table': 'dy_order',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DyProductCategoryManage',
|
||||
fields=[
|
||||
('id', models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id')),
|
||||
('modifier', models.CharField(blank=True, help_text='修改人', max_length=100, null=True, verbose_name='修改人')),
|
||||
('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=100, null=True, verbose_name='数据归属部门')),
|
||||
('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')),
|
||||
('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),
|
||||
('category_id', models.CharField(default='', max_length=20, verbose_name='分类ID')),
|
||||
('name', models.CharField(default='', max_length=20, verbose_name='分类名称')),
|
||||
('sort', models.PositiveSmallIntegerField(default=1, help_text='显示顺序', verbose_name='排序')),
|
||||
('status', models.BooleanField(default=True, help_text='状态', verbose_name='状态')),
|
||||
('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '商品分类(行业类目)',
|
||||
'verbose_name_plural': '商品分类(行业类目)',
|
||||
'db_table': 'dy_product_category',
|
||||
},
|
||||
),
|
||||
]
|
||||
0
backend/apps/lyTiktokUnion/migrations/__init__.py
Normal file
0
backend/apps/lyTiktokUnion/migrations/__init__.py
Normal file
300
backend/apps/lyTiktokUnion/models.py
Normal file
300
backend/apps/lyTiktokUnion/models.py
Normal file
@ -0,0 +1,300 @@
|
||||
from django.db import models
|
||||
from utils.models import CoreModel,BaseModel
|
||||
|
||||
# ================================================= #
|
||||
# ************** 平台使用的达人/抖客/团长账号授权信息 ************** #
|
||||
# ================================================= #
|
||||
|
||||
class DYSystemAccount(CoreModel):
|
||||
"""平台使用的达人/抖客/团长账号授权信息"""
|
||||
TYPE_CHOICES = (
|
||||
(0, "-"),
|
||||
(1, "抖客"),
|
||||
(2, "团长"),
|
||||
(3, "达人"),
|
||||
)
|
||||
avatar = models.CharField(max_length=255,verbose_name="头像", null=True, blank=True)
|
||||
nickname = models.CharField(max_length=100, help_text="用户昵称", verbose_name="用户昵称",default="", null=True, blank=True)
|
||||
open_id = models.CharField(max_length=100, db_index=True,null=True,blank=True,verbose_name='抖音open_id')
|
||||
fans_num = models.IntegerField(default=0,verbose_name="粉丝个数")
|
||||
buyin_id = models.CharField(max_length=50, db_index=True,null=True,blank=True,verbose_name='百应ID')
|
||||
pid = models.CharField(max_length=255, db_index=True, null=True, blank=True,verbose_name='抖客PID') # 抖客pid相当于抖客的推广位,可以有多个,达人也有自己的pid
|
||||
status = models.BooleanField(default=True, verbose_name="启用状态")
|
||||
access_token = models.CharField(max_length=255, null=True, blank=True, verbose_name='授权access_token')
|
||||
access_token_expire = models.DateTimeField(verbose_name='access_token过期时间', null=True, blank=True)
|
||||
refresh_token = models.CharField(max_length=255, null=True, blank=True,verbose_name='授权refresh_token')
|
||||
refresh_token_expire = models.DateTimeField(verbose_name='refresh_token过期时间', null=True, blank=True)#refresh_expires_in
|
||||
identity = models.IntegerField(choices=TYPE_CHOICES,verbose_name='用户类型',default=0)#用户类型
|
||||
is_delete = models.BooleanField(default=False, verbose_name="是否逻辑删除")
|
||||
|
||||
class Meta:
|
||||
db_table = 'dy_system_account'
|
||||
verbose_name = '平台使用的达人/抖客/团长账号授权信息'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
# ================================================= #
|
||||
# ************** 团长活动(招商)管理(一般为自己团长账号活动) model ************** #
|
||||
# ================================================= #
|
||||
|
||||
class DyColonelActivityManage(CoreModel):
|
||||
"""团长活动管理(用于团长活动商品的拉取)"""
|
||||
activity_id = models.CharField(max_length=50, verbose_name='活动ID',db_index=True,default="")
|
||||
activity_name = models.CharField(max_length=100, verbose_name='活动名称',null=True,blank=True)
|
||||
activity_start_time = models.CharField(max_length=100, verbose_name='任务开始时间',null=True,blank=True)
|
||||
activity_end_time = models.CharField(max_length=100, verbose_name='任务结束时间',null=True,blank=True)
|
||||
status = models.IntegerField(default=0, verbose_name="状态", help_text="状态")#1:未上线;2:报名未开始;3:报名中;4:推广未开始;5:推广中;7:报名结束
|
||||
colonel_buyin_id = models.CharField(max_length=50, verbose_name='团长百应ID',default="")
|
||||
institution_id = models.CharField(max_length=50, verbose_name='机构ID',default="")
|
||||
|
||||
class Meta:
|
||||
db_table = 'dy_colonel_activity'
|
||||
verbose_name = '团长活动管理'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return self.activity_name
|
||||
|
||||
# ================================================= #
|
||||
# ************** 商品分类 model ************** #
|
||||
# ================================================= #
|
||||
|
||||
class DyProductCategoryManage(CoreModel):
|
||||
"""商品分类(行业类目)"""
|
||||
category_id = models.CharField(max_length=20, verbose_name='分类ID',default="")
|
||||
name = models.CharField(max_length=20, verbose_name='分类名称',default="")
|
||||
sort = models.PositiveSmallIntegerField(default=1, verbose_name="排序", help_text="显示顺序")
|
||||
status = models.BooleanField(default=True, verbose_name="状态", help_text="状态")
|
||||
|
||||
class Meta:
|
||||
db_table = 'dy_product_category'
|
||||
verbose_name = '商品分类(行业类目)'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
# ================================================= #
|
||||
# ************** 商品管理 model ************** #
|
||||
# ================================================= #
|
||||
|
||||
class DyShopManage(BaseModel):
|
||||
"""店铺管理"""
|
||||
shop_id = models.CharField(max_length=40, verbose_name='店铺ID',db_index=True,unique=True,null=True,blank=True)
|
||||
shop_name = models.CharField(max_length=50, verbose_name='店铺名称',null=True,blank=True)
|
||||
shop_avatar_url = models.CharField(max_length=255, verbose_name='商家头像',null=True,blank=True)
|
||||
shop_total_score = models.CharField(max_length=255, verbose_name='商家评分(4种类型分数)',null=True,blank=True)#对象'shop_total_score': {'logistics_score': {'level': 1,'score': '99','text': '物流体验分'},'product_score': {....}
|
||||
shop_score = models.FloatField(verbose_name='商家体验分',default=0,null=True,blank=True)
|
||||
shop_score_level = models.PositiveSmallIntegerField(default=0, verbose_name="商家体验分等级") #商家体验分等级。1: 高;2: 中;3:低; 4: 差
|
||||
sort = models.PositiveSmallIntegerField(default=1, verbose_name="排序")
|
||||
status = models.BooleanField(default=True, verbose_name="状态", help_text="状态")
|
||||
|
||||
class Meta:
|
||||
db_table = 'dy_shop_manage'
|
||||
verbose_name = '店铺管理'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return self.shop_name
|
||||
|
||||
class DyProductManage(BaseModel):
|
||||
"""商品管理"""
|
||||
SOURCE_TYPE = (
|
||||
(0, "精选联盟"),
|
||||
(1, "团长活动"),
|
||||
)
|
||||
product_id = models.CharField(max_length=40, verbose_name='商品ID',db_index=True)
|
||||
title = models.CharField(max_length=100, verbose_name='商品标题',db_index=True,null=True,blank=True)
|
||||
price = models.IntegerField(verbose_name='商品价格',default=0)#单位分
|
||||
sales = models.IntegerField(verbose_name='历史总销量',default=0)
|
||||
begin_time = models.DateField(verbose_name='活动开始日期',null=True,blank=True)
|
||||
end_time = models.DateField(verbose_name='活动结束日期',null=True,blank=True)
|
||||
coupon = models.IntegerField(verbose_name='优惠券个数',default=0)
|
||||
coupon_price = models.IntegerField(verbose_name='劵后价',default=0)#单位分
|
||||
cover = models.CharField(max_length=255, verbose_name='商品封面',null=True,blank=True)
|
||||
category_id = models.CharField(max_length=20, verbose_name='行业分类ID',null=True,blank=True)
|
||||
category_name = models.CharField(max_length=30, verbose_name='行业分类名称',null=True,blank=True)
|
||||
cos_ratio = models.FloatField(verbose_name='普通佣金比例',default=0)#百分比小数,只存除%比的数字部分
|
||||
cos_fee = models.IntegerField(verbose_name='普通佣金金额',default=0)#单位分
|
||||
comment_score = models.FloatField(verbose_name='商品评分(5分制,保留一位小数)',default=0)
|
||||
special_cos_ratio = models.IntegerField(verbose_name='达人/团长/活动佣金比例',default=0)#百分比,只存除%比的数字部分
|
||||
special_cos_fee = models.IntegerField(verbose_name='达人/团长/活动佣金金额',default=0)#单位分
|
||||
activity_id = models.CharField(max_length=50, verbose_name='活动ID',db_index=True,null=True,blank=True)#团长活动ID、其他活动ID
|
||||
imgs = models.TextField(verbose_name='轮播图',null=True,blank=True)
|
||||
detail_url = models.CharField(max_length=1000,verbose_name='商品链接',null=True,blank=True)
|
||||
detail_brief = models.TextField(verbose_name='详情装修信息(商品详情图)',null=True,blank=True)
|
||||
sharable = models.BooleanField(default=True, verbose_name="是否可分销")
|
||||
is_assured = models.BooleanField(default=False, verbose_name="是否提供安心购服务")
|
||||
has_sxt = models.BooleanField(default=False, verbose_name="是否具有短视频随心推资质")
|
||||
has_subsidy_tag = models.BooleanField(default=False, verbose_name="是否是超值购商品(百亿补贴)")
|
||||
has_supermarket_tag = models.BooleanField(default=False, verbose_name="是否抖音超市(次日达)商品")
|
||||
has_douin_goods_tag = models.BooleanField(default=False, verbose_name="是否有【抖in好物】标签")
|
||||
has_shop_brand_tag = models.BooleanField(default=False, verbose_name="是否有品牌旗舰店标签([品牌]黑标)")
|
||||
in_stock = models.BooleanField(default=True, verbose_name="是否有库存")
|
||||
shop_id = models.CharField(max_length=40, verbose_name='店铺ID',db_index=True,null=True,blank=True)
|
||||
logistics_info = models.CharField(max_length=150, verbose_name='物流信息',null=True,blank=True)
|
||||
sort = models.PositiveSmallIntegerField(default=1, verbose_name="排序", help_text="显示顺序")#自定义
|
||||
status = models.BooleanField(default=True, verbose_name="商品状态", help_text="商品状态")#自定义
|
||||
is_calc_award = models.BooleanField(default=False, verbose_name="是否计算(拉新、新人扶持)奖励")#自定义
|
||||
order_num = models.IntegerField(verbose_name='近30天商品总销售量', default=0)
|
||||
view_num = models.IntegerField(verbose_name='近30天商品总浏览量', default=0)
|
||||
kol_num = models.IntegerField(verbose_name='近30天推广总达人数', default=0)
|
||||
source = models.IntegerField(choices=SOURCE_TYPE,verbose_name='商品接口来源',default=0)#自定义
|
||||
share_nums = models.IntegerField(verbose_name='前端总分享次数',default=0)#自定义
|
||||
sample_stock = models.IntegerField(verbose_name='申请领样库存',default=0)
|
||||
limit_d30_sales = models.IntegerField(verbose_name='领样要求:近30天橱窗销量', default=0)
|
||||
limit_fans_nums = models.IntegerField(verbose_name='领样要求:粉丝数', default=0)
|
||||
dyshop = models.ForeignKey(DyShopManage,verbose_name='关联商家',db_constraint=False,on_delete=models.SET_NULL,null=True)
|
||||
shop_score = models.FloatField(verbose_name='店铺分', default=0, null=True, blank=True)
|
||||
is_delete = models.BooleanField(default=False, verbose_name="是否逻辑删除", help_text="是否逻辑删除")
|
||||
|
||||
class Meta:
|
||||
db_table = 'dy_product_manage'
|
||||
verbose_name = '商品管理'
|
||||
verbose_name_plural = verbose_name
|
||||
unique_together = ('product_id','source')
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
class DyProductSaleData(BaseModel):
|
||||
"""商品月售信息"""
|
||||
inner_product_id = models.ForeignKey(DyProductManage,related_name='dtproductsd', verbose_name='内部关联商品ID',db_constraint=False, on_delete=models.CASCADE, null=True)
|
||||
product_id = models.CharField(max_length=40, verbose_name='商品ID', db_index=True)
|
||||
date = models.DateField(verbose_name='统计日期',null=True,blank=True)
|
||||
order_num = models.IntegerField(verbose_name='当日商品销售量明细',default=0)
|
||||
view_num = models.IntegerField(verbose_name='当日商品浏览量明细', default=0)
|
||||
kol_num = models.IntegerField(verbose_name='当日推广达人数明细', default=0)
|
||||
|
||||
class Meta:
|
||||
db_table = 'dy_product_saledata'
|
||||
verbose_name = '商品月售信息'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
# =====================================================================================#
|
||||
# ************** 赏金任务(抖音团长拥有赏金任务发布能力) model ************** #
|
||||
# =====================================================================================#
|
||||
|
||||
class DyAwardTaskManage(BaseModel):
|
||||
"""赏金任务"""
|
||||
TASK_STATUS_CHOICE = (
|
||||
(1, "未发布"),
|
||||
(2, "审核中"),
|
||||
(3, "审核未通过"),
|
||||
(4, "未开始"),
|
||||
(5, "进行中"),
|
||||
(6, "已结束"),
|
||||
)
|
||||
task_id = models.CharField(max_length=40, verbose_name='任务ID',db_index=True)
|
||||
task_name = models.CharField(max_length=100, verbose_name='任务名称',db_index=True,null=True,blank=True)
|
||||
conver = models.CharField(max_length=255, verbose_name='封面图',null=True,blank=True)
|
||||
award_description = models.CharField(max_length=255, verbose_name='奖励说明',null=True,blank=True)
|
||||
task_other_requirement = models.CharField(max_length=255, verbose_name='任务其他要求',null=True,blank=True)
|
||||
apply_cnt = models.IntegerField(verbose_name='已领取人数',default=0)
|
||||
promote_cnt = models.IntegerField(verbose_name='已推广人数',default=0)
|
||||
gmv = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='支付GMV',default=0)#单位元
|
||||
order_cnt = models.IntegerField(verbose_name='总订单量',default=0)
|
||||
can_apply = models.BooleanField(default=False, verbose_name="是否可报名", help_text="是否可报名")
|
||||
task_start_time = models.CharField(max_length=100, verbose_name='任务开始时间',null=True,blank=True)
|
||||
task_end_time = models.CharField(max_length=100, verbose_name='任务结束时间',null=True,blank=True)
|
||||
task_status = models.IntegerField(choices=TASK_STATUS_CHOICE,verbose_name='任务状态',default=0)#1:未发布,2:审核中,3:审核未通过,4:未开始,5:进行中,6:已结束
|
||||
product_id = models.CharField(max_length=40, verbose_name='商品ID',db_index=True,default=None)#默认一个任务只支持1个产品,如果需要支持多个,用逗号分割,并延长字段长度
|
||||
sort = models.PositiveSmallIntegerField(default=1, verbose_name="排序")
|
||||
status = models.BooleanField(default=True, verbose_name="状态", help_text="状态")
|
||||
|
||||
class Meta:
|
||||
db_table = 'dy_awardtask'
|
||||
verbose_name = '赏金任务'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
def __str__(self):
|
||||
return self.task_name
|
||||
|
||||
# ================================================= #
|
||||
# ************** 用户提现记录 model************** #
|
||||
# ================================================= #
|
||||
|
||||
class DYBalanceRecord(CoreModel):
|
||||
"""用户余额记录(带审核功能)"""
|
||||
PAYTYPE_CHOICES = (
|
||||
(10, "微信"),
|
||||
(20, "支付宝"),
|
||||
(30, "银行卡"),
|
||||
)
|
||||
TYPE_CHOICES = (
|
||||
(1, "提现"),
|
||||
(2, "充值"),
|
||||
)
|
||||
order_no = models.CharField(max_length=64, unique=True, verbose_name="订单编号(内部)")
|
||||
balance = models.DecimalField(max_digits=10, decimal_places=2, default=0, verbose_name='剩余余额')
|
||||
money = models.DecimalField(max_digits=10, decimal_places=2, default=0, verbose_name='金额')
|
||||
trade_no = models.CharField(max_length=200, verbose_name="交易订单号(外部)", null=True, blank=True)
|
||||
type = models.IntegerField(choices=TYPE_CHOICES, verbose_name="类型",default=1, help_text="类型")
|
||||
paytype = models.IntegerField(choices=PAYTYPE_CHOICES, verbose_name="提现方式",default=10, help_text="提现方式")
|
||||
audit_status = models.IntegerField(choices=((10, u'待审核'), (20, u'已通过'), (30, u'未通过')), verbose_name="审核状态",default=10)
|
||||
status = models.IntegerField(choices=((10, u'打款中'), (20, u'已完成'), (30, u'失败')), verbose_name="打款状态",default=10)
|
||||
audit_time = models.DateTimeField(verbose_name='管理员审核时间', null=True, blank=True)
|
||||
audit_remarks = models.CharField(max_length=200, verbose_name="审核备注", null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'dy_balance_recode'
|
||||
verbose_name = '用户余额记录'
|
||||
verbose_name_plural = verbose_name
|
||||
|
||||
|
||||
# ================================================= #
|
||||
# ************** 抖音订单管理 model ************** #
|
||||
# ================================================= #
|
||||
|
||||
class DyOrderManage(BaseModel):
|
||||
"""抖音订单管理"""
|
||||
FLOW_POINT_CHOICES = (
|
||||
('PAY_SUCC', "支付完成"),
|
||||
('REFUND', "退款"),
|
||||
('SETTLE', "结算"),
|
||||
('CONFIRM', "确认收货"),
|
||||
)
|
||||
MEDIA_TYPE_CHOICES = (
|
||||
('shop_list', "橱窗"),
|
||||
('video', "视频"),
|
||||
('live', "直播"),
|
||||
('others', "其他"),#其他(如图文、微头条、问答、西瓜长视频等)
|
||||
)
|
||||
ORDER_TYPE_CHOICES = (
|
||||
(0, ""),
|
||||
(1, "橱窗订单"),
|
||||
(2, "分享赚订单"),
|
||||
(3, "0元购订单"),
|
||||
(4, "垫付领样"),
|
||||
)
|
||||
order_id = models.CharField(max_length=100, verbose_name='订单号',null=True,blank=True,unique=True)
|
||||
flow_point = models.CharField(choices=FLOW_POINT_CHOICES, verbose_name="订单状态", help_text="订单状态",null=True,blank=True,max_length=20)
|
||||
product_id = models.CharField(max_length=40, verbose_name='商品ID',db_index=True)
|
||||
product_name = models.CharField(max_length=40, verbose_name='商品名称',null=True,blank=True)
|
||||
product_img = models.CharField(max_length=255, verbose_name='商品图片URL',null=True,blank=True)
|
||||
app = models.CharField(max_length=40, verbose_name='来源APP', null=True, blank=True)
|
||||
author_account = models.CharField(max_length=40, verbose_name="作者账号昵称", null=True, blank=True)
|
||||
author_openid = models.CharField(max_length=100, verbose_name="作者抖音open_id", null=True, blank=True)
|
||||
author_short_id = models.CharField(max_length=60, verbose_name="达人抖音号/火山号", null=True, blank=True)
|
||||
shop_id = models.CharField(max_length=60, verbose_name="商家ID", null=True, blank=True)
|
||||
shop_name = models.CharField(max_length=60, verbose_name="商家名称", null=True, blank=True)
|
||||
pick_source_client_key = models.CharField(max_length=80, verbose_name="选品App client_key", null=True, blank=True)
|
||||
pick_extra = models.CharField(max_length=60, verbose_name="选品来源自定义参数", null=True, blank=True)
|
||||
pid = models.CharField(max_length=160, verbose_name="分销PID", null=True, blank=True)#分销信息
|
||||
external_info = models.CharField(max_length=100, verbose_name="分销外部参数", null=True, blank=True) # 分销信息
|
||||
media_type_name = models.CharField(max_length=160, verbose_name="分销类型", null=True, blank=True) # 分销信息-分销类型:Live-直播间,ProductDetail-商品详情,Activity-活动(百亿补贴/秒杀)Mix-H5自建活动页
|
||||
media_type = models.CharField(choices=MEDIA_TYPE_CHOICES, verbose_name="带货体裁", help_text="带货体裁",null=True,blank=True,max_length=20)
|
||||
item_num = models.IntegerField(verbose_name="商品数目",default=1, help_text="商品数目")
|
||||
total_pay_amount = models.IntegerField(verbose_name="订单支付金额",default=0, help_text="订单支付金额") #单位分(用户实付)
|
||||
commission_rate = models.IntegerField(verbose_name="达人佣金率",default=0, help_text="达人佣金率") #此处保存为真实数据x1万之后,如真实是0.35,这里是3500
|
||||
update_time = models.DateTimeField(verbose_name="更新时间[联盟侧订单更新时间]",null=True,blank=True)
|
||||
pay_success_time = models.DateTimeField(verbose_name="付款时间", null=True, blank=True)
|
||||
settle_time = models.DateTimeField(verbose_name="结算时间,结算前为空", null=True, blank=True)
|
||||
refund_time = models.DateTimeField(verbose_name="退款订单退款时间", null=True, blank=True)
|
||||
pay_goods_amount = models.IntegerField(verbose_name="预估参与结算金额",default=0) #单位分
|
||||
settled_goods_amount = models.IntegerField(verbose_name="实际参与结算金额", default=0) #单位分
|
||||
estimated_commission = models.IntegerField(verbose_name="达人/渠道预估佣金收入", default=0) # 单位分
|
||||
real_commission = models.IntegerField(verbose_name="达人实际佣金收入", default=0) # 单位分
|
||||
order_type = models.IntegerField(choices=ORDER_TYPE_CHOICES, verbose_name="订单类型", help_text="订单类型",default=0)
|
||||
from_user_id = models.CharField(max_length=60, verbose_name="达人的openid", null=True, blank=True)#同一用户在不同的APPID中openId不相同
|
||||
inner_user_id = models.CharField(max_length=60, verbose_name="所属内部用户ID", null=True, blank=True)
|
||||
class Meta:
|
||||
db_table = 'dy_order'
|
||||
verbose_name = '抖音订单管理'
|
||||
verbose_name_plural = verbose_name
|
||||
583
backend/apps/lyTiktokUnion/tools/douyin.py
Normal file
583
backend/apps/lyTiktokUnion/tools/douyin.py
Normal file
@ -0,0 +1,583 @@
|
||||
#!/bin/python
|
||||
#coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | django-vue-lyadmin
|
||||
# +-------------------------------------------------------------------
|
||||
# | Author: lybbn
|
||||
# +-------------------------------------------------------------------
|
||||
# | QQ: 1042594286
|
||||
# +-------------------------------------------------------------------
|
||||
# | Date: 2023-07-20
|
||||
# +-------------------------------------------------------------------
|
||||
# 抖音商品同步
|
||||
# ------------------------------
|
||||
from utils.douyin.douyin_utils import douyin
|
||||
from utils.douyin.doudian_utils import doudian
|
||||
from utils.douyin.douyinUnion_utils import douyinUnion
|
||||
from utils.common import formatdatetime_convert,formatdatetime
|
||||
from apps.lyTiktokUnion.models import DYSystemAccount,DyProductSaleData,DyAwardTaskManage,DyColonelActivityManage,DyProductManage,DyShopManage,DyProductCategoryManage
|
||||
import datetime
|
||||
from config import DOUYIN_OPENAPI_CLIENT_KEY,DOUYIN_OPENAPI_CLIENT_SECRET,DOMAIN_HOST,DOUDIAN_TUANZHUANG_ZIYAN_APPKEY,DOUDIAN_TUANZHUANG_ZIYAN_APPSECRET,DOUDIAN_DKLIVE_ZIYAN_APPKEY,DOUDIAN_DKLIVE_ZIYAN_APPSECRET,DOUDIAN_DKFX_TOOL_APPKEY,DOUDIAN_DKFX_TOOL_APPSECRET
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
newdoudian_tz_yz = doudian(app_key=DOUDIAN_TUANZHUANG_ZIYAN_APPKEY,app_secret=DOUDIAN_TUANZHUANG_ZIYAN_APPSECRET,app_type="tool",code="test")
|
||||
newdouyin = douyin(client_key=DOUYIN_OPENAPI_CLIENT_KEY,client_secret=DOUYIN_OPENAPI_CLIENT_SECRET)
|
||||
newdouyinUnion = douyinUnion(client_key=DOUYIN_OPENAPI_CLIENT_KEY,client_secret=DOUYIN_OPENAPI_CLIENT_SECRET)
|
||||
newdoudian_dklive_yz = doudian(app_key=DOUDIAN_DKLIVE_ZIYAN_APPKEY,app_secret=DOUDIAN_DKLIVE_ZIYAN_APPSECRET,app_type="tool",code="test")
|
||||
newdoudian_dkfx_gj = doudian(app_key=DOUDIAN_DKFX_TOOL_APPKEY,app_secret=DOUDIAN_DKFX_TOOL_APPSECRET,app_type="tool",code="test")
|
||||
|
||||
def lyFloat(string):
|
||||
try:
|
||||
if string:
|
||||
return float(string)
|
||||
return 0
|
||||
except:
|
||||
return 0
|
||||
|
||||
def get_page_count(total, per_page):
|
||||
"""
|
||||
计算分页总数
|
||||
:param total: 记录总数
|
||||
:param per_page: 每页记录数
|
||||
:return: 分页总数
|
||||
"""
|
||||
page_count = total // per_page
|
||||
if total % per_page != 0:
|
||||
page_count += 1
|
||||
return page_count
|
||||
|
||||
def get_product_detail_brief(data):
|
||||
"""
|
||||
处理商品详情中的商品装修图,改为数组
|
||||
"""
|
||||
new_data = []
|
||||
if not data:
|
||||
return new_data
|
||||
for i in data:
|
||||
new_data.append(i.get('image').get('pic_url'))
|
||||
return new_data
|
||||
|
||||
# 检查达人账号授权是否过期
|
||||
def checkDouyinAccountExpire(instance):
|
||||
"""
|
||||
instance 为用户抖音达人授权数据对象
|
||||
return : 返回用户access_token和是否过期,为True表示已过期,前面access_token内容为报错消息内容
|
||||
"""
|
||||
now = datetime.datetime.now()
|
||||
if instance.access_token_expire >= now:
|
||||
return instance.access_token,False
|
||||
if instance.refresh_token_expire >=now:
|
||||
res = newdouyin.refresh_access_token(instance.refresh_token)
|
||||
if not res:
|
||||
return "刷新token错误",True
|
||||
if "error_code" in res["data"] and not res["data"]["error_code"] == 0:
|
||||
return "获取刷新access_token失败,错误码:%s,描述:%s"%(res["data"]["error_code"],res["data"]["description"]),True
|
||||
res = res['data']
|
||||
access_token = res.get("access_token")
|
||||
instance.access_token = access_token
|
||||
expires_in = res.get("expires_in")
|
||||
access_token_expire = now + datetime.timedelta(seconds=int(expires_in))
|
||||
instance.access_token_expire = access_token_expire
|
||||
instance.save()
|
||||
return access_token,False
|
||||
return "该抖音账号已过期,请重新授权",True
|
||||
|
||||
#获取精选联盟商品分类
|
||||
#https://buyin.jinritemai.com/dashboard/service-provider/doc-center?docId=637
|
||||
def getAllianceGoodsCategory(parent_id=0):
|
||||
path = '/alliance/materialsProductCategory'
|
||||
params = {"parent_id":parent_id}
|
||||
result = newdoudian_tz_yz.request(path=path, params=params)
|
||||
return result
|
||||
|
||||
#精选联盟商品同步(未完成)
|
||||
def allianceGoodsSync():
|
||||
"""
|
||||
return:False失败,True成功
|
||||
"""
|
||||
instance = DYSystemAccount.objects.filter(is_delete=False).first()
|
||||
msgData,isexpire = checkDouyinAccountExpire(instance)
|
||||
if isexpire:
|
||||
logger.error("同步精选联盟商品-请求商品列表,检查token已过期,内容:%s"%msgData)
|
||||
return False
|
||||
params = {"search_type":0,"sort_type":0,"page":1,"page_size":20}
|
||||
result = newdouyinUnion.product_list_search(access_token=msgData,open_id=instance.open_id,params=params)
|
||||
return True
|
||||
|
||||
#获取精选联盟商品分类(行业类目)
|
||||
#https://buyin.jinritemai.com/dashboard/service-provider/doc-center?docId=1882
|
||||
def getActivityProductCategoryList():
|
||||
path = '/alliance/activityProductCategoryList'
|
||||
params = {}
|
||||
result = newdoudian_tz_yz.request(path=path, params=params)
|
||||
return result
|
||||
|
||||
# 同步商品行业类目
|
||||
def activityProductCategoryListSync():
|
||||
"""
|
||||
return:False失败,True成功
|
||||
"""
|
||||
result = getActivityProductCategoryList()
|
||||
if result and result.get("code") == 10000:
|
||||
data = result.get("data").get("category_list")
|
||||
for i in data:
|
||||
DyProductCategoryManage.objects.get_or_create(category_id=i.get("category_id"),defaults={'name':i.get("name")})
|
||||
return True
|
||||
return False
|
||||
#获取团长活动列表
|
||||
#https://buyin.jinritemai.com/dashboard/service-provider/doc-center?docId=1330
|
||||
def getAllianceColonelActivityList(status=0,search_type=0,sort_type=0,page=1,page_size=20):
|
||||
"""
|
||||
status : 活动状态。0:任意状态; 1:未上线; 2:报名未开始; 3:报名中; 4:推广未开始; 5:推广中; 7:报名结束
|
||||
search_type: 排序选项,0:创建时间; 1:活动报名开始时间排序;2:活动报名结束时间排序
|
||||
"""
|
||||
path = '/alliance/instituteColonelActivityList'
|
||||
params ={"status":status,"search_type":search_type,"sort_type":sort_type,"page":page,"page_size":page_size}
|
||||
result = newdoudian_tz_yz.request(path=path, params=params)
|
||||
return result
|
||||
|
||||
# 同步团长活动列表
|
||||
def allianceColonelActivityListSync(status=0,search_type=0,sort_type=0,page=1,page_size=20):
|
||||
"""
|
||||
return:False失败,True成功
|
||||
"""
|
||||
result = getAllianceColonelActivityList(status=status,search_type=search_type,sort_type=sort_type,page=page,page_size=page_size)
|
||||
logger.info("同步团长活动列表,第%s页,返回内容:%s"%(page,result))
|
||||
if result and result.get("code") == 10000:
|
||||
total = int(result.get("data").get("total"))
|
||||
total_page = get_page_count(total,page_size)
|
||||
data = result.get("data").get("activity_list")
|
||||
colonel_buyin_id = result.get("data").get("colonel_buyin_id")
|
||||
institution_id = result.get("data").get("institution_id")
|
||||
for i in data:
|
||||
instance,created = DyColonelActivityManage.objects.get_or_create(activity_id=i.get("activity_id"),defaults={"activity_name":i.get("activity_name"),"activity_start_time":i.get("activity_start_time"),"activity_end_time":i.get("activity_end_time"),"status":i.get("status"),"colonel_buyin_id":colonel_buyin_id,"institution_id":institution_id})
|
||||
if not created:
|
||||
instance.activity_name = i.get("activity_name")
|
||||
instance.activity_start_time = i.get("activity_start_time")
|
||||
instance.activity_end_time = i.get("activity_end_time")
|
||||
instance.status = i.get("status")
|
||||
instance.colonel_buyin_id = colonel_buyin_id
|
||||
instance.institution_id = institution_id
|
||||
instance.update_datetime = datetime.datetime.now()
|
||||
instance.save()
|
||||
if page<total_page:
|
||||
#递归处理下一页数据
|
||||
next_page = page+1
|
||||
allianceColonelActivityListSync(status=status,search_type=search_type,sort_type=sort_type,page=next_page,page_size=page_size)
|
||||
return True
|
||||
elif result and result.get('code') == 40003 and result.get('err_no') == 30005:
|
||||
raise ValueError(result.get('团长授权已过期,请重新授权!!!'))
|
||||
return False
|
||||
|
||||
#获取团长活动商品列表
|
||||
#https://buyin.jinritemai.com/dashboard/service-provider/doc-center?docId=968
|
||||
def getColonelActivityProductList(activity_id="",search_type=4,sort_type=0,page=1,count=20,status=1):
|
||||
"""
|
||||
status : 活动商品状态,status = "all"查所有状态。0:待审核;1:推广中;2:申请未通过;3:合作已终止;6:合作已到期
|
||||
search_type: 召回结果排序条件,[推荐按更新时间倒序查]。0:报名时间;1:活动价格排序;2:活动佣金比例排序;4:更新时间排序
|
||||
"""
|
||||
path = '/alliance/colonelActivityProduct'
|
||||
params ={"activity_id":activity_id,"search_type":search_type,"sort_type":sort_type,"page":page,"count":count,"status":status}
|
||||
if status == "all":
|
||||
params.pop("status",None)
|
||||
result = newdoudian_tz_yz.request(path=path, params=params)
|
||||
return result
|
||||
|
||||
#批量获取商品详情
|
||||
#https://buyin.jinritemai.com/dashboard/service-provider/doc-center?docId=2867
|
||||
def getProductsDetail(product_ids=[],fields="detail_brief,base_info,promotion_info,share_info,comment_info,tags,rights_info,logistics_info,qualification_info,shop_info,coupon_info,month_sale_data"):
|
||||
"""
|
||||
product_ids:商品 ID 列表(最多20个)
|
||||
fields: 字段(需要返回的字段,多个用英文逗号隔开。不传只返回商品基础信息)
|
||||
"""
|
||||
path = '/buyin/productsDetail'
|
||||
params ={"product_ids":product_ids,"fields":fields}
|
||||
result = newdoudian_tz_yz.request(path=path, params=params)
|
||||
return result
|
||||
|
||||
#批量获取商品近30天推广详情
|
||||
#https://buyin.jinritemai.com/dashboard/service-provider/doc-center?docId=2867
|
||||
def getProductsMonthSale(product_ids=[],fields="base_info,month_sale_data"):
|
||||
"""
|
||||
product_ids:商品 ID 列表(最多20个)
|
||||
fields: 字段(需要返回的字段,多个用英文逗号隔开。不传只返回商品基础信息)
|
||||
"""
|
||||
path = '/buyin/productsDetail'
|
||||
params ={"product_ids":product_ids,"fields":fields}
|
||||
result = newdoudian_tz_yz.request(path=path, params=params)
|
||||
return result
|
||||
|
||||
#批量获取商品优惠券列表
|
||||
#https://buyin.jinritemai.com/dashboard/service-provider/doc-center?docId=2867
|
||||
def getProductsCouponInfo(product_ids=[],fields="base_info,coupon_info"):
|
||||
"""
|
||||
product_ids:商品 ID 列表(最多20个)
|
||||
fields: 字段(需要返回的字段,多个用英文逗号隔开。不传只返回商品基础信息)
|
||||
"""
|
||||
path = '/buyin/productsDetail'
|
||||
params ={"product_ids":product_ids,"fields":fields}
|
||||
result = newdoudian_tz_yz.request(path=path, params=params)
|
||||
return result
|
||||
# 团长活动商品列表递归同步到数据库
|
||||
def colonelActivityProductListSync(activity_id="",activity_start_time=None,activity_end_time=None,search_type=4,sort_type=0,page=1,count=20,status="all"):
|
||||
"""
|
||||
return:False失败,True成功
|
||||
"""
|
||||
result = getColonelActivityProductList(activity_id=activity_id,search_type=search_type,sort_type=sort_type,page=page,count=count,status=status)
|
||||
logger.info("团长活动商品列表递归同步到数据库,活动ID:%s,第%s页,返回内容:%s"%(activity_id,page,result))
|
||||
if result and result.get("code") == 10000:
|
||||
total = int(result.get("data").get("total",0))
|
||||
if total > 0:
|
||||
total_page = get_page_count(total,count)
|
||||
data = result.get("data").get("data")
|
||||
institution_id = result.get("data").get("institution_id")
|
||||
product_ids = []
|
||||
product_ids_delete = []
|
||||
for i in data:
|
||||
status = i.get("status")#活动商品状态:0:待审核;1:推广中;2:申请未通过;3:合作已终止;6:合作已到期
|
||||
product_id = i.get("product_id")
|
||||
if status == 1:
|
||||
shop_score = lyFloat(i.get("shop_score"))
|
||||
p_data = {'title':i.get("title"),'shop_score':shop_score,'activity_id':activity_id,'detail_url':i.get("detail_url"),'category_id':i.get("category_id"),'category_name':i.get("category_name"),'price':i.get("price"),'sales':i.get("sales"),'cover':i.get("cover"),'cos_ratio':i.get('cos_ratio'),'cos_fee':i.get('cos_fee'),'special_cos_ratio':int(i.get('activity_cos_ratio'))/100,'shop_id':i.get('shop_id'),'is_calc_award':True,'begin_time':activity_start_time,'end_time':activity_end_time}
|
||||
|
||||
product_ids.append(product_id)
|
||||
instance,created = DyProductManage.objects.get_or_create(product_id=product_id,source=1,defaults=p_data)
|
||||
if not created:
|
||||
p_data['update_datetime'] = datetime.datetime.now()
|
||||
p_data.pop('is_calc_award')
|
||||
p_data.pop('begin_time')
|
||||
p_data.pop('end_time')
|
||||
p_data['is_delete'] = False
|
||||
DyProductManage.objects.filter(product_id=product_id,source=1).update(**p_data)
|
||||
else:#团长下架商品或者商品到期,则平台删除(逻辑)此商品展示
|
||||
product_ids_delete.append(product_id)
|
||||
#清除失效商品
|
||||
if product_ids_delete:
|
||||
DyProductManage.objects.filter(product_id__in=product_ids_delete).update(status=False,is_delete=True)
|
||||
#更新商品详情
|
||||
if product_ids:
|
||||
res_details = getProductsDetail(product_ids=product_ids)
|
||||
logger.info("团长活动商品列表递归同步到数据库(商品详情),product_ids=%s,返回内容:%s"%(product_ids,res_details))
|
||||
if res_details and res_details.get("code") == 10000:
|
||||
logger.info("更新商品详情到数据库")
|
||||
d_data = res_details.get("data").get("products",[])
|
||||
if d_data:
|
||||
for i in d_data:
|
||||
#店铺数据更新
|
||||
shop_info = i.get('shop_info')
|
||||
shop_id = shop_info.get('shop_id')
|
||||
shop_total_score = shop_info.get('shop_total_score')
|
||||
if shop_total_score:
|
||||
shop_info_dic = {'shop_name':shop_info.get('shop_name'),'shop_total_score':shop_total_score,'shop_score':shop_total_score.get('shop_score').get('score'),'shop_score_level':shop_total_score.get('shop_score').get('level')}
|
||||
else:
|
||||
shop_info_dic = {'shop_name':shop_info.get('shop_name'),'shop_total_score':shop_total_score}
|
||||
logger.info("正在更新店铺信息:%s"%shop_info_dic)
|
||||
s_instance,s_created = DyShopManage.objects.get_or_create(shop_id=shop_id,defaults=shop_info_dic)
|
||||
if not s_created:
|
||||
shop_info_dic['update_datetime'] = datetime.datetime.now()
|
||||
DyShopManage.objects.filter(shop_id=shop_id).update(**shop_info_dic)
|
||||
#商品更新
|
||||
product_id = i.get("base_info").get('product_id')
|
||||
imgs = i.get("base_info").get('imgs')
|
||||
logistics_info = i.get('logistics_info').get('text')
|
||||
has_sxt = i.get('qualification_info').get('has_sxt')
|
||||
detail_brief = get_product_detail_brief(i.get('detail_brief').get('component_info'))
|
||||
comment_score = i.get('comment_info').get('comment_score')
|
||||
is_assured = i.get('rights_info').get('is_assured')
|
||||
sharable = i.get('share_info').get('sharable')
|
||||
has_subsidy_tag = i.get('tags').get('has_subsidy_tag')
|
||||
has_douin_goods_tag = i.get('tags').get('has_douin_goods_tag')
|
||||
has_shop_brand_tag = i.get('tags').get('has_shop_brand_tag')
|
||||
has_supermarket_tag = i.get('tags').get('has_supermarket_tag')
|
||||
coupon_price = i.get('coupon_info').get('coupon_price')
|
||||
coupon = len(i.get('coupon_info').get('available_coupons',[]))
|
||||
order_num_30 = i.get('month_sale_data').get('order_num',0)
|
||||
view_num_30 = i.get('month_sale_data').get('view_num', 0)
|
||||
kol_num_30 = i.get('month_sale_data').get('kol_num', 0)
|
||||
daily_statistics = i.get('month_sale_data').get('daily_statistics', [])
|
||||
product_queryset = DyProductManage.objects.filter(product_id=product_id)
|
||||
product_queryset.update(view_num=view_num_30,order_num = order_num_30,kol_num = kol_num_30,dyshop=s_instance,coupon_price=coupon_price,coupon=coupon,imgs=imgs,logistics_info=logistics_info,has_sxt=has_sxt,detail_brief=detail_brief,comment_score=comment_score,is_assured=is_assured,has_subsidy_tag=has_subsidy_tag,has_douin_goods_tag=has_douin_goods_tag,has_shop_brand_tag=has_shop_brand_tag,has_supermarket_tag=has_supermarket_tag,sharable=sharable,update_datetime = datetime.datetime.now())
|
||||
ds_obj_list = []
|
||||
product_instance = product_queryset.first()
|
||||
for ds in daily_statistics:
|
||||
d_statics_date = datetime.datetime.strptime(ds.get('date'), '%Y%m%d')
|
||||
if not DyProductSaleData.objects.filter(product_id=product_id,date=d_statics_date).exists():
|
||||
ds_obj_list.append(DyProductSaleData(inner_product_id_id=product_instance.id,product_id=product_id,date=d_statics_date,order_num=ds.get('order_num'),view_num=ds.get('view_num'),kol_num=ds.get('kol_num')))
|
||||
if ds_obj_list:
|
||||
DyProductSaleData.objects.bulk_create(ds_obj_list)
|
||||
if page<total_page:
|
||||
#递归处理下一页数据
|
||||
next_page = page+1
|
||||
colonelActivityProductListSync(activity_id=activity_id,activity_start_time=activity_start_time,activity_end_time=activity_end_time,search_type=search_type,sort_type=sort_type,page=next_page,count=count,status=status)
|
||||
return True
|
||||
return True
|
||||
return False
|
||||
|
||||
#精选联盟团长活动商品同步
|
||||
def allianceColonelGoodsSync():
|
||||
"""
|
||||
return:False失败,True成功
|
||||
"""
|
||||
if allianceColonelActivityListSync():
|
||||
queryset = DyColonelActivityManage.objects.all()
|
||||
if queryset:
|
||||
for i in queryset:
|
||||
activity_start_time = formatdatetime_convert(i.activity_start_time)
|
||||
activity_end_time = formatdatetime_convert(i.activity_end_time)
|
||||
colonelActivityProductListSync(activity_id=i.activity_id,activity_start_time=activity_start_time,activity_end_time=activity_end_time)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
|
||||
#精选联盟 团长赏金任务列表查询
|
||||
#https://buyin.jinritemai.com/dashboard/service-provider/doc-center?docId=3573
|
||||
def getColonelAwardTaskList(task_info="",status=0,page=1,page_size=20):
|
||||
"""
|
||||
task_info:任务id或名称
|
||||
status:状态 0:不限,1:未发布,2:审核中,3:审核未通过,4:未开始,5:进行中,6:已结束, 默认0
|
||||
page: 页数
|
||||
page_size: 每页条数,最大20。默认20
|
||||
"""
|
||||
path = '/buyin/colonelAwardTaskList'
|
||||
params ={"task_info":task_info,"status":status,"page":page,"page_size":page_size}
|
||||
result = newdoudian_tz_yz.request(path=path, params=params)
|
||||
return result
|
||||
|
||||
# 同步团长赏金任务列表
|
||||
def allianceColonelAwardTaskListSync(status=0,page=1,page_size=20):
|
||||
"""
|
||||
return:False失败,True成功
|
||||
"""
|
||||
result = getColonelAwardTaskList(status=status,page=page,page_size=page_size)
|
||||
logger.info("同步团长赏金任务列表,第%s页,返回内容:%s"%(page,result))
|
||||
if result and result.get("code") == 10000:
|
||||
total = int(result.get("data").get("total"))
|
||||
total_page = get_page_count(total,page_size)
|
||||
data = result.get('data').get('results')
|
||||
for i in data:
|
||||
instance,created = DyAwardTaskManage.objects.get_or_create(task_id=i.get("task_id"),defaults={"task_name":i.get("task_name"),"task_start_time":i.get("task_start_time"),"task_end_time":i.get("task_end_time"),"task_status":i.get("status"),"apply_cnt":i.get("apply_cnt"),"promote_cnt":i.get("promote_cnt"),"gmv":i.get("gmv"),"order_cnt":i.get("order_cnt"),"can_apply":i.get("can_apply")})
|
||||
if not created:
|
||||
instance.task_name = i.get("task_name")
|
||||
instance.task_start_time = i.get("task_start_time")
|
||||
instance.task_end_time = i.get("task_end_time")
|
||||
instance.task_status = i.get("status")
|
||||
instance.apply_cnt = i.get("apply_cnt")
|
||||
instance.promote_cnt = i.get("promote_cnt")
|
||||
instance.gmv = i.get("gmv")
|
||||
instance.order_cnt = i.get("order_cnt")
|
||||
instance.can_apply = i.get("can_apply")
|
||||
instance.update_datetime = datetime.datetime.now()
|
||||
instance.save()
|
||||
if int(i.get("status")) not in [5]:
|
||||
instance.can_apply = False
|
||||
instance.save()
|
||||
if page<total_page:
|
||||
#递归处理下一页数据
|
||||
next_page = page+1
|
||||
allianceColonelAwardTaskListSync(status=status,page=next_page,page_size=page_size)
|
||||
return True
|
||||
elif result and result.get('code') == 40003 and result.get('err_no') == 30005:
|
||||
raise ValueError('团长授权已过期,请重新授权!!!')
|
||||
return False
|
||||
|
||||
#精选联盟赏金任务同步
|
||||
def colonelAwardTaskSync():
|
||||
"""
|
||||
return:False失败,True成功
|
||||
"""
|
||||
result = allianceColonelAwardTaskListSync()
|
||||
return result
|
||||
|
||||
#达人赏金任务报名
|
||||
def darenApplyAwardTask(access_token="",task_id=""):
|
||||
return newdouyinUnion.daren_award_task_apply(access_token=access_token,task_id=task_id)
|
||||
|
||||
# 处理抖音对数量的中文处理,获取真实数字
|
||||
def getDouyinNums(data):
|
||||
if data == '暂无数据':
|
||||
return 0
|
||||
if "已售" in data:
|
||||
data = data.replace("已售","").replace(" ","")
|
||||
if '万+' in data:
|
||||
tp = data.split("万+")
|
||||
return tp[0]*10000
|
||||
elif '万' in data:
|
||||
tp = data.split("万")
|
||||
return tp[0] * 10000
|
||||
return data
|
||||
|
||||
# 获取达人橱窗近30天销量
|
||||
def getDaren30daysSales(access_token="",open_id=""):
|
||||
res = newdouyinUnion.daren_reputation(access_token=access_token,open_id=open_id)
|
||||
if not res:
|
||||
return None
|
||||
return getDouyinNums(res['last_month_sales'])
|
||||
|
||||
# 获取达人橱窗近30天gmv
|
||||
def getDaren30daysGmv(access_token="",open_id="",cursor=0):
|
||||
end_time = datetime.datetime.now()
|
||||
start_time = end_time - datetime.timedelta(days=30)
|
||||
end_time = formatdatetime(end_time)
|
||||
start_time = formatdatetime(start_time)
|
||||
day30gmv = 0
|
||||
res = newdouyinUnion.daren_orders(access_token=access_token,open_id=open_id,params={'size':20,'cursor':cursor,'start_time':start_time,'end_time':end_time})
|
||||
if res:
|
||||
for i in res.get('orders'):
|
||||
day30gmv = day30gmv + round((i.get('total_pay_amount'))/100,1)
|
||||
cursor = i.get('cursor')
|
||||
if cursor:
|
||||
day30gmv = day30gmv + getDaren30daysGmv(access_token=access_token,open_id=open_id,cursor=cursor)
|
||||
return day30gmv
|
||||
|
||||
# 抖客商品分销转链 写入PID把商品链接转为自己专属的商品抖口令、抖音码、deeplink和zlink
|
||||
# https://op.jinritemai.com/docs/api-docs/61/1464
|
||||
def buyinKolProductShare(access_token=None,product_url="",pid="",external_info="",other_params={}):
|
||||
"""
|
||||
product_url:商品URL/口令/短链接
|
||||
pid: 抖客PID
|
||||
external_info:自定义字段(跟单用),只允许 数字、字母和_,限制长度为40
|
||||
"""
|
||||
path = '/buyin/kolProductShare'
|
||||
params = {"product_url": product_url, "pid": pid,"external_info":external_info,"need_qr_code":False,"platform":0,"use_coupon":True,"need_share_link":True,"ins_activity_param":"","need_zlink":True}
|
||||
if other_params:
|
||||
params = dict(params,**other_params)
|
||||
result = newdoudian_dkfx_gj.request(path=path, params=params,access_token=access_token)
|
||||
return result
|
||||
|
||||
# 达人PID查询接口(达人查询自己的PID列表,需要达人百应授权)
|
||||
# https://buyin.jinritemai.com/dashboard/service-provider/doc-center?docId=1461
|
||||
def buyinKolPidList(access_token=None):
|
||||
"""
|
||||
access_token:用户侧的access_token
|
||||
"""
|
||||
path = '/buyin/kolPidList'
|
||||
params = {}
|
||||
result = newdoudian_dklive_yz.request(path=path, params=params,access_token=access_token)
|
||||
return result
|
||||
|
||||
# 达人PID创建,需要达人授权
|
||||
# https://buyin.jinritemai.com/dashboard/service-provider/doc-center?docId=1460
|
||||
def buyinKolPidCreate(access_token=None,media_type=4,site_name="DY商品分享",media_name="DY商品分享"):
|
||||
"""
|
||||
access_token:用户侧的access_token
|
||||
media_type:推广媒体类型,0:其他, 1:微信, 2:QQ, 3:微博, 4:抖音, 5:头条
|
||||
site_name:推广位名称(最长10个字符)
|
||||
media_name:渠道名称,没有media_id时自动创建渠道;存在media_id时无效
|
||||
"""
|
||||
path = '/buyin/kolPidCreate'
|
||||
params = {'media_type':media_type,'site_name':site_name,'media_name':media_name}
|
||||
if access_token:
|
||||
result = newdoudian_dklive_yz.request(path=path, params=params,access_token=access_token)
|
||||
else:
|
||||
result = newdoudian_dklive_yz.request(path=path, params=params)
|
||||
pid = None
|
||||
logger.info("创建达人PID返回信息:%s" % result)
|
||||
if result and result.get('code') == 10000 and result.get('data'):
|
||||
pid = result.get('data').get('pid')
|
||||
return pid
|
||||
|
||||
# 抖客PID创建,需要抖客授权
|
||||
# https://buyin.jinritemai.com/dashboard/service-provider/doc-center?docId=1273
|
||||
def buyinInstitutePidCreate(access_token=None,media_type=4,site_name="DY商品分享",media_name="DY商品分享"):
|
||||
"""
|
||||
access_token:用户侧的access_token
|
||||
media_type:推广媒体类型,0:其他, 1:微信, 2:QQ, 3:微博, 4:抖音, 5:头条
|
||||
site_name:推广位名称(最长10个字符)
|
||||
media_name:渠道名称,没有media_id时自动创建渠道;存在media_id时无效
|
||||
"""
|
||||
path = '/buyin/institutePidCreate'
|
||||
params = {'media_type':media_type,'site_name':site_name,'media_name':media_name}
|
||||
if access_token:
|
||||
result = newdoudian_dklive_yz.request(path=path, params=params,access_token=access_token)
|
||||
else:
|
||||
result = newdoudian_dklive_yz.request(path=path, params=params)
|
||||
pid = None
|
||||
logger.info("创建抖客PID返回信息:%s"%result)
|
||||
if result and result.get('code') == 10000 and result.get('data'):
|
||||
pid = result.get('data').get('pid')
|
||||
return pid
|
||||
|
||||
# 独立抖客PID创建(需获取 抖客授权)-需【联盟抖客分销】工具型应用
|
||||
# https://op.jinritemai.com/docs/api-docs/61/3081
|
||||
def buyinDoukePidCreate(access_token=None,media_type=4,site_name="DY商品分享",media_name="DY商品分享"):
|
||||
"""
|
||||
access_token:用户侧的access_token
|
||||
media_type:推广媒体类型,0:其他, 1:微信, 2:QQ, 3:微博, 4:抖音, 5:头条
|
||||
site_name:推广位名称(最长10个字符)
|
||||
media_name:渠道名称,没有media_id时自动创建渠道;存在media_id时无效
|
||||
"""
|
||||
path = '/buyin/doukePidCreate'
|
||||
params = {'media_type':media_type,'site_name':site_name,'media_name':media_name}
|
||||
result = newdoudian_dkfx_gj.request(path=path, params=params,access_token=access_token)
|
||||
pid = None
|
||||
logger.info("buyinDoukePidCreate创建独立抖客PID返回信息:%s"%result)
|
||||
if result and result.get('code') == 10000 and result.get('data'):
|
||||
pid = result.get('data').get('pid')
|
||||
return pid
|
||||
|
||||
# 查询团长抖音、火山、头条和西瓜订单,需要团长百应授权
|
||||
# https://buyin.jinritemai.com/dashboard/service-provider/doc-center?docId=1603
|
||||
def buyinInstituteOrderColonel(start_time="",end_time="",size=200,cursor=0,time_type="pay"):
|
||||
"""
|
||||
size:每页订单数目,取值区间: [1, 200]
|
||||
cursor: 下一页索引(第一页传“0”)
|
||||
start_time: 支付时间开始,最早支持90天前 2006-01-02 15:04:05
|
||||
end_time: 支付时间结束
|
||||
time_type: 查询时间类型。pay: 支付时间(默认); update:联盟侧更新时间,非订单状态更新时间
|
||||
"""
|
||||
path = '/buyin/instituteOrderColonel'
|
||||
params = {"start_time": start_time, "end_time": end_time,"size":size,"cursor":cursor,"time_type":time_type}
|
||||
result = newdoudian_tz_yz.request(path=path, params=params)
|
||||
return result
|
||||
|
||||
# 同步 查询团长订单
|
||||
def buyinInstituteOrderColonelSync(start_time="",end_time="",size=200,cursor=0,time_type="pay"):
|
||||
"""
|
||||
return:获取的订单数据列表
|
||||
"""
|
||||
order_data = []
|
||||
result = buyinInstituteOrderColonel(start_time=start_time,end_time=end_time,size=size,cursor=cursor,time_type=time_type)
|
||||
if result and result.get("code") == 10000:
|
||||
data = result.get("data",{})
|
||||
orders = data.get('orders',[])
|
||||
order_data.extend(orders)
|
||||
thecursor = data.get('cursor')
|
||||
if thecursor:
|
||||
n_data = buyinInstituteOrderColonelSync(start_time=start_time, end_time=end_time, size=size, cursor=thecursor,time_type=time_type)
|
||||
order_data.extend(n_data)
|
||||
return order_data
|
||||
|
||||
# 同步 达人的订单(最早90天)-按时间同步
|
||||
def allianceKolOrdersSync(access_token=None,openid=None,size=20,cursor=0,start_time=None,end_time=None,time_type='pay'):
|
||||
"""
|
||||
return:获取的订单数据列表
|
||||
"""
|
||||
order_data = []
|
||||
params = {'size':size,'cursor':cursor,'start_time':start_time,'end_time':end_time,'time_type':time_type}
|
||||
result = newdouyinUnion.daren_orders(access_token=access_token,open_id=openid,params=params)
|
||||
if result and "err_no" in result["data"] and result["data"]["err_no"] == 0:
|
||||
data = result.get("data",{})
|
||||
orders = data.get('orders',[])
|
||||
order_data.extend(orders)
|
||||
thecursor = data.get('cursor')
|
||||
if thecursor:
|
||||
n_data = allianceKolOrdersSync(access_token=access_token,openid=openid,size=size,cursor=thecursor,start_time=start_time,end_time=end_time,time_type='pay')
|
||||
order_data.extend(n_data)
|
||||
return order_data
|
||||
|
||||
# 批量根据订单号查询 达人的订单清下(最多支持10个订单号)
|
||||
def batchAllianceKolOrdersByIds(access_token=None,openid=None,size=20,cursor=0,order_ids=""):
|
||||
"""
|
||||
order_ids:订单号。多个订单号用英文,分隔,最多支持10个订单号
|
||||
return:获取的订单数据列表
|
||||
"""
|
||||
order_data = []
|
||||
params = {'size':size,'cursor':cursor,'order_ids':order_ids}
|
||||
result = newdouyinUnion.daren_orders(access_token=access_token,open_id=openid,params=params)
|
||||
if result and "err_no" in result["data"] and result["data"]["err_no"] == 0:
|
||||
data = result.get("data",{})
|
||||
orders = data.get('orders',[])
|
||||
order_data.extend(orders)
|
||||
return order_data
|
||||
91
backend/apps/lyTiktokUnion/tools/lyPublic.py
Normal file
91
backend/apps/lyTiktokUnion/tools/lyPublic.py
Normal file
@ -0,0 +1,91 @@
|
||||
import datetime
|
||||
|
||||
|
||||
# 获取橱窗pick_extra
|
||||
def get_cc_pick_extra(userid):
|
||||
return 'ly_' + userid + '_cc'
|
||||
|
||||
# 获取分享赚external_info
|
||||
def get_product_share_external_info(userid):
|
||||
return "ly_"+ userid + "_pdshare"
|
||||
|
||||
# 获取转链external_info
|
||||
def get_zl_external_info(userid):
|
||||
return "ly_"+ userid + "_pdzl"
|
||||
|
||||
# 获取垫付external_info
|
||||
def get_product_df_external_info(userid):
|
||||
return "ly_"+ userid + "_pddf"
|
||||
|
||||
# 获取0元购external_info
|
||||
def get_product_0y_external_info(userid):
|
||||
return "ly_"+ userid + "_0y"
|
||||
|
||||
def get_cc_userid(pick_extra):
|
||||
try:
|
||||
userid = None
|
||||
if pick_extra:
|
||||
data = pick_extra.split('_')
|
||||
if len(data) == 3 and data[0] == 'ly' and data[2] == 'cc':
|
||||
userid = data[1]
|
||||
return userid
|
||||
except:
|
||||
return None
|
||||
def get_product_share_userid(external_info):
|
||||
try:
|
||||
userid = None
|
||||
if external_info:
|
||||
data = external_info.split('_')
|
||||
if len(data) == 3 and data[0] == 'ly' and data[2] == 'pdshare':
|
||||
userid = data[1]
|
||||
return userid
|
||||
except:
|
||||
return None
|
||||
|
||||
def get_product_df_userid(external_info):
|
||||
try:
|
||||
userid = None
|
||||
if external_info:
|
||||
data = external_info.split('_')
|
||||
if len(data) == 3 and data[0] == 'ly' and data[2] == 'pddf':
|
||||
userid = data[1]
|
||||
return userid
|
||||
except:
|
||||
return None
|
||||
|
||||
def get_product_0y_userid(external_info):
|
||||
try:
|
||||
userid = None
|
||||
if external_info:
|
||||
data = external_info.split('_')
|
||||
if len(data) == 3 and data[0] == 'ly' and data[2] == '0y':
|
||||
userid = data[1]
|
||||
return userid
|
||||
except:
|
||||
return None
|
||||
|
||||
def get_product_zl_userid(external_info):
|
||||
try:
|
||||
userid = None
|
||||
if external_info:
|
||||
data = external_info.split('_')
|
||||
if len(data) == 3 and data[0] == 'ly' and data[2] == 'pdzl':
|
||||
userid = data[1]
|
||||
return userid
|
||||
except:
|
||||
return None
|
||||
|
||||
def getSalesStr(sale):
|
||||
if sale<=999:
|
||||
return "%s"%sale
|
||||
else:
|
||||
return "%s万"%round(sale/10000,1)
|
||||
return sale
|
||||
|
||||
def getActiveEndStr(date):
|
||||
now = datetime.datetime.now().date()
|
||||
delta = date - now
|
||||
days = delta.days
|
||||
if days<0:
|
||||
return 0
|
||||
return days
|
||||
32
backend/apps/lyTiktokUnion/urls.py
Normal file
32
backend/apps/lyTiktokUnion/urls.py
Normal file
@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#路由文件
|
||||
|
||||
from django.urls import path, re_path
|
||||
from rest_framework import routers
|
||||
|
||||
from apps.lyTiktokUnion.views.dyBalanceRecordViews import DYBalanceRecordViewSet
|
||||
from apps.lyTiktokUnion.views.dySystemAccountViews import DYSystemAccountViewSet
|
||||
from apps.lyTiktokUnion.views.dyAwardTaskManageViews import DyAwardTaskManageViewSet
|
||||
from apps.lyTiktokUnion.views.dyProductManageViews import DyProductManageViewSet
|
||||
|
||||
system_url = routers.SimpleRouter()
|
||||
system_url.register(r'cashout', DYBalanceRecordViewSet)
|
||||
system_url.register(r'systemaccount', DYSystemAccountViewSet)
|
||||
system_url.register(r'awardTask', DyAwardTaskManageViewSet)
|
||||
system_url.register(r'product', DyProductManageViewSet)
|
||||
|
||||
urlpatterns = [
|
||||
path('cashout/audit/', DYBalanceRecordViewSet.as_view({'post': 'audit'}), name='后台提现审核'),
|
||||
path('systemaccount/getCodeBuyinUserinfo/', DYSystemAccountViewSet.as_view({'get': 'getCodeBuyinUserinfo'}), name='后台获取抖客/团长列表'),
|
||||
path('systemaccount/createCodeBuyin/', DYSystemAccountViewSet.as_view({'post': 'createCodeBuyin'}), name='后台生成抖客/单人授权拼链接'),
|
||||
path('awardTask/syncAwardTask/',DyAwardTaskManageViewSet.as_view({'post':'sync_alliance_AwardTask'}), name='后台同步赏金任务'),
|
||||
re_path('awardTask/editsort/(?P<pk>.*?)/',DyAwardTaskManageViewSet.as_view({'put':'editsort'}), name='赏金任务管理排序'),
|
||||
re_path('awardTask/disablestatus/(?P<pk>.*?)/',DyAwardTaskManageViewSet.as_view({'put':'disablestatus'}), name='赏金任务管理禁用'),
|
||||
re_path('product/disablestatus/(?P<pk>.*?)/',DyProductManageViewSet.as_view({'put':'disablestatus'}), name='后台禁用商品'),
|
||||
re_path('product/disablecalcaward/(?P<pk>.*?)/',DyProductManageViewSet.as_view({'put':'disablecalcaward'}), name='后台禁用商品计算奖励'),
|
||||
re_path('product/editsort/(?P<pk>.*?)/',DyProductManageViewSet.as_view({'put':'editsort'}), name='后台修改商品排序'),
|
||||
re_path('product/editAttribute/(?P<pk>.*?)/',DyProductManageViewSet.as_view({'put':'editAttribute'}), name='修改商品属性'),
|
||||
path('product/syncColonelGoods/',DyProductManageViewSet.as_view({'post':'sync_alliance_colonel_goods'}), name='后台同步团长活动商品'),
|
||||
]
|
||||
urlpatterns += system_url.urls
|
||||
161
backend/apps/lyTiktokUnion/views/doudianCallback.py
Normal file
161
backend/apps/lyTiktokUnion/views/doudianCallback.py
Normal file
@ -0,0 +1,161 @@
|
||||
from rest_framework.views import APIView
|
||||
from apps.lyTiktokUnion.models import DYSystemAccount
|
||||
from utils.jsonResponse import SuccessResponse,ErrorResponse,DetailResponse
|
||||
from utils.common import get_parameter_dic,formatdatetime,float2dot,getminrandomodernum,ast_convert,getLyStateEncrypt,verifyLyStateEncript
|
||||
from django.db.models import Q,F,Sum,Count
|
||||
from django.db import transaction
|
||||
from rest_framework import serializers
|
||||
from rest_framework_simplejwt.authentication import JWTAuthentication
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from utils.pagination import CustomPagination
|
||||
from utils.douyin.doudian_utils import doudian
|
||||
import time
|
||||
import datetime
|
||||
from django.http import HttpResponse,HttpResponseRedirect
|
||||
from utils.douyin.douyinUnion_utils import douyinUnion
|
||||
from config import DOUDIAN_TUANZHUANG_ZIYAN_APPKEY,DOUDIAN_TUANZHUANG_ZIYAN_APPSECRET,DOUDIAN_DKLIVE_ZIYAN_APPKEY,DOUDIAN_DKLIVE_ZIYAN_APPSECRET,DOUDIAN_DKFX_TOOL_APPKEY,DOUDIAN_DKFX_TOOL_APPSECRET
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
#联盟抖店应用自研型/工具型授权回调(使用地址)
|
||||
class DoudianUnionCodeCallbackView(APIView):
|
||||
'''
|
||||
#联盟抖店应用自研型授权回调(使用地址)-自研型登录右上角设置-授权管理-点击应用授权后点击【使用】即开始授权code回调
|
||||
get:
|
||||
'''
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
|
||||
def get(self, request):
|
||||
reqdata = get_parameter_dic(request)
|
||||
code = reqdata.get('code',None)
|
||||
state = reqdata.get('state',None)#无state回调
|
||||
if not all([code]):
|
||||
# return HttpResponse("非法请求")
|
||||
return HttpResponseRedirect("/#/login")
|
||||
logger.info("抖店联盟团长自研获取code回调信息code=%s|state=%s"%(code,state))
|
||||
newdoudian_tz_yz = doudian(app_key=DOUDIAN_TUANZHUANG_ZIYAN_APPKEY,app_secret=DOUDIAN_TUANZHUANG_ZIYAN_APPSECRET,app_type="tool",code=code)
|
||||
res = newdoudian_tz_yz.init_token(code=code)
|
||||
if not res:
|
||||
return HttpResponse("获取授权失败!!!")
|
||||
name = ""
|
||||
buyinid = ""
|
||||
buyininfo = newdoudian_tz_yz.getBuyinInstitutionInfo()
|
||||
access_token_expire_in = int(newdoudian_tz_yz.get_cache_access_token_ttl())
|
||||
refresh_access_expire_in = int(newdoudian_tz_yz.get_cache_refresh_access_token_ttl())
|
||||
if buyininfo and buyininfo.get("code") == 10000:
|
||||
name = buyininfo.get("data").get("colonel").get("name")
|
||||
buyinid = buyininfo.get("data").get("colonel").get("buyin_id")
|
||||
newdoudian_tz_yz.set_cache_buyin_id(buyin_id=buyinid, expires=access_token_expire_in)
|
||||
else:
|
||||
buyinid = newdoudian_tz_yz.get_cache_buyin_id()
|
||||
new_access_token = newdoudian_tz_yz.get_access_token()
|
||||
new_refresh_token = newdoudian_tz_yz.get_cache_refresh_token()
|
||||
nowtimestap = int(time.time())
|
||||
access_token_expire = datetime.datetime.fromtimestamp(nowtimestap + access_token_expire_in)
|
||||
refresh_token_expire = datetime.datetime.fromtimestamp(nowtimestap + refresh_access_expire_in)
|
||||
defaults = {'access_token': new_access_token, 'refresh_token': new_refresh_token, 'identity': 2,'nickname': name, 'access_token_expire': access_token_expire,'refresh_token_expire': refresh_token_expire}
|
||||
instance, created = DYSystemAccount.objects.get_or_create(buyin_id=buyinid, defaults=defaults)
|
||||
if not created:
|
||||
instance.buyin_id = buyinid
|
||||
instance.nickname = name
|
||||
instance.access_token = new_access_token
|
||||
instance.refresh_token = new_refresh_token
|
||||
instance.access_token_expire = access_token_expire
|
||||
instance.refresh_token_expire = refresh_token_expire
|
||||
instance.is_delete = False
|
||||
instance.status = True
|
||||
instance.save()
|
||||
DYSystemAccount.objects.filter(identity=2).exclude(id=instance.id).update(is_delete=True, status=False)
|
||||
return HttpResponse("获取授权成功!")
|
||||
|
||||
#联盟抖店应用自研型/工具型授权回调(使用地址)--直播间 联盟抖客分销[工具型]
|
||||
class DoudianUnionDKLiveCodeCallbackView(APIView):
|
||||
'''
|
||||
1、联盟抖店应用自研型授权回调(使用地址)-自研型登录右上角设置-授权管理-点击应用授权后点击【使用】即开始授权code回调(此种方式无state返回)
|
||||
2、抖客/达人可以使用拼链接的形式此种方式有state返回
|
||||
get:
|
||||
'''
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
|
||||
def get(self, request):
|
||||
reqdata = get_parameter_dic(request)
|
||||
code = reqdata.get('code',None)
|
||||
state = reqdata.get('state',None)
|
||||
if not all([code]):
|
||||
# return HttpResponse("非法请求")
|
||||
return HttpResponseRedirect("/#/login")
|
||||
logger.info("抖店联盟联盟抖客分销获取code回调信息code=%s|state=%s"%(code,state))
|
||||
newdoudian_dkfx_gj = doudian(app_key=DOUDIAN_DKFX_TOOL_APPKEY,app_secret=DOUDIAN_DKFX_TOOL_APPSECRET,app_type="tool",code="test")
|
||||
res = newdoudian_dkfx_gj.init_token(code=code)
|
||||
if not res:
|
||||
return HttpResponse("获取授权失败!!!")
|
||||
|
||||
name = ""
|
||||
buyinid = ""
|
||||
buyininfo = newdoudian_dkfx_gj.getBuyinInstitutionInfo()
|
||||
logger.info("抖客授权获取百应ID信息结果%s"%buyininfo)
|
||||
access_token_expire_in = int(newdoudian_dkfx_gj.get_cache_access_token_ttl())
|
||||
refresh_access_expire_in = int(newdoudian_dkfx_gj.get_cache_refresh_access_token_ttl())
|
||||
if buyininfo and buyininfo.get("code") == 10000:
|
||||
name = buyininfo.get("data").get("douke").get("name")
|
||||
buyinid = buyininfo.get("data").get("douke").get("buyin_id")
|
||||
newdoudian_dkfx_gj.set_cache_buyin_id(buyin_id=buyinid,expires=access_token_expire_in)
|
||||
else:
|
||||
buyinid = newdoudian_dkfx_gj.get_cache_buyin_id()
|
||||
new_access_token = newdoudian_dkfx_gj.get_access_token()
|
||||
new_refresh_token = newdoudian_dkfx_gj.get_cache_refresh_token()
|
||||
nowtimestap = int(time.time())
|
||||
access_token_expire = datetime.datetime.fromtimestamp(nowtimestap + access_token_expire_in)
|
||||
refresh_token_expire = datetime.datetime.fromtimestamp(nowtimestap + refresh_access_expire_in)
|
||||
defaults = {'access_token': new_access_token,'refresh_token': new_refresh_token,'identity':1,'nickname':name,'access_token_expire':access_token_expire,'refresh_token_expire':refresh_token_expire}
|
||||
instance, created = DYSystemAccount.objects.get_or_create(buyin_id=buyinid,defaults=defaults)
|
||||
if not created:
|
||||
instance.buyin_id = buyinid
|
||||
instance.nickname = name
|
||||
instance.access_token = new_access_token
|
||||
instance.refresh_token = new_refresh_token
|
||||
instance.access_token_expire = access_token_expire
|
||||
instance.refresh_token_expire = refresh_token_expire
|
||||
instance.is_delete = False
|
||||
instance.status = True
|
||||
instance.save()
|
||||
DYSystemAccount.objects.filter(identity=1).exclude(id=instance.id).update(is_delete=True, status=False)
|
||||
return HttpResponse("获取授权成功!")
|
||||
|
||||
#联盟抖店应用自研型/工具型授权回调(使用地址)--直播间 联盟抖客直播间分销
|
||||
class DoudianUnionDKLiveCodeCallbackView2(APIView):
|
||||
'''
|
||||
1、联盟抖店应用自研型授权回调(使用地址)-自研型登录右上角设置-授权管理-点击应用授权后点击【使用】即开始授权code回调(此种方式无state返回)
|
||||
2、抖客/达人可以使用拼链接的形式此种方式有state返回
|
||||
get:
|
||||
'''
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
|
||||
def get(self, request):
|
||||
reqdata = get_parameter_dic(request)
|
||||
code = reqdata.get('code',None)
|
||||
state = reqdata.get('state',None)#无state回调
|
||||
if not all([code]):
|
||||
# return HttpResponse("非法请求")
|
||||
return HttpResponseRedirect("/#/login")
|
||||
logger.info("抖店联盟抖客直播间分销获取code回调信息code=%s|state=%s"%(code,state))
|
||||
newdoudian_dklive_yz = doudian(app_key=DOUDIAN_DKLIVE_ZIYAN_APPKEY,app_secret=DOUDIAN_DKLIVE_ZIYAN_APPSECRET,app_type="tool",code=code)
|
||||
res = newdoudian_dklive_yz.init_token(code=code)
|
||||
if not res:
|
||||
return HttpResponse("获取授权失败!!!")
|
||||
if state:
|
||||
name = newdoudian_dklive_yz.get_cache_buyin_id()
|
||||
buyinid = newdoudian_dklive_yz.get_cache_buyin_id()
|
||||
defaults = {'access_token': newdoudian_dklive_yz.get_access_token(),'refresh_token': newdoudian_dklive_yz.get_cache_refresh_token()}
|
||||
instance, created = DYSystemAccount.objects.get_or_create(nickname=name, buyin_id=buyinid,defaults=defaults)
|
||||
if not created:
|
||||
instance.is_delete = False
|
||||
instance.status = True
|
||||
instance.save()
|
||||
DYSystemAccount.objects.all().exclude(id=instance.id).update(is_delete=True, status=False)
|
||||
return HttpResponse("获取授权成功!")
|
||||
167
backend/apps/lyTiktokUnion/views/douyinMsgWebhook.py
Normal file
167
backend/apps/lyTiktokUnion/views/douyinMsgWebhook.py
Normal file
@ -0,0 +1,167 @@
|
||||
from rest_framework.views import APIView
|
||||
from apps.lyTiktokUnion.models import DyOrderManage
|
||||
from utils.jsonResponse import SuccessResponse,ErrorResponse,DetailResponse
|
||||
from utils.common import get_parameter_dic,formatdatetime,float2dot,getminrandomodernum,ast_convert,getLyStateEncrypt,verifyLyStateEncript,ly_md5
|
||||
from django.db.models import Q,F,Sum,Count
|
||||
from django.db import transaction
|
||||
from rest_framework import serializers
|
||||
from rest_framework_simplejwt.authentication import JWTAuthentication
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from utils.pagination import CustomPagination
|
||||
from utils.douyin.doudian_utils import doudian
|
||||
import datetime
|
||||
import json
|
||||
from django.http import HttpResponse,HttpResponseRedirect
|
||||
from config import DOUYIN_OPENAPI_CLIENT_KEY,DOUYIN_OPENAPI_CLIENT_SECRET,DOUDIAN_DKFX_TOOL_APPKEY,DOUDIAN_DKFX_TOOL_APPSECRET
|
||||
from apps.lyTiktokUnion.tools.lyPublic import *
|
||||
import logging
|
||||
import hashlib
|
||||
import datetime
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def convertString2Datetime(string):
|
||||
if not string:
|
||||
return None
|
||||
try:
|
||||
dt = datetime.datetime.strptime(string, '%Y-%m-%d %H:%M:%S')
|
||||
return dt
|
||||
except:
|
||||
return None
|
||||
|
||||
def sha1_encrypt(content):
|
||||
sha1_obj = hashlib.sha1()
|
||||
sha1_obj.update(content.encode('utf-8'))
|
||||
return sha1_obj.hexdigest()
|
||||
|
||||
#订阅达人的订单新增和更新消息
|
||||
#{'event': 'alliance_daren_order', 'client_key': 'awai74ahekaxxx', 'from_user_id': '_000_LzNb0HBhiwF1coI9Li860_5g2m6xxx', 'content': '{"order_id":"692400803131648xxx","product_id":"36467746704155xxx","product_name":"商品名xxx","product_img":"https://p6-aio.ecombdimg.com/obj/ecom-shop-material/gkByqoQb_m_bc4e51af32bf950fcxxxx491d604_sx_320245_www800-800","author_account":"xxx说","author_openid":"_000_LzNb0HBhiwF1coI9Li860_5g2m6xxx","shop_name":"xx工厂店","total_pay_amount":1290,"commission_rate":2000,"estimated_commission":232,"real_commission":0,"flow_point":"PAY_SUCC","app":"抖音","update_time":"2023-11-22 17:41:47","pay_success_time":"2023-11-22 17:41:36","pay_goods_amount":1290,"settled_goods_amount":0,"shop_id":1315421xx,"estimated_tech_service_fee":26,"item_num":1,"estimated_total_commission":258,"pick_source_client_key":"awai74ahekaxxx","author_short_id":"185236xxx","pick_extra":"ly_F1sv3mtH97_cc","author_buyin_id":"6958644459006165xxx","media_type":"shop_list","app_id":1128,"is_stepped_plan":false,"product_activity_id":"0"}', 'log_id': '02170064610750500000000000000000000ffxxxx'}
|
||||
class DouyinAllianceDarenOrderWebhookView(APIView):
|
||||
'''
|
||||
订阅达人的订单新增和更新消息
|
||||
post:
|
||||
'''
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
|
||||
def post(self, request):
|
||||
douyin_sign = request.META.get('X-Douyin-Signature')
|
||||
reqdata = get_parameter_dic(request)
|
||||
logger.info("获取达人订单webhook通知,头部信息:X-Douyin-Signature=%s,通知内容:%s"%(douyin_sign,reqdata))
|
||||
event = reqdata.get('event')
|
||||
client_key = reqdata.get('client_key')
|
||||
content = reqdata.get('content')
|
||||
from_user_id = reqdata.get('from_user_id')
|
||||
log_id = reqdata.get('log_id')
|
||||
if event == "verify_webhook":
|
||||
challenge = content.get('challenge')
|
||||
return HttpResponse(json.dumps({'challenge':challenge}), content_type='application/json')
|
||||
elif event == "alliance_daren_order":
|
||||
if not douyin_sign and not sha1_encrypt(f'{DOUYIN_OPENAPI_CLIENT_SECRET}{reqdata}'):
|
||||
logger.error("【签名校验错误】获取达人订单webhook通知,头部信息:X-Douyin-Signature=%s,通知内容:%s" % (
|
||||
douyin_sign, request.body))
|
||||
return HttpResponse("非法请求")
|
||||
content = json.loads(content)
|
||||
content['update_time'] = content['update_time'] if content.get('update_time',None) else None
|
||||
content['settle_time'] = content['settle_time'] if content.get('settle_time',None) else None
|
||||
content['refund_time'] = content['refund_time'] if content.get('refund_time',None) else None
|
||||
pid_info = content.pop('pid_info',None)
|
||||
order_id = content.get('order_id')
|
||||
defaults = content
|
||||
if content.get('media_type') == 'shop_list' and defaults.get('pick_extra',None):
|
||||
defaults['from_user_id'] = from_user_id
|
||||
if pid_info:
|
||||
defaults['pid'] = pid_info.get('pid')
|
||||
defaults['external_info'] = pid_info.get('external_info')
|
||||
defaults['media_type_name'] = pid_info.get('media_type_name')
|
||||
defaults['order_type'] = 1
|
||||
defaults['inner_user_id'] = get_cc_userid(defaults['pick_extra'])
|
||||
defaults.pop("app_id",None)
|
||||
defaults.pop("author_buyin_id",None)
|
||||
defaults.pop("estimated_tech_service_fee",None)
|
||||
defaults.pop("estimated_total_commission",None)
|
||||
defaults.pop("is_stepped_plan",None)
|
||||
defaults.pop("product_activity_id",None)
|
||||
instance,created = DyOrderManage.objects.get_or_create(order_id=order_id,defaults=defaults)
|
||||
if not created:
|
||||
instance.update_time = content['update_time']
|
||||
instance.estimated_commission = content['estimated_commission']
|
||||
instance.real_commission = content['real_commission']
|
||||
instance.settled_goods_amount = content['settled_goods_amount']
|
||||
instance.pay_goods_amount = content['pay_goods_amount']
|
||||
instance.settle_time = content['settle_time']
|
||||
instance.refund_time = content['refund_time']
|
||||
instance.flow_point = content['flow_point']
|
||||
instance.save()
|
||||
else:
|
||||
logger.info("获取达人订单webhook通知,order_id=%s,处理结果:忽略"%order_id)
|
||||
return HttpResponse("ok")
|
||||
|
||||
#订阅抖客的订单新增和更新消息
|
||||
#https://buyin.jinritemai.com/dashboard/service-provider/msg-center?docId=3084
|
||||
#消息推送失败,可以再抖店开放平台【开发】-【应用中心】-选择应用-【查看详情】-【应用配置】-【消息推送】-【推送记录】中进行手动点击【重新推送】
|
||||
class DouyinAllianceDouKeOrderWebhookView(APIView):
|
||||
'''
|
||||
订阅独立抖客的订单新增和更新消息
|
||||
post:
|
||||
'''
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
|
||||
def post(self, request):
|
||||
app_key = request.META.get('HTTP_APP_ID')
|
||||
event_sign = request.META.get('HTTP_EVENT_SIGN')
|
||||
body = request.body.decode('UTF-8')
|
||||
reqdata = json.loads(body)
|
||||
#抖音测试消息
|
||||
success_response = json.dumps({"code": 0, "msg": "success"})
|
||||
if reqdata[0]['tag'] == '0' and reqdata[0]['msg_id'] == '0':
|
||||
return HttpResponse(success_response, content_type='application/json')
|
||||
#校验消息
|
||||
new_event_sign = ly_md5(f'{DOUDIAN_DKFX_TOOL_APPKEY}{reqdata}')
|
||||
if not DOUDIAN_DKFX_TOOL_APPKEY == app_key and not event_sign==new_event_sign:
|
||||
logger.error("【签名校验错误】获取抖客订单webhook通知,头部信息requestMETA=%s:app-id=%s,event-sign=%s,通知内容:%s" % (request.META,app_key,event_sign,request.body))
|
||||
return HttpResponse('非法请求')
|
||||
logger.info("获取抖客订单webhook通知,头部信息requestMETA=%s:app-id=%s,event-sign=%s,通知内容:%s" % (request.META, app_key, event_sign, reqdata))
|
||||
for d in reqdata:
|
||||
if d.get('tag') == '418':
|
||||
data = json.loads(d.get('data'))
|
||||
data['update_time'] = data['update_time'] if data.get('update_time',None) else None
|
||||
data['settle_time'] = data['settle_time'] if data.get('settle_time',None) else None
|
||||
data['refund_time'] = data['refund_time'] if data.get('refund_time',None) else None
|
||||
pid_info = data.pop('pid_info')
|
||||
order_id = data.get('order_id')
|
||||
# defaults = data
|
||||
defaults = {'pid':'','external_info':'','media_type_name':'','order_type':0,'inner_user_id':'','flow_point':data.get('flow_point'),'product_id':data.get('product_id'),'product_name':data.get('product_name'),'product_img':data.get('product_img'),'item_num':int(data.get('item_num')),'app':'','author_account':data.get('author_account'),'author_openid':'','author_short_id':'','shop_id':data.get('shop_id'),'shop_name':data.get('shop_name'),'pick_source_client_key':'','pick_extra':'','media_type':data.get('media_type'),'total_pay_amount':int(data.get('total_pay_amount')),'commission_rate':0,'update_time':convertString2Datetime(data.get('update_time')),'pay_success_time':convertString2Datetime(data.get('pay_success_time')),'settle_time':convertString2Datetime(data.get('settle_time')),'refund_time':convertString2Datetime(data.get('refund_time')),'pay_goods_amount':int(data.get('pay_goods_amount')),'settled_goods_amount':int(data.get('settled_goods_amount')),'estimated_commission':int(data.get('ads_estimated_commission')),'real_commission':int(data.get('ads_real_commission')),'from_user_id':''}
|
||||
defaults['pid'] = pid_info.get('pid')
|
||||
defaults['external_info'] = pid_info.get('external_info')
|
||||
if defaults['external_info'] and 'ly_' in defaults['external_info']:
|
||||
defaults['media_type_name'] = pid_info.get('media_type_name')
|
||||
if get_product_share_userid(defaults['external_info']):
|
||||
defaults['order_type'] = 2
|
||||
defaults['inner_user_id'] = get_product_share_userid(defaults['external_info'])
|
||||
elif get_product_0y_userid(defaults['external_info']):
|
||||
defaults['order_type'] = 3
|
||||
defaults['inner_user_id'] = get_product_0y_userid(defaults['external_info'])
|
||||
elif get_product_df_userid(defaults['external_info']):
|
||||
defaults['order_type'] = 4
|
||||
defaults['inner_user_id'] = get_product_df_userid(defaults['external_info'])
|
||||
logger.info("获取抖客订单webhook通知,order_id=%s,处理结果:此为垫付买样订单,暂时忽略"%order_id)
|
||||
continue
|
||||
else:#其他类型忽略
|
||||
logger.info("获取抖客订单webhook通知,order_id=%s,处理结果:忽略"%order_id)
|
||||
continue
|
||||
instance, created = DyOrderManage.objects.get_or_create(order_id=order_id, defaults=defaults)
|
||||
if not created:
|
||||
instance.update_time = data['update_time']
|
||||
instance.estimated_commission = data['ads_estimated_commission']
|
||||
instance.real_commission = data['ads_real_commission']
|
||||
instance.settled_goods_amount = data['settled_goods_amount']
|
||||
instance.settle_time = data['settle_time']
|
||||
instance.refund_time = data['refund_time']
|
||||
instance.flow_point = data['flow_point']
|
||||
instance.save()
|
||||
else:
|
||||
logger.info("获取抖客订单webhook通知,order_id=%s,处理结果:忽略"%order_id)
|
||||
continue
|
||||
return HttpResponse(success_response, content_type='application/json')
|
||||
79
backend/apps/lyTiktokUnion/views/dyAwardTaskManageViews.py
Normal file
79
backend/apps/lyTiktokUnion/views/dyAwardTaskManageViews.py
Normal file
@ -0,0 +1,79 @@
|
||||
from rest_framework.views import APIView
|
||||
from apps.lyTiktokUnion.models import DyAwardTaskManage
|
||||
from utils.jsonResponse import SuccessResponse,ErrorResponse,DetailResponse
|
||||
from utils.common import get_parameter_dic,formatdatetime
|
||||
from django.db.models import Q,F,Sum,Count
|
||||
from django.db import transaction
|
||||
from rest_framework import serializers
|
||||
from rest_framework_simplejwt.authentication import JWTAuthentication
|
||||
from utils.serializers import CustomModelSerializer
|
||||
from utils.viewset import CustomModelViewSet
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from utils.pagination import CustomPagination
|
||||
from apps.lyTiktokUnion.tools.douyin import colonelAwardTaskSync
|
||||
import django_filters
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class DyAwardTaskManageFilterset(django_filters.rest_framework.FilterSet):
|
||||
#开始时间
|
||||
beginAt = django_filters.DateTimeFilter(field_name='update_datetime', lookup_expr='gte') # 指定过滤的字段
|
||||
#结束时间
|
||||
endAt = django_filters.DateTimeFilter(field_name='update_datetime', lookup_expr='lte')
|
||||
# 模糊搜索
|
||||
task_id = django_filters.CharFilter(field_name='task_id', lookup_expr='icontains') # icontains表示该字段模糊搜索
|
||||
task_name = django_filters.CharFilter(field_name='task_name', lookup_expr='icontains') # icontains表示该字段模糊搜索
|
||||
status = django_filters.CharFilter(field_name='status')
|
||||
task_status = django_filters.CharFilter(field_name='task_status')
|
||||
|
||||
class Meta:
|
||||
model = DyAwardTaskManage
|
||||
fields = ['beginAt', 'endAt','task_status','status','task_id','task_name']
|
||||
|
||||
|
||||
class DyAwardTaskManageSerializer(CustomModelSerializer):
|
||||
task_status_name = serializers.SerializerMethodField()
|
||||
|
||||
def get_task_status_name(self,obj):
|
||||
return obj.get_task_status_display()
|
||||
|
||||
class Meta:
|
||||
model = DyAwardTaskManage
|
||||
read_only_fields = ["id"]
|
||||
fields = '__all__'
|
||||
|
||||
class DyAwardTaskManageViewSet(CustomModelViewSet):
|
||||
queryset = DyAwardTaskManage.objects.all().order_by("-sort")
|
||||
serializer_class = DyAwardTaskManageSerializer
|
||||
filterset_class = DyAwardTaskManageFilterset
|
||||
# search_fields = ("name",)
|
||||
|
||||
def editsort(self,request,*args, **kwargs):
|
||||
"""修改排序"""
|
||||
sort = get_parameter_dic(request)['sort']
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
instance = queryset.filter(id=kwargs.get('pk')).first()
|
||||
instance.sort = sort
|
||||
instance.save()
|
||||
return SuccessResponse(msg="修改成功")
|
||||
|
||||
def disablestatus(self,request,*args, **kwargs):
|
||||
"""禁用状态"""
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
instance = queryset.filter(id=kwargs.get('pk')).first()
|
||||
if instance:
|
||||
if instance.status:
|
||||
instance.status = False
|
||||
else:
|
||||
instance.status = True
|
||||
instance.save()
|
||||
return SuccessResponse(data=None, msg="修改成功")
|
||||
else:
|
||||
return ErrorResponse(msg="未获取到该数据或无权限")
|
||||
|
||||
def sync_alliance_AwardTask(self,request):
|
||||
"""同步赏金任务"""
|
||||
isok = colonelAwardTaskSync()
|
||||
if isok:
|
||||
return SuccessResponse(msg="赏金任务同步成功")
|
||||
return ErrorResponse(msg="赏金任务同步失败" )
|
||||
130
backend/apps/lyTiktokUnion/views/dyBalanceRecordViews.py
Normal file
130
backend/apps/lyTiktokUnion/views/dyBalanceRecordViews.py
Normal file
@ -0,0 +1,130 @@
|
||||
from rest_framework.views import APIView
|
||||
from apps.lyTiktokUnion.models import DYBalanceRecord
|
||||
from utils.jsonResponse import SuccessResponse,ErrorResponse,DetailResponse
|
||||
from utils.common import get_parameter_dic,formatdatetime
|
||||
from django.db.models import Q,F,Sum,Count
|
||||
from django.db import transaction
|
||||
from rest_framework import serializers
|
||||
from rest_framework_simplejwt.authentication import JWTAuthentication
|
||||
from utils.serializers import CustomModelSerializer
|
||||
from utils.viewset import CustomModelViewSet
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from utils.pagination import CustomPagination
|
||||
from apps.oauth.models import OAuthWXUser
|
||||
from utils.weixinpay import WxAppPay
|
||||
from mysystem.models import Users
|
||||
import json
|
||||
import django_filters
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class DYBalanceRecordFilterset(django_filters.rest_framework.FilterSet):
|
||||
#开始时间
|
||||
beginAt = django_filters.DateTimeFilter(field_name='create_datetime', lookup_expr='gte') # 指定过滤的字段
|
||||
#结束时间
|
||||
endAt = django_filters.DateTimeFilter(field_name='create_datetime', lookup_expr='lte')
|
||||
# 模糊搜索
|
||||
order_no = django_filters.CharFilter(field_name='order_no', lookup_expr='icontains') # icontains表示该字段模糊搜索
|
||||
# 模糊搜索
|
||||
audit_status = django_filters.CharFilter(field_name='audit_status') # icontains表示该字段模糊搜索
|
||||
|
||||
class Meta:
|
||||
model = DYBalanceRecord
|
||||
fields = ['beginAt', 'endAt','order_no','audit_status']
|
||||
|
||||
|
||||
class DYBalanceRecordSerializer(CustomModelSerializer):
|
||||
"""
|
||||
提现管理-序列化器
|
||||
"""
|
||||
userinfo = serializers.SerializerMethodField()
|
||||
status_name = serializers.SerializerMethodField()
|
||||
audit_status_name = serializers.SerializerMethodField()
|
||||
|
||||
def get_status_name(self,obj):
|
||||
return obj.get_status_display()
|
||||
|
||||
def get_audit_status_name(self,obj):
|
||||
return obj.get_audit_status_display()
|
||||
|
||||
def get_userinfo(self, obj):
|
||||
if obj.creator_id:
|
||||
creator = Users.objects.filter(id=obj.creator_id).first()
|
||||
if creator:
|
||||
return {
|
||||
'id': creator.id,
|
||||
'nickname': creator.nickname,
|
||||
'avatar': creator.avatar,
|
||||
'mobile': creator.mobile,
|
||||
}
|
||||
return {
|
||||
'id': "",
|
||||
'nickname': "",
|
||||
'avatar': "",
|
||||
'mobile': "",
|
||||
}
|
||||
|
||||
class Meta:
|
||||
model = DYBalanceRecord
|
||||
read_only_fields = ["id"]
|
||||
fields = '__all__'
|
||||
|
||||
class DYBalanceRecordViewSet(CustomModelViewSet):
|
||||
"""
|
||||
提现管理 接口
|
||||
"""
|
||||
queryset = DYBalanceRecord.objects.filter(type=1).order_by("-create_datetime")
|
||||
serializer_class = DYBalanceRecordSerializer
|
||||
filterset_class = DYBalanceRecordFilterset
|
||||
search_fields = ("creator__nickname","creator__mobile")
|
||||
|
||||
def audit(self, request):
|
||||
reqData = get_parameter_dic(request)
|
||||
id = reqData.get('id',None)
|
||||
action = reqData.get('action',None)
|
||||
remark = reqData.get('audit_remarks',None)
|
||||
# 按照原来的过滤查询
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
instance = queryset.filter(id=id,status=1).first()
|
||||
if instance is not None:
|
||||
user = instance.user
|
||||
if action == "pass":
|
||||
oldbalance = user.balance
|
||||
newbalance = oldbalance - instance.money
|
||||
# instance.status = 20
|
||||
instance.audit_status = 20
|
||||
instance.balance = newbalance
|
||||
# instance.audit_remarks = remark
|
||||
instance.save()
|
||||
#给用户钱包余额扣钱
|
||||
# user.balance = newbalance
|
||||
# user.save()
|
||||
# 开始提现
|
||||
trade_no = instance.order_no # 商户订单号
|
||||
amount = float(instance.money)*100 # '企业付款金额,单位为分'
|
||||
# openid = OAuthWXUser.objects.filter(user=user).values_list('xcx_openid', flat=True).first()
|
||||
openid = user.username
|
||||
response = WxAppPay().cashout(trade_no,amount,openid)
|
||||
content = json.loads(response.content)
|
||||
if response.status_code == 200:
|
||||
instance.status = 20
|
||||
instance.trade_no = content['batch_id']
|
||||
instance.save()
|
||||
user.save()
|
||||
else:
|
||||
logger.error("微信小程序提现返回错误,用户openid:%s,微信返回错误信息:%s" % (openid, response.text))
|
||||
instance.status = 30
|
||||
instance.save()
|
||||
return DetailResponse(msg="已通过")
|
||||
|
||||
elif action == "deny":
|
||||
instance.status = 30
|
||||
instance.audit_status = 30
|
||||
instance.audit_remarks = remark
|
||||
instance.save()
|
||||
return DetailResponse(msg="已拒绝")
|
||||
else:
|
||||
return ErrorResponse(msg="审核动作错误,只能为pass或deny")
|
||||
else:
|
||||
return ErrorResponse(msg="未获取到需要审核的数据或已审核过")
|
||||
|
||||
150
backend/apps/lyTiktokUnion/views/dyProductManageViews.py
Normal file
150
backend/apps/lyTiktokUnion/views/dyProductManageViews.py
Normal file
@ -0,0 +1,150 @@
|
||||
from rest_framework.views import APIView
|
||||
from apps.lyTiktokUnion.models import DyProductManage,DyProductCategoryManage
|
||||
from utils.jsonResponse import SuccessResponse,ErrorResponse,DetailResponse
|
||||
from utils.common import get_parameter_dic,formatdatetime
|
||||
from django.db.models import Q,F,Sum,Count
|
||||
from django.db import transaction
|
||||
from rest_framework import serializers
|
||||
from rest_framework_simplejwt.authentication import JWTAuthentication
|
||||
from utils.serializers import CustomModelSerializer
|
||||
from utils.viewset import CustomModelViewSet
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from utils.pagination import CustomPagination
|
||||
from apps.lyTiktokUnion.tools.douyin import allianceColonelGoodsSync
|
||||
import json
|
||||
import django_filters
|
||||
import datetime
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class DyProductManageFilterset(django_filters.rest_framework.FilterSet):
|
||||
#开始时间
|
||||
beginAt = django_filters.DateTimeFilter(field_name='update_datetime', lookup_expr='gte') # 指定过滤的字段
|
||||
#结束时间
|
||||
endAt = django_filters.DateTimeFilter(field_name='update_datetime', lookup_expr='lte')
|
||||
# 模糊搜索
|
||||
title = django_filters.CharFilter(field_name='title', lookup_expr='icontains') # icontains表示该字段模糊搜索
|
||||
status = django_filters.CharFilter(field_name='status')
|
||||
is_calc_award = django_filters.CharFilter(field_name='is_calc_award')
|
||||
source = django_filters.CharFilter(field_name='source')
|
||||
product_id = django_filters.CharFilter(field_name='product_id', lookup_expr='icontains')
|
||||
|
||||
class Meta:
|
||||
model = DyProductManage
|
||||
fields = ['beginAt', 'endAt','title','status','source','is_calc_award','product_id']
|
||||
|
||||
|
||||
class DyProductManageSerializer(CustomModelSerializer):
|
||||
source_name = serializers.SerializerMethodField()
|
||||
|
||||
def get_source_name(self,obj):
|
||||
return obj.get_source_display()
|
||||
|
||||
class Meta:
|
||||
model = DyProductManage
|
||||
read_only_fields = ["id"]
|
||||
fields = '__all__'
|
||||
|
||||
class DyProductManageViewSet(CustomModelViewSet):
|
||||
queryset = DyProductManage.objects.filter(is_delete=False).order_by("-sort")
|
||||
serializer_class = DyProductManageSerializer
|
||||
filterset_class = DyProductManageFilterset
|
||||
# search_fields = ("name",)
|
||||
|
||||
def sync_alliance_colonel_goods(self,request):
|
||||
"""同步团长活动商品"""
|
||||
isok = allianceColonelGoodsSync()
|
||||
if isok:
|
||||
return SuccessResponse(msg="团长活动商品同步成功")
|
||||
return ErrorResponse(msg="团长活动商品同步失败" )
|
||||
|
||||
def editAttribute(self,request,*args, **kwargs):
|
||||
"""编辑属性"""
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
instance = queryset.filter(id=kwargs.get('pk')).first()
|
||||
reqData = get_parameter_dic(request)
|
||||
instance.sample_stock = reqData.get('sample_stock',0)
|
||||
instance.limit_d30_sales = reqData.get('limit_d30_sales', 0)
|
||||
instance.begin_time = reqData.get('begin_time', None)
|
||||
instance.end_time = reqData.get('end_time', None)
|
||||
instance.save()
|
||||
return DetailResponse(msg="修改成功")
|
||||
|
||||
def editsort(self,request,*args, **kwargs):
|
||||
"""修改排序"""
|
||||
sort = get_parameter_dic(request)['sort']
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
instance = queryset.filter(id=kwargs.get('pk')).first()
|
||||
instance.sort = sort
|
||||
instance.save()
|
||||
return SuccessResponse(msg="修改成功")
|
||||
|
||||
def disablestatus(self,request,*args, **kwargs):
|
||||
"""禁用状态"""
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
instance = queryset.filter(id=kwargs.get('pk')).first()
|
||||
if instance:
|
||||
if instance.status:
|
||||
instance.status = False
|
||||
else:
|
||||
instance.status = True
|
||||
instance.save()
|
||||
return SuccessResponse(data=None, msg="修改成功")
|
||||
else:
|
||||
return ErrorResponse(msg="未获取到该数据或无权限")
|
||||
|
||||
def disablecalcaward(self,request,*args, **kwargs):
|
||||
"""禁用计算奖励"""
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
instance = queryset.filter(id=kwargs.get('pk')).first()
|
||||
if instance:
|
||||
if instance.is_calc_award:
|
||||
instance.is_calc_award = False
|
||||
else:
|
||||
instance.is_calc_award = True
|
||||
instance.save()
|
||||
return SuccessResponse(data=None, msg="修改成功")
|
||||
else:
|
||||
return ErrorResponse(msg="未获取到该数据或无权限")
|
||||
|
||||
|
||||
class DyProductCategoryManageFilterset(django_filters.rest_framework.FilterSet):
|
||||
#开始时间
|
||||
beginAt = django_filters.DateTimeFilter(field_name='update_datetime', lookup_expr='gte') # 指定过滤的字段
|
||||
#结束时间
|
||||
endAt = django_filters.DateTimeFilter(field_name='update_datetime', lookup_expr='lte')
|
||||
# 模糊搜索
|
||||
name = django_filters.CharFilter(field_name='name', lookup_expr='icontains') # icontains表示该字段模糊搜索
|
||||
status = django_filters.CharFilter(field_name='status')
|
||||
|
||||
class Meta:
|
||||
model = DyProductCategoryManage
|
||||
fields = ['beginAt', 'endAt','name','status']
|
||||
|
||||
|
||||
class DyProductCategoryManageSerializer(CustomModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = DyProductCategoryManage
|
||||
read_only_fields = ["id"]
|
||||
fields = '__all__'
|
||||
|
||||
class DyProductCategoryManageViewSet(CustomModelViewSet):
|
||||
queryset = DyProductCategoryManage.objects.all().order_by("-sort")
|
||||
serializer_class = DyProductCategoryManageSerializer
|
||||
filterset_class = DyProductCategoryManageFilterset
|
||||
search_fields = ("name",)
|
||||
|
||||
def disablestatus(self,request,*args, **kwargs):
|
||||
"""禁用状态"""
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
instance = queryset.filter(id=kwargs.get('pk')).first()
|
||||
if instance:
|
||||
if instance.status:
|
||||
instance.status = False
|
||||
else:
|
||||
instance.status = True
|
||||
instance.save()
|
||||
return SuccessResponse(data=None, msg="修改成功")
|
||||
else:
|
||||
return ErrorResponse(msg="未获取到该数据或无权限")
|
||||
303
backend/apps/lyTiktokUnion/views/dySystemAccountViews.py
Normal file
303
backend/apps/lyTiktokUnion/views/dySystemAccountViews.py
Normal file
@ -0,0 +1,303 @@
|
||||
from rest_framework.views import APIView
|
||||
from apps.lyTiktokUnion.models import DYSystemAccount
|
||||
from utils.jsonResponse import SuccessResponse,ErrorResponse,DetailResponse
|
||||
from utils.common import get_parameter_dic,formatdatetime
|
||||
from django.db.models import Q,F,Sum,Count
|
||||
from django.db import transaction
|
||||
from rest_framework import serializers
|
||||
from rest_framework_simplejwt.authentication import JWTAuthentication
|
||||
from utils.serializers import CustomModelSerializer
|
||||
from utils.viewset import CustomModelViewSet
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from utils.pagination import CustomPagination
|
||||
from utils.douyin.douyin_utils import douyin
|
||||
from utils.douyin.douyinUnion_utils import douyinUnion
|
||||
from config import DOUYIN_OPENAPI_CLIENT_KEY,DOUYIN_OPENAPI_CLIENT_SECRET,DOMAIN_HOST
|
||||
from apps.lyTiktokUnion.tools.douyin import newdoudian_dklive_yz,newdoudian_dkfx_gj,newdoudian_tz_yz
|
||||
from django.http import HttpResponse
|
||||
import datetime
|
||||
import time
|
||||
import json
|
||||
import django_filters
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class DYSystemAccountFilterset(django_filters.rest_framework.FilterSet):
|
||||
#开始时间
|
||||
beginAt = django_filters.DateTimeFilter(field_name='update_datetime', lookup_expr='gte') # 指定过滤的字段
|
||||
#结束时间
|
||||
endAt = django_filters.DateTimeFilter(field_name='update_datetime', lookup_expr='lte')
|
||||
# 模糊搜索
|
||||
nickname = django_filters.CharFilter(field_name='nickname', lookup_expr='icontains') # icontains表示该字段模糊搜索
|
||||
status = django_filters.CharFilter(field_name='status')
|
||||
|
||||
class Meta:
|
||||
model = DYSystemAccount
|
||||
fields = ['beginAt', 'endAt','nickname','status']
|
||||
|
||||
|
||||
class DYSystemAccountSerializer(CustomModelSerializer):
|
||||
token_status = serializers.SerializerMethodField()
|
||||
|
||||
def get_token_status(self,obj):
|
||||
status = {
|
||||
"access_token":False,
|
||||
"refresh_token":False
|
||||
}
|
||||
now = datetime.datetime.now()
|
||||
if obj.access_token_expire >= now:
|
||||
status['access_token'] = True
|
||||
if obj.refresh_token_expire >=now:
|
||||
status['refresh_token'] = True
|
||||
return status
|
||||
|
||||
class Meta:
|
||||
model = DYSystemAccount
|
||||
read_only_fields = ["id"]
|
||||
fields = '__all__'
|
||||
|
||||
# 刷新抖店的认证,保持access_token为有效状态
|
||||
def keepDouDianTokenOnline():
|
||||
queryset = DYSystemAccount.objects.filter(is_delete=False)
|
||||
for m in queryset:
|
||||
if m.identity == 1:
|
||||
dk_accesstoken_ttl = newdoudian_dkfx_gj.get_cache_access_token_ttl()
|
||||
dk_refreshtoken_ttl = newdoudian_dkfx_gj.get_cache_refresh_access_token_ttl()
|
||||
if (dk_accesstoken_ttl < 0 or dk_accesstoken_ttl < 2*60*60) and dk_refreshtoken_ttl > 0:
|
||||
logger.info("开始刷新抖客token...")
|
||||
refresh_bool = newdoudian_dkfx_gj.refresh_token()
|
||||
if refresh_bool:
|
||||
nowtimestap = int(time.time())
|
||||
access_token_expire_in = int(newdoudian_dkfx_gj.get_cache_access_token_ttl())
|
||||
refresh_access_expire_in = int(newdoudian_dkfx_gj.get_cache_refresh_access_token_ttl())
|
||||
new_access_token = newdoudian_dkfx_gj.get_access_token()
|
||||
new_refresh_token = newdoudian_dkfx_gj.get_cache_refresh_token()
|
||||
access_token_expire = datetime.datetime.fromtimestamp(nowtimestap + access_token_expire_in)
|
||||
refresh_token_expire = datetime.datetime.fromtimestamp(nowtimestap + refresh_access_expire_in)
|
||||
m.access_token = new_access_token
|
||||
m.refresh_token = new_refresh_token
|
||||
m.access_token_expire = access_token_expire
|
||||
m.refresh_token_expire = refresh_token_expire
|
||||
m.save()
|
||||
logger.info("刷新抖客token成功")
|
||||
else:
|
||||
logger.error("刷新抖客token失败")
|
||||
if m.identity == 2:
|
||||
tz_accesstoken_ttl = newdoudian_tz_yz.get_cache_access_token_ttl()
|
||||
tz_refreshtoken_ttl = newdoudian_tz_yz.get_cache_refresh_access_token_ttl()
|
||||
if (tz_accesstoken_ttl < 0 or tz_accesstoken_ttl < 2*60*60) and tz_refreshtoken_ttl > 0:
|
||||
logger.info("开始刷新团长token...")
|
||||
refresh_bool = newdoudian_tz_yz.refresh_token()
|
||||
if refresh_bool:
|
||||
nowtimestap = int(time.time())
|
||||
access_token_expire_in = int(newdoudian_tz_yz.get_cache_access_token_ttl())
|
||||
refresh_access_expire_in = int(newdoudian_tz_yz.get_cache_refresh_access_token_ttl())
|
||||
new_access_token = newdoudian_tz_yz.get_access_token()
|
||||
new_refresh_token = newdoudian_tz_yz.get_cache_refresh_token()
|
||||
access_token_expire = datetime.datetime.fromtimestamp(nowtimestap + access_token_expire_in)
|
||||
refresh_token_expire = datetime.datetime.fromtimestamp(nowtimestap + refresh_access_expire_in)
|
||||
m.access_token = new_access_token
|
||||
m.refresh_token = new_refresh_token
|
||||
m.access_token_expire = access_token_expire
|
||||
m.refresh_token_expire = refresh_token_expire
|
||||
m.save()
|
||||
logger.info("刷新团长token成功")
|
||||
else:
|
||||
logger.error("刷新团长token失败")
|
||||
|
||||
class DYSystemAccountViewSet(CustomModelViewSet):
|
||||
queryset = DYSystemAccount.objects.filter(is_delete=False).order_by("create_datetime")
|
||||
serializer_class = DYSystemAccountSerializer
|
||||
filterset_class = DYSystemAccountFilterset
|
||||
search_fields = ("nickname",)
|
||||
|
||||
#重写delete方法,并改为逻辑删除
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
instance = self.get_object_list()
|
||||
for i in range(len(instance)):
|
||||
instance[i].is_delete = True
|
||||
model = self.get_serializer().Meta.model
|
||||
model.objects.bulk_update(instance,fields=['is_delete'])
|
||||
return SuccessResponse(data=[], msg="删除成功")
|
||||
|
||||
# 工具型应用 抖客/达人 拼链接 方式 应用授权,抖客再浏览器登录自己的抖客账号,登录后访问此授权链接点击授权,会重定向授权code到 此 应用的【使用地址】(包括state)
|
||||
# 拼接后的地址如: https://buyin.jinritemai.com/dashboard/institution/through-power?app_id=7252637730696414757&state=state
|
||||
def createCodeBuyin(self,request):
|
||||
id = get_parameter_dic(request).get("id",None)
|
||||
if not id:#新增授权
|
||||
url = newdoudian_dkfx_gj.build_auth_url(type=3)
|
||||
else:#重新授权
|
||||
url = newdoudian_dkfx_gj.build_auth_url(type=3,state=id)
|
||||
return SuccessResponse(data=url)
|
||||
|
||||
def getCodeBuyinUserinfo(self,request):
|
||||
#先触发刷新token
|
||||
keepDouDianTokenOnline()
|
||||
datas = []
|
||||
data = {
|
||||
'app_key':newdoudian_dkfx_gj.getCurrentAppKey(),
|
||||
'buyin_id': "",
|
||||
'name':"",
|
||||
'status':"过期",
|
||||
'identity':'抖客',
|
||||
'expiretime':""
|
||||
}
|
||||
instance = DYSystemAccount.objects.filter(identity=1,is_delete=False).first()
|
||||
if instance:
|
||||
buyin_id = instance.buyin_id
|
||||
data['buyin_id'] = buyin_id
|
||||
data['name'] = instance.nickname
|
||||
if newdoudian_dkfx_gj.get_cache_access_token_ttl():
|
||||
data['status'] = "正常"
|
||||
data['expiretime'] = round((newdoudian_dkfx_gj.get_cache_access_token_ttl())/3600,1)
|
||||
else:
|
||||
data['name'] = "请使用抖客授权"
|
||||
datas.append(data)
|
||||
|
||||
data2 = {
|
||||
'app_key':newdoudian_tz_yz.getCurrentAppKey(),
|
||||
'buyin_id':"",
|
||||
'name':"",
|
||||
'status':"过期",
|
||||
'identity':'团长',
|
||||
'expiretime':""
|
||||
}
|
||||
|
||||
instance2 = DYSystemAccount.objects.filter(identity=2, is_delete=False).first()
|
||||
if instance2:
|
||||
buyin_id = instance2.buyin_id
|
||||
data2['buyin_id'] = buyin_id
|
||||
data2['name'] = instance2.nickname
|
||||
if newdoudian_tz_yz.get_cache_access_token_ttl():
|
||||
data2['status'] = "正常"
|
||||
data2['expiretime'] = round((newdoudian_tz_yz.get_cache_access_token_ttl()) / 3600, 1)
|
||||
else:
|
||||
data2['name'] = "请使用团长授权"
|
||||
datas.append(data2)
|
||||
return SuccessResponse(data=datas)
|
||||
|
||||
# 生成抖音授权码链接
|
||||
def createCode(self,request):
|
||||
id = get_parameter_dic(request).get("id",None)
|
||||
newdouyin = douyin(client_key=DOUYIN_OPENAPI_CLIENT_KEY,client_secret=DOUYIN_OPENAPI_CLIENT_SECRET)
|
||||
scope = "user_info,data.external.user,alliance.colonel.service,alliance.kol.buyin_id,alliance.product.skus,alliance.kol.store_manage,alliance.kol.reputation,alliance.kol.orders,alliance.kol.materials,alliance.kol.reputation,alliance.picksource.convert,alliance.kol.live_status"
|
||||
redirect_uri = DOMAIN_HOST+"/api/system/douyincodeCallback/"
|
||||
if not id:#新增授权
|
||||
url = newdouyin.create_code(scope=scope,redirect_uri=redirect_uri)
|
||||
else:#重新授权
|
||||
url = newdouyin.create_code(scope=scope,redirect_uri=redirect_uri,state=id)
|
||||
return SuccessResponse(data=url)
|
||||
|
||||
# 刷新抖音授权码链接
|
||||
def refreshAccessToken(self,request):
|
||||
id = get_parameter_dic(request)['id']
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
instance = queryset.filter(id=id,is_delete=False).first()
|
||||
if not instance:
|
||||
return ErrorResponse(msg="id error")
|
||||
now = datetime.datetime.now()
|
||||
refresh_datetime = datetime.timedelta(days=3)#提前三天刷新refresh_token
|
||||
if instance.refresh_token_expire < now:
|
||||
return ErrorResponse(msg="refresh_token已过期,请重新授权")
|
||||
newdouyin = douyin(client_key=DOUYIN_OPENAPI_CLIENT_KEY,client_secret=DOUYIN_OPENAPI_CLIENT_SECRET)
|
||||
#先检查是否要刷新refresh_token
|
||||
if instance.refresh_token_expire <= (now + refresh_datetime):
|
||||
refresh_res = newdouyin.refresh_refresh_token(instance.refresh_token)
|
||||
if refresh_res:
|
||||
if "error_code" in refresh_res["data"] and refresh_res["data"]["error_code"] == 0:
|
||||
refresh_res = refresh_res['data']
|
||||
refresh_token = refresh_res.get("refresh_token")
|
||||
refresh_expires_in = refresh_res.get("refresh_expires_in")
|
||||
refresh_token_expire = now + datetime.timedelta(seconds=int(refresh_expires_in))
|
||||
instance.refresh_token_expire = refresh_token_expire
|
||||
instance.refresh_token = refresh_token
|
||||
instance.save()
|
||||
#开始刷新access_token
|
||||
res = newdouyin.refresh_access_token(instance.refresh_token)
|
||||
if not res:
|
||||
return ErrorResponse(msg="刷新token错误")
|
||||
if "error_code" in res["data"] and not res["data"]["error_code"] == 0:
|
||||
return ErrorResponse(msg="获取刷新access_token失败,错误码:%s,描述:%s"%(res["data"]["error_code"],res["data"]["description"]))
|
||||
res = res['data']
|
||||
instance.access_token = res.get("access_token")
|
||||
expires_in = res.get("expires_in")
|
||||
access_token_expire = now + datetime.timedelta(seconds=int(expires_in))
|
||||
instance.access_token_expire = access_token_expire
|
||||
instance.save()
|
||||
return SuccessResponse(msg="刷新成功")
|
||||
|
||||
|
||||
#抖音后台达人账号code授权回调
|
||||
class DouyinSytemDarenCodeCallbackView(APIView):
|
||||
'''
|
||||
抖音后台达人账号code授权回调
|
||||
get:
|
||||
抖音后台达人账号code授权回调
|
||||
'''
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
|
||||
def get(self, request):
|
||||
newdouyin = douyin(client_key=DOUYIN_OPENAPI_CLIENT_KEY,client_secret=DOUYIN_OPENAPI_CLIENT_SECRET)
|
||||
data = get_parameter_dic(request)
|
||||
code,state = newdouyin.callback_code(data)
|
||||
if not all([code,state]):
|
||||
return HttpResponse("非法请求")
|
||||
logger.info("获取code回调信息code=%s|state=%s"%(code,state))
|
||||
if state == "state":#新增
|
||||
now = datetime.datetime.now()
|
||||
res = newdouyin.get_access_token(code=code)
|
||||
if not res:
|
||||
return HttpResponse("获取access_token失败")
|
||||
if "error_code" in res["data"] and not res["data"]["error_code"] == 0:
|
||||
return HttpResponse("获取access_token失败,错误码:%s,描述:%s"%(res["data"]["error_code"],res["data"]["description"]))
|
||||
logger.info("新增系统抖客/达人授权信息,获取token信息:%s"%(res))
|
||||
res = res['data']
|
||||
access_token = res.get("access_token")
|
||||
refresh_token = res.get("refresh_token")
|
||||
refresh_expires_in = res.get("refresh_expires_in")
|
||||
expires_in = res.get("expires_in")
|
||||
access_token_expire = now + datetime.timedelta(seconds=int(expires_in))
|
||||
refresh_token_expire = now + datetime.timedelta(seconds=int(refresh_expires_in))
|
||||
open_id = res.get("open_id")
|
||||
# newdouyinunion = douyinUnion(client_key=DOUYIN_OPENAPI_CLIENT_KEY,client_secret=DOUYIN_OPENAPI_CLIENT_SECRET)
|
||||
# buyin_id_res = newdouyinunion.get_buyin_id(access_token=access_token,open_id=open_id)
|
||||
# if not buyin_id_res:
|
||||
# return HttpResponse("获取百应ID失败")
|
||||
# logger.info("新增系统抖客/达人授权信息,获取百应ID信息:%s"%(buyin_id_res))
|
||||
# buyin_id = buyin_id_res.get("buyin_id",None)
|
||||
userinfo = newdouyin.get_userinfo(access_token=access_token,open_id=open_id)
|
||||
if not userinfo:
|
||||
return HttpResponse("获取userinfo失败")
|
||||
logger.info("新增系统抖客/达人授权信息,获取用户基本信息%s"%(userinfo))
|
||||
if "error_code" in userinfo["data"] and not userinfo["data"]["error_code"] == 0:
|
||||
return HttpResponse("获取userinfo失败,错误码:%s,描述:%s"%(userinfo["data"]["error_code"],userinfo["data"]["description"]))
|
||||
userinfo = userinfo['data']
|
||||
avatar = userinfo.get("avatar")
|
||||
nickname = userinfo.get("nickname")
|
||||
DYSystemAccount.objects.create(refresh_token=refresh_token,access_token=access_token,open_id=open_id,avatar=avatar,nickname=nickname,access_token_expire=access_token_expire,refresh_token_expire=refresh_token_expire)
|
||||
return HttpResponse("获取授权成功!!!")
|
||||
#重新授权
|
||||
instance = DYSystemAccount.objects.filter(id=state,is_delete=False).first()
|
||||
if not instance:
|
||||
return HttpResponse("非法请求2")
|
||||
now = datetime.datetime.now()
|
||||
res = newdouyin.get_access_token(code=code)
|
||||
if not res:
|
||||
return HttpResponse("获取access_token失败")
|
||||
if "error_code" in res["data"] and not res["data"]["error_code"] == 0:
|
||||
return HttpResponse("获取access_token失败,错误码:%s,描述:%s"%(res["data"]["error_code"],res["data"]["description"]))
|
||||
res = res['data']
|
||||
access_token = res.get("access_token")
|
||||
refresh_token = res.get("refresh_token")
|
||||
refresh_expires_in = res.get("refresh_expires_in")
|
||||
expires_in = res.get("expires_in")
|
||||
access_token_expire = now + datetime.timedelta(seconds=int(expires_in))
|
||||
refresh_token_expire = now + datetime.timedelta(seconds=int(refresh_expires_in))
|
||||
instance.access_token = access_token
|
||||
instance.refresh_token = refresh_token
|
||||
instance.access_token_expire = access_token_expire
|
||||
instance.refresh_token_expire = refresh_token_expire
|
||||
instance.save()
|
||||
return HttpResponse("获取授权成功!!!")
|
||||
|
||||
0
backend/apps/lyautocode/__init__.py
Normal file
0
backend/apps/lyautocode/__init__.py
Normal file
5
backend/apps/lyautocode/apps.py
Normal file
5
backend/apps/lyautocode/apps.py
Normal file
@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
class LyautocodeConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = 'apps.lyautocode'
|
||||
0
backend/apps/lyautocode/filters/__init__.py
Normal file
0
backend/apps/lyautocode/filters/__init__.py
Normal file
28
backend/apps/lyautocode/filters/filter_StudentManage.py
Normal file
28
backend/apps/lyautocode/filters/filter_StudentManage.py
Normal file
@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import django_filters
|
||||
from apps.lyautocode.models.models_StudentManage import StudentManage
|
||||
|
||||
class StudentManageFilterSet(django_filters.rest_framework.FilterSet):
|
||||
"""
|
||||
学生管理 过滤器
|
||||
URL格式:http://127.0.0.1:8000/?beginAt=2020-12-02 12:00:00&endAt=2021-12-13 12:00:00
|
||||
field_name: 过滤字段名,一般应该对应模型中字段名
|
||||
lookup_expr: 查询时所要进行的操作,和ORM中运算符一致
|
||||
fields:指明过滤字段,可以是列表,也可以字典
|
||||
exclude = ['password'] 排除字段,不允许使用列表中字典进行过滤
|
||||
自定义字段名可以和模型中不一致,但一定要用参数field_name指明对应模型中的字段名
|
||||
"""
|
||||
#开始时间
|
||||
beginAt = django_filters.DateTimeFilter(field_name='create_datetime', lookup_expr='gte') # 指定过滤的字段
|
||||
#结束时间
|
||||
endAt = django_filters.DateTimeFilter(field_name='create_datetime', lookup_expr='lte')
|
||||
name = django_filters.CharFilter(field_name='name',lookup_expr='icontains')
|
||||
birthday_beginAt = django_filters.DateFilter(field_name='birthday', lookup_expr='gte')
|
||||
birthday_endAt = django_filters.DateFilter(field_name='birthday', lookup_expr='lte')
|
||||
status = django_filters.BooleanFilter(field_name='status')
|
||||
user = django_filters.CharFilter(field_name='user__mobile',lookup_expr='exact')
|
||||
|
||||
class Meta:
|
||||
model = StudentManage
|
||||
fields = ['beginAt', 'endAt', 'name', 'birthday_beginAt', 'birthday_endAt', 'status', 'user']
|
||||
73
backend/apps/lyautocode/migrations/0001_initial.py
Normal file
73
backend/apps/lyautocode/migrations/0001_initial.py
Normal file
@ -0,0 +1,73 @@
|
||||
# Generated by Django 4.1.3 on 2023-05-13 11:29
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import utils.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('mysystem', '0007_alter_dept_options'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='StudentManage',
|
||||
fields=[
|
||||
('id', models.CharField(default=utils.models.make_uuid, help_text='Id', max_length=100, primary_key=True, serialize=False, verbose_name='Id')),
|
||||
('modifier', models.CharField(blank=True, help_text='修改人', max_length=100, null=True, verbose_name='修改人')),
|
||||
('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=100, null=True, verbose_name='数据归属部门')),
|
||||
('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')),
|
||||
('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),
|
||||
('avatar', models.CharField(blank=True, default='', max_length=200, null=True, verbose_name='头像')),
|
||||
('name', models.CharField(default='', max_length=50, verbose_name='姓名')),
|
||||
('gender', models.SmallIntegerField(blank=True, default=0, null=True, verbose_name='性别')),
|
||||
('gender2', models.SmallIntegerField(default=0, verbose_name='性别选择')),
|
||||
('age', models.SmallIntegerField(blank=True, null=True, verbose_name='年龄')),
|
||||
('birthday', models.DateField(blank=True, null=True, verbose_name='生日')),
|
||||
('status', models.BooleanField(blank=True, default=False, null=True, verbose_name='状态')),
|
||||
('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
|
||||
('user', models.ForeignKey(blank=True, db_constraint=False, default='', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='lyauto_studentmanage_user_user', to=settings.AUTH_USER_MODEL, verbose_name='关联账号')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '学生管理',
|
||||
'verbose_name_plural': '学生管理',
|
||||
'db_table': 't_student',
|
||||
'ordering': ['-create_datetime'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LyAutoCode',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('modifier', models.CharField(blank=True, help_text='修改人', max_length=100, null=True, verbose_name='修改人')),
|
||||
('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')),
|
||||
('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),
|
||||
('is_delete', models.BooleanField(default=False, help_text='是否逻辑删除', verbose_name='是否逻辑删除')),
|
||||
('verbose_name', models.CharField(help_text='表中文名称', max_length=30, verbose_name='表中文名称')),
|
||||
('class_name', models.CharField(help_text='模型类名', max_length=32, verbose_name='模型类名')),
|
||||
('db_table', models.CharField(help_text='表名', max_length=50, verbose_name='表名')),
|
||||
('foreign_key', models.CharField(blank=True, help_text='外键配置', max_length=100, null=True, verbose_name='外键配置')),
|
||||
('column', models.TextField(blank=True, help_text='字段列信息列表', null=True, verbose_name='字段列信息列表')),
|
||||
('other_config', models.TextField(blank=True, help_text='其他配置', null=True, verbose_name='其他配置')),
|
||||
('remark', models.CharField(blank=True, help_text='描述', max_length=100, null=True, verbose_name='描述')),
|
||||
('is_mount', models.BooleanField(default=False, help_text='是否生成挂载', verbose_name='是否生成挂载')),
|
||||
('file_name_old', models.CharField(blank=True, help_text='上一次生成文件名(通用)', max_length=100, null=True, verbose_name='上一次生成文件名(通用)')),
|
||||
('parent_menu', models.CharField(blank=True, help_text='上级菜单', max_length=50, null=True, verbose_name='上级菜单')),
|
||||
('menu_sort', models.PositiveSmallIntegerField(default=0, help_text='菜单排序', verbose_name='菜单排序')),
|
||||
('creator', models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
|
||||
('menu', models.ForeignKey(blank=True, db_constraint=False, help_text='关联菜单', null=True, on_delete=django.db.models.deletion.SET_NULL, to='mysystem.menu', verbose_name='关联菜单')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '代码生成',
|
||||
'verbose_name_plural': '代码生成',
|
||||
'db_table': 'lyadmin_autocode',
|
||||
'ordering': ('-create_datetime',),
|
||||
},
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.1.8 on 2023-05-17 14:41
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('lyautocode', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='studentmanage',
|
||||
name='file',
|
||||
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='附件'),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.1.3 on 2023-12-06 21:58
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('lyautocode', '0002_studentmanage_file'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='studentmanage',
|
||||
name='id',
|
||||
field=models.BigAutoField(help_text='Id', primary_key=True, serialize=False, verbose_name='Id'),
|
||||
),
|
||||
]
|
||||
0
backend/apps/lyautocode/migrations/__init__.py
Normal file
0
backend/apps/lyautocode/migrations/__init__.py
Normal file
26
backend/apps/lyautocode/models/__init__.py
Normal file
26
backend/apps/lyautocode/models/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
import os
|
||||
import importlib
|
||||
from django.conf import settings
|
||||
|
||||
autoCodePath = os.path.join(settings.BASE_DIR, 'apps','lyautocode','models')
|
||||
|
||||
def get_modules(package="."):
|
||||
"""
|
||||
获取包名下所有非__init__的模块名
|
||||
"""
|
||||
modules = []
|
||||
files = os.listdir(package)
|
||||
|
||||
for file in files:
|
||||
if not file.startswith("__"):
|
||||
name, ext = os.path.splitext(file)
|
||||
modules.append("." + name)
|
||||
|
||||
return modules
|
||||
|
||||
|
||||
modules = get_modules(autoCodePath)
|
||||
|
||||
# 将包下的所有模块,逐个导入
|
||||
for module in modules:
|
||||
importlib.import_module(module, 'apps.lyautocode.models')
|
||||
25
backend/apps/lyautocode/models/models_StudentManage.py
Normal file
25
backend/apps/lyautocode/models/models_StudentManage.py
Normal file
@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.db import models
|
||||
from utils.models import CoreModel,BaseModel,SimpleCoreModel
|
||||
from mysystem.models import Users
|
||||
|
||||
class StudentManage(CoreModel):
|
||||
"""
|
||||
学生管理
|
||||
"""
|
||||
avatar = models.CharField(verbose_name="头像", max_length=200, null=True,blank=True,default='')
|
||||
name = models.CharField(verbose_name="姓名", max_length=50,default='')
|
||||
gender = models.SmallIntegerField(verbose_name="性别", null=True,blank=True,default=0)
|
||||
gender2 = models.SmallIntegerField(verbose_name="性别选择",default=0)
|
||||
age = models.SmallIntegerField(verbose_name="年龄", null=True,blank=True)
|
||||
birthday = models.DateField(verbose_name="生日", null=True,blank=True)
|
||||
status = models.BooleanField(verbose_name="状态", default=False, null=True,blank=True)
|
||||
user = models.ForeignKey(Users, on_delete=models.CASCADE, related_name="lyauto_studentmanage_user_user", db_constraint=False, verbose_name="关联账号", null=True,blank=True,default='')
|
||||
file = models.CharField(verbose_name="附件", max_length=255, null=True,blank=True)
|
||||
|
||||
class Meta:
|
||||
db_table = "t_student"
|
||||
verbose_name = "学生管理"
|
||||
verbose_name_plural = verbose_name
|
||||
ordering = ['-create_datetime']
|
||||
app_label = 'lyautocode'#不放在lyautocode的app中此配置项可去掉
|
||||
27
backend/apps/lyautocode/models/models_autocode.py
Normal file
27
backend/apps/lyautocode/models/models_autocode.py
Normal file
@ -0,0 +1,27 @@
|
||||
from django.db import models
|
||||
from utils.models import CoreModel,BaseModel,SimpleCoreModel,table_prefix
|
||||
from mysystem.models import Menu
|
||||
|
||||
class LyAutoCode(SimpleCoreModel):
|
||||
"""
|
||||
代码生成
|
||||
"""
|
||||
verbose_name = models.CharField(max_length=30, verbose_name="表中文名称", help_text="表中文名称")
|
||||
class_name = models.CharField(max_length=32, verbose_name="模型类名", help_text="模型类名")
|
||||
db_table = models.CharField(max_length=50, verbose_name="表名", help_text="表名")
|
||||
foreign_key = models.CharField(max_length=100, verbose_name="外键配置", help_text="外键配置", null=True, blank=True)
|
||||
column = models.TextField(verbose_name="字段列信息列表", help_text="字段列信息列表", null=True, blank=True)
|
||||
other_config = models.TextField(verbose_name="其他配置", help_text="其他配置", null=True, blank=True)
|
||||
remark = models.CharField(max_length=100, verbose_name="描述", null=True, blank=True, help_text="描述")
|
||||
is_mount = models.BooleanField(default=False, verbose_name="是否生成挂载", help_text="是否生成挂载")
|
||||
file_name_old = models.CharField(max_length=100, null=True, blank=True, verbose_name="上一次生成文件名(通用)", help_text="上一次生成文件名(通用)")#第一次创建默认为class_name通用名
|
||||
parent_menu = models.CharField(max_length=50, verbose_name="上级菜单", null=True, blank=True, help_text="上级菜单")
|
||||
menu_sort = models.PositiveSmallIntegerField(default=0, verbose_name="菜单排序", help_text="菜单排序")
|
||||
menu = models.ForeignKey(Menu, on_delete=models.SET_NULL, verbose_name="关联菜单", null=True, blank=True,db_constraint=False, help_text="关联菜单")
|
||||
|
||||
class Meta:
|
||||
db_table = table_prefix + "autocode"
|
||||
verbose_name = '代码生成'
|
||||
verbose_name_plural = verbose_name
|
||||
app_label = 'lyautocode'
|
||||
ordering = ('-create_datetime',)
|
||||
0
backend/apps/lyautocode/serializers/__init__.py
Normal file
0
backend/apps/lyautocode/serializers/__init__.py
Normal file
@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from rest_framework import serializers
|
||||
from apps.lyautocode.models.models_StudentManage import StudentManage
|
||||
from utils.serializers import CustomModelSerializer
|
||||
from utils.common import ast_convert
|
||||
|
||||
class StudentManageSerializer(CustomModelSerializer):
|
||||
"""
|
||||
学生管理 序列化器
|
||||
"""
|
||||
user_lyautocode_name=serializers.SerializerMethodField(read_only=True)
|
||||
def get_user_lyautocode_name(self,obj):
|
||||
if obj.user_id:
|
||||
data = {'name': 'obj.user.name', 'mobile': 'obj.user.mobile'}
|
||||
for d in data:
|
||||
data[d] = eval(data[d])
|
||||
return data
|
||||
return ''
|
||||
|
||||
class Meta:
|
||||
model = StudentManage
|
||||
read_only_fields = ["id"]
|
||||
fields = '__all__'
|
||||
29
backend/apps/lyautocode/urls/__init__.py
Normal file
29
backend/apps/lyautocode/urls/__init__.py
Normal file
@ -0,0 +1,29 @@
|
||||
import os
|
||||
import importlib
|
||||
from django.conf import settings
|
||||
|
||||
autoCodePath = os.path.join(settings.BASE_DIR, 'apps','lyautocode','urls')
|
||||
|
||||
def get_modules(package="."):
|
||||
"""
|
||||
获取包名下所有非__init__的模块名
|
||||
"""
|
||||
modules = []
|
||||
files = os.listdir(package)
|
||||
|
||||
for file in files:
|
||||
if not file.startswith("__"):
|
||||
name, ext = os.path.splitext(file)
|
||||
modules.append("." + name)
|
||||
|
||||
return modules
|
||||
|
||||
|
||||
modules = get_modules(autoCodePath)
|
||||
|
||||
urlpatterns = []
|
||||
|
||||
# 将包下的所有模块,逐个导入
|
||||
for module in modules:
|
||||
mod = importlib.import_module(module, 'apps.lyautocode.urls')
|
||||
urlpatterns += mod.urlpatterns
|
||||
14
backend/apps/lyautocode/urls/urls_StudentManage.py
Normal file
14
backend/apps/lyautocode/urls/urls_StudentManage.py
Normal file
@ -0,0 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#路由文件
|
||||
|
||||
from django.urls import path, re_path
|
||||
from rest_framework import routers
|
||||
|
||||
from apps.lyautocode.views.views_StudentManage import StudentManageViewSet
|
||||
|
||||
system_url = routers.SimpleRouter()
|
||||
system_url.register(r'StudentManage', StudentManageViewSet)
|
||||
|
||||
urlpatterns = []
|
||||
urlpatterns += system_url.urls
|
||||
19
backend/apps/lyautocode/urls/urls_autocode.py
Normal file
19
backend/apps/lyautocode/urls/urls_autocode.py
Normal file
@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#路由文件
|
||||
|
||||
from django.urls import path, re_path
|
||||
from rest_framework import routers
|
||||
|
||||
from apps.lyautocode.views.views_autocode import LyAutoCodeViewSet
|
||||
|
||||
system_url = routers.SimpleRouter()
|
||||
system_url.register(r'autocode', LyAutoCodeViewSet)
|
||||
|
||||
urlpatterns = [
|
||||
path('autocode/previewcode/',LyAutoCodeViewSet.as_view({'get':'previewCode'}), name='预览'),
|
||||
path('autocode/generatemount/',LyAutoCodeViewSet.as_view({'post':'generateMount'}), name='同步文件'),
|
||||
path('autocode/syncdb/',LyAutoCodeViewSet.as_view({'post':'syncdb'}), name='同步数据库'),
|
||||
|
||||
]
|
||||
urlpatterns += system_url.urls
|
||||
26
backend/apps/lyautocode/views/__init__.py
Normal file
26
backend/apps/lyautocode/views/__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
import os
|
||||
import importlib
|
||||
from django.conf import settings
|
||||
|
||||
autoCodePath = os.path.join(settings.BASE_DIR, 'apps','lyautocode','views')
|
||||
|
||||
def get_modules(package="."):
|
||||
"""
|
||||
获取包名下所有非__init__的模块名
|
||||
"""
|
||||
modules = []
|
||||
files = os.listdir(package)
|
||||
|
||||
for file in files:
|
||||
if not file.startswith("__"):
|
||||
name, ext = os.path.splitext(file)
|
||||
modules.append("." + name)
|
||||
|
||||
return modules
|
||||
|
||||
|
||||
modules = get_modules(autoCodePath)
|
||||
|
||||
# 将包下的所有模块,逐个导入
|
||||
for module in modules:
|
||||
importlib.import_module(module, 'apps.lyautocode.views')
|
||||
18
backend/apps/lyautocode/views/views_StudentManage.py
Normal file
18
backend/apps/lyautocode/views/views_StudentManage.py
Normal file
@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from utils.viewset import CustomModelViewSet
|
||||
from apps.lyautocode.models.models_StudentManage import StudentManage
|
||||
from apps.lyautocode.serializers.serializer_StudentManage import StudentManageSerializer
|
||||
from apps.lyautocode.filters.filter_StudentManage import StudentManageFilterSet
|
||||
|
||||
class StudentManageViewSet(CustomModelViewSet):
|
||||
"""
|
||||
学生管理 接口
|
||||
"""
|
||||
|
||||
queryset = StudentManage.objects.all()
|
||||
serializer_class = StudentManageSerializer
|
||||
filterset_class = StudentManageFilterSet
|
||||
|
||||
export_download_filename = "导出学生管理数据"
|
||||
export_field_dict = {'avatar': '头像', 'name': '姓名', 'gender': '性别', 'gender2': '性别选择', 'age': '年龄', 'birthday': '生日', 'status': '状态', 'user_lyaudocode_name': '关联账号'}
|
||||
|
||||
152
backend/apps/lyautocode/views/views_autocode.py
Normal file
152
backend/apps/lyautocode/views/views_autocode.py
Normal file
@ -0,0 +1,152 @@
|
||||
from apps.lyautocode.models.models_autocode import *
|
||||
from utils.jsonResponse import SuccessResponse,DetailResponse,ErrorResponse
|
||||
from utils.common import get_parameter_dic, ast_convert,starts_with_digit
|
||||
from utils.models import is_table_exists
|
||||
from rest_framework import serializers
|
||||
from utils.serializers import CustomModelSerializer
|
||||
from utils.viewset import CustomModelViewSet
|
||||
from utils.autocode.lygeneratecode import GenerateCode
|
||||
from django.db.models import Q
|
||||
from django.db import transaction
|
||||
from django.utils.autoreload import restart_with_reloader
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class LyAutoCodeSerializer(CustomModelSerializer):
|
||||
"""
|
||||
代码生成 -序列化器
|
||||
"""
|
||||
|
||||
def to_representation(self, instance): # 序列化
|
||||
ret = super().to_representation(instance)
|
||||
ret['column'] = ast_convert(ret['column']) # 可以保存的修改字段值的方法
|
||||
ret['other_config'] = ast_convert(ret['other_config']) # 可以保存的修改字段值的方法
|
||||
return ret
|
||||
|
||||
|
||||
class Meta:
|
||||
model = LyAutoCode
|
||||
read_only_fields = ["id"]
|
||||
fields = '__all__'
|
||||
|
||||
class LyAutoCodeCreateUpdateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
代码生成 -序列化器
|
||||
"""
|
||||
column = serializers.JSONField()
|
||||
other_config = serializers.JSONField()
|
||||
|
||||
class Meta:
|
||||
model = LyAutoCode
|
||||
read_only_fields = ["id"]
|
||||
fields = '__all__'
|
||||
|
||||
class LyAutoCodeViewSet(CustomModelViewSet):
|
||||
"""
|
||||
后台代码生成 接口:
|
||||
"""
|
||||
queryset = LyAutoCode.objects.filter(is_delete=False).order_by('-create_datetime')
|
||||
serializer_class = LyAutoCodeSerializer
|
||||
create_serializer_class = LyAutoCodeCreateUpdateSerializer
|
||||
update_serializer_class = LyAutoCodeCreateUpdateSerializer
|
||||
search_fields = ('class_name','verbose_name','db_table')
|
||||
|
||||
# 重写delete方法,并改为逻辑删除
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
real_delete = get_parameter_dic(request).get('real_delete',None)
|
||||
instance = self.get_object_list()
|
||||
if not real_delete:
|
||||
for i in range(len(instance)):
|
||||
instance[i].is_delete = True
|
||||
model = self.get_serializer().Meta.model
|
||||
model.objects.bulk_update(instance, fields=['is_delete'])
|
||||
return DetailResponse(msg="删除成功")
|
||||
else:
|
||||
for i in range(len(instance)):
|
||||
serializer = self.get_serializer(instance[i])
|
||||
object = serializer.data
|
||||
if instance[i].is_mount:
|
||||
menu_instance = None
|
||||
if instance[i].menu:
|
||||
menu_instance = instance[i].menu
|
||||
GenerateCode().generate(object, True, menu_instance,True)
|
||||
self.perform_destroy(instance)
|
||||
return DetailResponse(msg="删除成功")
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
params = request.data
|
||||
class_name = params.get('class_name',None)
|
||||
if starts_with_digit(class_name):
|
||||
return ErrorResponse(msg="类名【%s】不能以数字开头!!!"%class_name)
|
||||
db_table = params.get('db_table', None)
|
||||
if self.filter_queryset(self.get_queryset()).filter(Q(class_name=class_name)|Q(db_table=db_table)).exists():
|
||||
return ErrorResponse(msg="存在同名的类名或表名")
|
||||
if is_table_exists(db_table):
|
||||
return ErrorResponse(msg="存在同名的表名,请更改后再试")
|
||||
params['file_name_old'] = class_name
|
||||
serializer = self.get_serializer(data=params, request=request)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
self.perform_create(serializer)
|
||||
return DetailResponse(data=serializer.data, msg="新增成功")
|
||||
|
||||
def previewCode(self,request):
|
||||
id = get_parameter_dic(request).get('id',None)
|
||||
instance = self.filter_queryset(self.get_queryset()).filter(id=id).first()
|
||||
serializer = self.get_serializer(instance)
|
||||
data = GenerateCode().generate(serializer.data)
|
||||
return SuccessResponse(data=data,total=len(data))
|
||||
|
||||
def generateMount(self,request):
|
||||
id = get_parameter_dic(request).get('id',None)
|
||||
isReload = get_parameter_dic(request).get('isReload',False)
|
||||
instance = self.filter_queryset(self.get_queryset()).filter(id=id).first()
|
||||
if not instance:
|
||||
return ErrorResponse(msg="id error")
|
||||
serializer = self.get_serializer(instance)
|
||||
object = serializer.data
|
||||
|
||||
# vue的route name
|
||||
VueIndexName = 'lyAutoCode%s' % object['class_name']
|
||||
try:
|
||||
# 在事务开始前创建保存点
|
||||
save_id = transaction.savepoint()
|
||||
# 以下代码会在事务中执行
|
||||
# 配置路由菜单
|
||||
parent_menu = object['parent_menu'] if object['parent_menu'] else None
|
||||
if instance.menu:
|
||||
menu_instance = instance.menu
|
||||
Menu.objects.filter(id=menu_instance.id).update(name=object['verbose_name'], web_path=VueIndexName,sort=object['menu_sort'],parent_id=parent_menu)
|
||||
else:
|
||||
menu_instance,created = Menu.objects.get_or_create(name=object['verbose_name'], sort=object['menu_sort'],web_path=VueIndexName, isautopm=1)
|
||||
if parent_menu:
|
||||
menu_instance.parent_id = parent_menu
|
||||
menu_instance.save()
|
||||
instance.menu = menu_instance
|
||||
instance.save()
|
||||
|
||||
data = GenerateCode().generate(object, True, menu_instance)
|
||||
if not data:
|
||||
# 回滚到保存点
|
||||
transaction.savepoint_rollback(save_id)
|
||||
return ErrorResponse(msg="生成文件错误")
|
||||
instance.file_name_old = object['class_name']
|
||||
instance.is_mount = True
|
||||
instance.save()
|
||||
# 提交从保存点到当前状态的所有数据库事务操作
|
||||
transaction.savepoint_commit(save_id)
|
||||
if isReload:
|
||||
restart_with_reloader()
|
||||
return DetailResponse(msg="操作成功")
|
||||
except Exception as e:
|
||||
# 回滚到保存点
|
||||
transaction.savepoint_rollback(save_id)
|
||||
# 处理代码
|
||||
raise e
|
||||
|
||||
def syncdb(self,request):
|
||||
result = GenerateCode().syncdb()
|
||||
if result:
|
||||
return DetailResponse(msg="数据库同步成功")
|
||||
return ErrorResponse(msg="数据库同步失败,请查看后端IDE报错内容,或重启后端项目重试同步")
|
||||
|
||||
|
||||
0
backend/apps/lycrontab/__init__.py
Normal file
0
backend/apps/lycrontab/__init__.py
Normal file
3
backend/apps/lycrontab/admin.py
Normal file
3
backend/apps/lycrontab/admin.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
backend/apps/lycrontab/apps.py
Normal file
6
backend/apps/lycrontab/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class LycrontabConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.lycrontab'
|
||||
41
backend/apps/lycrontab/filters.py
Normal file
41
backend/apps/lycrontab/filters.py
Normal file
@ -0,0 +1,41 @@
|
||||
#!/bin/python
|
||||
#coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | system: django-vue-lyadmin
|
||||
# +-------------------------------------------------------------------
|
||||
# | Author: lybbn
|
||||
# +-------------------------------------------------------------------
|
||||
# | QQ: 1042594286
|
||||
# +-------------------------------------------------------------------
|
||||
|
||||
# ------------------------------
|
||||
# django-filter 过滤
|
||||
# ------------------------------
|
||||
import django_filters
|
||||
from django_celery_results.models import TaskResult
|
||||
from django_celery_beat.models import PeriodicTask
|
||||
|
||||
class CeleryPeriodicTaskFilterSet(django_filters.FilterSet):
|
||||
"""
|
||||
PeriodicTask 过滤
|
||||
"""
|
||||
|
||||
name = django_filters.CharFilter(field_name='name', lookup_expr='icontains')
|
||||
enabled = django_filters.BooleanFilter(field_name='enabled')
|
||||
task = django_filters.CharFilter(field_name='task', lookup_expr='icontains')
|
||||
|
||||
class Meta:
|
||||
model = PeriodicTask
|
||||
fields = ['name', 'enabled','task']
|
||||
|
||||
class CeleryTaskResultFilterSet(django_filters.FilterSet):
|
||||
"""
|
||||
TaskResult过滤
|
||||
"""
|
||||
|
||||
date_created = django_filters.BaseRangeFilter(field_name="date_created")
|
||||
|
||||
class Meta:
|
||||
model = TaskResult
|
||||
fields = ['id','task_id', 'status', 'date_done', 'date_created', 'result', 'task_name','periodic_task_name']
|
||||
|
||||
0
backend/apps/lycrontab/migrations/__init__.py
Normal file
0
backend/apps/lycrontab/migrations/__init__.py
Normal file
20
backend/apps/lycrontab/tasks.py
Normal file
20
backend/apps/lycrontab/tasks.py
Normal file
@ -0,0 +1,20 @@
|
||||
#!/bin/python
|
||||
#coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | system: django-vue-lyadmin
|
||||
# +-------------------------------------------------------------------
|
||||
# | Author: lybbn
|
||||
# +-------------------------------------------------------------------
|
||||
# | QQ: 1042594286
|
||||
# +-------------------------------------------------------------------
|
||||
|
||||
# ------------------------------
|
||||
# celery 任务文件
|
||||
# ------------------------------
|
||||
|
||||
from application.celery import app
|
||||
|
||||
@app.task(bind=True)
|
||||
def lytask_test(self,arg1="",arg2=""):
|
||||
print(self.request)
|
||||
return "django-vue-lyadmin lycrontab running:参数%s-参数%s"%(arg1,arg2)
|
||||
26
backend/apps/lycrontab/urls.py
Normal file
26
backend/apps/lycrontab/urls.py
Normal file
@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@Remark: 计划任务的路由文件
|
||||
"""
|
||||
from django.urls import path, re_path
|
||||
from rest_framework import routers
|
||||
|
||||
from apps.lycrontab.views.celery_crontab_schedule import CrontabScheduleModelViewSet
|
||||
from apps.lycrontab.views.celery_interval_schedule import IntervalScheduleModelViewSet
|
||||
from apps.lycrontab.views.celery_periodic_task import PeriodicTaskModelViewSet
|
||||
from apps.lycrontab.views.celery_task_result import CeleryTaskResultViewSet
|
||||
|
||||
system_url = routers.SimpleRouter()
|
||||
|
||||
system_url.register(r'intervalschedule', IntervalScheduleModelViewSet)
|
||||
system_url.register(r'crontabschedule', CrontabScheduleModelViewSet)
|
||||
system_url.register(r'periodictask', PeriodicTaskModelViewSet)
|
||||
system_url.register(r'taskresult', CeleryTaskResultViewSet)
|
||||
|
||||
urlpatterns = [
|
||||
re_path('periodictask/enabled/(?P<pk>.*?)/',PeriodicTaskModelViewSet.as_view({'put':'taskenabled'}), name='开始/暂停任务'),
|
||||
path('periodictask/tasklist/',PeriodicTaskModelViewSet.as_view({'get':'tasklist'}), name='获取本地所有tasks文件中的task任务方法'),
|
||||
path('periodictask/exectask/',PeriodicTaskModelViewSet.as_view({'post':'exec_task'}), name='执行一次任务'),
|
||||
]
|
||||
|
||||
urlpatterns += system_url.urls
|
||||
33
backend/apps/lycrontab/views/celery_clocked_schedule.py
Normal file
33
backend/apps/lycrontab/views/celery_clocked_schedule.py
Normal file
@ -0,0 +1,33 @@
|
||||
#!/bin/python
|
||||
#coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | system: django-vue-lyadmin
|
||||
# +-------------------------------------------------------------------
|
||||
# | Author: lybbn
|
||||
# +-------------------------------------------------------------------
|
||||
# | QQ: 1042594286
|
||||
# +-------------------------------------------------------------------
|
||||
|
||||
# ------------------------------
|
||||
# django_celery_beat ClockedSchedule view
|
||||
# ------------------------------
|
||||
|
||||
from django_celery_beat.models import ClockedSchedule
|
||||
|
||||
from utils.serializers import CustomModelSerializer
|
||||
from utils.viewset import CustomModelViewSet
|
||||
|
||||
class ClockedScheduleSerializer(CustomModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = ClockedSchedule
|
||||
read_only_fields = ["id"]
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class ClockedScheduleModelViewSet(CustomModelViewSet):
|
||||
"""
|
||||
时钟时间(DateTimeField)运行一次性任务
|
||||
"""
|
||||
queryset = ClockedSchedule.objects.all()
|
||||
serializer_class = ClockedScheduleSerializer
|
||||
40
backend/apps/lycrontab/views/celery_crontab_schedule.py
Normal file
40
backend/apps/lycrontab/views/celery_crontab_schedule.py
Normal file
@ -0,0 +1,40 @@
|
||||
#!/bin/python
|
||||
#coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | system: django-vue-lyadmin
|
||||
# +-------------------------------------------------------------------
|
||||
# | Author: lybbn
|
||||
# +-------------------------------------------------------------------
|
||||
# | QQ: 1042594286
|
||||
# +-------------------------------------------------------------------
|
||||
|
||||
# ------------------------------
|
||||
# django_celery_beat CrontabSchedule view
|
||||
# ------------------------------
|
||||
|
||||
from django_celery_beat.models import CrontabSchedule
|
||||
|
||||
from utils.serializers import CustomModelSerializer
|
||||
from utils.viewset import CustomModelViewSet
|
||||
|
||||
class CrontabScheduleSerializer(CustomModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = CrontabSchedule
|
||||
read_only_fields = ["id"]
|
||||
exclude = ('timezone',)
|
||||
# fields = '__all__'
|
||||
|
||||
|
||||
class CrontabScheduleModelViewSet(CustomModelViewSet):
|
||||
"""
|
||||
crontab 的周期性任务(同linux的crontab)
|
||||
|
||||
minute="0" 分钟
|
||||
hour="*" 小时
|
||||
day_of_week="*" 每周的星期几
|
||||
day_of_month="10-15" 每月的某一天或间隔
|
||||
month_of_year="*" 每年的某一个月
|
||||
"""
|
||||
queryset = CrontabSchedule.objects.all()
|
||||
serializer_class = CrontabScheduleSerializer
|
||||
40
backend/apps/lycrontab/views/celery_interval_schedule.py
Normal file
40
backend/apps/lycrontab/views/celery_interval_schedule.py
Normal file
@ -0,0 +1,40 @@
|
||||
#!/bin/python
|
||||
#coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | system: django-vue-lyadmin
|
||||
# +-------------------------------------------------------------------
|
||||
# | Author: lybbn
|
||||
# +-------------------------------------------------------------------
|
||||
# | QQ: 1042594286
|
||||
# +-------------------------------------------------------------------
|
||||
|
||||
# ------------------------------
|
||||
# django_celery_beat IntervalSchedule view
|
||||
# ------------------------------
|
||||
|
||||
from django_celery_beat.models import IntervalSchedule
|
||||
|
||||
from utils.serializers import CustomModelSerializer
|
||||
from utils.viewset import CustomModelViewSet
|
||||
|
||||
|
||||
class IntervalScheduleSerializer(CustomModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = IntervalSchedule
|
||||
read_only_fields = ["id"]
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class IntervalScheduleModelViewSet(CustomModelViewSet):
|
||||
"""
|
||||
以特定(固定间隔)时间间隔(例如每 5 秒)运行的计划(每 /月/日/时/分/秒/微秒)
|
||||
DAYS = 'days'
|
||||
HOURS = 'hours'
|
||||
MINUTES = 'minutes'
|
||||
SECONDS = 'seconds'
|
||||
MICROSECONDS = 'microseconds'
|
||||
"""
|
||||
queryset = IntervalSchedule.objects.all()
|
||||
serializer_class = IntervalScheduleSerializer
|
||||
|
||||
386
backend/apps/lycrontab/views/celery_periodic_task.py
Normal file
386
backend/apps/lycrontab/views/celery_periodic_task.py
Normal file
@ -0,0 +1,386 @@
|
||||
#!/bin/python
|
||||
#coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | system: django-vue-lyadmin
|
||||
# +-------------------------------------------------------------------
|
||||
# | Author: lybbn
|
||||
# +-------------------------------------------------------------------
|
||||
# | QQ: 1042594286
|
||||
# +-------------------------------------------------------------------
|
||||
|
||||
# ------------------------------
|
||||
# django_celery_beat PeriodicTask view
|
||||
# https://docs.celeryq.dev/en/stable/reference/celery.schedules.html#celery.schedules.crontab
|
||||
# ------------------------------
|
||||
|
||||
import json
|
||||
from django_celery_beat.models import PeriodicTask,CrontabSchedule, cronexp,PeriodicTasks,IntervalSchedule
|
||||
from django_celery_results.models import TaskResult
|
||||
from rest_framework import serializers
|
||||
|
||||
from utils.jsonResponse import SuccessResponse, ErrorResponse,DetailResponse
|
||||
from utils.serializers import CustomModelSerializer
|
||||
from utils.viewset import CustomModelViewSet
|
||||
from apps.lycrontab.views.celery_interval_schedule import IntervalScheduleSerializer
|
||||
from utils.common import get_parameter_dic,ast_convert
|
||||
from rest_framework.exceptions import APIException
|
||||
from apps.lycrontab.filters import CeleryPeriodicTaskFilterSet
|
||||
from application import settings
|
||||
from django.db import transaction
|
||||
|
||||
CrontabSchedule.__str__ = lambda self : '{0} {1} {2} {3} {4}'.format(
|
||||
cronexp(self.minute), cronexp(self.hour),
|
||||
cronexp(self.day_of_month), cronexp(self.month_of_year),
|
||||
cronexp(self.day_of_week)
|
||||
)
|
||||
|
||||
def get_task_list():
|
||||
"""
|
||||
获取本地所有app目录中所有的tasks任务文件中“lytask_”开头的任务方法
|
||||
:return:
|
||||
"""
|
||||
task_list = []
|
||||
task_dict_list = []
|
||||
for app in settings.INSTALLED_APPS:
|
||||
try:
|
||||
exec(f"""
|
||||
from {app} import tasks
|
||||
for t in [i for i in dir(tasks) if i.startswith('lytask_')]:
|
||||
task_dict = dict()
|
||||
task_dict['label'] = '{app}.tasks.' + t
|
||||
task_dict['value'] = '{app}.tasks.' + t
|
||||
task_list.append('{app}.tasks.' + t)
|
||||
task_dict_list.append(task_dict)
|
||||
""")
|
||||
except ImportError :
|
||||
pass
|
||||
return {'task_list': task_list, 'task_dict_list': task_dict_list}
|
||||
|
||||
|
||||
|
||||
def cronConvert(cron):
|
||||
"""
|
||||
解析cron表达式为字典形式
|
||||
:param cron: * * * * * 分、时、天、月、周
|
||||
:return:
|
||||
"""
|
||||
cron = cron.split(" ")
|
||||
result = {
|
||||
"minute":cron[0],
|
||||
"hour":cron[1],
|
||||
"day":cron[2],
|
||||
"month":cron[3],
|
||||
"week":cron[4]
|
||||
}
|
||||
return result
|
||||
|
||||
class IntervalScheduleSerializer(CustomModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = IntervalSchedule
|
||||
read_only_fields = ["id"]
|
||||
fields = '__all__'
|
||||
|
||||
class CrontabScheduleSerializer(CustomModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = CrontabSchedule
|
||||
read_only_fields = ["id"]
|
||||
exclude = ('timezone',)
|
||||
# fields = '__all__'
|
||||
|
||||
class PeriodicTaskSerializer(CustomModelSerializer):
|
||||
|
||||
crontab = serializers.StringRelatedField(read_only=True)
|
||||
crontab_id = serializers.SerializerMethodField(read_only=True)
|
||||
interval = serializers.SerializerMethodField(read_only=True)
|
||||
interval_id = serializers.SerializerMethodField(read_only=True)
|
||||
type = serializers.SerializerMethodField(read_only=True)
|
||||
|
||||
def get_crontab_id(self,obj):
|
||||
return obj.crontab_id
|
||||
|
||||
def get_interval(self,obj):
|
||||
if obj.interval_id:
|
||||
return {
|
||||
'every':obj.interval.every,
|
||||
'period':obj.interval.period
|
||||
}
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_interval_id(self,obj):
|
||||
return obj.interval_id
|
||||
|
||||
def get_type(self,obj):
|
||||
type = 0
|
||||
if obj.crontab_id:
|
||||
type = 1
|
||||
return type
|
||||
|
||||
class Meta:
|
||||
model = PeriodicTask
|
||||
read_only_fields = ["id"]
|
||||
fields = '__all__'
|
||||
|
||||
class PeriodicTaskCreateUpdateSerializer(CustomModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = PeriodicTask
|
||||
read_only_fields = ["id","total_run_count","date_changed"]
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class PeriodicTaskModelViewSet(CustomModelViewSet):
|
||||
"""
|
||||
任务数据模型
|
||||
"""
|
||||
queryset = PeriodicTask.objects.exclude(name="celery.backend_cleanup").order_by("id")
|
||||
serializer_class = PeriodicTaskSerializer
|
||||
create_serializer_class = PeriodicTaskCreateUpdateSerializer
|
||||
update_serializer_class = PeriodicTaskCreateUpdateSerializer
|
||||
filterset_class = CeleryPeriodicTaskFilterSet
|
||||
|
||||
@transaction.atomic
|
||||
def create(self, request, *args, **kwargs):
|
||||
body_data = request.data.copy()
|
||||
type = int(body_data.pop('type'))
|
||||
args1 = body_data.get('args','')
|
||||
if not isinstance(ast_convert(args1),list):
|
||||
return ErrorResponse(msg="参数格式错误")
|
||||
if type not in [0,1]:
|
||||
return ErrorResponse(msg="type类型错误")
|
||||
if type == 1:
|
||||
body_data.pop('interval')
|
||||
cron = body_data.get('crontab')
|
||||
cron_dict = cronConvert(cron)
|
||||
minute = cron_dict["minute"]
|
||||
hour = cron_dict["hour"]
|
||||
day = cron_dict["day"]
|
||||
month = cron_dict["month"]
|
||||
week = cron_dict["week"]
|
||||
cron_data = {
|
||||
'minute': minute,
|
||||
'hour': hour,
|
||||
'day_of_week': week,
|
||||
'day_of_month': day,
|
||||
'month_of_year': month
|
||||
}
|
||||
task = body_data.get('task')
|
||||
result = None
|
||||
task_list = get_task_list()
|
||||
task_list = task_list.get('task_list')
|
||||
if task in task_list:
|
||||
# 添加crontab
|
||||
serializer = CrontabScheduleSerializer(data=cron_data, request=request)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
self.perform_create(serializer)
|
||||
|
||||
# 添加任务
|
||||
body_data['crontab'] = serializer.data.get('id')
|
||||
# body_data['enabled'] = False
|
||||
# header = {}
|
||||
# header['periodic_task_name'] = body_data['name']
|
||||
# header['task_name'] = body_data['task']
|
||||
# body_data['headers'] = json.dumps(header)
|
||||
serializer = self.get_serializer(data=body_data, request=request)
|
||||
res = serializer.is_valid()
|
||||
if not res:
|
||||
raise APIException({"msg": f"添加失败,已经有一个名为 {body_data['name']} 的任务了"}, code=4000)
|
||||
self.perform_create(serializer)
|
||||
result = serializer.data
|
||||
task_obj = PeriodicTask.objects.get(id=result.get('id'))
|
||||
PeriodicTasks.changed(task_obj)
|
||||
return DetailResponse(msg="添加成功", data=result)
|
||||
else:
|
||||
return ErrorResponse(msg="没有该任务方法,请先添加", data=None)
|
||||
else:
|
||||
body_data.pop('crontab')
|
||||
interval = body_data.get('interval')
|
||||
every = interval['every']
|
||||
period = interval['period']
|
||||
if not all([every,period]):
|
||||
return ErrorResponse(msg="间隔时间错误")
|
||||
task = body_data.get('task')
|
||||
result = None
|
||||
task_list = get_task_list()
|
||||
task_list = task_list.get('task_list')
|
||||
if task in task_list:
|
||||
# 添加crontab
|
||||
serializer = IntervalScheduleSerializer(data=interval, request=request)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
self.perform_create(serializer)
|
||||
|
||||
# 添加任务
|
||||
body_data['interval'] = serializer.data.get('id')
|
||||
# body_data['enabled'] = False
|
||||
serializer = self.get_serializer(data=body_data, request=request)
|
||||
res = serializer.is_valid()
|
||||
if not res:
|
||||
raise APIException({"msg": f"添加失败,已经有一个名为 {body_data['name']} 的任务了"}, code=4000)
|
||||
self.perform_create(serializer)
|
||||
result = serializer.data
|
||||
task_obj = PeriodicTask.objects.get(id=result.get('id'))
|
||||
PeriodicTasks.changed(task_obj)
|
||||
return DetailResponse(msg="添加成功", data=result)
|
||||
else:
|
||||
return ErrorResponse(msg="没有该任务方法,请先添加", data=None)
|
||||
|
||||
@transaction.atomic
|
||||
def update(self, request, *args, **kwargs):
|
||||
body_data = request.data.copy()
|
||||
type = int(body_data.pop('type'))
|
||||
args1 = body_data.get('args','')
|
||||
if not isinstance(ast_convert(args1),list):
|
||||
return ErrorResponse(msg="参数格式错误")
|
||||
if type not in [0, 1]:
|
||||
return ErrorResponse(msg="type类型错误")
|
||||
if type == 1:
|
||||
body_data.pop('interval')
|
||||
body_data.pop('interval_id')
|
||||
cron = body_data.get('crontab')
|
||||
cron_id = body_data.get('crontab_id')
|
||||
cron_dict = cronConvert(cron)
|
||||
minute = cron_dict["minute"]
|
||||
hour = cron_dict["hour"]
|
||||
day = cron_dict["day"]
|
||||
month = cron_dict["month"]
|
||||
week = cron_dict["week"]
|
||||
cron_data = {
|
||||
'minute': minute,
|
||||
'hour': hour,
|
||||
'day_of_week': week,
|
||||
'day_of_month': day,
|
||||
'month_of_year': month
|
||||
}
|
||||
task = body_data.get('task')
|
||||
result = None
|
||||
task_list = get_task_list()
|
||||
task_list = task_list.get('task_list')
|
||||
if task in task_list:
|
||||
# 编辑crontab
|
||||
cond_instance = CrontabSchedule.objects.filter(id=cron_id).first()
|
||||
oldcron = '{0} {1} {2} {3} {4}'.format(
|
||||
cronexp(cond_instance.minute), cronexp(cond_instance.hour),
|
||||
cronexp(cond_instance.day_of_month), cronexp(cond_instance.month_of_year),
|
||||
cronexp(cond_instance.day_of_week)
|
||||
)
|
||||
if cron.strip() == oldcron:
|
||||
body_data['crontab'] = cron_id
|
||||
else:
|
||||
# cond_instance.minute = minute
|
||||
# cond_instance.hour = hour
|
||||
# cond_instance.day_of_month = day
|
||||
# cond_instance.month_of_year = month
|
||||
# cond_instance.day_of_week = week
|
||||
# body_data['crontab'] = cond_instance.id
|
||||
cron_data['id'] = cron_id
|
||||
serializer = CrontabScheduleSerializer(cond_instance, data=cron_data, request=request)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
self.perform_update(serializer)
|
||||
body_data['crontab'] = cond_instance.id
|
||||
header = {}
|
||||
header['periodic_task_name'] = body_data['name']
|
||||
header['task_name'] = body_data['task']
|
||||
body_data['headers'] = json.dumps(header)
|
||||
partial = kwargs.pop('partial', False)
|
||||
instance = self.get_object()
|
||||
serializer1 = self.get_serializer(instance, data=body_data, request=request, partial=partial)
|
||||
serializer1.is_valid(raise_exception=True)
|
||||
self.perform_update(serializer1)
|
||||
|
||||
if getattr(instance, '_prefetched_objects_cache', None):
|
||||
# If 'prefetch_related' has been applied to a queryset, we need to
|
||||
# forcibly invalidate the prefetch cache on the instance.
|
||||
instance._prefetched_objects_cache = {}
|
||||
task_obj = PeriodicTask.objects.get(id=instance.id)
|
||||
PeriodicTasks.changed(task_obj)
|
||||
return DetailResponse(data=serializer1.data, msg="更新成功")
|
||||
else:
|
||||
return ErrorResponse(msg="没有该任务方法,请先添加", data=None)
|
||||
else:
|
||||
body_data.pop('crontab')
|
||||
body_data.pop('crontab_id')
|
||||
interval = body_data.get('interval')
|
||||
interval_id = body_data.get('interval_id')
|
||||
every = interval['every']
|
||||
period = interval['period']
|
||||
if not all([every, period]):
|
||||
return ErrorResponse(msg="间隔时间错误")
|
||||
task = body_data.get('task')
|
||||
result = None
|
||||
task_list = get_task_list()
|
||||
task_list = task_list.get('task_list')
|
||||
if task in task_list:
|
||||
# 编辑crontab
|
||||
interval_instance = IntervalSchedule.objects.filter(id=interval_id).first()
|
||||
if interval_instance.every == every and interval_instance.period == period:
|
||||
body_data['interval'] = interval_id
|
||||
else:
|
||||
interval['id'] = interval_id
|
||||
serializer = IntervalScheduleSerializer(interval_instance, data=interval, request=request)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
self.perform_update(serializer)
|
||||
body_data['interval'] = interval_instance.id
|
||||
partial = kwargs.pop('partial', False)
|
||||
instance = self.get_object()
|
||||
serializer1 = self.get_serializer(instance, data=body_data, request=request, partial=partial)
|
||||
serializer1.is_valid(raise_exception=True)
|
||||
self.perform_update(serializer1)
|
||||
|
||||
if getattr(instance, '_prefetched_objects_cache', None):
|
||||
# If 'prefetch_related' has been applied to a queryset, we need to
|
||||
# forcibly invalidate the prefetch cache on the instance.
|
||||
instance._prefetched_objects_cache = {}
|
||||
task_obj = PeriodicTask.objects.get(id=instance.id)
|
||||
PeriodicTasks.changed(task_obj)
|
||||
return DetailResponse(data=serializer1.data, msg="更新成功")
|
||||
else:
|
||||
return ErrorResponse(msg="没有该任务方法,请先添加", data=None)
|
||||
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
instance = self.get_object_list()
|
||||
for i in instance:
|
||||
if i.crontab_id:
|
||||
i.crontab.delete()
|
||||
if i.interval_id:
|
||||
i.interval.delete()
|
||||
self.perform_destroy(instance)
|
||||
return DetailResponse(data=[], msg="删除成功")
|
||||
|
||||
|
||||
def tasklist(self, request, *args, **kwargs):
|
||||
"""获取本地tasks任务所有方法"""
|
||||
result = get_task_list()
|
||||
task_list = result.get('task_dict_list')
|
||||
return SuccessResponse(msg='获取成功', data=task_list, total=len(task_list))
|
||||
|
||||
def taskenabled(self, request, *args, **kwargs):
|
||||
"""开始、暂停任务"""
|
||||
instance = self.get_object()
|
||||
instance.enabled = get_parameter_dic(request).get('enabled',False)
|
||||
instance.save()
|
||||
PeriodicTasks.changed(instance)
|
||||
return DetailResponse(msg="修改成功")
|
||||
|
||||
def exec_task(self, request, *args, **kwargs):
|
||||
task_name = get_parameter_dic(request).get('task_name', '')
|
||||
id = get_parameter_dic(request).get('id', '')
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
instance = queryset.filter(id=id).first()
|
||||
if not instance:
|
||||
return ErrorResponse(msg="任务不存在")
|
||||
newargs = instance.args
|
||||
data = {
|
||||
'task': None
|
||||
}
|
||||
test = f"""
|
||||
from {'.'.join(task_name.split('.')[:-1])} import {task_name.split('.')[-1]}
|
||||
task = {task_name.split('.')[-1]}.apply_async(args={newargs},periodic_task_name='{instance.name}')
|
||||
"""
|
||||
exec(test, data)
|
||||
if not data["task"]:
|
||||
return ErrorResponse(msg="执行失败")
|
||||
task_id = data.get('task', ).id
|
||||
return SuccessResponse(data={'task_id': task_id},msg="执行成功")
|
||||
39
backend/apps/lycrontab/views/celery_task_result.py
Normal file
39
backend/apps/lycrontab/views/celery_task_result.py
Normal file
@ -0,0 +1,39 @@
|
||||
#!/bin/python
|
||||
#coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | system: django-vue-lyadmin
|
||||
# +-------------------------------------------------------------------
|
||||
# | Author: lybbn
|
||||
# +-------------------------------------------------------------------
|
||||
# | QQ: 1042594286
|
||||
# +-------------------------------------------------------------------
|
||||
|
||||
# ------------------------------
|
||||
# django_celery_results TaskResult view
|
||||
# ------------------------------
|
||||
|
||||
from django_celery_results.models import TaskResult
|
||||
|
||||
from utils.serializers import CustomModelSerializer
|
||||
from utils.viewset import CustomModelViewSet
|
||||
|
||||
from apps.lycrontab.filters import CeleryTaskResultFilterSet
|
||||
|
||||
|
||||
|
||||
class CeleryTaskResultSerializer(CustomModelSerializer):
|
||||
"""
|
||||
定时任务结果 序列化器
|
||||
"""
|
||||
class Meta:
|
||||
model = TaskResult
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class CeleryTaskResultViewSet(CustomModelViewSet):
|
||||
"""
|
||||
定时任务 接口
|
||||
"""
|
||||
queryset = TaskResult.objects.all()
|
||||
serializer_class = CeleryTaskResultSerializer
|
||||
filter_class = CeleryTaskResultFilterSet
|
||||
0
backend/apps/lymessages/__init__.py
Normal file
0
backend/apps/lymessages/__init__.py
Normal file
3
backend/apps/lymessages/admin.py
Normal file
3
backend/apps/lymessages/admin.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
backend/apps/lymessages/apps.py
Normal file
6
backend/apps/lymessages/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class LymessagesConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.lymessages'
|
||||
74
backend/apps/lymessages/migrations/0001_initial.py
Normal file
74
backend/apps/lymessages/migrations/0001_initial.py
Normal file
@ -0,0 +1,74 @@
|
||||
# Generated by Django 4.0.5 on 2022-06-27 13:55
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import utils.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='MyMessage',
|
||||
fields=[
|
||||
('id', models.CharField(default=utils.models.make_uuid, help_text='Id', max_length=100, primary_key=True, serialize=False, verbose_name='Id')),
|
||||
('description', models.CharField(blank=True, help_text='描述', max_length=100, null=True, verbose_name='描述')),
|
||||
('modifier', models.CharField(blank=True, help_text='修改人', max_length=100, null=True, verbose_name='修改人')),
|
||||
('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=100, null=True, verbose_name='数据归属部门')),
|
||||
('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')),
|
||||
('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),
|
||||
('msg_chanel', models.IntegerField(choices=[(1, '系统通知'), (2, '平台公告')], default=1, verbose_name='消息渠道')),
|
||||
('public', models.BooleanField(default=False, verbose_name='是否公开')),
|
||||
('msg_title', models.CharField(blank=True, max_length=100, null=True, verbose_name='消息标题')),
|
||||
('msg_content', models.TextField(blank=True, null=True, verbose_name='消息内容')),
|
||||
('to_path', models.CharField(blank=True, max_length=256, null=True, verbose_name='跳转路径')),
|
||||
('status', models.BooleanField(default=True, verbose_name='通知状态')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '消息中心',
|
||||
'verbose_name_plural': '消息中心',
|
||||
'db_table': 'tb_message',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MyMessageTemplate',
|
||||
fields=[
|
||||
('id', models.CharField(default=utils.models.make_uuid, help_text='Id', max_length=100, primary_key=True, serialize=False, verbose_name='Id')),
|
||||
('description', models.CharField(blank=True, help_text='描述', max_length=100, null=True, verbose_name='描述')),
|
||||
('modifier', models.CharField(blank=True, help_text='修改人', max_length=100, null=True, verbose_name='修改人')),
|
||||
('dept_belong_id', models.CharField(blank=True, help_text='数据归属部门', max_length=100, null=True, verbose_name='数据归属部门')),
|
||||
('update_datetime', models.DateTimeField(auto_now=True, help_text='修改时间', null=True, verbose_name='修改时间')),
|
||||
('create_datetime', models.DateTimeField(auto_now_add=True, help_text='创建时间', null=True, verbose_name='创建时间')),
|
||||
('code', models.CharField(max_length=100, unique=True, verbose_name='模板code')),
|
||||
('title', models.CharField(max_length=100, verbose_name='消息标题')),
|
||||
('content', models.TextField(verbose_name='消息模板内容')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '消息模板',
|
||||
'verbose_name_plural': '消息模板',
|
||||
'db_table': 'tb_message_template',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MyMessageUser',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('update_datetime', models.DateTimeField(auto_now=True, null=True, verbose_name='更新时间')),
|
||||
('create_datetime', models.DateTimeField(auto_now_add=True, null=True, verbose_name='创建时间')),
|
||||
('read_at', models.DateTimeField(blank=True, null=True, verbose_name='读取时间')),
|
||||
('is_delete', models.BooleanField(default=False, verbose_name='是否删除')),
|
||||
('is_read', models.BooleanField(default=False, verbose_name='是否已读')),
|
||||
('messageid', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='lymessages.mymessage', verbose_name='消息ID')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '用户消息',
|
||||
'verbose_name_plural': '用户消息',
|
||||
'db_table': 'tb_message_user',
|
||||
},
|
||||
),
|
||||
]
|
||||
43
backend/apps/lymessages/migrations/0002_initial.py
Normal file
43
backend/apps/lymessages/migrations/0002_initial.py
Normal file
@ -0,0 +1,43 @@
|
||||
# Generated by Django 4.0.5 on 2022-06-27 13:55
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('lymessages', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='mymessageuser',
|
||||
name='revuserid',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messagerevuserid', to=settings.AUTH_USER_MODEL, verbose_name='接收者用户ID'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='mymessageuser',
|
||||
name='senduserid',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='messagesenduserid', to=settings.AUTH_USER_MODEL, verbose_name='发送者用户ID'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='mymessagetemplate',
|
||||
name='creator',
|
||||
field=models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='mymessage',
|
||||
name='creator',
|
||||
field=models.ForeignKey(db_constraint=False, help_text='创建人', null=True, on_delete=django.db.models.deletion.SET_NULL, related_query_name='creator_query', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='mymessage',
|
||||
name='msg_type',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='lymessages.mymessagetemplate', verbose_name='消息类型'),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,21 @@
|
||||
# Generated by Django 4.1.6 on 2023-02-08 21:59
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('lymessages', '0002_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='mymessage',
|
||||
name='description',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='mymessagetemplate',
|
||||
name='description',
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,23 @@
|
||||
# Generated by Django 4.1.3 on 2023-02-09 22:03
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('lymessages', '0003_remove_mymessage_description_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='mymessage',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, help_text='描述', max_length=100, null=True, verbose_name='描述'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='mymessagetemplate',
|
||||
name='description',
|
||||
field=models.CharField(blank=True, help_text='描述', max_length=100, null=True, verbose_name='描述'),
|
||||
),
|
||||
]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user