新的提交信息03171806

This commit is contained in:
etoai 2025-03-07 10:45:33 +08:00 committed by root
commit c1d6b4602e
1520 changed files with 213041 additions and 0 deletions

31
LICENSE Normal file
View 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
View File

@ -0,0 +1,260 @@
说明本系统为django-vue-lyadmin专业版专业版与基础版大体框架一样只是功能有增加因此文档可以参考基础版
注意使用专业版开发时请使用superadmin/123456超级管理员登录系统方便测试和调试避免菜单权限问题
# Django-Vue-Lyadmin
[![img](https://img.shields.io/badge/python-%3E=3.9.x-green.svg)](https://python.org/) [![PyPI - Django Version badge](https://img.shields.io/badge/django%20versions-4.x-blue)](https://docs.djangoproject.com/zh-hans/4.0/) [![img](https://img.shields.io/badge/node-%3E%3D%2012.0.0-brightgreen)](https://nodejs.org/zh-cn/) [![img](https://gitee.com/lybbn/django-vue-lyadmin/badge/star.svg?theme=dark)](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前后端分离的管理后台快速开发平台内置简易商城模块去繁从简、还你一个干净的后台管理系统
* 前端采用Vue3elementplus 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框架
* 权限认证使用JWTdjangorestframework-simplejwt支持多终端认证系统
* 接口采用drfdjangorestframework支持后台一键关闭前端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项目启动视频讲解
[![Watch the video](https://gitee.com/lybbn/django-vue-lyadmin/raw/master/frontend/src/assets/img/099.png)](https://v.kuaishouapp.com/s/VIJPdIx6)
## lyadmin后端
~~~bash
1. 进入项目目录
2. 在 config.py 中配置数据库信息
mysql数据库版本建议8.0django4.2版本开始要求mysql8.x及以上
mysql数据库字符集utf8mb4mysql8.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
~~~
## 演示图
![image-task02](https://gitee.com/lybbn/django-vue-lyadmin/raw/master/frontend/src/assets/img/task02.png)
![image-task01](https://gitee.com/lybbn/django-vue-lyadmin/raw/master/frontend/src/assets/img/task01.png)
![image-188](https://gitee.com/lybbn/django-vue-lyadmin/raw/master/frontend/src/assets/img/188.png)
![image-100](https://gitee.com/lybbn/django-vue-lyadmin/raw/master/frontend/src/assets/img/100.png)
![image-099](https://gitee.com/lybbn/django-vue-lyadmin/raw/master/frontend/src/assets/img/099.png)
![image-098](https://gitee.com/lybbn/django-vue-lyadmin/raw/master/frontend/src/assets/img/098.png)
![image-088](https://gitee.com/lybbn/django-vue-lyadmin/raw/master/frontend/src/assets/img/088.png)
![image-04](https://gitee.com/lybbn/django-vue-lyadmin/raw/master/frontend/src/assets/img/04.png)
![image-02](https://gitee.com/lybbn/django-vue-lyadmin/raw/master/frontend/src/assets/img/02.png)
![image-03](https://gitee.com/lybbn/django-vue-lyadmin/raw/master/frontend/src/assets/img/03.png)
![image-06](https://gitee.com/lybbn/django-vue-lyadmin/raw/master/frontend/src/assets/img/06.png)
![image-05](https://gitee.com/lybbn/django-vue-lyadmin/raw/master/frontend/src/assets/img/05.png)
## 捐赠该项目
开源不易,可使用支付宝、微信扫下面二维码打赏支持。您的支持是我不断创作的动力!!!
<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
View 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
View File

@ -0,0 +1,62 @@
## lyadmin后端 专业版
注意本后端python版本请选择3.9及以上
~~~bash
1. 进入项目目录
2. 在 config.py 中配置数据库信息
mysql数据库版本建议8.0django4.2版本开始要求mysql8.x及以上
mysql数据库字符集utf8mb4mysql8.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_iocpsupport1.0.2cp311cp311win32.whl直接pip install xxx.whl即可
https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted

View File

@ -0,0 +1,4 @@
#Django连接MySQL时默认使用MySQLdb驱动但MySQLdb不支持Python3因此这里将MySQL驱动设置为pymysql
import pymysql
pymysql.install_as_MySQLdb()

View 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
)
),
})

View 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

View 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_rediswindows 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
View 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='后台管理默认页面'),
]

View 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
View File

View File

View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class AddressConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.address'

View 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',
},
),
]

View 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'),
),
]

View 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

View 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

View 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-20不返回下级行政区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')

View File

View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class LoginsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.logins'

View File

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View 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="注册成功")

View File

View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class LyformbuilderConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = 'apps.lyFormBuilder'

View 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'],
},
),
]

View 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='关联菜单'),
),
]

View File

@ -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',
),
]

View 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 = [
('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'),
),
]

View 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')

View 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',)

View 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中此配置项可去掉

View 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

View 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

View 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

View 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')

View 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报错内容或重启后端项目重试同步")

View 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': '手机号'}

View File

View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class LytiktokunionConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = 'apps.lyTiktokUnion'

View 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()

View File

@ -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")

View 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')},
),
]

View File

@ -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',
},
),
]

View 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

View 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

View 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

View 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

View 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("获取授权成功!")

View 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=%sapp-id=%s,event-sign=%s,通知内容:%s" % (request.META,app_key,event_sign,request.body))
return HttpResponse('非法请求')
logger.info("获取抖客订单webhook通知,头部信息requestMETA=%sapp-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')

View 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="赏金任务同步失败" )

View 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="未获取到需要审核的数据或已审核过")

View 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="未获取到该数据或无权限")

View 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("获取授权成功!!!")

View File

View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class LyautocodeConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = 'apps.lyautocode'

View 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']

View 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',),
},
),
]

View File

@ -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='附件'),
),
]

View 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 = [
('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'),
),
]

View 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')

View 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中此配置项可去掉

View 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',)

View 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__'

View 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

View 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

View 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

View 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')

View 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': '关联账号'}

View 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报错内容或重启后端项目重试同步")

View File

View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class LycrontabConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.lycrontab'

View 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']

View 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)

View 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

View 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

View 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

View 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

View 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="执行成功")

View 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

View File

View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class LymessagesConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.lymessages'

View 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',
},
),
]

View 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='消息类型'),
),
]

View File

@ -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',
),
]

View File

@ -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