commit
a7bd6e0f6b
94 changed files with 10458 additions and 0 deletions
@ -0,0 +1,12 @@ |
|||
.vscode/ |
|||
.vs/ |
|||
.idea |
|||
.vscode |
|||
.DS_Store |
|||
logs/ |
|||
celecelerybeat.* |
|||
celerybeat-schedule.* |
|||
*/__pycache__ |
|||
__pycache__/ |
|||
.pytest_cache/ |
|||
.idea/ |
|||
@ -0,0 +1,16 @@ |
|||
""" |
|||
ASGI config for ChaCeRndTrans 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 |
|||
|
|||
from django.core.asgi import get_asgi_application |
|||
|
|||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ChaCeRndTrans.settings') |
|||
|
|||
application = get_asgi_application() |
|||
@ -0,0 +1,45 @@ |
|||
import six |
|||
from rest_framework.response import Response |
|||
from rest_framework.serializers import Serializer |
|||
|
|||
|
|||
class CCAIResponse(Response): |
|||
def __init__(self, data=None, status=200, code=200, msg="成功", |
|||
template_name=None, headers=None, |
|||
exception=False, content_type=None, pagination=None, count=None): |
|||
|
|||
super(Response, self).__init__(None, status=status) |
|||
|
|||
if isinstance(data, Serializer): |
|||
msg = ( |
|||
"You passed a Serializer instance as data, but " |
|||
"probably meant to pass serialized `.data` or " |
|||
"`.error`. representation." |
|||
) |
|||
raise AssertionError(msg) |
|||
if status >= 400 and msg=='成功': |
|||
msg = "失败" |
|||
if code == 200: |
|||
code = status |
|||
if pagination or count: |
|||
self.data = { |
|||
"code": code, |
|||
"message": msg, |
|||
"data": data, |
|||
"count": count, |
|||
"pagination": pagination |
|||
} |
|||
else: |
|||
self.data = { |
|||
"code": code, |
|||
"message": msg, |
|||
"data": data, |
|||
} |
|||
self.template_name = template_name |
|||
self.exception = exception |
|||
self.content_type = content_type |
|||
|
|||
if headers: |
|||
for name, value in six.iteritems(headers): |
|||
self[name] = value |
|||
|
|||
@ -0,0 +1,85 @@ |
|||
from rest_framework import status |
|||
|
|||
# 成功 |
|||
OK = status.HTTP_200_OK |
|||
|
|||
NO_CONTENT = status.HTTP_204_NO_CONTENT |
|||
|
|||
ACCEPTED = status.HTTP_202_ACCEPTED |
|||
# 失败 |
|||
BAD = status.HTTP_400_BAD_REQUEST |
|||
# 无权限 |
|||
FORBIDDEN = status.HTTP_403_FORBIDDEN |
|||
# 未认证 |
|||
UNAUTHORIZED = status.HTTP_401_UNAUTHORIZED |
|||
# 创建 |
|||
CREATED = status.HTTP_201_CREATED |
|||
# NOT_FOUND |
|||
NOT_FOUND = status.HTTP_404_NOT_FOUND |
|||
# INTERNAL_SERVER_ERROR |
|||
SERVER_ERROR = status.HTTP_500_INTERNAL_SERVER_ERROR |
|||
# TOO_MANY_REQUESTS |
|||
TOO_MANY_REQUESTS = status.HTTP_429_TOO_MANY_REQUESTS |
|||
|
|||
# 达到次数限制 |
|||
USER_VISITLiMIT = 10001 |
|||
# 非账号客户 |
|||
NOT_CUSTOMER = 10002 |
|||
|
|||
# 添加企业成功-用于主账号已存在企业,子账号添加分配成功 |
|||
ALOCATE_CUSTOMER = 10003 |
|||
|
|||
# 订阅设置、超出订阅城市数量限制 |
|||
LIMIT_SETTING = 10004 |
|||
# 访问次数剩下50 |
|||
USER_VISIT50LiMIT = 10005 |
|||
|
|||
# 添加企业成功-企业未匹配到数据,为方便使用,请补充数据 |
|||
ADD_NO_QIXINBAO = 10100 |
|||
|
|||
# 同步失败 |
|||
SYN_ERROR = 10101 |
|||
|
|||
# 用户登录请求code编码 |
|||
# 用户不存在 |
|||
NO_FOUND_USER = 20001 |
|||
# 密码错误 |
|||
BAD_PSW = 20002 |
|||
# 用户未激活 |
|||
USER_NO_ACTIVE = 20003 |
|||
# 用户未绑定 |
|||
USER_NO_BIND = 20004 |
|||
# 已有手机号未注册 |
|||
PHONE_NO_BIND = 20005 |
|||
|
|||
#手机号已被注册 |
|||
PHONE_IS_BIND = 20006 |
|||
|
|||
# 未注册手机号 |
|||
NO_REGISTER_PHONE = 20007 |
|||
|
|||
# 会员过期 |
|||
USER_EXPIRED = 20008 |
|||
|
|||
# 修改密码,旧密码错误 |
|||
OLD_PWD_ERROR = 20009 |
|||
|
|||
# 验证码验证错误 |
|||
VERIFYCODE_SEND_ERROR = 20011 |
|||
# 验证码失效 |
|||
VERIFYCODE_NO_FOUND = 20012 |
|||
# 验证码验证错误 |
|||
VERIFYCODE_ERROR = 20013 |
|||
# 滑块验证码验证 |
|||
NEED_SLIDER_VERIFYCODE = 20014 |
|||
|
|||
# 邮箱号验证错误,请输入正确的邮箱账号 |
|||
ERROR_EMAIL = 20015 |
|||
|
|||
# 一个账号不能重复登录 |
|||
REPEATED_LOGIN = 30001 |
|||
|
|||
|
|||
# 请求参数错误 |
|||
PARAMS_ERR = "params error" |
|||
|
|||
@ -0,0 +1,44 @@ |
|||
# !/usr/bin/env python |
|||
# coding:utf8 |
|||
from django.conf import settings |
|||
|
|||
DATABASE_MAPPING = settings.DATABASE_APPS_MAPPING # 在setting中定义的路由表 |
|||
|
|||
|
|||
class DatabaseAppsRouter(object): |
|||
def db_for_read(self, model, **hints): |
|||
if model._meta.app_label in DATABASE_MAPPING: |
|||
return DATABASE_MAPPING[model._meta.app_label] |
|||
return None |
|||
|
|||
def db_for_write(self, model, **hints): |
|||
|
|||
if model._meta.app_label in DATABASE_MAPPING: |
|||
return DATABASE_MAPPING[model._meta.app_label] |
|||
return None |
|||
|
|||
def allow_relation(self, obj1, obj2, **hints): |
|||
|
|||
db_obj1 = DATABASE_MAPPING.get(obj1._meta.app_label) |
|||
db_obj2 = DATABASE_MAPPING.get(obj2._meta.app_label) |
|||
if db_obj1 and db_obj2: |
|||
if db_obj1 == db_obj2: |
|||
return True |
|||
else: |
|||
return False |
|||
return None |
|||
|
|||
def allow_syncdb(self, db, model): |
|||
|
|||
if db in DATABASE_MAPPING.values(): |
|||
return DATABASE_MAPPING.get(model._meta.app_label) == db |
|||
elif model._meta.app_label in DATABASE_MAPPING: |
|||
return False |
|||
return None |
|||
|
|||
def allow_migrate(self, db, app_label, model_name=None, **hints): |
|||
if db in DATABASE_MAPPING.values(): |
|||
return DATABASE_MAPPING.get(app_label) == db |
|||
elif app_label in DATABASE_MAPPING: |
|||
return False |
|||
return None |
|||
@ -0,0 +1,458 @@ |
|||
""" |
|||
Django settings for ChaCeRDE project. |
|||
Generated by 'django-admin startproject' using Django 3.1.4. |
|||
For more information on this file, see |
|||
https://docs.djangoproject.com/en/3.1/topics/settings/ |
|||
For the full list of settings and their values, see |
|||
https://docs.djangoproject.com/en/3.1/ref/settings/ |
|||
""" |
|||
import datetime |
|||
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")) |
|||
|
|||
|
|||
# Quick-start development settings - unsuitable for production |
|||
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ |
|||
|
|||
# SECURITY WARNING: keep the secret key used in production secret! |
|||
# 测试/开发 |
|||
SECRET_KEY = 'mm3oss9fkz(0tirduc^^!-bv%o(u4=_-9geo=1(*s=9s#04flo' |
|||
# 生产环境 |
|||
# SECRET_KEY = 'QPEVJt.$wnGS8yonA+XWPW7B92X8-=T9pF95iTr9eJEWU7IfuA' |
|||
|
|||
# 数据ID的hash |
|||
ID_KEY = "gcos8pr02#f%dpr0!#u7(l0bl#oo1fcq)ee-@y-v$2_oz+j9ufsmv" |
|||
|
|||
# 数值编码 |
|||
FONT_ID_KEY = "gcos8pr02#f%dpr0!#u7(l0bl#oo1fcq)ee-@y-v$2_oz+j9ufsmv" |
|||
|
|||
# SECURITY WARNING: don't run with debug turned on in production! |
|||
DEBUG = False # 本地开发的时候记得设置为TRUE,否则拿不到静态文件, 提交到线上时记得改为FALSE |
|||
# DEBUG = True # 本地开发的时候记得设置为TRUE,否则拿不到静态文件, 提交到线上时记得改为FALSE |
|||
DEVELOP_DEBUG = False |
|||
|
|||
TEST_DOMAIN = 'https://test.nw.chace-ai.net' |
|||
RELEASE_DOMAIN = 'https://narwhale.chace-ai.net' |
|||
|
|||
# 短信接口平台url |
|||
MSG_URL = 'http://www.chace-ai.cn/api/message/send/' |
|||
TEST_MSG_URL = 'http://test.chace-ai.cn/api/message/send/' |
|||
|
|||
ALLOWED_HOSTS = ['*'] |
|||
# 跳转404链接页面 |
|||
RED_404_URL = 'https://www.chace-ai.com/404/' |
|||
|
|||
MEDIA_ROOT = os.path.join(BASE_DIR, "media") |
|||
if DEBUG: |
|||
# 文件存放路径和文件访问域名 |
|||
FILE_PATH = MEDIA_ROOT # 开发本地存储目录 |
|||
FILE_HTTP = "http://127.0.0.1:8000" # 本地开发使用 媒体域名拼接使用 |
|||
else: |
|||
# 文件存放路径和文件访问域名 |
|||
FILE_PATH = "/data/chacerndtrans" # 线上本地存储目录 |
|||
FILE_HTTP = "https://test.nw.chace-ai.net" # 线上开发使用 媒体域名拼接使用 |
|||
|
|||
|
|||
# Application definition |
|||
|
|||
INSTALLED_APPS = [ |
|||
'django.contrib.admin', |
|||
'django.contrib.auth', |
|||
'django.contrib.contenttypes', |
|||
'django.contrib.sessions', |
|||
'django.contrib.messages', |
|||
'django.contrib.staticfiles', |
|||
'django_apscheduler', |
|||
'simple_history', |
|||
"rest_framework", |
|||
"corsheaders", # 跨域 |
|||
"drf_yasg", # 用于集成 drf与swagger |
|||
"django_filters", |
|||
"utils", |
|||
"common", # 通用模块 |
|||
"rbac", # 权限模块 |
|||
"staff", # 权限模块 |
|||
"tasks", # 定时任务 |
|||
] |
|||
|
|||
MIDDLEWARE = [ |
|||
'corsheaders.middleware.CorsMiddleware', |
|||
'django.middleware.security.SecurityMiddleware', |
|||
'django.contrib.sessions.middleware.SessionMiddleware', |
|||
'django.middleware.common.CommonMiddleware', |
|||
'django.middleware.csrf.CsrfViewMiddleware', |
|||
'django.contrib.auth.middleware.AuthenticationMiddleware', |
|||
'django.contrib.messages.middleware.MessageMiddleware', |
|||
'django.middleware.clickjacking.XFrameOptionsMiddleware', |
|||
'simple_history.middleware.HistoryRequestMiddleware', # 操作记录 |
|||
|
|||
'utils.middleware.RequestLogMiddleware', # 将当前的request信息保存到当前线程供日志打印使用 |
|||
'utils.middleware.ExceptionLoggingMiddleware', |
|||
] |
|||
|
|||
#跨域增加忽略 |
|||
CORS_ALLOW_CREDENTIALS = True |
|||
CORS_ORIGIN_ALLOW_ALL = True |
|||
CORS_ORIGIN_WHITELIST = ( |
|||
'' |
|||
) |
|||
|
|||
CORS_ALLOW_METHODS = ( |
|||
'DELETE', |
|||
'GET', |
|||
'OPTIONS', |
|||
'PATCH', |
|||
'POST', |
|||
'PUT', |
|||
'VIEW', |
|||
) |
|||
|
|||
CORS_ALLOW_HEADERS = ( |
|||
'XMLHttpRequest', |
|||
'X_FILENAME', |
|||
'accept-encoding', |
|||
'authorization', |
|||
'content-type', |
|||
'dnt', |
|||
'origin', |
|||
'user-agent', |
|||
'x-csrftoken', |
|||
'x-requested-with', |
|||
'Pragma', |
|||
) |
|||
|
|||
ROOT_URLCONF = 'ChaCeRndTrans.urls' |
|||
|
|||
TEMPLATES = [ |
|||
{ |
|||
'BACKEND': 'django.template.backends.django.DjangoTemplates', |
|||
'DIRS': [], |
|||
'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', |
|||
], |
|||
}, |
|||
}, |
|||
] |
|||
|
|||
WSGI_APPLICATION = 'ChaCeRndTrans.wsgi.application' |
|||
|
|||
|
|||
# Database |
|||
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases |
|||
|
|||
DATABASES = { |
|||
"default": { |
|||
"ENGINE": "django.db.backends.mysql", |
|||
# "ENGINE": "dj_db_conn_pool.backends.mysql", |
|||
"NAME": "chace_rnd_3x", |
|||
"USER": "ccwtdm", |
|||
"PASSWORD": "fhRZLEu562wi23M4QC4iYq615UZEvgeB", |
|||
"HOST": "47.112.242.103", |
|||
"PORT": 17601, |
|||
# 本地数据库 |
|||
# "USER": "root", |
|||
# "PASSWORD": "123456", |
|||
# "HOST": "localhost", |
|||
# "PORT": 3306, |
|||
# "CONN_MAX_AGE": 600, |
|||
"OPTIONS": { |
|||
"charset": "utf8mb4" |
|||
}, |
|||
'POOL_OPTIONS': { |
|||
'POOL_SIZE': 20, |
|||
'MAX_OVERFLOW': 1000, |
|||
'RECYCLE': 60 * 60 |
|||
} |
|||
}, |
|||
"chace_rnd": { |
|||
"ENGINE": "django.db.backends.mysql", |
|||
# "ENGINE": "dj_db_conn_pool.backends.mysql", |
|||
"NAME": "chace_rnd", |
|||
"USER": "ccwtdm", |
|||
"PASSWORD": "fhRZLEu562wi23M4QC4iYq615UZEvgeB", |
|||
"HOST": "47.112.242.103", |
|||
"PORT": 17601, |
|||
# 本地数据库 |
|||
# "USER": "root", |
|||
# "PASSWORD": "123456", |
|||
# "HOST": "localhost", |
|||
# "PORT": 3306, |
|||
# "CONN_MAX_AGE": 600, |
|||
"OPTIONS": { |
|||
"charset": "utf8mb4" |
|||
}, |
|||
'POOL_OPTIONS': { |
|||
'POOL_SIZE': 20, |
|||
'MAX_OVERFLOW': 1000, |
|||
'RECYCLE': 60 * 60 |
|||
} |
|||
}, |
|||
} |
|||
|
|||
# use multi-database in django |
|||
DATABASE_ROUTERS = ["ChaCeRndTrans.database_router.DatabaseAppsRouter"] |
|||
DATABASE_APPS_MAPPING = { |
|||
# example: |
|||
#"app_name":"database_name", |
|||
"chace_rnd": "chace_rnd", |
|||
} |
|||
|
|||
|
|||
# Password validation |
|||
# https://docs.djangoproject.com/en/3.1/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', |
|||
}, |
|||
] |
|||
|
|||
REST_FRAMEWORK = { |
|||
"DEFAULT_VERSION": "v1", # 默认的版本 |
|||
"ALLOWED_VERSIONS": ["v1", "v2"], # 允许的版本 |
|||
"VERSION_PARAM": "version", # GET方式url中参数的名字 ?version=xxx |
|||
"DEFAULT_SCHEMA_CLASS": "rest_framework.schemas.coreapi.AutoSchema", |
|||
'DEFAULT_RENDERER_CLASSES': ( |
|||
'rest_framework.renderers.JSONRenderer', |
|||
), |
|||
"DEFAULT_FILTER_BACKENDS": [ |
|||
"django_filters.rest_framework.DjangoFilterBackend", |
|||
"rest_framework.filters.SearchFilter", |
|||
"rest_framework.filters.OrderingFilter", |
|||
], |
|||
# "DEFAULT_PERMISSION_CLASSES": [ |
|||
# "rest_framework.permissions.IsAuthenticated", |
|||
# # "rest_framework.permissions.AllowAny", |
|||
# ], |
|||
"DEFAULT_AUTHENTICATION_CLASSES": [ |
|||
# 此方法是 djangorestframework-jwt 的方法,用于检查用户token是否合法 |
|||
"rest_framework_jwt.authentication.JSONWebTokenAuthentication", |
|||
"rest_framework.authentication.BasicAuthentication", |
|||
"rest_framework.authentication.SessionAuthentication", |
|||
], |
|||
# 自定义异常处理 |
|||
"EXCEPTION_HANDLER": "utils.custom.chacerde_exception_handler", |
|||
# 限流 |
|||
"DEFAULT_THROTTLE_CLASSES": ( |
|||
# "rest_framework.throttling.AnonRateThrottle", # 限制所有匿名未认证的用户 |
|||
# "rest_framework.throttling.UserRateThrottle" # 限制认证用户,使用User id 来区分 |
|||
"rest_framework.throttling.ScopedRateThrottle", # 限制用户对于每个视图的访问频次,使用ip或user id |
|||
"utils.throttles.CustomThrottle", |
|||
), |
|||
# DEFAULT_THROTTLE_RATES 可以使用 second, minute, hour 或day来指明周期。 |
|||
"DEFAULT_THROTTLE_RATES": { |
|||
# 'anon':s '10/s', # 匿名用户对应的节流次数 |
|||
# 'user': '200/s' # 登录用户对应 的节流次数 |
|||
}, |
|||
"DATETIME_FORMAT": "%Y-%m-%d %H:%M:%S", |
|||
} |
|||
|
|||
# jwt setting |
|||
JWT_AUTH = { |
|||
"JWT_AUTH_COOKIE": "chacewang", |
|||
"JWT_EXPIRATION_DELTA": datetime.timedelta(days=3), |
|||
"JWT_ISSUER": "https://www.chacewang.com", |
|||
"JWT_AUTH_HEADER_PREFIX": "Bearer", |
|||
"JWT_ALLOW_REFRESH": True, |
|||
"JWT_REFRESH_EXPIRATION_DELTA": datetime.timedelta(days=1) |
|||
} |
|||
|
|||
# **********************缓存设置*************************** |
|||
CACHES = { |
|||
"default": { |
|||
"BACKEND": "django_redis.cache.RedisCache", |
|||
"LOCATION": "redis://:123456@47.112.242.103:16799", |
|||
"OPTIONS": { |
|||
"CLIENT_CLASS": "django_redis.client.DefaultClient", |
|||
# "PASSWORD": "jKCrqEb3XINybCyGp38H&z4eHbHrby" |
|||
"CONNECTION_POOL_KWARGS": {"max_connections": 100}, |
|||
} |
|||
}, |
|||
} |
|||
|
|||
MAX_RETRIES = 3 # 最大重试次数 |
|||
|
|||
REDIS_TIMEOUT = 7*24*60*60 |
|||
DAY_REDIS_TIMEOUT = 24*60*60 |
|||
CUBES_REDIS_TIMEOUT = 60*60 |
|||
NEVER_REDIS_TIMEOUT = 365*24*60*60 |
|||
|
|||
# # swagger 配置项 |
|||
SWAGGER_SETTINGS = { |
|||
# 基础样式 |
|||
"SECURITY_DEFINITIONS": { |
|||
"basic": { |
|||
"type": "basic" |
|||
}, |
|||
}, |
|||
"DEFAULT_INFO": "DjangoDrfTest.urls.swagger_info", # 这里注意,更改DjangoDrfTest |
|||
# 如果需要登录才能够查看接口文档, 登录的链接使用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, |
|||
} |
|||
|
|||
# Internationalization |
|||
# https://docs.djangoproject.com/en/3.1/topics/i18n/ |
|||
|
|||
LANGUAGE_CODE = "zh-hans" |
|||
|
|||
TIME_ZONE = "Asia/Shanghai" |
|||
|
|||
USE_I18N = True |
|||
|
|||
USE_L10N = True |
|||
|
|||
USE_TZ = False |
|||
|
|||
# 用户验证 |
|||
AUTH_USER_MODEL = "rbac.UserProfile" |
|||
|
|||
# Static files (CSS, JavaScript, Images) |
|||
# https://docs.djangoproject.com/en/3.1/howto/static-files/ |
|||
|
|||
STATIC_URL = '/static/' |
|||
|
|||
FILE_PATH = "/data/chacewang_file/" # 本地存储目录 |
|||
SHOW_UPLOAD_PATH = "/upload/" # 前端显示目录 |
|||
MEDIA_URL = "/media/" |
|||
MEDIA_ROOT = os.path.join(BASE_DIR, "media") |
|||
MAX_FILE_SIZE = 104857600 # 限制最大文件100MB |
|||
MAX_IMAGE_SIZE = 2097152 # 限制最大图片文件2MB |
|||
|
|||
MAX_MP3_FILE_SIZE = 20 # 音频文件最大限制20M |
|||
MAX_MP4_FILE_SIZE = 100 # 视频文件最大限制100M |
|||
MAX_IMAGE_FILE_SIZE = 5 # 图片文件最大限制5M |
|||
MAX_PDF_FILE_SIZE = 80 # PDF文件最大限制80M |
|||
|
|||
DATA_UPLOAD_MAX_MEMORY_SIZE = 10485760 # 单位为字节数, 此处配置的值为10M |
|||
|
|||
# ********************************************************** |
|||
# ********************** 全局常量 ************************** |
|||
# ********************************************************** |
|||
MY_PAGE_SIZE = 10 # 默认分页,每页显示条数 |
|||
MY_ARTICLE_PAGE_SIZE = 5 # 客户端文章列表分页,每页显示条数 |
|||
MY_PAGE_SIZE_QUERY_PARAM = "size" # 可以通过传入pager1/?page=2&size=4,改变默认每页显示的个数 |
|||
MY_MAX_PAGE_SIZE = 100 # 最大条数不超过100 |
|||
MY_PAGE_QUERY_PARAM = "page" # 获取页码数的 |
|||
# DEFAULT_PAGINATION_CLASS = 'path.to.PageNumberPaginationWithoutCount' # 去掉分页的count |
|||
|
|||
# 存放日志的路径 |
|||
# window |
|||
BASE_LOG_DIR = os.path.join(BASE_DIR, "logs") |
|||
# linux |
|||
# BASE_LOG_DIR = '/var/log/project/' |
|||
|
|||
# 如果不存在这个logs文件夹,就自动创建一个 |
|||
if not os.path.exists(BASE_LOG_DIR): |
|||
os.mkdir(BASE_LOG_DIR) |
|||
|
|||
LOGGING = { |
|||
"version": 1, # 保留字 |
|||
"disable_existing_loggers": False, # 禁用已经存在的logger实例 |
|||
"formatters": { |
|||
"standard": { |
|||
"format": "[%(asctime)s][%(levelname)s]""[%(filename)s:%(lineno)d][%(message)s]" |
|||
}, |
|||
"simple": { |
|||
"format": "[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s" |
|||
}, |
|||
|
|||
}, |
|||
# 过滤器 |
|||
"filters": { |
|||
"require_debug_true": { |
|||
"()": "django.utils.log.RequireDebugTrue", |
|||
}, |
|||
# 注册该过滤器 |
|||
"request_info": { |
|||
"()": "utils.middleware.RequestLogFilter", |
|||
} |
|||
}, |
|||
"handlers": { |
|||
"default": { |
|||
"level": "INFO", |
|||
"class": "logging.handlers.TimedRotatingFileHandler", # 保存到文件,根据时间自动切 |
|||
"filename": os.path.join(BASE_LOG_DIR, "chacewang_info.log"), |
|||
"backupCount": 3, # 备份数为3 xx.log --> xx.log.2018-08-23_00-00-00 --> xx.log.2018-08-24_00-00-00 --> ... |
|||
"when": "D", # 每天一切, 可选值有S/秒 M/分 H/小时 D/天 W0-W6/周(0=周一) midnight/如果没指定时间就默认在午夜 |
|||
"formatter": "standard", |
|||
"encoding": "utf-8", |
|||
}, |
|||
"warn": { |
|||
"level": "WARNING", |
|||
"class": "logging.handlers.TimedRotatingFileHandler", # 保存到文件,根据时间自动切 |
|||
"filename": os.path.join(BASE_LOG_DIR, "chacewang_warning.log"), |
|||
"backupCount": 3, # 备份数为3 xx.log --> xx.log.2018-08-23_00-00-00 --> xx.log.2018-08-24_00-00-00 --> ... |
|||
"when": "D", # 每天一切, 可选值有S/秒 M/分 H/小时 D/天 W0-W6/周(0=周一) midnight/如果没指定时间就默认在午夜 |
|||
"formatter": "standard", |
|||
"encoding": "utf-8", |
|||
}, |
|||
# 专门用来记错误日志 |
|||
"error": { |
|||
"level": "ERROR", |
|||
"class": "logging.handlers.TimedRotatingFileHandler", # 保存到文件,根据时间自动切 |
|||
"filename": os.path.join(BASE_LOG_DIR, "chacewang_err.log"), # 日志文件 |
|||
"backupCount": 3, # 备份数为3 xx.log --> xx.log.2018-08-23_00-00-00 --> xx.log.2018-08-24_00-00-00 --> ... |
|||
"when": "D", # 每天一切, 可选值有S/秒 M/分 H/小时 D/天 W0-W6/周(0=周一) midnight/如果没指定时间就默认在午夜 |
|||
"formatter": "standard", |
|||
"encoding": "utf-8", |
|||
}, |
|||
# 按文件大小分割 |
|||
# "DEMO": { |
|||
# "level": "INFO", |
|||
# "class": "logging.handlers.RotatingFileHandler", # 保存到文件,根据文件大小自动切 |
|||
# "filename": os.path.join(BASE_LOG_DIR, "chacewang_info.log"), # 日志文件 |
|||
# "maxBytes": 1024 * 1024 * 50, # 日志大小 50M |
|||
# "backupCount": 3, # 备份数为3 xx.log --> xx.log.1 --> xx.log.2 --> xx.log.3 |
|||
# "formatter": "standard", |
|||
# "encoding": "utf-8", |
|||
# }, |
|||
}, |
|||
"loggers": { |
|||
"info": { |
|||
"handlers": ["default"], |
|||
"level": "INFO", |
|||
"propagate": True, # 向不向更高级别的logger传递 |
|||
}, |
|||
"warn": { |
|||
"handlers": ["warn"], |
|||
"level": "WARNING", |
|||
"propagate": True, |
|||
}, |
|||
"error": { |
|||
"handlers": ["error"], |
|||
"level": "ERROR", |
|||
"propagate": True, |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
"""ChaCeRndTrans 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.conf.urls import url |
|||
from django.contrib import admin |
|||
from django.urls import path, re_path, include |
|||
from drf_yasg.views import get_schema_view |
|||
from drf_yasg import openapi |
|||
from django.conf.urls.static import static |
|||
from ChaCeRndTrans import settings |
|||
|
|||
schema_view = get_schema_view( |
|||
openapi.Info( |
|||
title="独角鲸 API接口文档平台", # 必传 |
|||
default_version="v1", # 必传 |
|||
# description="这是查策AI管理后台的接口文档", |
|||
# terms_of_service="https://www.chacewang.net", |
|||
# contact=openapi.Contact(email="1361575048@qq.com"), |
|||
# license=openapi.License(name="BSD License"), |
|||
), |
|||
public=True, |
|||
# permission_classes=(permissions.AllowAny,), # 权限类 |
|||
|
|||
|
|||
) |
|||
|
|||
urlpatterns = [ |
|||
# swagger接口文档路由,三种不同风格的接口文档,自选其一 |
|||
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')), |
|||
re_path(r"^swagger(?P<format>\.json|\.yaml)$", schema_view.without_ui(cache_timeout=0), name="schema-json"), |
|||
path("swagger/", schema_view.with_ui("swagger", cache_timeout=0), name="schema-swagger-ui"), |
|||
path("redoc/", schema_view.with_ui("redoc", cache_timeout=0), name="schema-redoc"), |
|||
path('admin/', admin.site.urls), |
|||
path("", include("rbac.urls")), |
|||
path("", include("common.urls")), |
|||
# path("", include("tasks.urls")), |
|||
# path(r"^media/(?P<path>.*)$", serve, {'document_root': settings.MEDIA_ROOT}), |
|||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) |
|||
@ -0,0 +1,16 @@ |
|||
""" |
|||
WSGI config for ChaCeRndTrans 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', 'ChaCeRndTrans.settings') |
|||
|
|||
application = get_wsgi_application() |
|||
@ -0,0 +1,3 @@ |
|||
from django.contrib import admin |
|||
|
|||
# Register your models here. |
|||
@ -0,0 +1,6 @@ |
|||
from django.apps import AppConfig |
|||
|
|||
|
|||
class CommonConfig(AppConfig): |
|||
default_auto_field = 'django.db.models.BigAutoField' |
|||
name = 'common' |
|||
@ -0,0 +1,123 @@ |
|||
# Generated by Django 3.1.4 on 2024-01-24 15:29 |
|||
|
|||
from django.db import migrations, models |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
initial = True |
|||
|
|||
dependencies = [ |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.CreateModel( |
|||
name='Area', |
|||
fields=[ |
|||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('DataDictionaryDetailId', models.CharField(default='', max_length=36, verbose_name='字典详情id')), |
|||
('Code', models.CharField(blank=True, max_length=10, null=True, verbose_name='地区编号')), |
|||
('Abbreviation', models.CharField(blank=True, max_length=50, null=True, verbose_name='地区简称')), |
|||
('TopImg', models.CharField(blank=True, max_length=500, null=True, verbose_name='封面图')), |
|||
('BaiDuCode', models.CharField(blank=True, max_length=30, null=True, verbose_name='百度地图编号')), |
|||
('IsMunicipality', models.BooleanField(blank=True, null=True, verbose_name='是否自辖市')), |
|||
('IsProvince', models.BooleanField(blank=True, null=True, verbose_name='是否省级')), |
|||
('IsCity', models.BooleanField(blank=True, null=True, verbose_name='是否市级')), |
|||
('IsHotCity', models.BooleanField(blank=True, null=True, verbose_name='是否热门城市')), |
|||
('IsTownship', models.BooleanField(blank=True, null=True, verbose_name='是否区/乡镇')), |
|||
('CreateByUid', models.IntegerField(blank=True, null=True, verbose_name='创建人ID')), |
|||
('CreateBy', models.CharField(blank=True, max_length=36, null=True, verbose_name='创建人ID')), |
|||
('CreateByName', models.CharField(blank=True, max_length=30, null=True, verbose_name='创建人名称')), |
|||
('UpdateByUid', models.IntegerField(blank=True, null=True, verbose_name='更新人ID')), |
|||
('UpdateBy', models.CharField(blank=True, max_length=36, null=True, verbose_name='更新人ID')), |
|||
('UpdateByName', models.CharField(blank=True, max_length=30, null=True, verbose_name='更新人名称')), |
|||
('CreateDateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), |
|||
('UpdateDateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), |
|||
], |
|||
options={ |
|||
'verbose_name': '地区', |
|||
'verbose_name_plural': '地区', |
|||
'ordering': ['id'], |
|||
'managed': False, |
|||
}, |
|||
), |
|||
migrations.CreateModel( |
|||
name='DataDictionary', |
|||
fields=[ |
|||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('DataDictionaryId', models.CharField(blank=True, max_length=36, null=True, verbose_name='字典id')), |
|||
('CompanyId', models.CharField(blank=True, max_length=50, null=True, verbose_name='企业id')), |
|||
('ParentId', models.CharField(blank=True, max_length=36, null=True, verbose_name='父类字典')), |
|||
('CategoryType', models.CharField(blank=True, max_length=50, null=True, verbose_name='字典类别')), |
|||
('DictionaryCode', models.CharField(blank=True, max_length=50, null=True, verbose_name='字典类别')), |
|||
('FullName', models.CharField(blank=True, max_length=50, null=True, verbose_name='字典名称')), |
|||
('Remark', models.CharField(blank=True, max_length=200, null=True, verbose_name='备注')), |
|||
('IsTree', models.BooleanField(blank=True, null=True, verbose_name='是否是树结构')), |
|||
('IsEnabled', models.BooleanField(blank=True, null=True, verbose_name='是否可用')), |
|||
('SortNo', models.IntegerField(blank=True, null=True, verbose_name='排序')), |
|||
('CreateByUid', models.IntegerField(blank=True, null=True, verbose_name='创建人ID')), |
|||
('AddByName', models.CharField(blank=True, max_length=30, null=True, verbose_name='创建人名称')), |
|||
('UpdateByUid', models.IntegerField(blank=True, null=True, verbose_name='更新人ID')), |
|||
('UpdateByName', models.CharField(blank=True, max_length=30, null=True, verbose_name='更新人名称')), |
|||
('AddDate', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), |
|||
('UpdateDate', models.DateTimeField(auto_now=True, verbose_name='更新时间')), |
|||
], |
|||
options={ |
|||
'verbose_name': '字典', |
|||
'verbose_name_plural': '字典', |
|||
'db_table': 'common_datadictionary', |
|||
'managed': False, |
|||
}, |
|||
), |
|||
migrations.CreateModel( |
|||
name='DataDictionaryDetail', |
|||
fields=[ |
|||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('DataDictionaryDetailId', models.CharField(blank=True, max_length=36, null=True, verbose_name='字典详情id')), |
|||
('DataDictionaryId', models.CharField(blank=True, max_length=50, null=True, verbose_name='字典id')), |
|||
('ParentId', models.CharField(blank=True, max_length=50, null=True, verbose_name='父类字典')), |
|||
('ParentCode', models.CharField(blank=True, max_length=50, null=True, verbose_name='字典id')), |
|||
('DictionaryCode', models.CharField(blank=True, max_length=50, null=True, verbose_name='键')), |
|||
('DictionaryValue', models.CharField(blank=True, max_length=100, null=True, verbose_name='值')), |
|||
('FullName', models.CharField(blank=True, max_length=300, null=True, verbose_name='字典名称')), |
|||
('Remark', models.CharField(blank=True, max_length=200, null=True, verbose_name='备注')), |
|||
('IsTree', models.BooleanField(blank=True, null=True, verbose_name='是否是树结构')), |
|||
('IsEnabled', models.BooleanField(blank=True, null=True, verbose_name='是否可用')), |
|||
('SortNo', models.IntegerField(blank=True, null=True, verbose_name='排序')), |
|||
('SortNoTwo', models.IntegerField(blank=True, null=True, verbose_name='查策网排序')), |
|||
('CreateByUid', models.IntegerField(blank=True, null=True, verbose_name='创建人ID')), |
|||
('AddByName', models.CharField(blank=True, max_length=30, null=True, verbose_name='创建人名称')), |
|||
('UpdateByUid', models.IntegerField(blank=True, null=True, verbose_name='更新人ID')), |
|||
('UpdateByName', models.CharField(blank=True, max_length=30, null=True, verbose_name='更新人名称')), |
|||
('AddDate', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), |
|||
('UpdateDate', models.DateTimeField(auto_now=True, verbose_name='更新时间')), |
|||
], |
|||
options={ |
|||
'verbose_name': '字典详情', |
|||
'verbose_name_plural': '字典详情', |
|||
'db_table': 'common_datadictionarydetail', |
|||
'managed': False, |
|||
}, |
|||
), |
|||
migrations.CreateModel( |
|||
name='OperationHistoryLog', |
|||
fields=[ |
|||
('id', models.IntegerField(primary_key=True, serialize=False, verbose_name='操作记录id')), |
|||
('des', models.CharField(blank=True, max_length=50, null=True, verbose_name='操作描述')), |
|||
('detail', models.TextField(blank=True, null=True, verbose_name='操作详情')), |
|||
('ip', models.CharField(blank=True, max_length=20, null=True, verbose_name='操作人IP')), |
|||
('user_id', models.IntegerField(blank=True, null=True, verbose_name='操作人ID')), |
|||
('username', models.CharField(blank=True, max_length=30, null=True, verbose_name='操作人名称')), |
|||
('update_user_id', models.IntegerField(blank=True, null=True, verbose_name='更新人ID')), |
|||
('update_username', models.CharField(blank=True, max_length=30, null=True, verbose_name='更新人名称')), |
|||
('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), |
|||
('update_time', models.DateTimeField(auto_now=True, verbose_name='更新时间')), |
|||
], |
|||
options={ |
|||
'verbose_name': '操作日志记录', |
|||
'verbose_name_plural': '操作日志记录', |
|||
'db_table': 'operation_history', |
|||
'managed': False, |
|||
}, |
|||
), |
|||
] |
|||
@ -0,0 +1,215 @@ |
|||
from django.db import models |
|||
|
|||
|
|||
class DataDictionary(models.Model): |
|||
""" |
|||
字典 |
|||
""" |
|||
DataDictionaryId = models.CharField(max_length=36, null=True, blank=True, verbose_name='字典id') |
|||
CompanyId = models.CharField(max_length=50, null=True, blank=True, verbose_name='企业id') |
|||
ParentId = models.CharField(max_length=36, null=True, blank=True, verbose_name='父类字典') |
|||
CategoryType = models.CharField(max_length=50, null=True, blank=True, verbose_name='字典类别') |
|||
DictionaryCode = models.CharField(max_length=50, null=True, blank=True, verbose_name='字典类别') |
|||
FullName = models.CharField(max_length=50, null=True, blank=True, verbose_name='字典名称') |
|||
Remark = models.CharField(max_length=200, null=True, blank=True, verbose_name='备注') |
|||
IsTree = models.BooleanField(null=True, blank=True, verbose_name="是否是树结构") |
|||
IsEnabled = models.BooleanField(null=True, blank=True, verbose_name="是否可用") |
|||
SortNo = models.IntegerField(null=True, blank=True, verbose_name="排序") |
|||
pid = models.ForeignKey("self", null=True, blank=True, on_delete=models.SET_NULL, verbose_name="父类字典") |
|||
CreateByUid = models.IntegerField(null=True, blank=True, verbose_name="创建人ID") |
|||
AddByName = models.CharField(max_length=30, null=True, blank=True, verbose_name="创建人名称") |
|||
UpdateByUid = models.IntegerField(null=True, blank=True, verbose_name="更新人ID") |
|||
UpdateByName = models.CharField(max_length=30, null=True, blank=True, verbose_name="更新人名称") |
|||
AddDate = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") |
|||
UpdateDate = models.DateTimeField(auto_now=True, verbose_name="更新时间") |
|||
|
|||
def __str__(self): |
|||
return self.FullName |
|||
|
|||
class Meta: |
|||
db_table = "common_datadictionary" |
|||
managed = False # False是不会为当前模型创建和删除数据表 |
|||
verbose_name = '字典' |
|||
verbose_name_plural = verbose_name |
|||
# app_label = 'chace' # 指定在chace数据库下创建数据表 |
|||
indexes = [ |
|||
models.Index(fields=['DataDictionaryId']), |
|||
models.Index(fields=['DictionaryCode']), |
|||
] |
|||
|
|||
|
|||
class DataDictionaryDetail(models.Model): |
|||
""" |
|||
字典详情 |
|||
""" |
|||
DataDictionaryDetailId = models.CharField(max_length=36, null=True, blank=True, verbose_name='字典详情id') |
|||
DataDictionaryId = models.CharField(max_length=50, null=True, blank=True, verbose_name='字典id') |
|||
ParentId = models.CharField(max_length=50, null=True, blank=True, verbose_name='父类字典') |
|||
ParentCode = models.CharField(max_length=50, null=True, blank=True, verbose_name='字典id') |
|||
DictionaryCode = models.CharField(max_length=50, null=True, blank=True, verbose_name='键') |
|||
DictionaryValue = models.CharField(max_length=100, null=True, blank=True, verbose_name='值') |
|||
FullName = models.CharField(max_length=300, null=True, blank=True, verbose_name='字典名称') |
|||
Remark = models.CharField(max_length=200, null=True, blank=True, verbose_name='备注') |
|||
IsTree = models.BooleanField(null=True, blank=True, verbose_name="是否是树结构") |
|||
IsEnabled = models.BooleanField(null=True, blank=True, verbose_name="是否可用") |
|||
SortNo = models.IntegerField(null=True, blank=True, verbose_name="排序") |
|||
SortNoTwo = models.IntegerField(null=True, blank=True, verbose_name="查策网排序") |
|||
CreateByUid = models.IntegerField(null=True, blank=True, verbose_name="创建人ID") |
|||
AddByName = models.CharField(max_length=30, null=True, blank=True, verbose_name="创建人名称") |
|||
UpdateByUid = models.IntegerField(null=True, blank=True, verbose_name="更新人ID") |
|||
UpdateByName = models.CharField(max_length=30, null=True, blank=True, verbose_name="更新人名称") |
|||
AddDate = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") |
|||
UpdateDate = models.DateTimeField(auto_now=True, verbose_name="更新时间") |
|||
|
|||
def __str__(self): |
|||
return self.FullName |
|||
|
|||
class Meta: |
|||
db_table = "common_datadictionarydetail" |
|||
managed = False # False是不会为当前模型创建和删除数据表 |
|||
verbose_name = '字典详情' |
|||
verbose_name_plural = verbose_name |
|||
# app_label = 'chace' # 指定在chace数据库下创建数据表 |
|||
indexes = [ |
|||
models.Index(fields=['DataDictionaryDetailId']), |
|||
models.Index(fields=['DataDictionaryId']), |
|||
models.Index(fields=['DictionaryCode']), |
|||
models.Index(fields=['DictionaryValue']), |
|||
models.Index(fields=['ParentCode']), |
|||
models.Index(fields=['IsEnabled']), |
|||
models.Index(fields=['ParentCode', 'IsEnabled']), |
|||
] |
|||
|
|||
|
|||
class OperationHistoryLog(models.Model): |
|||
""" |
|||
操作日志记录 |
|||
""" |
|||
id = models.IntegerField(primary_key=True, verbose_name='操作记录id') |
|||
des = models.CharField(max_length=50, null=True, blank=True, verbose_name="操作描述") |
|||
detail = models.TextField(null=True, blank=True, verbose_name="操作详情") |
|||
ip = models.CharField(max_length=20, null=True, blank=True, verbose_name="操作人IP") |
|||
user_id = models.IntegerField(null=True, blank=True, verbose_name="操作人ID") |
|||
username = models.CharField(max_length=30, null=True, blank=True, verbose_name="操作人名称") |
|||
update_user_id = models.IntegerField(null=True, blank=True, verbose_name="更新人ID") |
|||
update_username = models.CharField(max_length=30, null=True, blank=True, verbose_name="更新人名称") |
|||
create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") |
|||
update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间") |
|||
|
|||
def __str__(self): |
|||
return self.des |
|||
|
|||
class Meta: |
|||
db_table = "operation_history" |
|||
managed = False # False是不会为当前模型创建和删除数据表 |
|||
verbose_name = '操作日志记录' |
|||
verbose_name_plural = verbose_name |
|||
indexes = [ |
|||
models.Index(fields=['username']), |
|||
models.Index(fields=['des']), |
|||
models.Index(fields=['create_time']), |
|||
models.Index(fields=['detail']), |
|||
] |
|||
|
|||
|
|||
class DataDictionaryDetailZY(models.Model): |
|||
""" |
|||
字典详情 |
|||
""" |
|||
DataDictionaryDetailId = models.CharField(max_length=36, null=True, blank=True, verbose_name='字典详情id') |
|||
DataDictionaryId = models.CharField(max_length=50, null=True, blank=True, verbose_name='字典id') |
|||
ParentId = models.CharField(max_length=50, null=True, blank=True, verbose_name='父类字典') |
|||
ParentCode = models.CharField(max_length=50, null=True, blank=True, verbose_name='字典id') |
|||
DictionaryCode = models.CharField(max_length=50, null=True, blank=True, verbose_name='键') |
|||
DictionaryValue = models.CharField(max_length=100, null=True, blank=True, verbose_name='值') |
|||
FullName = models.CharField(max_length=300, null=True, blank=True, verbose_name='字典名称') |
|||
Remark = models.CharField(max_length=200, null=True, blank=True, verbose_name='备注') |
|||
IsTree = models.BooleanField(null=True, blank=True, verbose_name="是否是树结构") |
|||
IsEnabled = models.BooleanField(null=True, blank=True, verbose_name="是否可用") |
|||
SortNo = models.IntegerField(null=True, blank=True, verbose_name="排序") |
|||
SortNoTwo = models.IntegerField(null=True, blank=True, verbose_name="查策网排序") |
|||
CreateByUid = models.IntegerField(null=True, blank=True, verbose_name="创建人ID") |
|||
AddByName = models.CharField(max_length=30, null=True, blank=True, verbose_name="创建人名称") |
|||
UpdateByUid = models.IntegerField(null=True, blank=True, verbose_name="更新人ID") |
|||
UpdateByName = models.CharField(max_length=30, null=True, blank=True, verbose_name="更新人名称") |
|||
AddDate = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") |
|||
UpdateDate = models.DateTimeField(auto_now=True, verbose_name="更新时间") |
|||
|
|||
def __str__(self): |
|||
return self.FullName |
|||
|
|||
class Meta: |
|||
db_table = "common_datadictionarydetail" |
|||
managed = False # False是不会为当前模型创建和删除数据表 |
|||
verbose_name = '字典详情' |
|||
verbose_name_plural = verbose_name |
|||
app_label = 'chace' # 指定在chace数据库下创建数据表 |
|||
|
|||
|
|||
class Area(models.Model): |
|||
""" |
|||
地区 |
|||
""" |
|||
DataDictionaryDetailId = models.CharField(max_length=36, default="", verbose_name="字典详情id") |
|||
Code = models.CharField(max_length=10, null=True, blank=True, verbose_name="地区编号") |
|||
Abbreviation = models.CharField(max_length=50, null=True, blank=True, verbose_name="地区简称") |
|||
TopImg = models.CharField(max_length=500, null=True, blank=True, verbose_name="封面图") |
|||
BaiDuCode = models.CharField(max_length=30, null=True, blank=True, verbose_name="百度地图编号") |
|||
IsMunicipality = models.BooleanField(null=True, blank=True, verbose_name="是否自辖市") |
|||
IsProvince = models.BooleanField(null=True, blank=True, verbose_name="是否省级") |
|||
IsCity = models.BooleanField(null=True, blank=True, verbose_name="是否市级") |
|||
IsHotCity = models.BooleanField(null=True, blank=True, verbose_name="是否热门城市") |
|||
IsTownship = models.BooleanField(null=True, blank=True, verbose_name="是否区/乡镇") |
|||
CreateByUid = models.IntegerField(null=True, blank=True, verbose_name="创建人ID") |
|||
CreateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="创建人ID") |
|||
CreateByName = models.CharField(max_length=30, null=True, blank=True, verbose_name="创建人名称") |
|||
UpdateByUid = models.IntegerField(null=True, blank=True, verbose_name="更新人ID") |
|||
UpdateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="更新人ID") |
|||
UpdateByName = models.CharField(max_length=30, null=True, blank=True, verbose_name="更新人名称") |
|||
CreateDateTime = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") |
|||
UpdateDateTime = models.DateTimeField(auto_now=True, |
|||
verbose_name="更新时间") # 该字段仅在调用 Model.save() 时自动更新。在以其他方式(例如 QuerySet.update())更新其他字段时,不会更新该字段,但可以在此类更新中为字段指定自定义值。 |
|||
|
|||
def __str__(self): |
|||
return self.Abbreviation |
|||
|
|||
class Meta: |
|||
managed = False # False是不会为当前模型创建和删除数据表 |
|||
verbose_name = "地区" |
|||
verbose_name_plural = verbose_name |
|||
ordering = ["id"] |
|||
|
|||
|
|||
class Archive(models.Model): |
|||
""" |
|||
用户上传的资料管理 |
|||
""" |
|||
MainId = models.CharField(max_length=36, null=True, blank=True, verbose_name="文件唯一标识", help_text="文件唯一标识") |
|||
Name = models.CharField(max_length=255, null=True, blank=True, verbose_name="用户上传的文件名称", help_text="用户上传的文件名称") |
|||
Ext = models.CharField(max_length=10, null=True, blank=True, verbose_name="文件扩展名", help_text="文件扩展名") |
|||
ProjectCodes = models.TextField(null=True, blank=True, verbose_name="绑定项目code", help_text="绑定项目code") # 可能绑定多个项目 |
|||
Label = models.IntegerField(null=True, blank=True, verbose_name="类型标签", help_text="类型标签 类型:1-文档 2-pdf 3-excel 4-图片 5-ppt 6-mp4") |
|||
Url = models.CharField(max_length=255, null=True, blank=True, verbose_name="文件路径", help_text="文件路径") |
|||
Remark = models.CharField(max_length=255, null=True, blank=True, verbose_name='备注') |
|||
Global = models.IntegerField(null=True, blank=True, verbose_name="是否后台管理员上传全局查阅资料 # 1:是 2:不是") |
|||
|
|||
CreateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="创建人") |
|||
UpdateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="更新人") |
|||
CreateByUid = models.IntegerField(null=True, blank=True, verbose_name="创建人ID") |
|||
UpdateByUid = models.IntegerField(null=True, blank=True, verbose_name="更新人ID") |
|||
CreateDateTime = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") |
|||
UpdateDateTime = models.DateTimeField(auto_now=True, verbose_name="更新时间") |
|||
companyMid = models.CharField(null=True, blank=True, max_length=36, verbose_name="公司全局id") # 用于绑定公司 |
|||
|
|||
issuingDepartment = models.CharField(max_length=200, null=True, blank=True, verbose_name="发文部门") |
|||
dateOfPublication = models.DateField(null=True, blank=True, verbose_name="发文时间") |
|||
originalLink = models.CharField(null=True, blank=True, max_length=2000, verbose_name="原文链接") |
|||
digest = models.TextField(null=True, blank=True, verbose_name="摘要") |
|||
title = models.CharField(null=True, blank=True, max_length=200, verbose_name="摘要") |
|||
|
|||
class Meta: |
|||
db_table = "common_archive" |
|||
managed = False # False是不会为当前模型创建和删除数据表 |
|||
verbose_name = '用户上传的资料管理' |
|||
verbose_name_plural = verbose_name |
|||
app_label = 'chace_rnd' # 指定在chace数据库下创建数据表 |
|||
@ -0,0 +1,13 @@ |
|||
from rest_framework import serializers |
|||
|
|||
from common.models import Archive |
|||
|
|||
|
|||
class ArchiveSerializer(serializers.ModelSerializer): |
|||
""" |
|||
用户上传资料管理序列化 |
|||
""" |
|||
|
|||
class Meta: |
|||
model = Archive |
|||
fields = "__all__" |
|||
@ -0,0 +1,57 @@ |
|||
from rest_framework import serializers |
|||
from ..models import DataDictionary, DataDictionaryDetail |
|||
|
|||
|
|||
class DictSerializer(serializers.ModelSerializer): |
|||
''' |
|||
字典序列化 |
|||
''' |
|||
id = serializers.IntegerField(label='id', read_only=True) |
|||
dict_id = serializers.CharField(max_length=50, source='DataDictionaryId') |
|||
label = serializers.CharField(max_length=50, source='FullName') |
|||
parent_id = serializers.CharField(max_length=50, required=False, allow_blank=True, allow_null=True, source='ParentId') |
|||
dict_code = serializers.CharField(max_length=50, required=False, allow_blank=True, allow_null=True, source='DictionaryCode') |
|||
remark = serializers.CharField(required=False, allow_blank=True, allow_null=True, source='Remark') |
|||
is_enabled = serializers.BooleanField(required=False, label='是否可用', source='IsEnabled') |
|||
|
|||
create_id = serializers.IntegerField(label='创建人id', required=False, source='CreateByUid') |
|||
create_user = serializers.CharField(label='创建人', max_length=50, required=False, source="AddByName") |
|||
create_date = serializers.DateTimeField(label='创建时间', required=False, source="AddDate") |
|||
update_id = serializers.IntegerField(label='修改人id', required=False, source='UpdateByUid') |
|||
update_user = serializers.CharField(label='更新人', max_length=50, required=False, source="UpdateByName") |
|||
update_date = serializers.DateTimeField(label='更新时间', required=False, source="UpdateDate") |
|||
|
|||
class Meta: |
|||
model = DataDictionary |
|||
fields = ['id', 'dict_id', 'label', 'parent_id', 'dict_code', 'is_enabled', 'remark','create_id', 'create_user', |
|||
'create_date', 'update_id', 'update_user', 'update_date'] |
|||
|
|||
|
|||
class DictDetailSerializer(serializers.ModelSerializer): |
|||
''' |
|||
字典详情序列化 |
|||
''' |
|||
id = serializers.IntegerField(label='id', read_only=True) |
|||
dict_id = serializers.CharField(label='字典id', max_length=50, required=False, allow_blank=True, allow_null=True, source='DataDictionaryId') |
|||
dict_detail_id = serializers.CharField(label='字典详情id', max_length=50, required=False, allow_blank=True, allow_null=True, source='DataDictionaryDetailId') |
|||
label = serializers.CharField(label='字典名称', max_length=300, allow_null=True, source='FullName') |
|||
remark = serializers.CharField(label='备注', max_length=300, required=False, allow_blank=True, allow_null=True, source='Remark') |
|||
parent_id = serializers.CharField(label='父类字典', required=False, allow_blank=True, allow_null=True, max_length=50, source='ParentId') |
|||
dict_code = serializers.CharField(label='键', max_length=50, source='DictionaryCode') |
|||
dict_val = serializers.CharField(label='值', max_length=50, source='DictionaryValue') |
|||
parent_code = serializers.CharField(label='字典id', required=False, allow_blank=True, allow_null=True, max_length=50, source='ParentCode') |
|||
is_enabled = serializers.BooleanField(label='是否可用', required=False, source='IsEnabled') |
|||
sort_no_two = serializers.IntegerField(label='查策网排序', required=False, source='SortNoTwo') |
|||
create_id = serializers.IntegerField(label='创建人id', required=False, source='CreateByUid') |
|||
create_user = serializers.CharField(label='创建人', max_length=50, required=False, source="AddByName") |
|||
create_date = serializers.DateTimeField(label='创建时间', required=False, source="AddDate") |
|||
update_id = serializers.IntegerField(label='修改人id', required=False, source='UpdateByUid') |
|||
update_user = serializers.CharField(label='更新人', max_length=50, required=False, source="UpdateByName") |
|||
update_date = serializers.DateTimeField(label='更新时间', required=False, source="UpdateDate") |
|||
|
|||
class Meta: |
|||
model = DataDictionaryDetail |
|||
fields = ['id', 'dict_id', 'dict_detail_id', 'label', 'remark', 'parent_id', |
|||
'dict_code', 'dict_val', 'is_enabled', 'parent_code', 'create_id', 'create_user', |
|||
'create_date', 'update_id', 'update_user', 'update_date', 'sort_no_two' |
|||
] |
|||
@ -0,0 +1,3 @@ |
|||
from django.test import TestCase |
|||
|
|||
# Create your tests here. |
|||
@ -0,0 +1,25 @@ |
|||
from django.urls import path, include |
|||
from rest_framework import routers |
|||
|
|||
from common.views import dict, upload_file, archive |
|||
from common.views.area import NewRegisterAreaListAPIView |
|||
from common.views.history import HistoryLogAPIView |
|||
|
|||
router = routers.SimpleRouter() |
|||
router.register(r"dict/detail/list", dict.DataDictionaryDetailListViewSet, basename="dict_detail_list") |
|||
router.register(r"dict/detail", dict.DataDictionaryDetailTreeViewSet, basename="dict_detail") |
|||
router.register(r"dict", dict.DataDictionaryViewSet, basename="dict") |
|||
router.register(r"archive", archive.ArchiveViewSet, basename="archive") |
|||
|
|||
urlpatterns = [ |
|||
path(r"api/", include(router.urls)), |
|||
path(r'api/att/file/upload/', upload_file.UploadFileAPIView.as_view(), name='upload_file'), # 上传附件 |
|||
path(r'api/file/download/', upload_file.DownLoadFileAPIView.as_view(), name='download_file'), |
|||
path(r'api/file/delete/', upload_file.DeleteFileAPIView.as_view(), name='delete_file'), |
|||
path(r'api/history/list/', HistoryLogAPIView.as_view(), name='history_list'), # 操作日志 |
|||
path(r'api/dict/zy/detail/', dict.DataDictionaryDetailTreeViewSetZY.as_view({'get': 'list'}), |
|||
name='dict_detail_zy'), # 操作日志 |
|||
path(r'api/register/area/newlist/', NewRegisterAreaListAPIView.as_view(), name='area_list'), # 去除区县 |
|||
path(r"api/break/point/upload/", archive.BreakPointUploadAPIView.as_view(), name="break_point_upload"), |
|||
path(r"api/merge/file/", archive.MergeFile.as_view(), name="merge_file"), |
|||
] |
|||
@ -0,0 +1,3 @@ |
|||
from django.shortcuts import render |
|||
|
|||
# Create your views here. |
|||
@ -0,0 +1,959 @@ |
|||
import datetime |
|||
import json |
|||
import logging |
|||
import os |
|||
import shutil |
|||
import time |
|||
import traceback |
|||
import uuid |
|||
from concurrent.futures import ThreadPoolExecutor |
|||
from urllib import parse |
|||
|
|||
from django.db import transaction |
|||
from django.http import HttpResponse, FileResponse |
|||
from hashids import Hashids |
|||
from rest_framework.filters import OrderingFilter, SearchFilter |
|||
from rest_framework.generics import get_object_or_404 |
|||
from rest_framework.permissions import IsAuthenticated |
|||
from rest_framework.views import APIView |
|||
from rest_framework.viewsets import ModelViewSet |
|||
from rest_framework_jwt.authentication import JSONWebTokenAuthentication |
|||
|
|||
from ChaCeRndTrans.basic import CCAIResponse |
|||
from ChaCeRndTrans.code import BAD, SERVER_ERROR |
|||
from ChaCeRndTrans.settings import ID_KEY, FILE_PATH, FILE_HTTP, MEDIA_ROOT, MAX_MP4_FILE_SIZE |
|||
from common.models import Archive, OperationHistoryLog |
|||
from common.serializers.archive_serializer import ArchiveSerializer |
|||
from utils.custom import CommonPagination, RbacPermission, AESEnDECryptRelated, req_operate_by_user, asyncDeleteFile, \ |
|||
generate_random_str_for_fileName, generate_random_str, create_operation_history_log |
|||
|
|||
from django.core.files.storage import default_storage |
|||
|
|||
from rest_framework.decorators import action |
|||
|
|||
from django.db import connection |
|||
|
|||
from rbac.models import UserProfile |
|||
|
|||
err_logger = logging.getLogger('error') |
|||
|
|||
# 创建线程池 |
|||
threadPool = ThreadPoolExecutor(max_workers=10, thread_name_prefix="test_") |
|||
|
|||
|
|||
class ArchiveViewSet(ModelViewSet): |
|||
""" |
|||
用户上传的资料管理 |
|||
""" |
|||
perms_map = ({"*": "admin"}, {"*": "comadmin"}, {"*": "archive_all"}, {"get": "archive_list"}, {"post": "archive_create"}, {"put": "archive_update"}, |
|||
{"delete": "archive_delete"}) |
|||
queryset = Archive.objects.all() |
|||
serializer_class = ArchiveSerializer |
|||
pagination_class = CommonPagination |
|||
filter_backends = (OrderingFilter, SearchFilter) |
|||
ordering_fields = ("create_time",) |
|||
# search_fields = ("Name", "ProjectCodes", ) |
|||
authentication_classes = (JSONWebTokenAuthentication,) |
|||
permission_classes = (IsAuthenticated, RbacPermission) |
|||
|
|||
def get_object(self): |
|||
""" |
|||
重写get_object获取对象函数 |
|||
@return: |
|||
""" |
|||
pk = self.kwargs.get('pk') |
|||
try: |
|||
hashids = Hashids(salt=ID_KEY, min_length=32) |
|||
decode_id = hashids.decode(pk) |
|||
if not decode_id: |
|||
err_logger.error("decode id failed, id: \n%s" % pk) |
|||
return CCAIResponse("invalid ID", BAD) |
|||
# 使用id 获取对象 |
|||
obj = self.queryset.get(id=decode_id[0]) |
|||
except Exception as e: |
|||
err_logger.error("get object failed, id: \n%s" % pk) |
|||
return CCAIResponse("invalid ID", BAD) |
|||
# 检查对象是否存在 |
|||
self.check_object_permissions(self.request, obj) |
|||
return obj |
|||
|
|||
def list(self, request, *args, **kwargs): |
|||
""" |
|||
获取上传文件列表 |
|||
:param request: |
|||
:param args: |
|||
:param kwargs: |
|||
:return: |
|||
""" |
|||
try: |
|||
hashids = Hashids(salt=ID_KEY, min_length=32) |
|||
params = request.GET |
|||
page_size = params.get('size', None) |
|||
page = params.get('page', None) |
|||
companyMid = params.get('companyMid', None) |
|||
ProjectCodes = params.get('ProjectCodes', None) |
|||
title = params.get('title', None) |
|||
Label = params.get('Label', None) |
|||
pagination = {} |
|||
adminUserId = UserProfile.objects.filter(roles__id=1).first() # 查询管理员用户id |
|||
|
|||
sql = r''' |
|||
SELECT a.id, a.MainId, a.Name, a.Ext, a.ProjectCodes, a.Label, a.Url, a.Remark, a.Global, |
|||
a.Label, a.CreateBy, a.UpdateBy, a.CreateByUid, a.UpdateByUid, |
|||
a.CreateDateTime, a.UpdateDateTime, a.companyMid, a.title |
|||
FROM common_archive a |
|||
WHERE 1=1 |
|||
''' |
|||
count_sql = r'''SELECT a.id FROM common_archive a WHERE 1=1 ''' |
|||
where_params = [] |
|||
|
|||
if companyMid: |
|||
temp = " AND a.companyMid = '{}' ".format(companyMid) |
|||
sql = sql + temp |
|||
count_sql = count_sql + temp |
|||
# where_params.append(companyMid) |
|||
else: |
|||
return CCAIResponse("参数缺失!", BAD) |
|||
# 可能有多个项目 |
|||
if ProjectCodes: |
|||
temp = ' AND ( ' |
|||
project_codes_list = ProjectCodes.split(',') |
|||
for index, code in enumerate(project_codes_list): |
|||
if index == len(project_codes_list) - 1: |
|||
temp = temp + " FIND_IN_SET('{}', a.ProjectCodes) ".format(code) |
|||
else: |
|||
temp = temp + " FIND_IN_SET('{}', a.ProjectCodes) OR ".format(code) |
|||
temp = temp + ' ) ' |
|||
sql = sql + temp |
|||
count_sql = count_sql + temp |
|||
# temp = " AND FIND_IN_SET('{}', a.ProjectCodes) ".format(ProjectCodes |
|||
# where_params.append(ProjectCodes) |
|||
|
|||
if title: |
|||
temp = r''' AND a.title like %s ''' |
|||
sql = sql + temp |
|||
count_sql = count_sql + temp |
|||
where_params.append('%' + title + '%') |
|||
|
|||
if Label: |
|||
temp = r''' AND a.Label = '{}' '''.format(Label) |
|||
sql = sql + temp |
|||
count_sql = count_sql + temp |
|||
|
|||
# 每页最多20条 |
|||
if page_size: |
|||
if int(page_size) > 20: |
|||
err_logger.error("user: %s, page size over failed, size: \n%s" % (request.user.id, page_size)) |
|||
page_size = 20 |
|||
else: |
|||
page_size = 20 |
|||
|
|||
if page: |
|||
if int(page) > 2: |
|||
if request.user.id is None: |
|||
err_logger.error("user: %s, page over failed, size: \n%s" % ( |
|||
request.user.id, page)) |
|||
page = 1 |
|||
page_size = 20 |
|||
else: |
|||
page = 1 |
|||
start_index = (int(page) - 1) * int(page_size) |
|||
# 管理员上传的文件,所有用户可以查看 |
|||
# sql += " OR a.Global = {} ".format(1) |
|||
# count_sql += " OR a.Global = {} ".format(1) |
|||
# sql += " OR a.CreateByUid = {} ".format(adminUserId.id) |
|||
# count_sql += " OR a.CreateByUid = {} ".format(adminUserId.id) |
|||
count_sql = count_sql + ' ORDER BY {} desc'.format('a.id') |
|||
sql += ' ORDER BY {} desc LIMIT {}, {} '.format('a.CreateDateTime', start_index, page_size) |
|||
queryset = Archive.objects.raw(sql, where_params) |
|||
# 查看总数 |
|||
count = 0 |
|||
count_result = Archive.objects.raw(count_sql, where_params) |
|||
count = len(count_result) |
|||
# 返回分页结果 |
|||
rows = [] |
|||
for item in queryset: |
|||
item.__dict__.pop('_state') |
|||
item.__dict__['CreateDateTime'] = item.__dict__['CreateDateTime'].strftime('%Y-%m-%d %H:%M:%S') |
|||
item.__dict__['UpdateDateTime'] = item.__dict__['UpdateDateTime'].strftime('%Y-%m-%d %H:%M:%S') |
|||
item.__dict__['id'] = hashids.encode(item.__dict__['id']) |
|||
if item.__dict__['Url']: |
|||
media_folder = FILE_PATH.replace("\\", "/").split("/")[-1] |
|||
if media_folder == "media": # 开发时 |
|||
item.__dict__['Url'] = FILE_HTTP + parse.quote( |
|||
item.__dict__['Url'].replace("upload", "media")) |
|||
else: # 线上 |
|||
item.__dict__['Url'] = FILE_HTTP + parse.quote(item.__dict__['Url']) |
|||
rows.append(item.__dict__) |
|||
pagination['page'] = page |
|||
pagination['page_size'] = page_size |
|||
# encrypt_instance = AESEnDECryptRelated() |
|||
# new_ciphertext = encrypt_instance.start_encrypt(rows) |
|||
# print("e1:", new_ciphertext) |
|||
return CCAIResponse(data=rows, count=count, pagination=pagination) |
|||
|
|||
except Exception as e: |
|||
err_logger.error("user: %s, get archive list failed: \n%s" % ( |
|||
request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("获取文件列表失败", status=500) |
|||
|
|||
def retrieve(self, request, *args, **kwargs): |
|||
try: |
|||
hashids = Hashids(salt=ID_KEY, min_length=32) |
|||
archive_id = kwargs.get('pk') |
|||
archive_id = hashids.decode(archive_id)[0] |
|||
archive = get_object_or_404(self.queryset, pk=int(archive_id)) |
|||
data = self.get_serializer(archive, many=False) |
|||
return CCAIResponse(data=data) |
|||
|
|||
except Exception as e: |
|||
err_logger.error("user: %s, get archive detail failed: \n%s" % ( |
|||
request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("获取文件详情失败", status=500) |
|||
|
|||
def update(self, request, *args, **kwargs): |
|||
""" |
|||
修改已经上传的文件信息 |
|||
@param request: |
|||
@param args: |
|||
@param kwargs: |
|||
@return: |
|||
""" |
|||
try: |
|||
hashids = Hashids(salt=ID_KEY, min_length=32) |
|||
data = req_operate_by_user(request=request) |
|||
if not data['title']: |
|||
return CCAIResponse("Missing title", BAD) |
|||
# if not data['ProjectCodes']: |
|||
# return CCAIResponse("Missing ProjectCodes", BAD) |
|||
if data['ProjectCodes'] and len(data['ProjectCodes']) > 0: |
|||
data['ProjectCodes'] = ','.join(data['ProjectCodes']) |
|||
else: |
|||
data['ProjectCodes'] = None |
|||
if not data['Label']: |
|||
return CCAIResponse("Missing Label", BAD) |
|||
if not request.user.is_superuser and request.user.id != data['CreateByUid']: |
|||
return CCAIResponse("不能修改他人文件", BAD) |
|||
data['UpdateByUid'] = request.user.id |
|||
data['UpdateDateTime'] = datetime.datetime.now() |
|||
data['UpdateBy'] = request.user.name |
|||
archive_id = kwargs.get('pk') |
|||
archive_id = hashids.decode(archive_id)[0] |
|||
data['id'] = archive_id |
|||
partial = kwargs.pop('partial', False) # True:所有字段全部更新, False:仅更新提供的字段 |
|||
# instance = self.get_object(archive_id) |
|||
instance = self.get_object() |
|||
serializer = self.get_serializer(instance, data=data, partial=partial) |
|||
serializer.is_valid(raise_exception=True) |
|||
self.perform_update(serializer) |
|||
|
|||
if getattr(instance, '_prefetched_objects_cache', None): |
|||
# If 'prefetch_related' has been applied to a queryset, we need to |
|||
# forcibly invalidate the prefetch cache on the instance. |
|||
instance._prefetched_objects_cache = {} |
|||
# 操作记录 |
|||
info = { |
|||
"des": "更新文档", |
|||
"detail": "文档名称: " + data["Name"] |
|||
} |
|||
create_operation_history_log(request, info, OperationHistoryLog) |
|||
|
|||
return CCAIResponse(data="success") |
|||
|
|||
except Exception as e: |
|||
err_logger.error("user: %s, update article failed: \n%s" % ( |
|||
request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("更新文章失败", status=500) |
|||
|
|||
def create(self, request, *args, **kwargs): |
|||
try: |
|||
hashids = Hashids(salt=ID_KEY, min_length=32) |
|||
data = req_operate_by_user(request=request) |
|||
data = req_operate_by_user(request) |
|||
serializer = self.get_serializer(data=data) |
|||
serializer.is_valid(raise_exception=True) |
|||
self.perform_create(serializer) |
|||
# headers = self.get_success_headers(serializer.data) |
|||
# 操作记录 |
|||
info = { |
|||
"des": "非法创建文章", |
|||
"detail": "文章名称: " + params["title"] |
|||
} |
|||
create_operation_history_log(request, info, OperationHistoryLog) |
|||
return CCAIResponse(data="success") |
|||
except Exception as e: |
|||
err_logger.error("user: %s, create archive failed: \n%s" % ( |
|||
request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("创建文章失败", status=500) |
|||
|
|||
def destroy(self, request, *args, **kwargs): |
|||
""" |
|||
删除文件 |
|||
@param request: |
|||
@param args: |
|||
@param kwargs: |
|||
@return: |
|||
""" |
|||
try: |
|||
hashids = Hashids(salt=ID_KEY, min_length=32) |
|||
userid = request.user.id |
|||
is_superuser = request.user.is_superuser |
|||
archive_id = kwargs.get('pk') |
|||
archive_id = hashids.decode(archive_id)[0] |
|||
url_path = None |
|||
with transaction.atomic(): |
|||
instance = self.get_object() |
|||
url_path = instance.Url |
|||
self.perform_destroy(instance) |
|||
|
|||
# 通常在文件数据不是非常多的情况下,可以认为编码与文件是一对一,即编码唯一 |
|||
# 文件存放路径 |
|||
D_FILE_PATH = FILE_PATH.replace("\\", "/") |
|||
upload_folder = D_FILE_PATH.split("/")[-1] |
|||
if upload_folder == "media": # 开发时 |
|||
DEL_FILE_PATH = "/".join(D_FILE_PATH.split("/")[0:-1]) # 去掉"media", 因为url里面存在"upload" |
|||
url_path = url_path.replace("upload", "media") |
|||
else: # 线上 |
|||
DEL_FILE_PATH = D_FILE_PATH |
|||
url_path = url_path |
|||
|
|||
# 删除物理文件 DEL_FILE_PATH + url_path:文件完整的路径 |
|||
if os.path.exists(DEL_FILE_PATH + url_path): |
|||
# os.remove(url_path) # 直接删除文件,考虑到大文件删除比较慢,所以使用异步删除 |
|||
try: |
|||
threadPool.submit(asyncDeleteFile, request, DEL_FILE_PATH + url_path) |
|||
except Exception as e: |
|||
err_logger.error("user: %s, local delete archive failed: \n%s" % ( |
|||
request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("删除文件资源失败", status=200) |
|||
else: |
|||
err_logger.error("user: %s, want to delete archive dosen't exist, archive's name: \n%s" % ( |
|||
request.user.id, instance.Name)) |
|||
|
|||
info = { |
|||
"des": "删除文章", |
|||
"detail": "文件名称: " + instance.Name |
|||
} |
|||
create_operation_history_log(request, info, OperationHistoryLog) |
|||
return CCAIResponse(msg="删除成功!", status=200) |
|||
except Exception as e: |
|||
err_logger.error("user: %s, delete article failed: \n%s" % ( |
|||
request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("删除用户上传文件资源失败", status=500) |
|||
|
|||
@action(methods=["get"], detail=False, permission_classes=[IsAuthenticated], |
|||
url_path="getGlobal", url_name="getGlobal") |
|||
def getGlobal(self, request, *args, **kwargs): |
|||
""" |
|||
获取管理员上传的全局文件列表 |
|||
:param request: |
|||
:param args: |
|||
:param kwargs: |
|||
:return: |
|||
""" |
|||
try: |
|||
hashids = Hashids(salt=ID_KEY, min_length=32) |
|||
params = request.GET |
|||
page_size = params.get('size', None) |
|||
page = params.get('page', None) |
|||
companyMid = params.get('companyMid', None) |
|||
ProjectCodes = params.get('ProjectCodes', None) |
|||
title = params.get('title', None) |
|||
Label = params.get('Label', None) |
|||
pagination = {} |
|||
adminUserId = UserProfile.objects.filter(roles__id=1).first() # 查询管理员用户id |
|||
|
|||
sql = r''' |
|||
SELECT a.id, a.MainId, a.Name, a.Ext, a.ProjectCodes, a.Label, a.Url, a.Remark, a.Global, |
|||
a.Label, a.CreateBy, a.UpdateBy, a.CreateByUid, a.UpdateByUid, |
|||
a.CreateDateTime, a.UpdateDateTime, a.companyMid, |
|||
a.issuingDepartment, a.dateOfPublication, a.originalLink, a.digest, a.title |
|||
FROM common_archive a |
|||
WHERE 1=1 |
|||
''' |
|||
count_sql = r'''SELECT a.id FROM common_archive a WHERE 1=1 ''' |
|||
where_params = [] |
|||
|
|||
if title: |
|||
temp = r''' AND a.title like %s ''' |
|||
sql = sql + temp |
|||
count_sql = count_sql + temp |
|||
where_params.append('%' + title + '%') |
|||
|
|||
# 每页最多20条 |
|||
if page_size: |
|||
if int(page_size) > 20: |
|||
err_logger.error("user: %s, page size over failed, size: \n%s" % (request.user.id, page_size)) |
|||
page_size = 20 |
|||
else: |
|||
page_size = 20 |
|||
|
|||
if page: |
|||
if int(page) > 2: |
|||
if request.user.id is None: |
|||
err_logger.error("user: %s, page over failed, size: \n%s" % ( |
|||
request.user.id, page)) |
|||
page = 1 |
|||
page_size = 20 |
|||
else: |
|||
page = 1 |
|||
start_index = (int(page) - 1) * int(page_size) |
|||
# 管理员上传的文件,所有用户可以查看 |
|||
sql += " AND a.Global = {} ".format(1) |
|||
count_sql += " AND a.Global = {} ".format(1) |
|||
# sql += " OR a.CreateByUid = {} ".format(adminUserId.id) |
|||
# count_sql += " OR a.CreateByUid = {} ".format(adminUserId.id) |
|||
count_sql = count_sql + ' ORDER BY {} desc'.format('a.id') |
|||
sql += ' ORDER BY {} desc LIMIT {}, {} '.format('a.CreateDateTime', start_index, page_size) |
|||
queryset = Archive.objects.raw(sql, where_params) |
|||
# 查看总数 |
|||
count = 0 |
|||
count_result = Archive.objects.raw(count_sql, where_params) |
|||
count = len(count_result) |
|||
# 返回分页结果 |
|||
rows = [] |
|||
for item in queryset: |
|||
item.__dict__.pop('_state') |
|||
item.__dict__['CreateDateTime'] = item.__dict__['CreateDateTime'].strftime('%Y-%m-%d %H:%M:%S') |
|||
item.__dict__['UpdateDateTime'] = item.__dict__['UpdateDateTime'].strftime('%Y-%m-%d %H:%M:%S') |
|||
if item.__dict__['dateOfPublication']: |
|||
item.__dict__['dateOfPublication'] = item.__dict__['dateOfPublication'].strftime('%Y-%m-%d') |
|||
item.__dict__['id'] = hashids.encode(item.__dict__['id']) |
|||
if item.__dict__['Url']: |
|||
media_folder = FILE_PATH.replace("\\", "/").split("/")[-1] |
|||
if media_folder == "media": # 开发时 |
|||
item.__dict__['Url'] = FILE_HTTP + parse.quote( |
|||
item.__dict__['Url'].replace("upload", "media")) |
|||
else: # 线上 |
|||
item.__dict__['Url'] = FILE_HTTP + parse.quote(item.__dict__['Url']) |
|||
rows.append(item.__dict__) |
|||
pagination['page'] = page |
|||
pagination['page_size'] = page_size |
|||
# encrypt_instance = AESEnDECryptRelated() |
|||
# new_ciphertext = encrypt_instance.start_encrypt(rows) |
|||
# print("e1:", new_ciphertext) |
|||
return CCAIResponse(data=rows, count=count, pagination=pagination) |
|||
|
|||
except Exception as e: |
|||
err_logger.error("user: %s, get archive list failed: \n%s" % ( |
|||
request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("获取文件列表失败", status=500) |
|||
|
|||
|
|||
|
|||
class BreakPointUploadAPIView(APIView): |
|||
""" |
|||
音频、视屏、PDF上传接口(断点续传,BreakPoint组件接口) |
|||
""" |
|||
|
|||
perms_map = ({"*": "admin"}, {"*": "comadmin"}, {"*", "archive_all"}, {"post": "archive_create"}, {"put": "archive_update"}) |
|||
authentication_classes = (JSONWebTokenAuthentication,) |
|||
permission_classes = (IsAuthenticated, RbacPermission) |
|||
|
|||
def post(self, request): |
|||
""" |
|||
备查文件 上传接口 包含: 断点续传,或者直传,两种方式 |
|||
@param request: |
|||
@return: |
|||
""" |
|||
try: |
|||
params = request.data |
|||
hash_name = params.get("hashName", None) |
|||
hashids = Hashids(salt=ID_KEY, min_length=32) |
|||
type_ = params.get("type", 1) # 类型:1-文档 2-pdf 3-excel 4-图片 5-ppt 6-mp4 |
|||
if type_ is None: |
|||
return CCAIResponse("Miss hashName", status=BAD) |
|||
type_map = {1: 'doc', 2: 'pdf', 3: 'excel', 4: 'pic', 5: 'ppt', 6: 'mp4'} |
|||
typeName = type_map.get(int(type_), 'doc') # 文件类型目录名 |
|||
file_name = params.get("fileName", None) |
|||
ProjectCodes = params.get("ProjectCodes", None) # 绑定的项目ID,可能同时绑定多个项目 |
|||
Remark = params.get("Remark", None) # 备注 |
|||
companyMid = request.GET.get('companyMid', None) # 公司MainID |
|||
title = params.get('title', None) |
|||
last_dot_index = title.rfind('.') |
|||
if not title: |
|||
return CCAIResponse("missing fileName", status=BAD) |
|||
if last_dot_index != -1: |
|||
title = title[:last_dot_index] |
|||
else: |
|||
return title # 如果没有找到 '.',则返回原字符串 |
|||
|
|||
if companyMid is None: |
|||
return CCAIResponse("Miss companyMid", status=BAD) |
|||
if ProjectCodes and ProjectCodes !='' and len(ProjectCodes.split(',')) > 0: |
|||
ProjectCodes = ProjectCodes |
|||
else: |
|||
ProjectCodes = None |
|||
|
|||
if file_name is None: |
|||
return CCAIResponse("Miss fileName", status=BAD) |
|||
# adminUser = UserProfile.objects.filter(roles__id=1).first() # 查询管理员用户 |
|||
# Gobal 是否后台管理员上传全局查看资料 管理员为 1 |
|||
Global = 2 |
|||
if request.user.is_superuser: |
|||
Global = 1 |
|||
|
|||
# 将媒体文件统一放到upload下面 |
|||
D_FILE_PATH = FILE_PATH.replace("\\", "/") |
|||
media_folder = D_FILE_PATH.split("/")[-1] |
|||
if media_folder != "media": # 线上存储路径 |
|||
NEW_FILE_PATH = os.path.join(D_FILE_PATH, "upload") |
|||
if not os.path.exists(NEW_FILE_PATH): |
|||
os.makedirs(NEW_FILE_PATH) |
|||
else: # 本地开发时用到的路径 |
|||
NEW_FILE_PATH = D_FILE_PATH |
|||
|
|||
file_dir = os.path.join(NEW_FILE_PATH, "hash") # 存放hash文件的总目录 |
|||
|
|||
if hash_name: |
|||
# 断点续传 |
|||
file_folder_hash, current_chunk = hash_name.split( |
|||
"_") # 每一个文件块的文件名,例如:313b0fe8c583ab4f6e1ef4747e501f9f_0.mp3 |
|||
|
|||
file_folder_hash = str(request.user.id) + file_folder_hash # hash文件夹名称,加id主要防止不同用户上传一模一样的文件 |
|||
|
|||
h_file_name = os.path.splitext(file_name)[0] # file_name.split(".")[0] # 文件名(不包含扩展) |
|||
ext = os.path.splitext(file_name)[1].replace(".", "") # file_name.split(".")[-1] # 扩展 |
|||
hash_dir = os.path.join(file_dir, ext, h_file_name, file_folder_hash) # 用hash名命名的hash文件夹,存放hash文件块 |
|||
if not os.path.exists(hash_dir): |
|||
os.makedirs(hash_dir) |
|||
|
|||
files = os.listdir(hash_dir) |
|||
count = len(files) |
|||
if int(current_chunk) + 1 < count: |
|||
# 判断里面的文件是否连续,因为有些过期文件可能被定时任务删除了 |
|||
file_index = [] |
|||
for each_file in files: |
|||
index_str = each_file.split("_")[-1] |
|||
file_index.append(int(index_str.split(".")[0])) # 存放每个文件的下标 |
|||
file_index.sort() # 将下标进行排序 |
|||
compare_list = [] # 用来记录期望的文件下标 |
|||
for each_num in range(len(file_index)): |
|||
compare_list.append(each_num) |
|||
# 开始判断期望的文件下标数组是否和已经存在的文件下标数组相等 |
|||
if file_index == compare_list: |
|||
nextIndex = count |
|||
else: |
|||
shutil.rmtree(hash_dir) # 里面的文件不连续,先删除然后再新建一个一样的文件夹 |
|||
os.makedirs(hash_dir) |
|||
nextIndex = int(current_chunk) + 1 |
|||
file_chunk = request.FILES.get('file', '') # 获取每一个文件块 |
|||
save_path = os.path.join(hash_dir, hash_name + "." + ext) # 文件块存储路径 |
|||
file = open(save_path, "wb") |
|||
file.write(file_chunk.read()) |
|||
file.close() |
|||
|
|||
else: |
|||
nextIndex = int(current_chunk) + 1 |
|||
file_chunk = request.FILES.get('file', '') # 获取每一个文件块 |
|||
save_path = os.path.join(hash_dir, hash_name + "." + ext) # 文件块存储路径 |
|||
file = open(save_path, "wb") |
|||
file.write(file_chunk.read()) |
|||
file.close() |
|||
data = { |
|||
"code": 200, |
|||
"message": "成功", |
|||
"nextIndex": nextIndex |
|||
} |
|||
return HttpResponse(json.dumps(data), content_type="application/json") |
|||
|
|||
else: |
|||
# 直传 |
|||
file_obj_list = request.FILES.getlist('file') |
|||
if not file_obj_list: |
|||
return CCAIResponse("文件为空", BAD) |
|||
# tags = params.get("tags", None) |
|||
# roles = params['roles'] |
|||
# public = params.get("public", 1) |
|||
# is_download = params.get("is_download", 1) |
|||
type_ = params.get("type", 1) # 类型:1-文档 2-pdf 3-excel 4-图片 5-ppt 6-mp4 |
|||
type_map = {1: 'doc', 2: 'pdf', 3: 'excel', 4: 'pic', 5: 'ppt', 6: 'mp4'} |
|||
typeName = type_map.get(int(type_), 'doc') # 文件类型目录名 |
|||
# importance = params.get("importance", 1) |
|||
|
|||
err_data = { |
|||
"code": 500, |
|||
"message": "参数错误", |
|||
} |
|||
|
|||
suc_data = { |
|||
"code": 200, |
|||
"message": "成功" |
|||
} |
|||
|
|||
is_limit_size = (file_obj_list[0].size / 1024 / 1024) < MAX_MP4_FILE_SIZE # 不超过100M |
|||
if not is_limit_size: |
|||
data = { |
|||
"code": 500, |
|||
"message": "不允许上传超多100M的文件!", |
|||
} |
|||
return HttpResponse(json.dumps(data), content_type="application/json") |
|||
|
|||
import time |
|||
ct = time.time() # 取得系统时间 |
|||
local_time = time.localtime(ct) # 将系统时间转成结构化时间 |
|||
date_head = time.strftime("%Y%m%d", local_time) # 格式化时间 |
|||
date_m_secs = str(datetime.datetime.now().timestamp()).split(".")[-1] # 毫秒级时间戳 |
|||
time_stamp = "%s%.3s" % (date_head, date_m_secs) # 拼接时间字符串 |
|||
|
|||
if not os.path.exists(os.path.join(NEW_FILE_PATH, typeName)): |
|||
os.makedirs(os.path.join(NEW_FILE_PATH, typeName)) |
|||
save_dir = os.path.join(NEW_FILE_PATH, typeName, date_head) |
|||
# 如果不存在则创建目录 |
|||
if not os.path.exists(save_dir): |
|||
os.makedirs(save_dir) |
|||
|
|||
if file_name == "" or file_name is None: |
|||
return HttpResponse(json.dumps(err_data), content_type="application/json") |
|||
ext = os.path.splitext(file_name)[1].replace(".", "") # file_name.split(".")[-1] # 文件扩展名 |
|||
title = "无标题" |
|||
# 打开文件,没有新增 |
|||
for each in file_obj_list: |
|||
random_name = time_stamp + generate_random_str_for_fileName() + "." + ext |
|||
file_path = os.path.join(save_dir, random_name) # 文件存储路径 |
|||
destination = open(file_path, 'wb') |
|||
for chunk in each.chunks(): |
|||
destination.write(chunk) |
|||
destination.close() |
|||
title = os.path.splitext(each.name)[0] # "".join(each.name.split(".")[0:-1]) # 使用音频名字作为标题 |
|||
url = os.path.join('/upload', typeName, date_head, random_name) |
|||
url = url.replace("\\", "/") |
|||
# 将数据写入数据库article表 |
|||
Archive.objects.create( |
|||
MainId=uuid.uuid4().__str__(), |
|||
Name=title, |
|||
Ext=ext, |
|||
ProjectCodes=ProjectCodes, |
|||
Label=int(type_), |
|||
Url=url, |
|||
Remark=Remark, |
|||
Global=Global, |
|||
CreateBy=request.user.name, |
|||
UpdateBy=request.user.name, |
|||
CreateByUid=request.user.id, |
|||
UpdateByUid=request.user.id, |
|||
CreateDateTime=datetime.datetime.now(), |
|||
UpdateDateTime=datetime.datetime.now(), |
|||
companyMid=companyMid, |
|||
title=title |
|||
) |
|||
# 操作记录 |
|||
if int(type_) == 1: |
|||
info = { |
|||
"des": "上传文档", |
|||
"detail": "文档名称: " + title |
|||
} |
|||
elif int(type_) == 2: |
|||
info = { |
|||
"des": "上传PDF文件", |
|||
"detail": "PDF文件名称: " + title |
|||
} |
|||
elif int(type_) == 3: |
|||
info = { |
|||
"des": "上传Excel文件", |
|||
"detail": "Excel文件名称: " + title |
|||
} |
|||
elif int(type_) == 4: |
|||
info = { |
|||
"des": "上传图片", |
|||
"detail": "图片名称: " + title |
|||
} |
|||
else: |
|||
info = { |
|||
"des": "上传PPT文件", |
|||
"detail": "PPT文件名称: " + title |
|||
} |
|||
create_operation_history_log(request, info, OperationHistoryLog) |
|||
|
|||
return HttpResponse(json.dumps(suc_data), content_type="application/json") |
|||
|
|||
except Exception as e: |
|||
err_logger.error( |
|||
"user: %s, break point upload file failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("断点续传上传失败", status=500) |
|||
|
|||
|
|||
class MergeFile(APIView): |
|||
authentication_classes = (JSONWebTokenAuthentication,) |
|||
permission_classes = (IsAuthenticated,) |
|||
""" |
|||
合并文件(断点续传, BreakPoint组件接口) |
|||
""" |
|||
|
|||
def get(self, request): # 合并文件 |
|||
try: |
|||
params = request.GET |
|||
err_data = { |
|||
"code": 500, |
|||
"message": "参数错误", |
|||
"state": 0 |
|||
} |
|||
|
|||
hashids = Hashids(salt=ID_KEY, min_length=32) |
|||
|
|||
hash_name = params.get("hashName", None) |
|||
file_name = params.get("fileName", None) |
|||
file_total_length = params.get("chunks", 0) |
|||
type_ = params.get("type", None) |
|||
type_map = {1: 'doc', 2: 'pdf', 3: 'excel', 4: 'pic', 5: 'ppt', 6: 'mp4'} |
|||
path_type = type_map.get(int(type_), 'doc') # 文件类型目录名 |
|||
ProjectCodes = params.get("ProjectCodes", None) |
|||
Remark = params.get("Remark", None) |
|||
companyMid = params.get("companyMid", None) |
|||
title = params.get('title', None) |
|||
last_dot_index = title.rfind('.') |
|||
if not title: |
|||
return CCAIResponse("missing fileName", status=BAD) |
|||
if last_dot_index != -1: |
|||
title = title[:last_dot_index] |
|||
else: |
|||
return title # 如果没有找到 '.',则返回原字符串 |
|||
|
|||
if not file_name: |
|||
return CCAIResponse("missing fileName", status=BAD) |
|||
# if ProjectCodes is None: |
|||
# return CCAIResponse("Miss ProjectCodes", status=BAD) |
|||
if not companyMid: |
|||
return CCAIResponse("Miss companyMid", status=BAD) |
|||
# Gobal 是否后台管理员上传全局查看资料 管理员为 1 |
|||
Global = 2 |
|||
if request.user.is_superuser: |
|||
Global = 1 |
|||
|
|||
# 将媒体文件统一放到upload下面 |
|||
D_FILE_PATH = FILE_PATH.replace("\\", "/") |
|||
media_folder = D_FILE_PATH.split("/")[-1] |
|||
if media_folder != "media": # 线上存储路径 |
|||
NEW_FILE_PATH = os.path.join(D_FILE_PATH, "upload") |
|||
if not os.path.exists(NEW_FILE_PATH): |
|||
os.makedirs(NEW_FILE_PATH) |
|||
else: # 本地开发时用到的路径 |
|||
NEW_FILE_PATH = D_FILE_PATH |
|||
|
|||
file_dir = os.path.join(NEW_FILE_PATH, "hash") # 存放hash文件的总目录 |
|||
|
|||
h_file_name, ext = os.path.splitext(file_name) # file_name.split(".") # 文件名, 扩展 |
|||
ext = ext.replace(".", "") |
|||
ext_name = "." + ext # 扩展名 |
|||
file_dir_hash = os.path.join(file_dir, ext, h_file_name) # hash文件下与文件名同名字的文件夹 |
|||
hash_name_new = str(request.user.id) + hash_name |
|||
hash_dir = os.path.join(file_dir_hash, hash_name_new) # 当前文件hash存储路径 |
|||
|
|||
import time |
|||
ct = time.time() # 取得系统时间 |
|||
local_time = time.localtime(ct) # 将系统时间转成结构化时间 |
|||
date_head = time.strftime("%Y%m%d", local_time) # 格式化时间 |
|||
date_m_secs = str(datetime.datetime.now().timestamp()).split(".")[-1] # 毫秒级时间戳 |
|||
time_stamp = "%s%.3s" % (date_head, date_m_secs) # 拼接时间字符串 |
|||
|
|||
if not os.path.exists(os.path.join(NEW_FILE_PATH, path_type)): |
|||
os.makedirs(os.path.join(NEW_FILE_PATH, path_type)) |
|||
save_dir = os.path.join(NEW_FILE_PATH, path_type, date_head) |
|||
# 如果不存在则创建目录 |
|||
if not os.path.exists(save_dir): # 合并后的文件存储目录 |
|||
os.makedirs(save_dir) |
|||
|
|||
random_name = time_stamp + generate_random_str() + ext_name |
|||
save_path = os.path.join(save_dir, random_name) # 合并后的文件存储路径 |
|||
count = len(os.listdir(hash_dir)) |
|||
if count != int(file_total_length): # 长度不相等,还未到达合并要求 |
|||
return HttpResponse(json.dumps({"state": 0}), content_type="application/json") |
|||
try: |
|||
temp = open(save_path, 'wb') # 创建新文件 |
|||
for i in range(0, count): |
|||
fp = open(hash_dir + "/" + hash_name + "_" + str(i) + ext_name, 'rb') # 以二进制读取分割文件 |
|||
temp.write(fp.read()) # 写入读取数据 |
|||
fp.close() |
|||
temp.close() |
|||
except Exception as e: |
|||
temp.close() # 在合并文件块失败的时候关闭临时合并的文件 |
|||
os.remove(save_path) # 删除临时合并的文件 |
|||
raise Exception |
|||
shutil.rmtree(hash_dir) # 删除 |
|||
self.judge_folder_null(file_dir_hash) |
|||
|
|||
title = h_file_name # 使用文件名字作为标题 |
|||
url = os.path.join('/upload', path_type, date_head, random_name) |
|||
url = url.replace("\\", "/") |
|||
# 将数据写入数据库Archive表 |
|||
Archive.objects.create( |
|||
MainId=uuid.uuid4().__str__(), |
|||
Name=title, |
|||
Ext=ext, |
|||
ProjectCodes=ProjectCodes, |
|||
Label=int(type_), |
|||
Url=url, |
|||
Remark=Remark, |
|||
Global=Global, |
|||
CreateBy=request.user.name, |
|||
UpdateBy=request.user.name, |
|||
CreateByUid=request.user.id, |
|||
UpdateByUid=request.user.id, |
|||
CreateDateTime=datetime.datetime.now(), |
|||
UpdateDateTime=datetime.datetime.now(), |
|||
companyMid=companyMid, |
|||
title=title |
|||
) |
|||
|
|||
data = { |
|||
"code": 200, |
|||
"message": "成功", |
|||
"state": 1 |
|||
} |
|||
# 操作记录 |
|||
if int(type_) == 1: |
|||
info = { |
|||
"des": "上传文档", |
|||
"detail": "文档名称: " + title |
|||
} |
|||
elif int(type_) == 2: |
|||
info = { |
|||
"des": "上传PDF文件", |
|||
"detail": "PDF文件名称: " + title |
|||
} |
|||
elif int(type_) == 3: |
|||
info = { |
|||
"des": "上传Excel文件", |
|||
"detail": "Excel文件名称: " + title |
|||
} |
|||
elif int(type_) == 4: |
|||
info = { |
|||
"des": "上传图片", |
|||
"detail": "图片名称: " + title |
|||
} |
|||
else: |
|||
info = { |
|||
"des": "上传ppt文件", |
|||
"detail": "ppt文件名称: " + title |
|||
} |
|||
create_operation_history_log(request, info, OperationHistoryLog) |
|||
return HttpResponse(json.dumps(data), content_type="application/json") |
|||
|
|||
except Exception as e: |
|||
err_logger.error( |
|||
"user: %s, break point merge file failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("断点续传合并文件失败", status=500) |
|||
|
|||
# 判断文件夹是否为空 |
|||
def judge_folder_null(self, path): |
|||
files = os.listdir(path) # 查找路径下的所有的文件夹及文件 |
|||
if len(files) == 0: |
|||
shutil.rmtree(path) |
|||
|
|||
|
|||
class UploadImageAPIView(APIView): |
|||
""" |
|||
富文本编辑器上传图片调用的接口 |
|||
""" |
|||
perms_map = ({"*": "admin"}, {"post": "article_create"}, {"put": "article_update"}) |
|||
authentication_classes = (JSONWebTokenAuthentication,) |
|||
permission_classes = (IsAuthenticated, RbacPermission) |
|||
|
|||
def post(self, request): |
|||
try: |
|||
# 允许的图片范围 |
|||
imgRange = [".png", ".jpg", ".jpeg", ".gif", ".jfif"] |
|||
|
|||
file_obj_list = request.FILES.getlist("file") |
|||
|
|||
is_limit_img = (file_obj_list[0].size / 1024 / 1024) < 5 # 不超过5M |
|||
if not is_limit_img: |
|||
return CCAIResponse("文件不许超过5M", status=500) |
|||
|
|||
ct = time.time() # 取得系统时间 |
|||
local_time = time.localtime(ct) # 将系统时间转成结构化时间 |
|||
date_head = time.strftime("%Y%m%d", local_time) # 格式化时间 |
|||
date_m_secs = str(datetime.datetime.now().timestamp()).split(".")[-1] # 毫秒级时间戳 |
|||
time_stamp = "%s%.3s" % (date_head, date_m_secs) # 拼接时间字符串 |
|||
|
|||
ext = "." + file_obj_list[0].name.split(".")[-1] |
|||
# 判断文件后缀是否符合要求 |
|||
img_flag = False |
|||
if ext in imgRange: |
|||
img_flag = True |
|||
|
|||
# 文件保存的位置 |
|||
if img_flag: |
|||
# 将媒体文件统一放到upload下面 |
|||
D_FILE_PATH = FILE_PATH.replace("\\", "/") |
|||
media_folder = D_FILE_PATH.split("/")[-1] |
|||
if media_folder != "media": # 线上存储路径 |
|||
NEW_FILE_PATH = os.path.join(D_FILE_PATH, "upload") |
|||
if not os.path.exists(NEW_FILE_PATH): |
|||
os.makedirs(NEW_FILE_PATH) |
|||
else: # 本地开发时用到的路径 |
|||
NEW_FILE_PATH = D_FILE_PATH |
|||
|
|||
if not os.path.exists(os.path.join(NEW_FILE_PATH, "images")): |
|||
os.makedirs(os.path.join(NEW_FILE_PATH, "images")) |
|||
save_dir = os.path.join(NEW_FILE_PATH, "images", date_head) |
|||
# 如果不存在则创建目录 |
|||
if not os.path.exists(save_dir): |
|||
os.makedirs(save_dir) |
|||
# 打开文件,没有新增 |
|||
for each in file_obj_list: |
|||
random_name = time_stamp + generate_random_str() + ext |
|||
file_path = os.path.join(save_dir, random_name) |
|||
destination = open(file_path, 'wb') |
|||
for chunk in each.chunks(): |
|||
destination.write(chunk) |
|||
destination.close() |
|||
url = os.path.join('/upload', "images", date_head, random_name) |
|||
url = url.replace("\\", "/") |
|||
# 操作记录 |
|||
info = { |
|||
"des": "富文本图片上传", |
|||
"detail": "图片地址: " + url |
|||
} |
|||
create_operation_history_log(request, info, OperationHistoryLog) |
|||
if media_folder != "media": # 线上存储路径 |
|||
url = FILE_HTTP + url |
|||
return CCAIResponse(url, status=200) |
|||
else: |
|||
url = url.replace("upload", "media") |
|||
url = FILE_HTTP + url |
|||
return CCAIResponse(url, status=200) |
|||
else: |
|||
return CCAIResponse("文件格式错误", status=500) |
|||
|
|||
except Exception as e: |
|||
err_logger.error("user: %s, vue-quill-editor upload image failed: \n%s" % ( |
|||
request.user.id, traceback.format_exc() |
|||
)) |
|||
return CCAIResponse("上传失败", status=500) |
|||
|
|||
|
|||
class PreviewWordAPIView(APIView): |
|||
""" |
|||
预览word文件 |
|||
""" |
|||
|
|||
# perms_map = ({"*": "admin"}, {"get": "get_preview"}) |
|||
# authentication_classes = (JSONWebTokenAuthentication,) |
|||
# permission_classes = (IsAuthenticated, RbacPermission) |
|||
|
|||
def get(self, request): |
|||
try: |
|||
file = open(r"C:\Users\Ykim H\Desktop\渡渡鸟知识库用户操作手册.doc", 'rb') |
|||
response = FileResponse(file) |
|||
response['Content-Type'] = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' |
|||
response['Content-Disposition'] = 'attachment;filename="渡渡鸟知识库用户操作手册.doc"' |
|||
return response |
|||
except Exception as e: |
|||
err_logger.error("user: %s, get doc file failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("下载渡渡鸟知识库用户操作手册.doc失败", SERVER_ERROR) |
|||
|
|||
|
|||
class CheckResourceAPIView(APIView): |
|||
# perms_map = ({"*": "admin"}, {"get": "get_preview"}) |
|||
# authentication_classes = (JSONWebTokenAuthentication,) |
|||
# permission_classes = (IsAuthenticated, RbacPermission) |
|||
|
|||
def get(self, request): |
|||
"""反hook""" |
|||
try: |
|||
print("来了") |
|||
return CCAIResponse("success") |
|||
except Exception as e: |
|||
err_logger.error("user: %s, check resource failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("资源检测失败", SERVER_ERROR) |
|||
@ -0,0 +1,72 @@ |
|||
import logging |
|||
import traceback |
|||
|
|||
from rest_framework.views import APIView |
|||
|
|||
# 去掉区县级 |
|||
from ChaCeRndTrans.basic import CCAIResponse |
|||
from ChaCeRndTrans.code import SERVER_ERROR |
|||
from common.models import Area |
|||
|
|||
logger = logging.getLogger('error') |
|||
|
|||
|
|||
class NewRegisterAreaListAPIView(APIView): |
|||
''' |
|||
注册地区 |
|||
''' |
|||
def get(self, request, format=None): |
|||
try: |
|||
sql = 'SELECT a.id, b.DataDictionaryDetailId as dict_id,' \ |
|||
'b.ParentCode as parent_code, b.ParentId as parent_id, b.DictionaryValue as dict_val, b.DictionaryCode as dict_code,' \ |
|||
'b.FullName as dict_name, b.Remark as remark, b.IsEnabled as is_enabled, b.SortNo as sort_no, a.BaiDuCode as baidu_code,' \ |
|||
'a.IsMunicipality as is_municipality, a.IsProvince as is_province, a.IsCity as is_city, a.IsTownship as is_town_ship, a.IsHotCity as is_hot_city,' \ |
|||
'a.Code as code, a.Abbreviation as abbreviation, b.SortNoTwo as sort_no_two, a.TopImg as top_img ' \ |
|||
'FROM common_area a ' \ |
|||
'LEFT JOIN common_datadictionarydetail b ' \ |
|||
'ON a.DataDictionaryDetailId = b.DataDictionaryDetailId WHERE b.FullName != "" and (a.IsMunicipality = 1 or a.IsProvince = 1 or a.IsCity = 1)' |
|||
query_rows = Area.objects.raw(sql) |
|||
|
|||
municipality_map = {} |
|||
municipality_data = [] |
|||
province_data = [] |
|||
all_data = [] |
|||
|
|||
dict_map = {} |
|||
tree_dict = {} |
|||
tree_data = [] |
|||
for item in query_rows: |
|||
item.__dict__.pop('_state') |
|||
|
|||
if item.parent_code != 'RegisterArea_' and item.dict_code != 'RegisterArea_' and item.dict_code.count( |
|||
'_') < 5 and 'RegisterArea_ZXS_Chongqing_' not in item.dict_code and 'RegisterArea_ZXS_Tianjin_' not in item.dict_code and 'RegisterArea_ZXS_Beijing_' not in item.dict_code and 'RegisterArea_ZXS_Shanghai_' not in item.dict_code: |
|||
dict_map[item.dict_code] = item.dict_name |
|||
tree_dict[item.dict_id] = item.__dict__ |
|||
|
|||
for i in tree_dict: |
|||
data = tree_dict[i] |
|||
parent_id = tree_dict[i]['parent_id'] |
|||
parent_code = tree_dict[i]['parent_code'] |
|||
if parent_id and parent_code in dict_map: |
|||
pid = parent_id |
|||
parent = tree_dict[pid] |
|||
parent.setdefault('children', []).append(data) |
|||
else: |
|||
tree_data.append(data) |
|||
for item in tree_data: |
|||
if item['is_municipality'] == 1: |
|||
municipality_data.append(item) |
|||
else: |
|||
province_data.append(item) |
|||
|
|||
municipality_map['dict_code'] = 'RegisterArea_ZXS' |
|||
municipality_map['dict_name'] = '直辖市' |
|||
municipality_map['children'] = municipality_data |
|||
|
|||
all_data.append(municipality_map) |
|||
all_data.extend(province_data) |
|||
return CCAIResponse(all_data) |
|||
|
|||
except Exception as e: |
|||
logger.error("user: %s, get area failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("获取地区失败", SERVER_ERROR) |
|||
@ -0,0 +1,413 @@ |
|||
# -*- coding: utf-8 -*- |
|||
import datetime |
|||
import distutils |
|||
import json |
|||
import logging |
|||
import traceback |
|||
import uuid |
|||
from collections import OrderedDict |
|||
|
|||
from django.db.models import Q |
|||
from rest_framework.views import View |
|||
from rest_framework import status |
|||
from rest_framework.decorators import action |
|||
from rest_framework.filters import SearchFilter, OrderingFilter |
|||
from rest_framework.permissions import IsAuthenticated |
|||
from rest_framework.viewsets import ModelViewSet |
|||
from rest_framework_jwt.authentication import JSONWebTokenAuthentication |
|||
|
|||
from ChaCeRndTrans.basic import CCAIResponse |
|||
from ChaCeRndTrans.code import SERVER_ERROR, PARAMS_ERR |
|||
from utils.custom import RbacPermission, CustomViewBase, req_operate_by_user, create_operation_history_log |
|||
from ..models import DataDictionary, DataDictionaryDetail, OperationHistoryLog, DataDictionaryDetailZY |
|||
from ..serializers.dict_serializer import DictSerializer, DictDetailSerializer |
|||
|
|||
logger = logging.getLogger('error') |
|||
|
|||
|
|||
class DataDictionaryViewSet(CustomViewBase): |
|||
''' |
|||
字典管理:增删改查 |
|||
''' |
|||
perms_map = ({'*': 'admin'}, {'*': 'dictionary_all'}, {'get': 'dictionary_list'}, {'post': 'dictionary_create'}, |
|||
{'put': 'dictionary_edit'}, |
|||
{'delete': 'dictionary_delete'}) |
|||
queryset = DataDictionary.objects.all() |
|||
serializer_class = DictSerializer |
|||
filter_backends = (SearchFilter, OrderingFilter) |
|||
search_fields = ('DictionaryCode', 'DictionaryValue') |
|||
ordering_fields = ('id',) |
|||
authentication_classes = (JSONWebTokenAuthentication,) |
|||
permission_classes = (RbacPermission,) |
|||
|
|||
def list(self, request, *args, **kwargs): |
|||
''' |
|||
重写list方法 |
|||
''' |
|||
queryset = self.filter_queryset(self.get_queryset()) |
|||
serializer = self.get_serializer(queryset, many=True) |
|||
tree_dict = {} |
|||
tree_data = [] |
|||
try: |
|||
for item in serializer.data: |
|||
tree_dict[item['dict_id']] = item |
|||
for i in tree_dict: |
|||
if tree_dict[i]['parent_id'] and tree_dict[i]['parent_id'] != "0": # 顶级的父级是 0,所以跳过 |
|||
pid = tree_dict[i]['parent_id'] |
|||
parent = tree_dict[pid] |
|||
parent.setdefault('children', []).append(tree_dict[i]) |
|||
else: |
|||
tree_data.append(tree_dict[i]) |
|||
results = tree_data |
|||
except Exception as e: |
|||
logger.error("get dict list failed: \n%s" % traceback.format_exc()) |
|||
return CCAIResponse("获取字典列表失败", SERVER_ERROR) |
|||
|
|||
return CCAIResponse(results) |
|||
|
|||
def create(self, request, *args, **kwargs): |
|||
'''添加数据POST''' |
|||
try: |
|||
data = req_operate_by_user(request) |
|||
data['dict_id'] = uuid.uuid4().__str__() |
|||
|
|||
serializer = self.get_serializer(data=data) |
|||
serializer.is_valid(raise_exception=True) |
|||
self.perform_create(serializer) |
|||
# headers = self.get_success_headers(serializer.data) |
|||
# 操作记录 |
|||
# info = { |
|||
# "des": "添加数据字典", |
|||
# "detail": "字典id: " + data['dict_id'] + ", " + "字典名称: " + data["label"] |
|||
# } |
|||
# create_operation_history_log(request, info, OperationHistoryLog) |
|||
return CCAIResponse(data="success") |
|||
except Exception as e: |
|||
logger.error("dict create failed: \n%s" % traceback.format_exc()) |
|||
return CCAIResponse("create failed", SERVER_ERROR) |
|||
|
|||
|
|||
class DataDictionaryDetailTreeViewSet(CustomViewBase): |
|||
''' |
|||
字典详情树 |
|||
''' |
|||
perms_map = ({'*': 'admin'}, {'*': 'dictionarydetail_all'}, {'get': 'dictionarydetail_list'}, |
|||
{'post': 'dictionarydetail_create'}, {'put': 'dictionarydetail_edit'}, |
|||
{'delete': 'dictionarydetail_delete'}) |
|||
queryset = DataDictionaryDetail.objects.all() |
|||
serializer_class = DictDetailSerializer |
|||
filter_backends = (SearchFilter, OrderingFilter) |
|||
ordering_fields = ('id',) |
|||
authentication_classes = (JSONWebTokenAuthentication,) |
|||
permission_classes = (IsAuthenticated,) |
|||
|
|||
def list(self, request, *args, **kwargs): |
|||
''' |
|||
重写list方法 |
|||
''' |
|||
parent_code = request.GET.get("parent_code") |
|||
is_enabled = request.GET.get("is_enabled") |
|||
label = request.GET.get("label") |
|||
order = request.GET.get("order") |
|||
sort = request.GET.get("ordering") |
|||
order_by = 'id' |
|||
if order and sort: |
|||
if order == '1': |
|||
order_by = sort |
|||
else: |
|||
order_by = '-' + sort |
|||
|
|||
tree_dict = {} |
|||
tree_data = [] |
|||
dict_map = {} |
|||
results = {} |
|||
|
|||
if parent_code: |
|||
try: |
|||
condtions = OrderedDict() |
|||
if is_enabled: |
|||
condtions['IsEnabled'] = is_enabled |
|||
if label: |
|||
condtions['FullName__contains'] = label |
|||
if 'TechArea' == parent_code: |
|||
Tech = DataDictionary.objects.filter(DictionaryCode=parent_code).first() |
|||
condtions['DataDictionaryId'] = Tech.DataDictionaryId |
|||
else: |
|||
condtions['ParentCode__istartswith'] = parent_code |
|||
# condtions['ParentCode'] = parent_code |
|||
|
|||
queryset = DataDictionaryDetail.objects.filter(**condtions).order_by(order_by) |
|||
serializer = self.get_serializer(queryset, many=True) |
|||
for item in serializer.data: |
|||
dict_map[item['dict_code']] = item['label'] |
|||
|
|||
for item in serializer.data: |
|||
tree_dict[item['dict_detail_id']] = item |
|||
for i in tree_dict: |
|||
data = tree_dict[i] |
|||
parent_id = tree_dict[i]['parent_id'] |
|||
parent_code = tree_dict[i]['parent_code'] |
|||
if parent_id and parent_code in dict_map and parent_code != 'CusMYType_XSSRZZL': |
|||
pid = parent_id |
|||
parent = tree_dict[pid] |
|||
parent.setdefault('children', []).append(data) |
|||
else: |
|||
tree_data.append(data) |
|||
if 'RND_COST' == request.GET.get("parent_code"): |
|||
results['list'] = sorted(tree_data, key=lambda x: distutils.version.LooseVersion(x['dict_code']), reverse=False) |
|||
else: |
|||
results['list'] = tree_data |
|||
|
|||
results['map'] = dict_map |
|||
return CCAIResponse(results) |
|||
except Exception as e: |
|||
logger.error("get dict detail tree failed: \n%s" % traceback.format_exc()) |
|||
return CCAIResponse("获取字典详情树失败", SERVER_ERROR) |
|||
else: |
|||
return CCAIResponse(PARAMS_ERR, SERVER_ERROR) |
|||
|
|||
def create(self, request, *args, **kwargs): |
|||
'''添加数据POST''' |
|||
try: |
|||
data = req_operate_by_user(request) |
|||
data['dict_detail_id'] = uuid.uuid4().__str__() |
|||
|
|||
serializer = self.get_serializer(data=data) |
|||
serializer.is_valid(raise_exception=True) |
|||
self.perform_create(serializer) |
|||
# headers = self.get_success_headers(serializer.data) |
|||
# 操作记录 |
|||
# info = { |
|||
# "des": "添加字典详情", |
|||
# "detail": "字典详情id: " + data['dict_detail_id'] + ", " + "字典详情名称: " + data["label"] |
|||
# } |
|||
# create_operation_history_log(request, info, OperationHistoryLog) |
|||
return CCAIResponse(data="success") |
|||
except Exception as e: |
|||
logger.error("dict detail create failed: \n%s" % traceback.format_exc()) |
|||
return CCAIResponse("create failed", SERVER_ERROR) |
|||
|
|||
# 获取地区字典接口 - 从redis从获取 |
|||
@action(methods=["get"], detail=False, permission_classes=[IsAuthenticated], |
|||
url_path="getDictList", url_name="getDictList") |
|||
def getDictList(self, request): |
|||
parent_code = request.GET.get("parent_code") |
|||
if parent_code: |
|||
if parent_code == '': |
|||
return CCAIResponse("parent_code参数不能为空!", status=status.HTTP_400_BAD_REQUEST) |
|||
from django_redis import get_redis_connection |
|||
startTime = datetime.datetime.now() |
|||
# print(datetime.datetime.now()) |
|||
conn = get_redis_connection('default') |
|||
# conn.hset('dicList', 'area', 1) |
|||
# print(conn.hget('dicList', parent_code)) |
|||
str = conn.hget('dicList', parent_code) |
|||
endTime = datetime.datetime.now() |
|||
# print(datetime.datetime.now()) |
|||
# print((endTime - startTime).seconds) |
|||
return CCAIResponse(data=str) |
|||
else: |
|||
return CCAIResponse("parent_code参数不能为空!", status=status.HTTP_400_BAD_REQUEST) |
|||
|
|||
# 设置地区字典接口 - 存储到redis中 |
|||
@action(methods=["get"], detail=False, permission_classes=[IsAuthenticated], |
|||
url_path="setDictList", url_name="setDictList") |
|||
def setDictList(self, request): |
|||
|
|||
parent_code = request.GET.get("parent_code") |
|||
is_enabled = request.GET.get("is_enabled") |
|||
label = request.GET.get("label") |
|||
order = request.GET.get("order") |
|||
sort = request.GET.get("ordering") |
|||
order_by = 'id' |
|||
if order and sort: |
|||
if order == '1': |
|||
order_by = sort |
|||
else: |
|||
order_by = '-' + sort |
|||
|
|||
tree_dict = {} |
|||
tree_data = [] |
|||
dict_map = {} |
|||
results = {} |
|||
|
|||
if parent_code: |
|||
try: |
|||
condtions = OrderedDict() |
|||
if is_enabled: |
|||
condtions['IsEnabled'] = is_enabled |
|||
if label: |
|||
condtions['FullName__contains'] = label |
|||
condtions['ParentCode__istartswith'] = parent_code |
|||
# condtions['ParentCode'] = parent_code |
|||
|
|||
queryset = DataDictionaryDetail.objects.filter(**condtions).order_by(order_by) |
|||
serializer = self.get_serializer(queryset, many=True) |
|||
for item in serializer.data: |
|||
dict_map[item['dict_code']] = item['label'] |
|||
for item in serializer.data: |
|||
tree_dict[item['dict_detail_id']] = item |
|||
for i in tree_dict: |
|||
if tree_dict[i]['parent_id'] and tree_dict[i]['parent_code'] in dict_map: |
|||
pid = tree_dict[i]['parent_id'] |
|||
parent = tree_dict[pid] |
|||
parent.setdefault('children', []).append(tree_dict[i]) |
|||
else: |
|||
tree_data.append(tree_dict[i]) |
|||
results['list'] = tree_data |
|||
|
|||
results['map'] = dict_map |
|||
from django_redis import get_redis_connection |
|||
conn = get_redis_connection('default') |
|||
str = json.dumps(results).encode('utf-8').decode('unicode_escape') |
|||
# print(str) |
|||
conn.hset('dicList', parent_code, str) |
|||
return CCAIResponse(data="success") |
|||
except Exception as e: |
|||
logger.error("get dict detail tree failed: \n%s" % traceback.format_exc()) |
|||
return CCAIResponse("获取字典详情树失败", SERVER_ERROR) |
|||
|
|||
|
|||
class DataDictionaryDetailListViewSet(ModelViewSet): |
|||
''' |
|||
字典详情列表 |
|||
''' |
|||
perms_map = ({'*': 'admin'}, {'*': 'dictionarydetail_list_all'}, {'get': 'dictionarydetail_list_list'}, |
|||
{'post': 'dictionarydetail_list_create'}, {'put': 'dictionarydetail_listlist_edit'}, |
|||
{'delete': 'dictionarydetail_list_delete'}) |
|||
queryset = DataDictionaryDetail.objects.all() |
|||
serializer_class = DictDetailSerializer |
|||
filter_backends = (SearchFilter, OrderingFilter) |
|||
ordering_fields = ('id',) |
|||
authentication_classes = (JSONWebTokenAuthentication,) |
|||
permission_classes = (IsAuthenticated,) |
|||
|
|||
def list(self, request, *args, **kwargs): |
|||
''' |
|||
重写list方法 |
|||
''' |
|||
parent_code = request.GET.get("parent_code") |
|||
is_enabled = request.GET.get("is_enabled") |
|||
label = request.GET.get("label") |
|||
order = request.GET.get("order") |
|||
sort = request.GET.get("ordering") |
|||
select_all = request.GET.get("select_all") |
|||
|
|||
dict_list = [] |
|||
dict_map = {} |
|||
dict_value_map = {} |
|||
results = {} |
|||
|
|||
order_by = '-id' |
|||
if order and sort: |
|||
if order == '1': |
|||
order_by = sort |
|||
else: |
|||
order_by = '-' + sort |
|||
|
|||
if parent_code: |
|||
try: |
|||
condtions = OrderedDict() |
|||
if label: |
|||
condtions['FullName__contains'] = label |
|||
if is_enabled: |
|||
condtions['IsEnabled'] = is_enabled |
|||
if select_all: |
|||
condtions['ParentCode__istartswith'] = parent_code |
|||
else: |
|||
condtions['ParentCode'] = parent_code |
|||
|
|||
queryset = DataDictionaryDetail.objects.filter(**condtions).order_by(order_by) |
|||
serializer = self.get_serializer(queryset, many=True) |
|||
|
|||
for item in serializer.data: |
|||
item['children'] = [] |
|||
item['hasChildren'] = True |
|||
dict_list.append(item) |
|||
dict_map[item['dict_code']] = item['label'] |
|||
dict_value_map[item['dict_val']] = item['label'] |
|||
|
|||
results['list'] = dict_list |
|||
|
|||
results['map'] = dict_map |
|||
results['value_map'] = dict_value_map |
|||
return CCAIResponse(results) |
|||
except Exception as e: |
|||
logger.error("get dict detail tree failed: \n%s" % traceback.format_exc()) |
|||
return CCAIResponse("获取字典详情列表失败", SERVER_ERROR) |
|||
else: |
|||
return CCAIResponse(PARAMS_ERR, SERVER_ERROR) |
|||
|
|||
|
|||
# 获取智云那边的区域 |
|||
class DataDictionaryDetailTreeViewSetZY(CustomViewBase): |
|||
''' |
|||
字典详情树 |
|||
''' |
|||
perms_map = ({'*': 'admin'}, {'*': 'dictionarydetail_all'}, {'get': 'dictionarydetail_list'}, |
|||
{'post': 'dictionarydetail_create'}, {'put': 'dictionarydetail_edit'}, |
|||
{'delete': 'dictionarydetail_delete'}) |
|||
queryset = DataDictionaryDetailZY.objects.all() |
|||
serializer_class = DictDetailSerializer |
|||
filter_backends = (SearchFilter, OrderingFilter) |
|||
ordering_fields = ('id',) |
|||
authentication_classes = (JSONWebTokenAuthentication,) |
|||
permission_classes = (IsAuthenticated,) |
|||
|
|||
def list(self, request, *args, **kwargs): |
|||
''' |
|||
重写list方法 |
|||
''' |
|||
parent_code = request.GET.get("parent_code") |
|||
is_enabled = request.GET.get("is_enabled") |
|||
label = request.GET.get("label") |
|||
order = request.GET.get("order") |
|||
sort = request.GET.get("ordering") |
|||
order_by = 'id' |
|||
if order and sort: |
|||
if order == '1': |
|||
order_by = sort |
|||
else: |
|||
order_by = '-' + sort |
|||
|
|||
tree_dict = {} |
|||
tree_data = [] |
|||
dict_map = {} |
|||
results = {} |
|||
|
|||
if parent_code: |
|||
try: |
|||
condtions = OrderedDict() |
|||
if is_enabled: |
|||
condtions['IsEnabled'] = is_enabled |
|||
if label: |
|||
condtions['FullName__contains'] = label |
|||
condtions['ParentCode__istartswith'] = parent_code |
|||
# condtions['ParentCode'] = parent_code |
|||
|
|||
queryset = DataDictionaryDetailZY.objects.filter(**condtions).order_by(order_by) |
|||
serializer = self.get_serializer(queryset, many=True) |
|||
for item in serializer.data: |
|||
dict_map[item['dict_code']] = item['label'] |
|||
|
|||
for item in serializer.data: |
|||
tree_dict[item['dict_detail_id']] = item |
|||
for i in tree_dict: |
|||
data = tree_dict[i] |
|||
parent_id = tree_dict[i]['parent_id'] |
|||
parent_code = tree_dict[i]['parent_code'] |
|||
if parent_id and parent_code in dict_map and parent_code != 'CusMYType_XSSRZZL': |
|||
pid = parent_id |
|||
parent = tree_dict[pid] |
|||
parent.setdefault('children', []).append(data) |
|||
else: |
|||
tree_data.append(data) |
|||
results['list'] = tree_data |
|||
|
|||
results['map'] = dict_map |
|||
return CCAIResponse(results) |
|||
except Exception as e: |
|||
logger.error("get dict detail tree failed: \n%s" % traceback.format_exc()) |
|||
return CCAIResponse("获取字典详情树失败", SERVER_ERROR) |
|||
else: |
|||
return CCAIResponse(PARAMS_ERR, SERVER_ERROR) |
|||
@ -0,0 +1,88 @@ |
|||
# -*- coding: utf-8 -*- |
|||
import logging |
|||
import traceback |
|||
|
|||
from rest_framework.permissions import IsAuthenticated |
|||
from rest_framework_jwt.authentication import JSONWebTokenAuthentication |
|||
from rest_framework.views import APIView |
|||
from django.core.paginator import Paginator |
|||
from hashids import Hashids |
|||
|
|||
from ChaCeRndTrans.basic import CCAIResponse |
|||
from utils.custom import RbacPermission |
|||
from ChaCeRndTrans.settings import ID_KEY |
|||
from common.models import OperationHistoryLog |
|||
|
|||
err_logger = logging.getLogger('error') |
|||
|
|||
|
|||
class HistoryLogAPIView(APIView): |
|||
""" |
|||
操作日志 |
|||
""" |
|||
perms_map = ({"*": "admin"},) |
|||
authentication_classes = (JSONWebTokenAuthentication,) |
|||
permission_classes = (IsAuthenticated, RbacPermission) |
|||
|
|||
def get(self, request, *args, **kwargs): |
|||
try: |
|||
hashids = Hashids(salt=ID_KEY, min_length=32) # 对ID加密再返回 |
|||
params = request.GET |
|||
page = params.get("page") |
|||
page_size = params.get("size") |
|||
des = params.get("des") |
|||
username = params.get("username") |
|||
ip = params.get("ip") |
|||
create_time = params.get("create_time") |
|||
detail = params.get("detail") |
|||
|
|||
if page != "" and page is not None: |
|||
page = int(page) |
|||
else: |
|||
page = 1 |
|||
if page_size != "" and page_size is not None: |
|||
page_size = int(page_size) |
|||
if page_size > 100: |
|||
page_size = 10 |
|||
else: |
|||
page_size = 10 |
|||
|
|||
query_result = OperationHistoryLog.objects.all().order_by("-create_time") |
|||
if des != "" and des is not None: |
|||
query_result = query_result.filter(des__icontains=des) |
|||
if username != "" and username is not None: |
|||
query_result = query_result.filter(username__icontains=username) |
|||
if detail != "" and detail is not None: |
|||
query_result = query_result.filter(detail__icontains=detail) |
|||
if ip != "" and ip is not None: |
|||
query_result = query_result.filter(ip__icontains=ip) |
|||
if create_time != "" and create_time is not None: |
|||
end_time = create_time.split(" ")[0] |
|||
end_time = end_time + " " + "23:59:59" |
|||
query_result = query_result.filter(create_time__range=[create_time, end_time]) |
|||
|
|||
p = Paginator(query_result, page_size) |
|||
try: |
|||
query = p.page(page).object_list.values() |
|||
except Exception as e: |
|||
query = p.page(p.num_pages).object_list.values() |
|||
page = p.num_pages |
|||
|
|||
data = [] |
|||
for each in query: |
|||
each['create_time'] = each['create_time'].strftime('%Y-%m-%d %H:%M:%S') |
|||
each['id'] = hashids.encode(each["id"]) |
|||
del each["update_time"], each["user_id"], each["update_user_id"], each["update_username"] |
|||
data.append(each) |
|||
|
|||
pagination = { |
|||
page: page, |
|||
page_size: page_size |
|||
} |
|||
count = p.count |
|||
return CCAIResponse(data=data, pagination=pagination, count=count, status=200) |
|||
|
|||
except Exception as e: |
|||
err_logger.error("user: %s, get history log failed: \n%s" % ( |
|||
request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("获取操作日志失败", status=500) |
|||
@ -0,0 +1,15 @@ |
|||
import logging |
|||
import traceback |
|||
from datetime import datetime |
|||
|
|||
from rest_framework.views import APIView |
|||
|
|||
from ChaCeRndTrans.basic import CCAIResponse |
|||
from ChaCeRndTrans.code import SERVER_ERROR, BAD |
|||
from project.models import Project |
|||
from rbac.models import Company |
|||
from staff.models import Staff |
|||
from utils.custom import RbacPermission |
|||
|
|||
logger = logging.getLogger('error') |
|||
|
|||
@ -0,0 +1,197 @@ |
|||
import logging |
|||
import os |
|||
import traceback |
|||
import uuid |
|||
import time |
|||
import datetime |
|||
from urllib import parse |
|||
|
|||
from django.http import FileResponse |
|||
from rest_framework import status |
|||
from rest_framework.permissions import IsAuthenticated |
|||
from rest_framework.views import APIView |
|||
from rest_framework_jwt.authentication import JSONWebTokenAuthentication |
|||
|
|||
from ChaCeRndTrans import settings |
|||
from ChaCeRndTrans.basic import CCAIResponse |
|||
from ChaCeRndTrans.code import SERVER_ERROR, PARAMS_ERR |
|||
from utils.custom import RbacPermission, create_operation_history_log, generate_random_str |
|||
from common.models import OperationHistoryLog |
|||
|
|||
logger = logging.getLogger('error') |
|||
|
|||
class UploadFileAPIView(APIView): |
|||
''' |
|||
上传附件 |
|||
''' |
|||
authentication_classes = (JSONWebTokenAuthentication,) |
|||
permission_classes = (IsAuthenticated,) |
|||
|
|||
def post(self, request, *args, **kwargs): |
|||
upload_file = request.FILES.get("file", None) |
|||
file_type = request.data.get("file_type", None) |
|||
try: |
|||
if not upload_file or not file_type: |
|||
return CCAIResponse(msg="上传失败", status=status.HTTP_400_BAD_REQUEST) |
|||
if upload_file.size > settings.MAX_FILE_SIZE: |
|||
return CCAIResponse(msg="上传文件需小于或等于10M", status=status.HTTP_400_BAD_REQUEST) |
|||
|
|||
# 上传的是学员模板 |
|||
# if file_type == "template-xlsx" or file_type == "template-xls": |
|||
# name = os.path.splitext(upload_file.name)[0] |
|||
# ext = os.path.splitext(upload_file.name)[-1] |
|||
# file_name = name + generate_random_str(3) + ext |
|||
# path_1, path_2 = file_type.split("-") |
|||
# # 本地存储目录 |
|||
# save_path = os.path.join(settings.FILE_PATH, path_1, path_2) |
|||
# save_path = save_path.replace('\\', '/') |
|||
# # 如果不存在则创建目录 |
|||
# if not os.path.exists(save_path): |
|||
# os.makedirs(save_path) |
|||
# files = os.listdir(save_path) # 查找路径下的所有的文件夹及文件 |
|||
# if path_2 == "xlsx": |
|||
# n_path = os.path.join(settings.FILE_PATH, path_1, "xls") |
|||
# if os.path.exists(n_path): |
|||
# files2 = os.listdir(n_path) # 查找路径下的所有的文件夹及文件 |
|||
# else: |
|||
# files2 = [] |
|||
# else: |
|||
# n_path = os.path.join(settings.FILE_PATH, path_1, "xlsx") |
|||
# if os.path.exists(n_path): |
|||
# files2 = os.listdir(n_path) # 查找路径下的所有的文件夹及文件 |
|||
# else: |
|||
# files2 = [] |
|||
# file_path = open(save_path + "/" + file_name, 'wb') |
|||
# for chunk in upload_file.chunks(): |
|||
# file_path.write(chunk) |
|||
# file_path.close() |
|||
# # 删除曾经的旧模板 |
|||
# if len(files) > 0: |
|||
# for each in files: |
|||
# # 删除文件 |
|||
# os.remove(save_path + "/" + each) |
|||
# if len(files2) > 0: |
|||
# for each in files: |
|||
# # 删除文件 |
|||
# os.remove(save_path + "/" + each) |
|||
# # 操作记录 |
|||
# # info = { |
|||
# # "des": "上传模板", |
|||
# # "detail": "后端存储路径: " + save_path + "/" + file_name |
|||
# # } |
|||
# # create_operation_history_log(request, info, OperationHistoryLog) |
|||
# return CCAIResponse("success", status=status.HTTP_200_OK) |
|||
|
|||
# 文件名进行url编码 |
|||
# file_name = parse.quote(upload_file.name) |
|||
file_name = upload_file.name |
|||
name = os.path.splitext(upload_file.name)[0] |
|||
ext = os.path.splitext(upload_file.name)[-1] |
|||
ct = time.time() # 取得系统时间 |
|||
local_time = time.localtime(ct) |
|||
date_head = time.strftime("%Y%m%d%H%M%S", local_time) # 格式化时间 |
|||
date_m_secs = str(datetime.datetime.now().timestamp()).split(".")[-1] # 毫秒级时间戳 |
|||
time_stamp = "%s%.3s" % (date_head, date_m_secs) # 拼接时间字符串 |
|||
# print(time_stamp) |
|||
# 本地存储目录 |
|||
save_path = os.path.join(settings.FILE_PATH, file_type) |
|||
save_path = save_path.replace('\\', '/') |
|||
# save_path = os.path.join(settings.FILE_PATH, file_type) |
|||
# 前端显示目录 |
|||
start = file_name.rindex('.') |
|||
# name = file_name[0: start] |
|||
# type = file_name[start:] |
|||
show_path = os.path.join(settings.SHOW_UPLOAD_PATH, file_type, time_stamp + ext) |
|||
show_path = show_path.replace('\\', '/') |
|||
# 如果不存在则创建目录 |
|||
if not os.path.exists(save_path): |
|||
os.makedirs(save_path) |
|||
|
|||
file_path = open(save_path + '/' + time_stamp + ext, 'wb') |
|||
for chunk in upload_file.chunks(): |
|||
file_path.write(chunk) |
|||
file_path.close() |
|||
|
|||
# 操作记录 |
|||
# info = { |
|||
# "des": "上传附件", |
|||
# "detail": "后端存储路径: " + save_path + "/" + file_name + ", 前端显示路径: " + show_path |
|||
# } |
|||
# create_operation_history_log(request, info, OperationHistoryLog) |
|||
return CCAIResponse(show_path) |
|||
except Exception as e: |
|||
logger.error("upload file failed: \n%s" % traceback.format_exc()) |
|||
return CCAIResponse("上传失败", SERVER_ERROR) |
|||
|
|||
class DeleteFileAPIView(APIView): |
|||
''' |
|||
删除附件 |
|||
''' |
|||
def post(self, request, *args, **kwargs): |
|||
|
|||
tempdict = request.data.copy() |
|||
if 'upload_url' not in tempdict and tempdict['upload_url'] == '': |
|||
return CCAIResponse("upload_url参数不能为空!", status=status.HTTP_400_BAD_REQUEST) |
|||
upload_url = tempdict['upload_url'].replace(settings.SHOW_UPLOAD_PATH, settings.FILE_PATH) |
|||
start = upload_url.rindex('/') |
|||
# name_start = upload_url.rindex('_') |
|||
# type_start = upload_url.rindex('.') |
|||
#拼接本地文件路劲 |
|||
# local_path = upload_url[0: start] + '/' + upload_url[name_start+1: type_start] |
|||
# local_url = local_path + upload_url[start: name_start] + upload_url[type_start:] |
|||
local_path = upload_url[0: start + 1] |
|||
import os |
|||
if os.path.exists(upload_url): # 如果文件存在 |
|||
# 删除文件,可使用以下两种方法。 |
|||
os.remove(upload_url) |
|||
# 删除空目录,不是空目录时候rmdir不会删除 |
|||
try: |
|||
os.rmdir(local_path) |
|||
except Exception as e: |
|||
logger.error("rmdir failed: \n%s" % traceback.format_exc()) |
|||
|
|||
# 操作记录 |
|||
# info = { |
|||
# "des": "删除附件", |
|||
# "detail": "后端存储路径: " + local_path |
|||
# } |
|||
# create_operation_history_log(request, info, OperationHistoryLog) |
|||
return CCAIResponse("success") |
|||
else: |
|||
return CCAIResponse("文件不存在") |
|||
|
|||
class DownLoadFileAPIView(APIView): |
|||
''' |
|||
下载附件 |
|||
''' |
|||
authentication_classes = (JSONWebTokenAuthentication,) |
|||
permission_classes = (IsAuthenticated,) |
|||
|
|||
def post(self, request, *args, **kwargs): |
|||
download_file = request.data.get("ccw_file_path") |
|||
try: |
|||
if download_file is None or download_file == "": |
|||
return CCAIResponse(PARAMS_ERR, SERVER_ERROR) |
|||
|
|||
re_file = download_file.replace('/upload/file/', settings.FILE_PATH) |
|||
file = open(re_file, 'rb') |
|||
# url解码 |
|||
file_name = parse.unquote(file.name) |
|||
response = FileResponse(file) |
|||
response['Content-Type'] = 'application/octet-stream' |
|||
response['Content-Disposition'] = 'attachment;filename="' + file_name + '"' |
|||
|
|||
# 操作记录 |
|||
# info = { |
|||
# "des": "下载附件", |
|||
# "detail": "后端存储路径: " + re_file + ", url解码文件名: " + file_name |
|||
# } |
|||
# create_operation_history_log(request, info, OperationHistoryLog) |
|||
|
|||
return response |
|||
except Exception as e: |
|||
logger.error("download file failed: \n%s" % traceback.format_exc()) |
|||
return CCAIResponse("下载失败", SERVER_ERROR) |
|||
|
|||
|
|||
|
|||
@ -0,0 +1,3 @@ |
|||
from django.contrib import admin |
|||
|
|||
# Register your models here. |
|||
@ -0,0 +1,6 @@ |
|||
from django.apps import AppConfig |
|||
|
|||
|
|||
class RbacConfig(AppConfig): |
|||
default_auto_field = 'django.db.models.BigAutoField' |
|||
name = 'rbac' |
|||
@ -0,0 +1,121 @@ |
|||
# Generated by Django 3.1.4 on 2024-02-02 15:47 |
|||
|
|||
import django.contrib.auth.models |
|||
import django.contrib.auth.validators |
|||
from django.db import migrations, models |
|||
import django.db.models.deletion |
|||
import django.utils.timezone |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
initial = True |
|||
|
|||
dependencies = [ |
|||
('auth', '0012_alter_user_first_name_max_length'), |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.CreateModel( |
|||
name='Company', |
|||
fields=[ |
|||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('name', models.CharField(help_text='企业名称', max_length=100, verbose_name='企业名称')), |
|||
('MainId', models.CharField(blank=True, help_text='guid全局id', max_length=36, null=True, verbose_name='guid全局id')), |
|||
('userMid', models.CharField(blank=True, help_text='企业所属人的全局id', max_length=36, null=True, verbose_name='企业所属人的全局id')), |
|||
('EUCC', models.CharField(blank=True, help_text='企业统一信用代码', max_length=50, null=True, verbose_name='企业统一信用代码')), |
|||
('employeesCount', models.IntegerField(blank=True, help_text='企业员工总数', null=True, verbose_name='企业员工总数')), |
|||
('hightechCode', models.CharField(blank=True, help_text='高企编码', max_length=36, verbose_name='高企编码')), |
|||
], |
|||
options={ |
|||
'verbose_name': '公司信息', |
|||
'verbose_name_plural': '公司信息', |
|||
'ordering': ['id'], |
|||
}, |
|||
), |
|||
migrations.CreateModel( |
|||
name='Menu', |
|||
fields=[ |
|||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('name', models.CharField(max_length=30, unique=True, verbose_name='菜单名')), |
|||
('route_name', models.CharField(blank=True, max_length=30, null=True, verbose_name='组件名称')), |
|||
('icon', models.CharField(blank=True, max_length=50, null=True, verbose_name='图标')), |
|||
('path', models.CharField(blank=True, max_length=158, null=True, verbose_name='链接地址')), |
|||
('is_frame', models.BooleanField(default=False, verbose_name='外部菜单')), |
|||
('is_show', models.BooleanField(default=True, verbose_name='显示标记')), |
|||
('sort', models.IntegerField(blank=True, null=True, verbose_name='排序标记')), |
|||
('component', models.CharField(blank=True, max_length=200, null=True, verbose_name='组件')), |
|||
('pid', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='rbac.menu', verbose_name='父菜单')), |
|||
], |
|||
options={ |
|||
'verbose_name': '菜单', |
|||
'verbose_name_plural': '菜单', |
|||
'ordering': ['id'], |
|||
}, |
|||
), |
|||
migrations.CreateModel( |
|||
name='Permission', |
|||
fields=[ |
|||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('name', models.CharField(max_length=30, unique=True, verbose_name='权限名')), |
|||
('method', models.CharField(blank=True, max_length=50, null=True, verbose_name='方法')), |
|||
('pid', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='rbac.permission', verbose_name='父权限')), |
|||
], |
|||
options={ |
|||
'verbose_name': '权限', |
|||
'verbose_name_plural': '权限', |
|||
'ordering': ['id'], |
|||
}, |
|||
), |
|||
migrations.CreateModel( |
|||
name='Role', |
|||
fields=[ |
|||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('name', models.CharField(max_length=32, verbose_name='角色')), |
|||
('desc', models.CharField(blank=True, max_length=50, null=True, verbose_name='描述')), |
|||
('companyMid', models.CharField(blank=True, max_length=36, null=True, verbose_name='公司全局id')), |
|||
('menus', models.ManyToManyField(blank=True, to='rbac.Menu', verbose_name='菜单')), |
|||
('permissions', models.ManyToManyField(blank=True, to='rbac.Permission', verbose_name='权限')), |
|||
], |
|||
), |
|||
migrations.CreateModel( |
|||
name='UserProfile', |
|||
fields=[ |
|||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('password', models.CharField(max_length=128, verbose_name='password')), |
|||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), |
|||
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), |
|||
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), |
|||
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), |
|||
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), |
|||
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='aigc status')), |
|||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), |
|||
('name', models.CharField(blank=True, max_length=20, null=True, verbose_name='姓名')), |
|||
('MainId', models.CharField(blank=True, max_length=36, null=True, verbose_name='guid全局id')), |
|||
('mobile', models.CharField(blank=True, max_length=50, verbose_name='手机号码')), |
|||
('gender', models.CharField(blank=True, max_length=10, null=True, verbose_name='性别')), |
|||
('email', models.EmailField(blank=True, max_length=50, null=True, verbose_name='邮箱')), |
|||
('image', models.CharField(default='/upload/logo/default.jpg', max_length=100, verbose_name='头像')), |
|||
('area_code', models.CharField(blank=True, max_length=1000, null=True, verbose_name='地区代码')), |
|||
('area_name', models.CharField(blank=True, max_length=15, null=True, verbose_name='地区名')), |
|||
('is_sub', models.IntegerField(default=2, verbose_name='是否子账号')), |
|||
('wx_phone_info', models.CharField(blank=True, max_length=500, null=True, verbose_name='微信手机信息')), |
|||
('wx_openid', models.CharField(blank=True, max_length=255, null=True, verbose_name='微信小程序唯一标识')), |
|||
('parent_id', models.IntegerField(blank=True, null=True, verbose_name='父账号id')), |
|||
('companyMid', models.CharField(blank=True, max_length=36, null=True, verbose_name='公司全局id')), |
|||
('is_active', models.BooleanField(blank=True, default=True, null=True, verbose_name='用户是否可用')), |
|||
('company', models.ManyToManyField(blank=True, to='rbac.Company', verbose_name='公司')), |
|||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), |
|||
('roles', models.ManyToManyField(blank=True, to='rbac.Role', verbose_name='角色')), |
|||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), |
|||
], |
|||
options={ |
|||
'verbose_name': '用户信息', |
|||
'verbose_name_plural': '用户信息', |
|||
'ordering': ['id'], |
|||
}, |
|||
managers=[ |
|||
('objects', django.contrib.auth.models.UserManager()), |
|||
], |
|||
), |
|||
] |
|||
@ -0,0 +1,129 @@ |
|||
from django.db import models |
|||
from django.contrib.auth.models import AbstractUser |
|||
|
|||
|
|||
class Menu(models.Model): |
|||
""" |
|||
菜单 |
|||
""" |
|||
name = models.CharField(max_length=30, unique=True, verbose_name="菜单名") |
|||
route_name = models.CharField(max_length=30, null=True, blank=True, verbose_name="组件名称") |
|||
icon = models.CharField(max_length=50, null=True, blank=True, verbose_name="图标") |
|||
path = models.CharField(max_length=158, null=True, blank=True, verbose_name="链接地址") |
|||
is_frame = models.BooleanField(default=False, verbose_name="外部菜单") |
|||
is_show = models.BooleanField(default=True, verbose_name="显示标记") |
|||
sort = models.IntegerField(blank=True, null=True, verbose_name="排序标记") |
|||
component = models.CharField(max_length=200, null=True, blank=True, verbose_name="组件") |
|||
pid = models.ForeignKey("self", null=True, blank=True, on_delete=models.SET_NULL, verbose_name="父菜单") |
|||
label = models.IntegerField(default=1, verbose_name="是否前台菜单 # 1:是 2:不是") |
|||
|
|||
def __str__(self): |
|||
return self.name |
|||
|
|||
class Meta: |
|||
verbose_name = "菜单" |
|||
verbose_name_plural = verbose_name |
|||
ordering = ["id"] |
|||
|
|||
|
|||
class Permission(models.Model): |
|||
""" |
|||
权限 |
|||
""" |
|||
name = models.CharField(max_length=30, unique=True, verbose_name="权限名") |
|||
method = models.CharField(max_length=50, null=True, blank=True, verbose_name="方法") |
|||
pid = models.ForeignKey("self", null=True, blank=True, on_delete=models.SET_NULL, verbose_name="父权限") |
|||
label = models.IntegerField(default=1, verbose_name="是否前台权限 # 1:是 2:不是") |
|||
|
|||
def __str__(self): |
|||
return self.name |
|||
|
|||
class Meta: |
|||
verbose_name = "权限" |
|||
verbose_name_plural = verbose_name |
|||
ordering = ["id"] |
|||
|
|||
|
|||
class Role(models.Model): |
|||
""" |
|||
角色 |
|||
""" |
|||
name = models.CharField(max_length=32, verbose_name="角色") |
|||
permissions = models.ManyToManyField("Permission", blank=True, verbose_name="权限") |
|||
menus = models.ManyToManyField("Menu", blank=True, verbose_name="菜单") |
|||
desc = models.CharField(max_length=50, blank=True, null=True, verbose_name="描述") |
|||
label = models.IntegerField(default=1, verbose_name="是否前台角色 # 1:是 2:不是") |
|||
|
|||
companyMid = models.CharField(null=True, blank=True, max_length=36, verbose_name="公司全局id") # 用于绑定公司 |
|||
|
|||
# class Meta: |
|||
# unique_together = ('name', 'companymid') # name companymid 两个字段共同形成唯一约束 |
|||
|
|||
|
|||
class UserProfile(AbstractUser): |
|||
""" |
|||
用户 |
|||
""" |
|||
name = models.CharField(max_length=20, null=True, blank=True, verbose_name="姓名") |
|||
MainId = models.CharField(max_length=36, null=True, blank=True, verbose_name="guid全局id") |
|||
mobile = models.CharField(max_length=50, null=False, blank=True, verbose_name="手机号码") |
|||
gender = models.CharField(max_length=10, null=True, blank=True, verbose_name="性别") |
|||
email = models.EmailField(max_length=50, null=True, blank=True, verbose_name="邮箱") |
|||
image = models.CharField(max_length=100, default="/image/default.png", verbose_name="头像") |
|||
area_code = models.CharField(max_length=1000, null=True, blank=True, verbose_name="地区代码") # 地区代码 |
|||
area_name = models.CharField(max_length=15, null=True, blank=True, verbose_name="地区名") # 地区名 |
|||
is_sub = models.IntegerField(default=2, verbose_name="是否子账号") # 1:是;2:否。 |
|||
wx_phone_info = models.CharField(null=True, blank=True, max_length=500, verbose_name="微信手机信息") |
|||
wx_openid = models.CharField(null=True, blank=True, max_length=255, verbose_name="微信小程序唯一标识") |
|||
parent_id = models.IntegerField(null=True, blank=True, verbose_name="父账号id") |
|||
companyMid = models.CharField(null=True, blank=True, max_length=36, verbose_name="公司全局id") # 用于绑定公司 |
|||
is_active = models.BooleanField(null=True, blank=True, default=True, verbose_name="用户是否可用") # 用户锁定与激活用户 |
|||
label = models.IntegerField(default=1, verbose_name="是否前台用户 # 1:是 2:不是") |
|||
|
|||
roles = models.ManyToManyField("Role", verbose_name="角色", blank=True) |
|||
company = models.ManyToManyField("Company", verbose_name="公司", blank=True) |
|||
|
|||
class Meta: |
|||
verbose_name = "用户信息" |
|||
verbose_name_plural = verbose_name |
|||
ordering = ["id"] |
|||
|
|||
def __str__(self): |
|||
return self.username |
|||
|
|||
def get_companies(self): |
|||
return self.company.all() |
|||
|
|||
# 重写方法,校验密码时,不区分大小写 |
|||
def check_password(self, raw_password): |
|||
return super().check_password(raw_password.lower()) |
|||
|
|||
|
|||
class Company(models.Model): |
|||
""" |
|||
公司 |
|||
""" |
|||
name = models.CharField(max_length=100, verbose_name="企业名称", help_text="企业名称") |
|||
MainId = models.CharField(max_length=36, null=True, blank=True, verbose_name="guid全局id", help_text="guid全局id") |
|||
userMid = models.CharField(max_length=36, null=True, blank=True, verbose_name="企业所属人的全局id", help_text="企业所属人的全局id") |
|||
EUCC = models.CharField(max_length=50, null=True, blank=True, verbose_name="企业统一信用代码", help_text="企业统一信用代码") |
|||
employeesCount = models.IntegerField(null=True, blank=True, verbose_name="企业员工总数", help_text="企业员工总数") |
|||
hightechCode = models.CharField(max_length=36, blank=True, verbose_name="高企编码", help_text="高企编码") |
|||
hightechDate = models.DateField(null=True, blank=True, verbose_name="获取高企时间", help_text="获取高企时间") |
|||
AmStart = models.CharField(max_length=10, default='8:00', null=True, blank=True, verbose_name="早班起始时间", help_text="早班起始时间") |
|||
AmEnd = models.CharField(max_length=10, default='12:00', null=True, blank=True, verbose_name="早班结束时间", help_text="早班结束时间") |
|||
PmStart = models.CharField(max_length=10, default='14:00', null=True, blank=True, verbose_name="午班起始时间", help_text="午班起始时间") |
|||
PmEnd = models.CharField(max_length=10, default='18:00', null=True, blank=True, verbose_name="午班结束时间", help_text="午班结束时间") |
|||
tech = models.CharField(max_length=200, null=True, blank=True, verbose_name='技术领域id', help_text="技术领域id") |
|||
tolerance = models.IntegerField(default=900, verbose_name="打卡容错时间,单位s, 默认900s=15min", help_text="打卡容错时间,单位s, 默认900s=15min") |
|||
checkIn = models.IntegerField(default=0, verbose_name="打卡方案,0-原方案, 1-新方案(细颗粒度)", help_text="打卡方案,0-原方案, 1-新方案(细颗粒度)") |
|||
|
|||
class Meta: |
|||
verbose_name = "公司信息" |
|||
verbose_name_plural = verbose_name |
|||
ordering = ["id"] |
|||
|
|||
def __str__(self): |
|||
return self.name |
|||
|
|||
|
|||
@ -0,0 +1,13 @@ |
|||
from rest_framework import serializers |
|||
from ..models import StuBatch |
|||
|
|||
|
|||
class BatchSerializer(serializers.ModelSerializer): |
|||
""" |
|||
批次管理序列化 |
|||
""" |
|||
|
|||
class Meta: |
|||
model = StuBatch |
|||
fields = ("id", "name", "desc", "create_username", "update_username", "create_time", "year", "user_id") |
|||
extra_kwargs = {"name": {"required": True, "error_messages": {"required": "必须填写批次名"}}} |
|||
@ -0,0 +1,13 @@ |
|||
from rest_framework import serializers |
|||
from ..models import Menu |
|||
|
|||
|
|||
class MenuSerializer(serializers.ModelSerializer): |
|||
""" |
|||
菜单序列化 |
|||
""" |
|||
|
|||
class Meta: |
|||
model = Menu |
|||
fields = ("id", "name", "route_name", "icon", "path", "is_show", "is_frame", "sort", "component", "pid", "label") |
|||
extra_kwargs = {"name": {"required": True, "error_messages": {"required": "必须填写菜单名"}}} |
|||
@ -0,0 +1,12 @@ |
|||
from rest_framework import serializers |
|||
from ..models import Permission |
|||
|
|||
class PermissionListSerializer(serializers.ModelSerializer): |
|||
""" |
|||
权限列表序列化 |
|||
""" |
|||
menuname = serializers.ReadOnlyField(source="menus.name") |
|||
|
|||
class Meta: |
|||
model = Permission |
|||
fields = ("id","name","method","menuname","pid", "label") |
|||
@ -0,0 +1,31 @@ |
|||
from rest_framework import serializers |
|||
from ..models import Role |
|||
|
|||
|
|||
class RoleListSerializer(serializers.ModelSerializer): |
|||
""" |
|||
角色序列化 |
|||
""" |
|||
|
|||
class Meta: |
|||
model = Role |
|||
fields = "__all__" |
|||
# depth = 1 |
|||
|
|||
|
|||
class RoleModifySerializer(serializers.ModelSerializer): |
|||
class Meta: |
|||
model = Role |
|||
fields = "__all__" |
|||
# extra_kwargs = {"menus": {"required": True, "error_messages": {"required": "必须填写角色名"}}} |
|||
|
|||
# def validate_menus(self, menus): |
|||
# if not menus: |
|||
# raise serializers.ValidationError("必须选择菜单") |
|||
# return menus |
|||
|
|||
|
|||
class FrontRoleModifySerializer(serializers.ModelSerializer): |
|||
class Meta: |
|||
model = Role |
|||
fields = ['id', 'name', 'permissions', 'menus', 'companyMid', 'desc'] |
|||
@ -0,0 +1,127 @@ |
|||
import uuid |
|||
|
|||
from rest_framework import serializers |
|||
|
|||
from .role_serializer import RoleListSerializer |
|||
from ..models import UserProfile, Company |
|||
import re |
|||
|
|||
|
|||
class UserListSerializer(serializers.ModelSerializer): |
|||
""" |
|||
用户列表的序列化 |
|||
""" |
|||
roles = serializers.SerializerMethodField() |
|||
company = serializers.SerializerMethodField() |
|||
parent_user = serializers.SerializerMethodField() |
|||
|
|||
def get_roles(self, obj): |
|||
return obj.roles.values() |
|||
|
|||
def get_company(self, obj): |
|||
return obj.company.values() |
|||
|
|||
def get_parent_user(self, obj): |
|||
if obj.parent_id: |
|||
parent = UserProfile.objects.filter(id=obj.parent_id).values() |
|||
else: |
|||
parent = None |
|||
return parent |
|||
|
|||
class Meta: |
|||
model = UserProfile |
|||
fields = ["id", "username", "name", "mobile", "email", "image", "area_name", |
|||
"is_active", "roles", "is_superuser", 'companyMid', 'company', 'label', 'is_sub', 'parent_user'] |
|||
depth = 1 |
|||
|
|||
|
|||
class UserModifySerializer(serializers.ModelSerializer): |
|||
""" |
|||
用户编辑的序列化 |
|||
""" |
|||
mobile = serializers.CharField(max_length=11) |
|||
username = serializers.CharField(required=True, allow_blank=False) |
|||
|
|||
|
|||
class Meta: |
|||
model = UserProfile |
|||
fields = ["id", "username", "name", "mobile", "email", "image", |
|||
"is_active", "roles", "is_superuser", 'label', 'company'] |
|||
|
|||
def validate_username(self, value): |
|||
return value |
|||
|
|||
def validate_mobile(self, mobile): |
|||
REGEX_MOBILE = "^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$" |
|||
if not re.match(REGEX_MOBILE, mobile): |
|||
raise serializers.ValidationError("手机号码不合法") |
|||
user = UserProfile.objects.filter(mobile=mobile).first() |
|||
if user: |
|||
user_id = self.context['view'].kwargs.get("pk", "0") |
|||
# print(user_id, user.id) |
|||
if int(user_id) == user.id: |
|||
return mobile |
|||
else: |
|||
raise serializers.ValidationError("手机号 "+mobile+" 已经被注册") |
|||
return mobile |
|||
|
|||
|
|||
class UserCreateSerializer(serializers.ModelSerializer): |
|||
""" |
|||
创建用户序列化 |
|||
""" |
|||
username = serializers.CharField(required=True, allow_blank=False) |
|||
mobile = serializers.CharField(max_length=11) |
|||
|
|||
class Meta: |
|||
model = UserProfile |
|||
fields = ["id", "MainId", "username", "name", "mobile", "email", "is_active", "area_name", "roles", |
|||
"password", "is_superuser", "companyMid", "company", 'label'] |
|||
|
|||
def validate_username(self, username): |
|||
if UserProfile.objects.filter(username=username): |
|||
raise serializers.ValidationError("用户名 "+username+" 已存在") |
|||
return username |
|||
|
|||
def validate_mobile(self, mobile): |
|||
REGEX_MOBILE = "^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$" |
|||
if not re.match(REGEX_MOBILE, mobile): |
|||
raise serializers.ValidationError("手机号码不合法") |
|||
if UserProfile.objects.filter(mobile=mobile): |
|||
raise serializers.ValidationError("手机号 "+mobile+" 已经被注册") |
|||
return mobile |
|||
|
|||
def perform_create(self, serializer): |
|||
usermid = uuid.uuid4() |
|||
serializer.save(MainId=usermid) |
|||
|
|||
|
|||
class UserInfoListSerializer(serializers.ModelSerializer): |
|||
""" |
|||
公共users |
|||
""" |
|||
|
|||
class Meta: |
|||
model = UserProfile |
|||
fields = ("id", "name", "mobile", "email") |
|||
|
|||
|
|||
class CompanySerializer(serializers.ModelSerializer): |
|||
""" |
|||
公司序列化 |
|||
""" |
|||
|
|||
class Meta: |
|||
model = Company |
|||
fields = "__all__" |
|||
|
|||
|
|||
class CompanyRoleSerializer(serializers.ModelSerializer): |
|||
""" |
|||
公司角色序列化 |
|||
""" |
|||
roles = RoleListSerializer(many=True) |
|||
|
|||
class Meta: |
|||
model = Company |
|||
fields = "__all__" |
|||
@ -0,0 +1,14 @@ |
|||
from django.db.models.signals import post_save |
|||
from django.dispatch import receiver |
|||
from django.contrib.auth import get_user_model |
|||
|
|||
User = get_user_model() |
|||
|
|||
|
|||
@receiver(post_save, sender=User) |
|||
# 注册一个信号,在创建用户时自动加密密码 |
|||
def create_user(sender, instance=None, created=False, **kwargs): |
|||
if created: |
|||
password = instance.password |
|||
# instance.set_password(password) |
|||
# instance.save() |
|||
@ -0,0 +1,3 @@ |
|||
from django.test import TestCase |
|||
|
|||
# Create your tests here. |
|||
@ -0,0 +1,30 @@ |
|||
from django.urls import path, include |
|||
from rbac.views import user, menu, role, permission, message, SubAccount, FrontRole, company |
|||
from rest_framework import routers |
|||
|
|||
from rbac.views.Slider_Verification import SliderVerification |
|||
|
|||
router = routers.SimpleRouter() |
|||
router.register(r"users", user.UserViewSet, basename="users") |
|||
router.register(r"menus", menu.MenuViewSet, basename="menus") |
|||
router.register(r"permissions", permission.PermissionViewSet, basename="permissions") |
|||
router.register(r"roles", role.RoleViewSet, basename="roles") |
|||
router.register(r"subAccount", SubAccount.SubAccountViewSet, basename="subAccount") |
|||
router.register(r"frontrole", FrontRole.FrontRoleViewSet, basename="frontrole") |
|||
router.register(r"company", company.CompanyCustomViewSet, basename="company") |
|||
|
|||
urlpatterns = [ |
|||
path(r"api/", include(router.urls)), |
|||
path(r"auth/login/", user.UserAuthView.as_view()), |
|||
path(r"api/login/message/", message.MessageView.as_view()), |
|||
path(r"auth/info/", user.UserInfoView.as_view(), name="user_info"), |
|||
path(r"auth/build/menus/", user.UserBuildMenuView.as_view(), name="build_menus"), |
|||
path(r"api/menu/tree/", menu.MenuTreeView.as_view(), name="menus_tree"), |
|||
path(r"api/permission/tree/", permission.PermissionTreeView.as_view(), name="permissions_tree"), |
|||
path(r"api/user/list/", user.UserListView.as_view(), name="user_list"), |
|||
path(r'api/slider_code/', SliderVerification.as_view(), name='slider_code'), # 获取滑块验证码 |
|||
path(r"auth/register/", user.UserRegisterView.as_view()), # 注册接口 |
|||
path(r"api/company/name/list/", company.CompanyNameListAPIView.as_view()), # 模糊查询公司名称 |
|||
path(r"api/user/refresh/token/", user.RefreshTokenView.as_view(), name="user_list"), |
|||
# path(r"auth/bind/wechat/", user.UserBindWeChat.as_view()), # 微信绑定接口 |
|||
] |
|||
@ -0,0 +1,3 @@ |
|||
from django.shortcuts import render |
|||
|
|||
# Create your views here. |
|||
@ -0,0 +1,96 @@ |
|||
import logging |
|||
import traceback |
|||
|
|||
from django.db.models import Q |
|||
from rest_framework.decorators import action |
|||
from rest_framework.filters import SearchFilter, OrderingFilter |
|||
from rest_framework.permissions import IsAuthenticated |
|||
from rest_framework_jwt.authentication import JSONWebTokenAuthentication |
|||
|
|||
from ChaCeRndTrans.basic import CCAIResponse |
|||
from ChaCeRndTrans.code import * |
|||
from utils.custom import CommonPagination, RbacPermission, CustomViewBase, req_operate_by_user |
|||
from ..models import Role, Company |
|||
from ..serializers.role_serializer import RoleListSerializer, FrontRoleModifySerializer |
|||
|
|||
logger = logging.getLogger('error') |
|||
|
|||
|
|||
class FrontRoleViewSet(CustomViewBase): |
|||
""" |
|||
前台角色管理:增删改查 (提供前台用户管理自己的公司的角色,所有数据需要绑定公司mid) |
|||
""" |
|||
perms_map = ( |
|||
{"*": "admin"}, |
|||
{"*": "frontRole_all"}, |
|||
{"get": "frontRole_list"}, |
|||
{"post": "frontRole_create"}, |
|||
{"put": "frontRole_edit"}, |
|||
{"delete": "frontRole_delete"} |
|||
) |
|||
queryset = Role.objects.all().order_by('id') |
|||
serializer_class = RoleListSerializer |
|||
pagination_class = CommonPagination |
|||
filter_backends = (SearchFilter, OrderingFilter) |
|||
search_fields = ("name",) |
|||
ordering_fields = ("id",) |
|||
authentication_classes = (JSONWebTokenAuthentication,) |
|||
permission_classes = (RbacPermission,) |
|||
|
|||
def get_queryset(self): |
|||
companyMid = self.request.query_params.get('companyMid', None) |
|||
queryset = Role.objects.all() |
|||
|
|||
if companyMid: |
|||
# 查询该公司自定义角色及默认的前台角色 |
|||
queryset = queryset.filter(Q(companyMid=companyMid) | (Q(companyMid__isnull=True) & Q(label=1))) |
|||
return queryset |
|||
|
|||
def get_serializer_class(self): |
|||
if self.action == "list": |
|||
return RoleListSerializer |
|||
return FrontRoleModifySerializer |
|||
|
|||
def create(self, request, *args, **kwargs): |
|||
""" |
|||
前台用户自定义角色,绑定公司,label=1(默认为前台角色) |
|||
""" |
|||
try: |
|||
data = req_operate_by_user(request) |
|||
companyMid = request.query_params.get('companyMid', None) |
|||
if not companyMid: |
|||
return CCAIResponse("Missing company information", BAD) |
|||
is_com_exist = Company.objects.filter(MainId=companyMid).exists() |
|||
if is_com_exist: |
|||
data['companyMid'] = companyMid |
|||
data['label'] = 1 # 前台角色 |
|||
serializer = self.get_serializer(data=data) |
|||
serializer.is_valid(raise_exception=True) |
|||
self.perform_create(serializer) |
|||
# headers = self.get_success_headers(serializer.data) |
|||
return CCAIResponse(data="success") |
|||
else: |
|||
return CCAIResponse("Company information error", BAD) |
|||
except Exception: |
|||
logger.error("user: %s, aigc create failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("create failed", SERVER_ERROR) |
|||
|
|||
@action(methods=["GET"], detail=False, permission_classes=[IsAuthenticated], |
|||
url_path="rolelist", url_name="rolelist") |
|||
def rolelist(self, request): |
|||
""" |
|||
获取对应公司的角色,及前台默认角色 |
|||
""" |
|||
try: |
|||
companyMid = request.GET.get('companyMid') |
|||
if companyMid: |
|||
rolelist = Role.objects.filter( |
|||
Q(companyMid=companyMid) | (Q(companyMid__isnull=True) & Q(label=1))).values('name', 'id').all() |
|||
# serializer = RoleListSerializer(rolelist, many=True) |
|||
return CCAIResponse(rolelist) |
|||
else: |
|||
return CCAIResponse(BAD, msg='公司数据缺失') |
|||
except Exception as e: |
|||
logger.error( |
|||
"user: %s get rolelist failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse(msg='获取公司角色列表失败', status=SERVER_ERROR) |
|||
@ -0,0 +1,286 @@ |
|||
import base64 |
|||
import io |
|||
import json |
|||
import logging |
|||
import math |
|||
import os |
|||
import traceback |
|||
from PIL import Image, ImageDraw, ImageShow |
|||
import random |
|||
|
|||
from django_redis import get_redis_connection |
|||
from rest_framework.views import APIView |
|||
|
|||
from rest_framework.response import Response |
|||
|
|||
from ChaCeRndTrans.basic import CCAIResponse |
|||
from ChaCeRndTrans.code import BAD, SERVER_ERROR |
|||
from ChaCeRndTrans.settings import MEDIA_ROOT |
|||
|
|||
logger = logging.getLogger('error') |
|||
|
|||
# 验证码图片索引0-19,共20张图 |
|||
image_start = 0 |
|||
image_end = 11 |
|||
DEBUG = False |
|||
|
|||
|
|||
# 随机生成滑块验证码 |
|||
class SliderVerificationCode: |
|||
def __init__(self): |
|||
self.bg_color = (255, 255, 255) # 背景色 |
|||
self.slider_color = (125, 125, 125) # 滑块颜色 |
|||
self.img_size = (260, 120) # 图片尺寸 |
|||
|
|||
self.block_size = (40, 40) # 滑块尺寸 |
|||
self.blockX = 0 # 滑块的横坐标 |
|||
self.blockY = 0 # 滑块的纵坐标 |
|||
self.block_radius = 5 # 滑块凹凸半径 |
|||
self.block_position = self.generate_block_position() # 生成滑块位置 |
|||
|
|||
def generate_block_position(self): |
|||
block_x = random.randint(0, self.img_size[0] - self.block_size[0]) # 随机生成滑块起始横坐标 |
|||
block_y = random.randint(0, self.img_size[1] - self.block_size[1]) # 随机生成滑块起始纵坐标 |
|||
return block_x, block_y |
|||
|
|||
|
|||
class SliderVerification(APIView): |
|||
|
|||
def get(self, request): |
|||
try: |
|||
params = request.GET |
|||
username = params.get('username') # 手机号码/账号/邮箱 |
|||
if username is None: |
|||
return CCAIResponse("缺少username参数", BAD) |
|||
slider_code = SliderVerificationCode() |
|||
# 获取画布的宽高 |
|||
canvasWidth, canvasHeight = slider_code.img_size |
|||
# 滑块的宽高,半径 |
|||
block_width, block_height = slider_code.block_size |
|||
block_radius = slider_code.block_radius |
|||
path = MEDIA_ROOT + '/VerificationCode_image' |
|||
canvasImage = get_image(path) |
|||
# 调整原图到指定大小 |
|||
canvasImage = image_resize(canvasImage, canvasWidth, canvasHeight) |
|||
if DEBUG: |
|||
info = {"Title": "这是 canvasImage "} |
|||
ImageShow.show(canvasImage, info) |
|||
|
|||
# 随机生成滑块坐标 |
|||
blockX = random.randint(block_width, canvasWidth - block_width - 10) |
|||
blockY = random.randint(10, canvasHeight - block_height + 1) |
|||
slider_code.blockX = blockX |
|||
slider_code.blockY = blockY |
|||
# 新建滑块 |
|||
block_image = Image.new("RGBA", (block_width, block_height)) |
|||
# 新建的图像根据轮廓图颜色赋值,源图生成遮罩 |
|||
cut_by_template(canvasImage, block_image, block_width, block_height, block_radius, blockX, blockY) |
|||
if DEBUG: |
|||
print(canvasImage) |
|||
print(block_image) |
|||
print(blockX) |
|||
print(blockY) |
|||
print(block_width) |
|||
print(block_height) |
|||
print(block_radius) |
|||
info = {"Title": "这是 final block "} |
|||
ImageShow.show(block_image, info) |
|||
info = {"Title": "这是 final canvasImage "} |
|||
ImageShow.show(canvasImage, info) |
|||
canvas_str = image_to_base64(canvasImage) |
|||
blockc_str = image_to_base64(block_image) |
|||
|
|||
# 将滑块的X坐标 存到redis中做验证 |
|||
slider_key = 'chace_rnd_slider_' + username |
|||
conn = get_redis_connection('default') |
|||
conn.set(slider_key, blockX) |
|||
conn.expire(slider_key, 60) |
|||
rows = [] |
|||
img_dict = {} |
|||
img_dict['canvas_str'] = canvas_str |
|||
img_dict['blockc_str'] = blockc_str |
|||
img_dict['blockY'] = blockY |
|||
rows.append(img_dict) |
|||
request.session['blockX'] = blockX # 备选方案,如果redis连不上,拿session中的 |
|||
return CCAIResponse(rows) |
|||
|
|||
except Exception as e: |
|||
logger.error( |
|||
"user: %s, get slider verification code failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("获取滑块验证码失败", SERVER_ERROR) |
|||
|
|||
def post(self, request): |
|||
try: |
|||
params = request.data |
|||
username = params.get('username') # 手机号码 |
|||
slide_blockX = params.get('blockX') # 获取用户提交的滑块位置坐标 |
|||
if username is None: |
|||
return CCAIResponse("缺少username参数", BAD) |
|||
|
|||
if slide_blockX is None: |
|||
return CCAIResponse("缺少 blockX 参数", BAD) |
|||
else: |
|||
slide_blockX = int(slide_blockX) |
|||
|
|||
try: |
|||
conn = get_redis_connection('default') |
|||
cache_slider = conn.get('chace_rnd_slider_' + username) # 获取滑块缓存的X坐标 |
|||
if DEBUG: |
|||
print(cache_slider) |
|||
except Exception as e: |
|||
logger.error("user: %s, connect redis failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
if cache_slider: |
|||
cache_slider = json.loads(cache_slider) |
|||
else: |
|||
cache_slider = request.session.get('blockX') # 从session中获取滑块位置坐标 |
|||
|
|||
rows = {"code": 400} |
|||
if cache_slider is None: |
|||
rows = {"code": 401} |
|||
return CCAIResponse(rows) |
|||
|
|||
if abs(int(cache_slider) - int(slide_blockX)) < 5: # 判断是否验证成功 |
|||
conn.set("rnd_manual_slider_" + username, username, ex=60) |
|||
return CCAIResponse("验证成功") |
|||
else: |
|||
return CCAIResponse(rows) |
|||
|
|||
except Exception as e: |
|||
logger.error( |
|||
"user: %s, POST slider verification code failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("提交滑块验证码失败", SERVER_ERROR) |
|||
|
|||
|
|||
# 获取验证码资源图 |
|||
def get_image(folder_path): |
|||
# pass # 获取指定范围内的随机数 |
|||
image_index = random.randint(image_start, image_end) # 获取随机码 0-19的随机数 |
|||
image_path = os.path.join(folder_path, str(image_index) + '.jpg') |
|||
image = Image.open(image_path) |
|||
if DEBUG: |
|||
print("image_index") |
|||
print(image_index) |
|||
print(image_path) |
|||
info = {"Title": "这是 srcImage "} |
|||
ImageShow.show(image, info) |
|||
return image |
|||
|
|||
|
|||
# 调整图片的尺寸大小 |
|||
def image_resize(original_image, width, height): |
|||
image = original_image.resize((width, height)) # Image.ANTIALIAS 是选择高质量缩放滤镜 |
|||
if DEBUG: |
|||
info = {"Title": "这是 resizeImage "} |
|||
ImageShow.show(image, info) |
|||
# result_image = Image.new('RGBA', (width, height)) # 创建一个空白的image |
|||
# graphics2D = result_image.convert('RGBA') # 转换为RGBA模式,就可以在上面绘制图像 |
|||
# graphics2D.paste(image, (0, 0)) |
|||
# return result_image |
|||
return image |
|||
|
|||
|
|||
# 抠图,并生成阻塞块 |
|||
def cut_by_template(canvas_image, block_image, block_width, block_height, block_radius, block_x, block_y): |
|||
water_image = Image.new("RGBA", (block_width, block_height), (0, 0, 0, 0)) |
|||
# 阻滑块的轮廓图 |
|||
block_data = get_block_data(block_width, block_height, block_radius) |
|||
# 防止数组越界,保证边界为0 |
|||
block_data[0] = [0] * len(block_data[0]) |
|||
block_data[-1] = [0] * len(block_data[-1]) |
|||
# 创建滑块具体形状 |
|||
for i in range(block_width): |
|||
# 将第1列 和 最后1列,设置为0,防止数组越界,保证边界为0 |
|||
block_data[i][0] = 0 |
|||
block_data[i][-1] = 0 |
|||
for j in range(block_height): |
|||
# 原图中对应位置变色处理 |
|||
if block_data[i][j] == 1: |
|||
# 背景设置为黑色 |
|||
water_image.putpixel((i, j), (0, 0, 0, 255)) |
|||
block_image.putpixel((i, j), canvas_image.getpixel((block_x + i, block_y + j))) |
|||
# 轮廓设置为白色,取带像素和无像素的界点,判断该点是不是临界轮廓点 |
|||
if block_data[i + 1][j] == 0 or block_data[i][j + 1] == 0 or block_data[i - 1][j] == 0 or \ |
|||
block_data[i][j - 1] == 0: |
|||
block_image.putpixel((i, j), (255, 255, 255, 255)) |
|||
water_image.putpixel((i, j), (255, 255, 255, 255)) |
|||
# 把背景设为透明 |
|||
else: |
|||
block_image.putpixel((i, j), (0, 0, 0, 0)) |
|||
water_image.putpixel((i, j), (0, 0, 0, 0)) |
|||
# 在画布上添加阻塞块水印 |
|||
if DEBUG: |
|||
info = {"Title": "这是block_image"} |
|||
ImageShow.show(block_image, info) |
|||
info = {"Title": "这是 water_image "} |
|||
ImageShow.show(water_image, info) |
|||
add_block_watermark(canvas_image, water_image, block_x, block_y) |
|||
|
|||
|
|||
# 构建拼图轮廓轨迹 |
|||
def get_block_data(block_width, block_height, block_radius): |
|||
""" |
|||
先创建一个二维数组data,然后随机生成两个圆的坐标,并在4个方向上随机找到2个方向添加凸/凹 |
|||
它获取凸/凹起位置坐标,并随机选择凸/凹类型。 |
|||
最后,计算需要的小图轮廓,用二维数组来表示,二维数组有两张值,0和1,其中0表示没有颜色,1有颜色。 |
|||
""" |
|||
data = [[0 for _ in range(block_width)] for _ in range(block_height)] # 初始化二维数组,元素为0 |
|||
po = math.pow(block_radius, 2) |
|||
# 随机生成两个圆的坐标,在4个方向上 随机找到2个方向添加凸/凹 |
|||
# 凸/凹1 |
|||
face1 = random.randint(0, 4) |
|||
# 凸/凹2 |
|||
face2 = random.randint(0, 4) |
|||
# 保证两个凸/凹不在同一位置 |
|||
while face1 == face2: |
|||
face2 = random.randint(0, 4) |
|||
# 获取凸/凹起位置坐标 |
|||
circle1 = get_circle_coords(face1, block_width, block_height, block_radius) |
|||
circle2 = get_circle_coords(face2, block_width, block_height, block_radius) |
|||
# 随机凸/凹类型 |
|||
shape = random.randint(0, 1) |
|||
# 圆的标准方程 (x-a)²+(y-b)²=r²,标识圆心(a,b),半径为r的圆 |
|||
# 计算需要的小图轮廓,用二维数组来表示,二维数组有两张值,0和1,其中0表示没有颜色,1有颜色 |
|||
for i in range(block_width): |
|||
for j in range(block_height): |
|||
# 创建中间的方形区域 |
|||
if ( |
|||
i >= block_radius and i <= block_width - block_radius and j >= block_radius and j <= block_height - block_radius): |
|||
data[i][j] = 1 |
|||
double_d1 = math.pow(i - circle1[0], 2) + math.pow(j - circle1[1], 2) |
|||
double_d2 = math.pow(i - circle2[0], 2) + math.pow(j - circle2[1], 2) |
|||
# 创建两个凸/凹 |
|||
if double_d1 <= po or double_d2 <= po: |
|||
data[i][j] = shape |
|||
return data |
|||
|
|||
|
|||
# 根据朝向获取圆心坐标 |
|||
def get_circle_coords(face, block_width, block_height, block_radius): |
|||
""" |
|||
根据传入的face值(0表示上,1表示左,2表示下,3表示右), |
|||
返回一个包含两个整数的数组,分别表示圆心的 横坐标 和 纵坐标。 |
|||
""" |
|||
if face == 0: # 上 |
|||
return [block_width // 2 - 1, block_radius] |
|||
elif face == 1: # 左 |
|||
return [block_radius, block_height // 2 - 1] |
|||
elif face == 2: # 下 |
|||
return [block_width // 2 - 1, block_height - block_radius - 1] |
|||
# elif face == 3: # 右 |
|||
# return [block_width - block_radius - 1, block_height // 2 - 1] |
|||
else: |
|||
return [block_width - block_radius - 1, block_height // 2 - 1] # face == 4, 按3处理 |
|||
|
|||
|
|||
# 在画布上添加阻塞块水印 |
|||
def add_block_watermark(canvas_image, block_image, x, y): |
|||
draw = ImageDraw.Draw(canvas_image) |
|||
canvas_image.paste(block_image, (x, y), block_image) |
|||
|
|||
|
|||
# 将图片转换为base64 |
|||
def image_to_base64(image): |
|||
buffered = io.BytesIO() |
|||
image.save(buffered, format="png") |
|||
img_str = base64.b64encode(buffered.getvalue()) |
|||
return img_str.decode('utf-8') |
|||
@ -0,0 +1,393 @@ |
|||
import logging |
|||
import random |
|||
import string |
|||
import traceback |
|||
import uuid |
|||
|
|||
from django.db import transaction |
|||
from django.db.models import Q |
|||
from django_filters.rest_framework import DjangoFilterBackend |
|||
from rest_framework.decorators import action |
|||
from rest_framework.filters import SearchFilter, OrderingFilter |
|||
from rest_framework.permissions import IsAuthenticated |
|||
from rest_framework_jwt.authentication import JSONWebTokenAuthentication |
|||
|
|||
from ChaCeRndTrans import settings |
|||
from ChaCeRndTrans.basic import CCAIResponse |
|||
from ChaCeRndTrans.code import SERVER_ERROR, BAD, OK |
|||
from ChaCeRndTrans.settings import RELEASE_DOMAIN, TEST_DOMAIN |
|||
from rbac.models import UserProfile, Role, Company |
|||
from rbac.serializers.role_serializer import RoleListSerializer |
|||
from rbac.serializers.user_serializer import UserListSerializer, CompanyRoleSerializer, UserCreateSerializer, \ |
|||
UserModifySerializer |
|||
from utils.custom import CustomViewBase, sha1_encrypt, CommonPagination, is_valid_username |
|||
|
|||
logger = logging.getLogger('error') |
|||
|
|||
|
|||
class SubAccountViewSet(CustomViewBase): |
|||
''' |
|||
子账号 |
|||
''' |
|||
perms_map = ( |
|||
{"*": "admin"}, |
|||
{"*": "sub_account_all"}, |
|||
{"get": "sub_account_list"}, |
|||
{"post": "sub_account_create"}, |
|||
{"put": "sub_account_edit"}, |
|||
{"delete": "sub_account_delete"} |
|||
) |
|||
queryset = UserProfile.objects.all() |
|||
serializer_class = UserListSerializer |
|||
pagination_class = CommonPagination |
|||
filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter) |
|||
filter_fields = ("is_active",) |
|||
search_fields = ("username", "name", "mobile", "email") |
|||
ordering_fields = ("id",) |
|||
authentication_classes = (JSONWebTokenAuthentication,) |
|||
|
|||
def get_serializer_class(self): |
|||
# 根据请求类型动态变更serializer |
|||
if self.action == "create": |
|||
return UserCreateSerializer |
|||
elif self.action == "list": |
|||
return UserListSerializer |
|||
return UserModifySerializer |
|||
|
|||
def get_queryset(self): |
|||
""" |
|||
我的企业-子账号 |
|||
1.后台人员直接查看当前公司所有子用户 |
|||
2.前台用户只能查看当前公司,当前用户的子用户 |
|||
""" |
|||
companyMid = self.request.query_params.get('companyMid', None) |
|||
parent_id = self.request.query_params.get('parent_id', None) |
|||
queryset = UserProfile.objects.all() |
|||
query = Q() |
|||
if 1 == self.request.user.label: # 前台用户 |
|||
if parent_id: # 查询当前用户的子账号 |
|||
query &= Q(parent_id=parent_id) |
|||
if companyMid: # 且子账号需要有关联本公司 |
|||
query &= Q(company__MainId=companyMid) |
|||
else: # 后台用户 |
|||
if companyMid: # 且子账号需要有关联本公司 |
|||
query &= Q(company__MainId=companyMid) |
|||
query &= ~Q(parent_id=None) |
|||
queryset = queryset.filter(query) |
|||
return queryset |
|||
|
|||
# def list(self, request, *args, **kwargs): |
|||
# pagination = {} |
|||
# try: |
|||
# params = request.GET |
|||
# page_size = params.get('size', None) |
|||
# page = params.get('page', None) |
|||
# name = params.get("name", None) # 子账号真实姓名 |
|||
# mobile = params.get("mobile", None) # 手机号码 |
|||
# is_active = params.get("is_active", None) # 是否激活 |
|||
# if page is None: |
|||
# page = 1 |
|||
# if page_size is None: |
|||
# page_size = 10 |
|||
# start_index = (int(page) - 1) * int(page_size) |
|||
# |
|||
# parent_id = params.get("parent_id", None) |
|||
# if parent_id is None: |
|||
# return CCAIResponse("缺少参数parent_id", BAD) |
|||
# main_account = UserProfile.objects.filter(id=parent_id).first() |
|||
# count = 0 |
|||
# row = [] |
|||
# sql = """ |
|||
# select U.id, U.username, U.is_active, U.mobile, U.name |
|||
# from rbac_userprofile as U |
|||
# where U.is_sub = 1 and U.parent_id = {} |
|||
# """.format(parent_id) |
|||
# sql_count = """ |
|||
# select U.id, count(U.id) as count |
|||
# from rbac_userprofile as U |
|||
# where U.is_sub = 1 and U.parent_id = {} |
|||
# """.format(parent_id) |
|||
# if name: |
|||
# temp = r""" and U.name = '{}' """.format(name) |
|||
# sql = sql + temp |
|||
# sql_count = sql_count + temp |
|||
# if mobile: |
|||
# temp = r""" and U.mobile = '{}' """.format(mobile) |
|||
# sql = sql + temp |
|||
# sql_count = sql_count + temp |
|||
# if is_active: |
|||
# temp = r""" and U.is_active = '{}' """.format(is_active) |
|||
# sql = sql + temp |
|||
# sql_count = sql_count + temp |
|||
# sql = sql + """ ORDER BY U.id desc limit {}, {} """.format(start_index, int(page_size)) |
|||
# sub_account = UserProfile.objects.raw(sql) |
|||
# count_result = UserProfile.objects.raw(sql_count) |
|||
# if len(count_result) > 0: |
|||
# count = count_result[0].count |
|||
# if sub_account: |
|||
# for item in sub_account: |
|||
# item.__dict__.pop('_state') |
|||
# row.append(item.__dict__) |
|||
# return CCAIResponse(data=row, count=count) |
|||
# except Exception as e: |
|||
# logger.error("get sub account list failed: \n%s" % traceback.format_exc()) |
|||
# return CCAIResponse("获取子账号列表失败", SERVER_ERROR) |
|||
|
|||
def list(self, request, *args, **kwargs): |
|||
try: |
|||
queryset = self.filter_queryset(self.get_queryset().filter().all()) |
|||
page = self.paginate_queryset(queryset) |
|||
domain = RELEASE_DOMAIN |
|||
if settings.DEVELOP_DEBUG: |
|||
domain = TEST_DOMAIN |
|||
if page is not None: |
|||
serializer = self.get_serializer(page, many=True) |
|||
return_data = serializer.data |
|||
# for item in return_data: |
|||
# start = item['image'].rindex('/media/') |
|||
# item['image'] = domain + item['image'][start:] |
|||
return self.get_paginated_response(return_data) |
|||
|
|||
serializer = self.get_serializer(queryset, many=True) |
|||
return_data = serializer.data |
|||
for item in return_data: |
|||
start = item['image'].rindex('/media/') |
|||
item['image'] = domain + item['image'][start:] |
|||
return CCAIResponse(data=return_data, status=200) |
|||
except Exception as e: |
|||
logger.error("get sub account list failed: \n%s" % traceback.format_exc()) |
|||
return CCAIResponse(msg='获取子用户列表失败', status=SERVER_ERROR) |
|||
def create(self, request, *args, **kwargs): |
|||
try: |
|||
companyMid = request.query_params.get('companyMid', None) |
|||
data = request.data.copy() |
|||
company = data.get('company') |
|||
roles = data.get('roles') |
|||
|
|||
if request.user.is_sub == 1: |
|||
return CCAIResponse("本账号为子账号,不允许开设子账号!", BAD) |
|||
|
|||
# 验证公司信息是否有效 |
|||
# if companyMid: |
|||
# company = Company.objects.filter(MainId=companyMid).first() |
|||
|
|||
# if roles: |
|||
# roleid_list = roles.split(',') |
|||
# |
|||
# if companies: |
|||
# companies_list = companies.split(',') |
|||
|
|||
# if not data['parent_id']: |
|||
# return CCAIResponse(data="缺少参数parent_id", status=BAD) |
|||
user_count = UserProfile.objects.filter(username=data['username']).count() |
|||
if user_count > 0: |
|||
return CCAIResponse(data="用户名冲突,请重新输入", msg="用户名冲突,请重新输入", status=BAD) |
|||
|
|||
if data['mobile']: |
|||
user_count = UserProfile.objects.filter(mobile=data['mobile']).count() |
|||
if user_count > 0: |
|||
return CCAIResponse(data="手机号码已被注册,请校对手机号码", msg="手机号码已被注册,请校对手机号码", |
|||
status=BAD) |
|||
if data['username']: |
|||
if not is_valid_username(data['username']): |
|||
return CCAIResponse(data="用户名命名不符合规定,只能使用汉字、英文字母、数字,不能使用特殊字符。", |
|||
msg="用户名命名不符合规定,只能使用汉字、英文字母、数字,不能使用特殊字符。", |
|||
status=BAD) |
|||
with transaction.atomic(): |
|||
# 生成用户与公司的全局id |
|||
userMid = uuid.uuid4().__str__() |
|||
# 创建用户 |
|||
user = UserProfile() |
|||
user.MainId = userMid |
|||
user.companyMid = companyMid # 默认绑定当前公司 |
|||
user.username = data['username'] |
|||
user.name = data['username'] |
|||
user.mobile = data['mobile'] |
|||
user.is_sub = 1 # 子账号账号 |
|||
user.is_active = 1 # 注册即激活 |
|||
user.label = 1 # 前台用户 |
|||
user.parent_id = request.user.id |
|||
# 创建随机密码,并返回 |
|||
random_password = ''.join(random.sample(string.ascii_letters + string.digits, 8)) |
|||
password = sha1_encrypt(random_password) |
|||
user.set_password(password) |
|||
user.save() |
|||
|
|||
com_list = Company.objects.filter(id__in=company) |
|||
for c in com_list: |
|||
# 为注册用户添加公司 |
|||
user.company.add(c) |
|||
|
|||
# 添加默认角色测试服29 / 正式服 32 |
|||
if settings.DEBUG: |
|||
role = Role.objects.get(id=29) |
|||
else: |
|||
role = Role.objects.get(id=32) |
|||
user.roles.add(role) |
|||
|
|||
if roles and len(roles) > 0: |
|||
# 为子账号添加角色 |
|||
roles = Role.objects.filter(id__in=roles).all() |
|||
for role in roles: |
|||
user.roles.add(role) |
|||
|
|||
return CCAIResponse(data=random_password, status=OK) |
|||
except Exception: |
|||
logger.error("user:%s, add sub account failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("创建子账号失败", SERVER_ERROR) |
|||
|
|||
def update(self, request, *args, **kwargs): |
|||
try: |
|||
data = request.data.copy() |
|||
|
|||
partial = kwargs.pop('partial', False) |
|||
if 'parent_id' not in data and data['parent_id'] is None: |
|||
return CCAIResponse("缺少参数parent_id", BAD) |
|||
# roles = data.get('roles') |
|||
# companies = data.get('companies') |
|||
# if roles: |
|||
# roles = roles.split(',') |
|||
# data['roles'] =roles |
|||
# if companies: |
|||
# companies = companies.split(',') |
|||
# data['companies'] =companies |
|||
|
|||
instance = self.get_object() |
|||
serializer = self.get_serializer(instance, data=data, partial=partial) |
|||
serializer.is_valid(raise_exception=True) |
|||
self.perform_update(serializer) |
|||
user = UserProfile.objects.filter(id=data['id']).first() |
|||
if user is None: |
|||
return CCAIResponse("账号不存在", SERVER_ERROR) |
|||
if data['mobile']: |
|||
user_count = UserProfile.objects.filter(mobile=data['mobile']).exclude(id=data['id']).count() |
|||
if user_count > 0: |
|||
return CCAIResponse(data="手机号码已被注册,请校对手机号码", msg="手机号码已被注册,请校对手机号码", |
|||
status=BAD) |
|||
if data['username']: |
|||
if not is_valid_username(data['username']): |
|||
return CCAIResponse(data="用户名格式不对,只能输入汉字,英文字母,数字!", |
|||
msg="用户名格式不对,只能输入汉字,英文字母,数字!", status=BAD) |
|||
user_count = UserProfile.objects.filter(username=data['username']).exclude(id=data['id']).count() |
|||
if user_count > 0: |
|||
return CCAIResponse("该用户名已被注册,请校对用户名", status=BAD) |
|||
# if data['ratio']: |
|||
# ratio = Decimal(data['ratio']) # 子账号的分销比例 = 父账号的分销比例 * 父账号为其分配的分销比例 |
|||
# user.ratio = ratio |
|||
user.username = data['username'] |
|||
user.mobile = data['mobile'] |
|||
user.save() |
|||
return CCAIResponse("success") |
|||
except Exception as e: |
|||
logger.error("user:%s, update sub account failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("更新子账号失败", SERVER_ERROR) |
|||
|
|||
def destroy(self, request, *args, **kwargs): |
|||
try: |
|||
with transaction.atomic(): |
|||
instance = self.get_object() |
|||
self.perform_destroy(instance) |
|||
users = UserProfile.objects.filter(username=instance.username) |
|||
for user in users: |
|||
user.delete() |
|||
return CCAIResponse(data="删除账号成功") |
|||
except Exception as e: |
|||
logger.error("user:%s, delete sub account failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse(data="删除子账号失败", msg="删除子账号失败", status=SERVER_ERROR) |
|||
|
|||
@action(methods=["POST"], detail=False, permission_classes=[IsAuthenticated], |
|||
url_path="disable_sub_user", url_name="disable_sub_user") |
|||
def disable_sub_user(self, request): |
|||
try: |
|||
data = request.data.copy() |
|||
if 'uid' not in data and data['uid'] is None: |
|||
return CCAIResponse(data="缺少参数uid", msg="缺少参数uid", status=BAD) |
|||
if 'is_active' not in data: |
|||
return CCAIResponse(data="缺少参数is_active", msg="缺少参数is_active", status=BAD) |
|||
user = UserProfile.objects.filter(id=data['uid']).first() |
|||
if user is None: |
|||
return CCAIResponse(data="账号不存在", msg="账号不存在", status=SERVER_ERROR) |
|||
if data['is_active']: |
|||
user.is_active = 1 |
|||
else: |
|||
user.is_active = 0 |
|||
user.save() |
|||
return CCAIResponse(data="修改子账号激活状态") |
|||
except Exception as e: |
|||
logger.error( |
|||
"user: %s disable sub user failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse(msg='修改子账号激活状态失败', status=SERVER_ERROR) |
|||
|
|||
@action(methods=["post"], detail=False, permission_classes=[IsAuthenticated], |
|||
url_path="reset_sub_password", url_name="reset_sub_password") |
|||
def reset_subAccount_password(self, request, pk=None): |
|||
try: |
|||
data = request.data.copy() |
|||
if 'uid' not in data and data['uid'] is None: |
|||
return CCAIResponse(data="缺少参数uid", msg="缺少参数uid", status=BAD) |
|||
user = UserProfile.objects.get(id=data['uid']) |
|||
if user and request.user.is_sub == 2: |
|||
with transaction.atomic(): |
|||
random_password = ''.join(random.sample(string.ascii_lowercase + string.digits, 8)) |
|||
password = sha1_encrypt(random_password) |
|||
user.set_password(password) |
|||
user.save() |
|||
return CCAIResponse(data=random_password, status=OK) |
|||
else: |
|||
return CCAIResponse(data="子账号异常,请联系管理员", status=BAD) |
|||
except Exception as e: |
|||
logger.error("main account reset sub account password failed: \n%s" % traceback.format_exc()) |
|||
return CCAIResponse(msg='修改子账号密码失败', status=SERVER_ERROR) |
|||
|
|||
# @action(methods=["post"], detail=False, permission_classes=[IsAuthenticated], |
|||
# url_path="set_sub_ratio", url_name="set_sub_ratio") |
|||
# def set_subAcount_ratio(self, request, pk=None): |
|||
# try: |
|||
# data = request.data.copy() |
|||
# if 'uid' not in data and data['uid'] is None: |
|||
# return CCAIResponse(data="缺少参数uid", msg="缺少参数uid", status=BAD) |
|||
# if 'ratio' not in data and data['ratio'] is None: |
|||
# return CCAIResponse(data="缺少参数ratio", msg="缺少参数ratio", status=BAD) |
|||
# user = UserProfile.objects.get(id=data['uid']) |
|||
# if user and request.user.is_sub == 2: |
|||
# user.ratio = data['ratio'] |
|||
# user.save() |
|||
# return CCAIResponse(data="修改子账号佣金分销比例成功!", msg="修改子账号佣金分销比例成功!", status=OK) |
|||
# except Exception as e: |
|||
# logger.error("main account set sub account ratio failed: \n%s" % traceback.format_exc()) |
|||
# return CCAIResponse(msg='修改子账号佣金分销比例失败', status=SERVER_ERROR) |
|||
|
|||
@action(methods=["get"], detail=False, permission_classes=[IsAuthenticated], |
|||
url_path="getCompanyAndRole", url_name="getCompanyAndRole") |
|||
def getCompanyAndRole(self, request): |
|||
""" |
|||
获取当前用户的关联公司与该公司的角色,及系统默认提供的角色(除去后台角色label=2) |
|||
""" |
|||
try: |
|||
|
|||
# 获取公司 |
|||
companyList = request.user.company.all() |
|||
companyMainIdList = [item.MainId for item in companyList] |
|||
|
|||
# 获取角色 |
|||
rolelist = Role.objects.filter( |
|||
~Q(label=2) & (Q(companyMid__in=companyMainIdList) | Q(companyMid__isnull=True))) |
|||
|
|||
sysRole = set() # 系统默认角色 |
|||
# 将角色封装到对应的公司中 (当前方法时间复杂度较高,有提升空间) |
|||
for com in companyList: |
|||
com.roles = [] |
|||
for role in rolelist: |
|||
if role.companyMid and role.companyMid != '': |
|||
if role.companyMid == com.MainId: |
|||
com.roles.append(role) |
|||
else: |
|||
sysRole.add(role) |
|||
data = { |
|||
'sysRole': RoleListSerializer(sysRole, many=True).data, |
|||
'comRole': CompanyRoleSerializer(companyList, many=True).data, |
|||
} |
|||
return CCAIResponse(data) |
|||
except Exception as e: |
|||
logger.error("getCompanyAndRole failed: \n%s" % traceback.format_exc()) |
|||
return CCAIResponse(msg='获取公司与角色映射失败', status=SERVER_ERROR) |
|||
@ -0,0 +1,560 @@ |
|||
import copy |
|||
import logging |
|||
import time |
|||
import traceback |
|||
import uuid |
|||
from itertools import islice |
|||
|
|||
import openpyxl |
|||
from django.core.exceptions import ObjectDoesNotExist |
|||
from django.db import transaction |
|||
from django.db.models import Q |
|||
from django.http import FileResponse |
|||
from django_filters.rest_framework import DjangoFilterBackend |
|||
from openpyxl.comments import Comment |
|||
from openpyxl.worksheet.cell_range import CellRange |
|||
from openpyxl.worksheet.datavalidation import DataValidation |
|||
from rest_framework.decorators import action |
|||
from rest_framework.filters import SearchFilter, OrderingFilter |
|||
from rest_framework.permissions import IsAuthenticated |
|||
from rest_framework.views import APIView |
|||
from rest_framework_jwt.authentication import JSONWebTokenAuthentication |
|||
|
|||
from ChaCeRndTrans import settings |
|||
from ChaCeRndTrans.basic import CCAIResponse |
|||
from ChaCeRndTrans.code import * |
|||
from common.models import DataDictionaryDetail, DataDictionary |
|||
from common.serializers.dict_serializer import DictDetailSerializer |
|||
from rbac.models import Company, UserProfile |
|||
from rbac.serializers.user_serializer import CompanySerializer |
|||
from utils.custom import CustomViewBase, CommonPagination, RbacPermission, req_operate_by_user, asyncDeleteFile, \ |
|||
ErrorTable, MyCustomError, time_int_to_str, time_str_to_int |
|||
|
|||
err_logger = logging.getLogger('error') |
|||
|
|||
|
|||
class CompanyCustomViewSet(CustomViewBase): |
|||
''' |
|||
公司管理 |
|||
''' |
|||
perms_map = ( |
|||
{"*": "admin"}, {"*": "company_all"}, {"get": "company_list"}, |
|||
{"post": "company_create"}, |
|||
{"put": "company_edit"}, {"delete": "company_delete"} |
|||
) |
|||
queryset = Company.objects.all() |
|||
serializer_class = CompanySerializer |
|||
pagination_class = CommonPagination |
|||
filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter) |
|||
# filter_fields = ("",) |
|||
search_fields = ("name", "EUCC") |
|||
ordering_fields = ("id",) |
|||
authentication_classes = (JSONWebTokenAuthentication,) |
|||
permission_classes = (RbacPermission,) |
|||
|
|||
def list(self, request, format=None): |
|||
pagination = {} |
|||
try: |
|||
params = request.GET |
|||
keyword = params.get('keyword') |
|||
page_size = params.get('size') |
|||
page = params.get('page') |
|||
sort = params.get('sort') |
|||
|
|||
order_by = 'N.id desc' |
|||
if sort: |
|||
order_by = sort |
|||
|
|||
if page is None: |
|||
page = 1 |
|||
if page_size is None: |
|||
page_size = 10 |
|||
start_index = (int(page) - 1) * int(page_size) |
|||
where = 'WHERE 1=1 ' |
|||
param = [] |
|||
if keyword: |
|||
where += 'AND (N.name LIKE %s OR N.EUCC LIKE %s ) ' |
|||
param.append('%' + keyword + '%') |
|||
param.append('%' + keyword + '%') |
|||
|
|||
sql = 'SELECT N.id, N.name, N.MainId, userMid, U.name as user, U.username,' \ |
|||
'EUCC, employeesCount, hightechCode, hightechDate, AmStart, AmEnd, PmStart, PmEnd, tech, tolerance, checkIn ' \ |
|||
'FROM chace_rnd.rbac_company AS N ' \ |
|||
'LEFT JOIN chace_rnd.rbac_userprofile as U ON U.MainId = N.userMid ' \ |
|||
'%s ORDER BY %s LIMIT %s,%s ' % (where, order_by, start_index, page_size) |
|||
query_rows = Company.objects.raw(sql, param) |
|||
|
|||
count_sql = """ select N.id, count(N.id) as count |
|||
from chace_rnd.rbac_company AS N %s """ % where |
|||
count_result = Company.objects.raw(count_sql, param) |
|||
|
|||
count = 0 |
|||
if len(count_result) > 0: |
|||
count = count_result[0].count |
|||
rows = [] |
|||
for item in query_rows: |
|||
item.__dict__.pop('_state') |
|||
if item.__dict__['tech'] and item.__dict__['tech'] != '': |
|||
item.__dict__['tech'] = item.__dict__['tech'].split(',') |
|||
if not item.__dict__['user'] or item.__dict__['user'] == '': |
|||
item.__dict__['user'] = item.__dict__['username'] |
|||
item.__dict__['tolerance'] = time_int_to_str(item.__dict__['tolerance']) |
|||
rows.append(item.__dict__) |
|||
|
|||
pagination = { |
|||
"page": page, |
|||
"page_size": page_size |
|||
} |
|||
|
|||
return CCAIResponse(rows, count=count, pagination=pagination) |
|||
|
|||
except Exception as e: |
|||
err_logger.error("user: %s, get company list failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("获取公司列表失败", SERVER_ERROR) |
|||
|
|||
@action(methods=["get"], detail=True, permission_classes=[IsAuthenticated], |
|||
url_path="getAssociationUser", url_name="getAssociationUser") |
|||
def getAssociationUser(self, request, pk=None): |
|||
""" |
|||
获取该公司关联的用户列表 |
|||
""" |
|||
try: |
|||
if not pk: |
|||
return CCAIResponse("请选择对应公司", BAD) |
|||
instance = Company.objects.get(id=int(pk)) |
|||
user_list = UserProfile.objects.filter(company=instance).values() |
|||
return CCAIResponse(user_list) |
|||
except Company.DoesNotExist: |
|||
return CCAIResponse("该公司已被删除!", BAD) |
|||
except Exception as e: |
|||
err_logger.error("user: %s, get getAssociationUser list failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("获取该公司关联的用户列表失败", SERVER_ERROR) |
|||
|
|||
def create(self, request, *args, **kwargs): |
|||
""" |
|||
直接新增公司 |
|||
""" |
|||
try: |
|||
data = req_operate_by_user(request) |
|||
# 用户选择为其添加公司的用户 |
|||
userMid = data.get('userMid') |
|||
if not userMid: |
|||
return CCAIResponse("Missing User Information", BAD) |
|||
user = UserProfile.objects.filter(MainId=userMid).first() |
|||
if not user: |
|||
return CCAIResponse("User Information Error", BAD) |
|||
companyMid = uuid.uuid4().__str__() |
|||
data['MainId'] = companyMid |
|||
tech = data.get('tech') # 技术领域 |
|||
if tech: |
|||
data['tech'] = ','.join(tech) |
|||
tolerance = data.get('tolerance') # 容错时间 |
|||
if tolerance: |
|||
data['tolerance'] = time_str_to_int(tolerance) |
|||
serializer = self.get_serializer(data=data) |
|||
serializer.is_valid(raise_exception=True) |
|||
with transaction.atomic(): |
|||
company = self.perform_create(serializer) |
|||
user.company.add(company) |
|||
# headers = self.get_success_headers(serializer.data) |
|||
return CCAIResponse(data="success") |
|||
|
|||
except Exception as e: |
|||
err_logger.error("user: %s, create company failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("直接新增公司失败", SERVER_ERROR) |
|||
|
|||
def perform_create(self, serializer): |
|||
# 重写序列化创建,返回创建的公司对象 |
|||
return serializer.save() |
|||
|
|||
def update(self, request, *args, **kwargs): |
|||
""" |
|||
如果用户修改拥有者 |
|||
""" |
|||
try: |
|||
data = req_operate_by_user(request) |
|||
tech = data.get('tech') |
|||
tolerance = data.get('tolerance') # 容错时间 |
|||
userMid = data.get('userMid') |
|||
if tech: |
|||
data['tech'] = ','.join(str(x) for x in tech) |
|||
if tolerance: |
|||
data['tolerance'] = time_str_to_int(tolerance) |
|||
partial = kwargs.pop('partial', False) # True:所有字段全部更新, False:仅更新提供的字段 |
|||
instance = self.get_object() |
|||
with transaction.atomic(): |
|||
# 检查是否更改拥有者 |
|||
flag = False |
|||
if userMid and instance.userMid != userMid: |
|||
flag = True |
|||
new_user = UserProfile.objects.filter(MainId=userMid).select_for_update().first() # 新的拥有者 |
|||
old_user = UserProfile.objects.filter(MainId=instance.userMid).select_for_update().first() # 旧的拥有者 |
|||
serializer = self.get_serializer(instance, data=data, partial=partial) |
|||
serializer.is_valid(raise_exception=True) |
|||
self.perform_update(serializer) |
|||
if flag: |
|||
if not new_user: |
|||
transaction.set_rollback(True) |
|||
return CCAIResponse("用户已被删除!", BAD) |
|||
else: |
|||
new_user.company.add(instance) |
|||
if old_user: |
|||
old_user.company.remove(instance) |
|||
|
|||
if getattr(instance, '_prefetched_objects_cache', None): |
|||
# If 'prefetch_related' has been applied to a queryset, we need to |
|||
# forcibly invalidate the prefetch cache on the instance. |
|||
instance._prefetched_objects_cache = {} |
|||
|
|||
return CCAIResponse(data="success") |
|||
except Exception as e: |
|||
err_logger.error("user: %s, update company Info failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("修改公司信息失败列表失败", SERVER_ERROR) |
|||
|
|||
@action(methods=["get"], detail=False, permission_classes=[IsAuthenticated], |
|||
url_path="getActiveUser", url_name="getActiveUser") |
|||
def getActiveUser(self, request): |
|||
""" |
|||
获取主账号可用用户列表,供公司拥有者选择 |
|||
""" |
|||
try: |
|||
users = UserProfile.objects.filter(is_sub=2, is_active=1, MainId__isnull=False).values("id", "name", "username", "MainId") |
|||
return CCAIResponse(users) |
|||
except Exception as e: |
|||
err_logger.error("user: %s, getActiveUser failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("获取可选择的公司拥有者列表失败", SERVER_ERROR) |
|||
|
|||
@action(methods=["get"], detail=False, permission_classes=[IsAuthenticated], |
|||
url_path="getCompanyList", url_name="getCompanyList") |
|||
def getCompanyList(self, request): |
|||
""" |
|||
后台用户获得公司列表 |
|||
""" |
|||
try: |
|||
if 2 == request.user.label: |
|||
companies = Company.objects.all().values('id', 'name') |
|||
result = [item for item in companies] |
|||
else: |
|||
return CCAIResponse() |
|||
return CCAIResponse(result) |
|||
except Exception as e: |
|||
err_logger.error("user: %s, getUserCompanyInfo failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("获取用户公司信息失败", SERVER_ERROR) |
|||
|
|||
@action(methods=["get"], detail=False, permission_classes=[IsAuthenticated], |
|||
url_path="getMainUserList", url_name="getMainUserList") |
|||
def getMainUserList(self, request): |
|||
""" |
|||
获取主账户id,username,name,MainId |
|||
""" |
|||
try: |
|||
keyword = request.GET.get('keyword') |
|||
query = Q(is_sub=2) |
|||
if keyword: |
|||
query &= (Q(username__icontains=keyword) | Q(name__icontains=keyword)) |
|||
mainUser = UserProfile.objects.filter(query).values('id', 'username', 'name', 'MainId') |
|||
return CCAIResponse(mainUser) |
|||
except Exception as e: |
|||
err_logger.error("user: %s, getMainUser list failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("获取主用户列表失败", SERVER_ERROR) |
|||
|
|||
@action(methods=["get"], detail=False, permission_classes=[IsAuthenticated], |
|||
url_path="getUserCompanyInfo", url_name="getUserCompanyInfo") |
|||
def getUserCompanyInfo(self, request): |
|||
try: |
|||
params = request.GET |
|||
companyMid = params.get('companyMid') |
|||
query = Q(MainId=companyMid) |
|||
try: |
|||
company = Company.objects.get(query) |
|||
except ObjectDoesNotExist: |
|||
return CCAIResponse('缺少公司参数', BAD) |
|||
data = CompanySerializer(company).data |
|||
if data.get('tech'): |
|||
data['tech'] = [int(x) for x in data['tech'].split(',')] |
|||
if 'tolerance' in data: |
|||
data['tolerance'] = time_int_to_str(data['tolerance']) |
|||
return CCAIResponse(data) |
|||
except Exception as e: |
|||
err_logger.error("user: %s, getUserCompanyInfo failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("获取用户公司信息失败", SERVER_ERROR) |
|||
|
|||
@action(methods=["get"], detail=False, permission_classes=[IsAuthenticated], |
|||
url_path="getTechnologyTree", url_name="getTechnologyTree") |
|||
def getTechnologyTree(self, request): |
|||
""" |
|||
技术领域树 |
|||
""" |
|||
try: |
|||
dataDictionary = DataDictionary.objects.get(DictionaryCode='TechArea') |
|||
dataDictionaryDetail = DataDictionaryDetail.objects.filter( |
|||
DataDictionaryId=dataDictionary.DataDictionaryId).values('id', 'DataDictionaryDetailId', 'ParentId', |
|||
'DictionaryCode', 'DictionaryValue', 'ParentCode', |
|||
'FullName') |
|||
data = DictDetailSerializer(dataDictionaryDetail, many=True) |
|||
tree_dict = {} |
|||
for item in data.data: |
|||
if item["parent_id"] is None: |
|||
item["children"] = [] |
|||
top_permission = copy.deepcopy(item) |
|||
tree_dict[item["dict_detail_id"]] = top_permission |
|||
else: |
|||
children_permission = copy.deepcopy(item) |
|||
tree_dict[item["dict_detail_id"]] = children_permission |
|||
|
|||
tree_data = [] |
|||
for i in tree_dict: |
|||
if tree_dict[i]["parent_id"]: |
|||
pid = tree_dict[i]["parent_id"] |
|||
parent = tree_dict[pid] |
|||
parent.setdefault("children", []).append(tree_dict[i]) |
|||
# from operator import itemgetter |
|||
# parent["children"] = sorted(parent["children"], key=itemgetter("id")) |
|||
else: |
|||
tree_data.append(tree_dict[i]) |
|||
return CCAIResponse(tree_data, status=OK) |
|||
except Exception as e: |
|||
err_logger.error("user: %s, getUserCompanyInfo failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("获取用户公司信息失败", SERVER_ERROR) |
|||
|
|||
@action(methods=["get"], detail=False, permission_classes=[IsAuthenticated], |
|||
url_path="download", url_name="download") |
|||
def download(self, request): |
|||
""" |
|||
导入模板下载 |
|||
""" |
|||
try: |
|||
# 查询公司的项目列表 |
|||
companymid = request.GET.get('companyMid') |
|||
if not companymid: |
|||
return CCAIResponse('Missing or invalid company information', BAD) |
|||
company = Company.objects.filter(MainId=companymid).first() |
|||
if not company: |
|||
return CCAIResponse('Company param error', BAD) |
|||
|
|||
users = UserProfile.objects.filter(is_sub=2, is_active=1, MainId__isnull=False).values("id", "name", "username", "MainId") |
|||
username_user = [str((item['name'] if item['name'] else '') + '(' + item['username'] + ')') for item in users] # 使用用户姓名(账号) 做唯一 |
|||
|
|||
title_data = ["*公司名称", "*拥有者", "企业统一信用代码", "高企编码", "企业员工总数"] |
|||
name = str(int(time.time() * 10000)) # 时间戳命名 |
|||
workbook = openpyxl.Workbook() |
|||
worksheet = workbook.active |
|||
worksheet.title = "公司导入模板" |
|||
|
|||
# 添加隐藏的选项参考表 |
|||
worksheet2 = workbook.create_sheet("选项参考表") |
|||
worksheet2.sheet_state = "hidden" # 将工作表隐藏起来 |
|||
worksheet2.cell(1, 1, '拥有者参考') |
|||
|
|||
# 写入拥有者参考数据 |
|||
for idx, project in enumerate(username_user, start=2): # 从第二行开始写入数据 |
|||
worksheet2.cell(idx, 1, str(project)) |
|||
|
|||
for index, item in enumerate(title_data, start=1): |
|||
worksheet.cell(1, index, item) |
|||
worksheet['A1'].comment = Comment("必填项,请输入正确的公司名称!", 'Author') |
|||
worksheet['B1'].comment = Comment("必填项,请从提供的选项中选择!", 'Author') |
|||
|
|||
# 创建一个数据有效性验证对象 |
|||
usersDv = DataValidation(type="list", formula1=f'=选项参考表!$A$2:$A${len(username_user) + 1}', showErrorMessage=True, errorTitle="输入错误", |
|||
error="请从给定的选项中选择") |
|||
|
|||
# 数据有效性验证应用 |
|||
worksheet.add_data_validation(usersDv) |
|||
|
|||
usersDv.add(CellRange("B2:B10000")) |
|||
|
|||
# 设置第一列的数据格式为文本类型 |
|||
worksheet.column_dimensions['A'].number_format = '@' |
|||
worksheet.column_dimensions['C'].number_format = '@' |
|||
worksheet.column_dimensions['D'].number_format = '@' |
|||
|
|||
# worksheet.cell(2, 10).value = '=H2*I2' |
|||
# for row in range(2, 10000): |
|||
# worksheet.cell(row, 10).value = '=IF(ISBLANK(H{}) OR ISBLANK(I{}), "", H{}*I{})'.format(row, row, row, row) |
|||
|
|||
workbook.save(filename=name + ".xlsx") |
|||
fileName = name + ".xlsx" |
|||
file = open(fileName, 'rb') |
|||
s_name = "公司导入模板.xlsx" |
|||
response = FileResponse(file) |
|||
response['Content-Type'] = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' # 规定返回xlsx |
|||
response['Content-Disposition'] = f'attachment;filename="{s_name}"' |
|||
response['file_name'] = fileName |
|||
|
|||
# 异步删除文件 |
|||
threadPool.submit(asyncDeleteFile, request, fileName) |
|||
|
|||
return response |
|||
except Exception as e: |
|||
err_logger.error("user: %s, get company template file failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("下载公司导入模板失败", SERVER_ERROR) |
|||
|
|||
@action(methods=["post"], detail=False, permission_classes=[IsAuthenticated], |
|||
url_path="import_excel", url_name="import_excel") |
|||
def import_excel(self, request): |
|||
"""导入excel表数据""" |
|||
try: |
|||
data = req_operate_by_user(request) |
|||
file_name = data.get('file_name') |
|||
|
|||
if not file_name: |
|||
return CCAIResponse('Missing params', BAD) |
|||
|
|||
|
|||
users = UserProfile.objects.filter(is_sub=2, is_active=1, MainId__isnull=False).values("id", "name", "username", "MainId") |
|||
username_user = {str((item['name'] if item['name'] else '') + '(' + item['username'] + ')'): item['MainId'] for item in users} # 使用用户姓名(账号) 做唯一 |
|||
|
|||
company_list = [] |
|||
excel_file_list = [] |
|||
|
|||
file_paths = file_name.split(",") |
|||
for key in range(len(file_paths)): |
|||
if file_paths[key] == '' or file_paths[key] is None: |
|||
continue |
|||
excel_file = file_paths[key].replace(settings.SHOW_UPLOAD_PATH, settings.FILE_PATH) |
|||
excel_file_list.append(excel_file) |
|||
|
|||
# 打开工作文件 |
|||
workbook = openpyxl.load_workbook(excel_file, data_only=True) |
|||
table = workbook['公司导入模板'] |
|||
# table = workbook.active |
|||
rows = table.max_row # 总行数 |
|||
check_value = [table.cell(row=1, column=i).value for i in range(1, 4)] # 获取第一行的值 |
|||
if check_value[0] != '*公司名称' and check_value[1] != '*拥有者' and check_value[2] != '*企业统一信用代码': |
|||
return CCAIResponse("文件不符合模板标准", SERVER_ERROR) |
|||
user_data = [] # 项目编号列 |
|||
for row in islice(table.iter_rows(values_only=True), 1, None): |
|||
if row[1]: |
|||
user_data.append(row[0]) # 项目编号列 |
|||
|
|||
try: |
|||
columns = ["companyName", "userName", "EUCC", "hightechCode", "employeesCount"] |
|||
columns_name = ["企业名称", "拥有者", "企业同一信用代码", "高企编码", "员工人数"] |
|||
errorTable = ErrorTable(columns) |
|||
# 循环外层控制事务,因为物料记录的创建依赖与项目分摊的创建 |
|||
with transaction.atomic(): |
|||
for rindex, row in enumerate(islice(table.iter_rows(values_only=True), 1, None)): |
|||
# 跳过空白隐藏行 |
|||
if all(cell is None for cell in row): |
|||
continue |
|||
|
|||
row_value = [r for r in row] |
|||
check_index = [0, 1,] |
|||
# 校验必填项 |
|||
skip = False |
|||
for index in check_index: # 前2项为必填项 |
|||
if row[index] is None or row[index] == '': |
|||
skip = True |
|||
errorTable.add_one_row(row_value, f'{columns_name[index]}为必填项') |
|||
break |
|||
if skip: |
|||
skip = False |
|||
continue |
|||
|
|||
if str(row[1]) not in username_user: |
|||
errorTable.add_one_row(row_value, f'用户错误') |
|||
continue |
|||
|
|||
# 获取对应的用户 |
|||
userMid = username_user[str(row[1])] |
|||
try: |
|||
user = UserProfile.objects.get(MainId=userMid) |
|||
except UserProfile.DoesNotExist: |
|||
errorTable.add_one_row(row_value, f'用户错误') |
|||
continue |
|||
# 新增公司 |
|||
companyMid = uuid.uuid4().__str__() # 公司mid |
|||
company = Company() |
|||
company.name = str(row_value[0]) |
|||
company.MainId = companyMid |
|||
company.userMid = userMid |
|||
if row_value[2]: |
|||
company.EUCC = str(row_value[2]) |
|||
if row_value[3]: |
|||
company.hightechCode = str(row_value[3]) |
|||
company.employeesCount = int(row_value[4]) if row_value[4] else 0 |
|||
|
|||
company.save() |
|||
user.company.add(company) |
|||
|
|||
if errorTable.has_data(): # 存在错误信息,回滚并返回所有错误行的提示信息 |
|||
transaction.set_rollback(True) |
|||
return CCAIResponse(errorTable.get_table(), 200) |
|||
|
|||
except MyCustomError as e: |
|||
return CCAIResponse(f'文件读取第{rindex}行数据多于标题列', BAD) |
|||
except Exception as e: |
|||
err_logger.error("user: %s, 文件路径:%s 记录公司导入错误日志: \n%s" % (request.user.id, excel_file, traceback.format_exc())) |
|||
return CCAIResponse('数据有误,请检查后重新导入', BAD) |
|||
|
|||
try: |
|||
for file_path in excel_file_list: |
|||
# 异步删除文件 |
|||
threadPool.submit(asyncDeleteFile, request, file_path) |
|||
# if os.path.exists(file_path): # 如果文件存在 |
|||
# # 删除文件,可使用以下两种方法。 |
|||
# os.remove(file_path) |
|||
# # 删除空目录,不是空目录时候rmdir不会删除 |
|||
# # try: |
|||
# # os.rmdir(file_path) |
|||
# # except Exception as e: |
|||
# # logger.error("user: %s, rmdir highTech failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
|
|||
except Exception as e: |
|||
err_logger.error("user: %s, import_excel view create failed: \n%s" % ( |
|||
request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("导入存入失败", SERVER_ERROR) |
|||
|
|||
return CCAIResponse(data="success") |
|||
except Exception as e: |
|||
err_logger.error("user: %s, import aigc file failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("导入公司名单失败", SERVER_ERROR) |
|||
|
|||
|
|||
|
|||
class CompanyNameListAPIView(APIView): |
|||
authentication_classes = (JSONWebTokenAuthentication,) |
|||
permission_classes = (IsAuthenticated,) |
|||
|
|||
''' |
|||
获取公司名称列表 |
|||
''' |
|||
# throttle_classes = (AnonRateThrottle, UserRateThrottle) |
|||
|
|||
def get(self, request, format=None): |
|||
blurry_company_info_req = '' |
|||
try: |
|||
params = request.GET |
|||
company_name = params.get('company_name') |
|||
|
|||
rows = [] |
|||
qixinbao_failed = False |
|||
if company_name: |
|||
blurry_company_info_req = getBlurryCompanyInfo(request, company_name, 0) |
|||
if blurry_company_info_req: |
|||
base_info = blurry_company_info_req.json() |
|||
if base_info['status'] == '200': |
|||
companys = base_info['data']['items'] |
|||
for company in companys: |
|||
dict = {'value': company['name']} |
|||
rows.append(dict) |
|||
|
|||
# rows = [ |
|||
# {'value':"深圳市骏鼎达新材料股份有限公司"}, |
|||
# {'value':"东莞市骏鼎达新材料科技有限公司"}, |
|||
# {'value':"昆山骏鼎达电子科技有限公司"}, |
|||
# {'value':"苏州骏鼎达新材料科技有限公司"}, |
|||
# {'value':"江门骏鼎达新材料科技有限公司"}, |
|||
# {'value':"深圳市骏鼎达新材料股份有限公司重庆分公司"}, |
|||
# {'value':"深圳市骏鼎达新材料股份有限公司武汉分公司"}, |
|||
# {'value':"龙川县骏鼎达新材料有限公司"}, |
|||
# {'value':"佛山市骏鼎达五金有限公司"}, |
|||
# {'value':"駿鼎達國際有限公司"} |
|||
# ] |
|||
|
|||
return CCAIResponse(rows) |
|||
|
|||
except Exception as e: |
|||
logger.error("user: %s, get blurry company failed: %s, detail: %s" % (request.user.id, traceback.format_exc(), blurry_company_info_req)) |
|||
return CCAIResponse("获取地区失败", SERVER_ERROR) |
|||
@ -0,0 +1,138 @@ |
|||
import logging |
|||
import traceback |
|||
|
|||
from rest_framework.viewsets import ModelViewSet |
|||
|
|||
from ChaCeRndTrans.basic import CCAIResponse |
|||
from ChaCeRndTrans.code import OK |
|||
from utils.custom import CommonPagination, RbacPermission, TreeAPIView, CustomViewBase |
|||
from ..models import Menu |
|||
from ..serializers.menu_serializer import MenuSerializer |
|||
from rest_framework.filters import SearchFilter, OrderingFilter |
|||
from rest_framework_jwt.authentication import JSONWebTokenAuthentication |
|||
from operator import itemgetter |
|||
|
|||
err_logger = logging.getLogger('error') |
|||
|
|||
|
|||
class MenuViewSet(ModelViewSet, TreeAPIView): |
|||
""" |
|||
菜单管理:增删改查 |
|||
""" |
|||
perms_map = ({"*": "admin"}, {"*": "menu_all"}, {"get": "menu_list"}, {"post": "menu_create"}, {"put": "menu_edit"}, |
|||
{"delete": "menu_delete"}) |
|||
queryset = Menu.objects.all() |
|||
serializer_class = MenuSerializer |
|||
pagination_class = CommonPagination |
|||
filter_backends = (SearchFilter, OrderingFilter) |
|||
search_fields = ("name",) |
|||
ordering_fields = ("sort",) |
|||
authentication_classes = (JSONWebTokenAuthentication,) |
|||
permission_classes = (RbacPermission,) |
|||
|
|||
|
|||
class MenuTreeView(TreeAPIView): |
|||
""" |
|||
菜单树 |
|||
""" |
|||
queryset = Menu.objects.all() |
|||
|
|||
def list(self, request, *args, **kwargs): |
|||
""" |
|||
根据是否前后台用户返回除去内部权限 |
|||
""" |
|||
if 1 == request.user.label: # 前台用户 |
|||
self.queryset = Menu.objects.filter(label=1).distinct() |
|||
response = super().list(request, *args, **kwargs) |
|||
return response |
|||
|
|||
|
|||
def get_all_menu_dict(): |
|||
""" |
|||
获取所有菜单数据,重组结构 |
|||
""" |
|||
try: |
|||
menus = Menu.objects.all() |
|||
serializer = MenuSerializer(menus, many=True) |
|||
tree_dict = {} |
|||
for item in serializer.data: |
|||
if item["pid"] is None: |
|||
if item["is_frame"]: |
|||
# 判断是否外部链接 |
|||
top_menu = { |
|||
"id": item["id"], |
|||
"path": item["path"], |
|||
"component": "Layout", |
|||
"children": [{ |
|||
"path": item["path"], |
|||
"meta": { |
|||
"name": item["name"], |
|||
"icon": item["icon"] |
|||
} |
|||
}], |
|||
"pid": item["pid"], |
|||
"sort": item["sort"] |
|||
} |
|||
else: |
|||
top_menu = { |
|||
"id": item["id"], |
|||
"route_name": item["route_name"], |
|||
"path": "/" + item["path"], |
|||
"redirect": "noredirect", |
|||
"component": "", |
|||
"alwaysShow": True, |
|||
"meta": { |
|||
"name": item["name"], |
|||
"icon": item["icon"] |
|||
}, |
|||
"pid": item["pid"], |
|||
"sort": item["sort"], |
|||
"children": [] |
|||
} |
|||
tree_dict[item["id"]] = top_menu |
|||
else: |
|||
if item["is_frame"]: |
|||
children_menu = { |
|||
"id": item["id"], |
|||
"route_name": item["route_name"], |
|||
"path": item["path"], |
|||
"component": "Layout", |
|||
"meta": { |
|||
"name": item["name"], |
|||
"icon": item["icon"], |
|||
}, |
|||
"pid": item["pid"], |
|||
"sort": item["sort"] |
|||
} |
|||
elif item["is_show"]: |
|||
children_menu = { |
|||
"id": item["id"], |
|||
"route_name": item["route_name"], |
|||
"path": item["path"], |
|||
"component": item["component"], |
|||
"meta": { |
|||
"name": item["name"], |
|||
"icon": item["icon"], |
|||
}, |
|||
"pid": item["pid"], |
|||
"sort": item["sort"] |
|||
} |
|||
else: |
|||
children_menu = { |
|||
"id": item["id"], |
|||
"route_name": item["route_name"], |
|||
"path": item["path"], |
|||
"component": item["component"], |
|||
"meta": { |
|||
"name": item["name"], |
|||
"noCache": True, |
|||
}, |
|||
"hidden": True, |
|||
"pid": item["pid"], |
|||
"sort": item["sort"] |
|||
} |
|||
tree_dict[item["id"]] = children_menu |
|||
return tree_dict |
|||
except Exception as e: |
|||
err_logger.error("get all menu from role failed: \n%s" % traceback.format_exc()) |
|||
return tree_dict |
|||
@ -0,0 +1,105 @@ |
|||
import datetime |
|||
import traceback |
|||
import requests, logging, random, re, json |
|||
|
|||
from rest_framework import status |
|||
from rest_framework.throttling import AnonRateThrottle |
|||
from rest_framework.views import APIView |
|||
from django_redis import get_redis_connection |
|||
|
|||
from ChaCeRndTrans import settings |
|||
from ChaCeRndTrans.basic import CCAIResponse |
|||
from ChaCeRndTrans.code import SERVER_ERROR, BAD # PHONE_NO_BIND, PHONE_IS_BIND, NO_REGISTER_PHONE |
|||
from rbac.models import UserProfile |
|||
|
|||
logger = logging.getLogger('error') |
|||
|
|||
msg_redis_code = 'rndMsgCode' # 用户短信验证码缓存标志 |
|||
|
|||
|
|||
def random_str(): |
|||
_str = '1234567890' |
|||
return ''.join(random.choice(_str) for i in range(4)) |
|||
|
|||
|
|||
def send_message(phone, template_type): |
|||
msg_code = random_str() |
|||
key = msg_redis_code + phone |
|||
visit_time_key = "dodo_visit_" + phone # 用于控制一段时间后放行短信发送 |
|||
conn = get_redis_connection('default') |
|||
visit_key = conn.get(visit_time_key) |
|||
|
|||
if visit_key is None: |
|||
conn.incrby(visit_time_key, 1) |
|||
else: |
|||
visit_count = int(conn.get(visit_time_key).decode('utf8')) |
|||
if visit_count < 10: |
|||
conn.incrby(visit_time_key, 1) |
|||
else: |
|||
# is_ttl = conn.ttl(visit_time_key) # 获取剩余时间,秒 |
|||
# if is_ttl != -1: |
|||
# return CCAIResponse(msg="发送短信频繁,请稍后再发送。", status=BAD) |
|||
conn.expire(visit_time_key, 60) |
|||
return CCAIResponse(data="发送短信频繁,请稍后再发送。", msg="发送短信频繁,请稍后再发送。", status=BAD) |
|||
# 验证码 |
|||
conn.hset(key, 'code', msg_code) |
|||
conn.expire(key, 300) |
|||
conn.expire(visit_time_key, 300) # 保证5分钟以后这个键会消失 |
|||
url = settings.MSG_URL |
|||
if settings.DEVELOP_DEBUG: |
|||
url = settings.TEST_MSG_URL |
|||
# params = '{"name":"szccwl","pwd":"md","address":"bz","phone":"13000000000"}' |
|||
params = {} |
|||
params['username'] = 'ccw' |
|||
params['password'] = 'chacewang123456' |
|||
params['phone'] = phone |
|||
params['template_type'] = template_type |
|||
args = '{"code":"%s"}' % msg_code |
|||
params['args'] = args |
|||
headers = { |
|||
'Content-Type': 'application/x-www-form-urlencoded' |
|||
} |
|||
try: |
|||
count = 0 |
|||
flag = False |
|||
result = {} |
|||
response = {} |
|||
while count < 3 and flag is False: |
|||
response = requests.request("GET", url, headers=headers, data=params) |
|||
if response.status_code == 200: |
|||
flag = True |
|||
else: |
|||
count = count + 1 |
|||
if flag is True: |
|||
result = json.loads(response.text) |
|||
if 'code' in result.keys() and result['code'] == 200: |
|||
return CCAIResponse('success') |
|||
else: |
|||
return CCAIResponse(data="发送短信失败", msg="发送短信失败!", status=BAD) |
|||
except Exception as e: |
|||
logger.error("get MessageView failed: \n%s" % traceback.format_exc()) |
|||
return CCAIResponse(msg='发送验证码出错', status=SERVER_ERROR) |
|||
|
|||
|
|||
class MessageView(APIView): |
|||
""" |
|||
发送验证码 |
|||
""" |
|||
|
|||
# throttle_classes = (AnonRateThrottle,) |
|||
def get(self, request, *args, **kwargs): |
|||
try: |
|||
params = request.GET |
|||
phone = params.get('phone') |
|||
if phone == None or phone == '': |
|||
return CCAIResponse(data="请输入正确的手机号", msg="手机号不能为空", status=status.HTTP_400_BAD_REQUEST) |
|||
ret = re.match(r"^1[3-9]\d{9}$", phone) |
|||
if ret: |
|||
# return CCAIResponse('success') |
|||
return send_message(phone, 'Ccw_TemplateType_Captcha') |
|||
else: |
|||
return CCAIResponse(data="请输入正确的手机号", msg="请输入正确的手机号", status=status.HTTP_400_BAD_REQUEST) |
|||
|
|||
except Exception as e: |
|||
logger.error("send MessageView failed: \n%s" % traceback.format_exc()) |
|||
return CCAIResponse(data="发送验证码出错", msg="发送验证码出错", status=SERVER_ERROR) |
|||
@ -0,0 +1,95 @@ |
|||
import logging |
|||
import traceback |
|||
|
|||
from rest_framework.viewsets import ModelViewSet |
|||
|
|||
from ChaCeRndTrans.basic import CCAIResponse |
|||
from ChaCeRndTrans.code import OK |
|||
from ..models import Permission |
|||
from ..serializers.permission_serializer import PermissionListSerializer |
|||
from utils.custom import CommonPagination, RbacPermission, TreeAPIView, CustomViewBase |
|||
from rest_framework.filters import SearchFilter, OrderingFilter |
|||
from rest_framework_jwt.authentication import JSONWebTokenAuthentication |
|||
from operator import itemgetter |
|||
|
|||
err_logger = logging.getLogger('error') |
|||
|
|||
|
|||
class PermissionViewSet(CustomViewBase, TreeAPIView): |
|||
""" |
|||
权限:增删改查 |
|||
""" |
|||
perms_map = ({"*": "admin"}, {"*": "permission_all"}, {"get": "permission_list"}, {"post": "permission_create"}, |
|||
{"put": "permission_edit"}, {"delete": "permission_delete"}) |
|||
queryset = Permission.objects.all() |
|||
serializer_class = PermissionListSerializer |
|||
pagination_class = CommonPagination |
|||
filter_backends = (SearchFilter, OrderingFilter) |
|||
search_fields = ("name",) |
|||
ordering_fields = ("id",) |
|||
authentication_classes = (JSONWebTokenAuthentication,) |
|||
permission_classes = (RbacPermission,) |
|||
|
|||
def list(self, request, *args, **kwargs): |
|||
tree_dict = get_all_permission_dict() |
|||
tree_data = [] |
|||
for i in tree_dict: |
|||
if tree_dict[i]["pid"]: |
|||
pid = tree_dict[i]["pid"] |
|||
parent = tree_dict[pid] |
|||
parent.setdefault("children", []).append(tree_dict[i]) |
|||
parent["children"] = sorted(parent["children"], key=itemgetter("id")) |
|||
else: |
|||
tree_data.append(tree_dict[i]) |
|||
return CCAIResponse(tree_data, status=OK) |
|||
|
|||
|
|||
class PermissionTreeView(TreeAPIView): |
|||
""" |
|||
权限树 |
|||
""" |
|||
queryset = Permission.objects.all() |
|||
|
|||
def list(self, request, *args, **kwargs): |
|||
""" |
|||
根据是否前后台用户返回除去内部权限 |
|||
""" |
|||
if 1 == request.user.label: |
|||
self.queryset = Permission.objects.filter(label=1) |
|||
response = super().list(request, *args, **kwargs) |
|||
return response |
|||
|
|||
|
|||
def get_all_permission_dict(): |
|||
""" |
|||
获取所有菜单数据,重组结构 |
|||
""" |
|||
try: |
|||
permission = Permission.objects.all() |
|||
serializer = PermissionListSerializer(permission, many=True) |
|||
tree_dict = {} |
|||
for item in serializer.data: |
|||
if item["pid"] is None: |
|||
top_permission = { |
|||
"id": item["id"], |
|||
"name": item["name"], |
|||
"method": item["method"], |
|||
"pid": item["pid"], |
|||
"label": item["label"], |
|||
"children": [] |
|||
} |
|||
tree_dict[item["id"]] = top_permission |
|||
else: |
|||
children_permission = { |
|||
"id": item["id"], |
|||
"name": item["name"], |
|||
"pid": item["pid"], |
|||
"method": item["method"], |
|||
"label": item["label"], |
|||
} |
|||
|
|||
tree_dict[item["id"]] = children_permission |
|||
return tree_dict |
|||
except Exception as e: |
|||
err_logger.error("get all menu from role failed: \n%s" % traceback.format_exc()) |
|||
return tree_dict |
|||
@ -0,0 +1,73 @@ |
|||
import logging |
|||
import traceback |
|||
|
|||
from rest_framework.viewsets import ModelViewSet, GenericViewSet |
|||
from rest_framework import mixins |
|||
|
|||
from ChaCeRndTrans.code import BAD, SERVER_ERROR |
|||
from ..models import Role |
|||
from ..serializers.role_serializer import RoleListSerializer, RoleModifySerializer |
|||
from utils.custom import CommonPagination, RbacPermission, CustomViewBase |
|||
from rest_framework.filters import SearchFilter, OrderingFilter |
|||
from rest_framework_jwt.authentication import JSONWebTokenAuthentication |
|||
from rest_framework.permissions import IsAuthenticated |
|||
|
|||
from utils.custom import CCAIResponse |
|||
|
|||
logger = logging.getLogger('error') |
|||
|
|||
|
|||
class RoleViewSet(CustomViewBase): |
|||
""" |
|||
角色管理:增删改查 |
|||
""" |
|||
perms_map = ( |
|||
{"*": "admin"}, |
|||
{"*": "role_all"}, |
|||
{"get": "role_list"}, |
|||
{"post": "role_create"}, |
|||
{"put": "role_edit"}, |
|||
{"delete": "role_delete"} |
|||
) |
|||
queryset = Role.objects.all().order_by('id') |
|||
serializer_class = RoleListSerializer |
|||
pagination_class = CommonPagination |
|||
filter_backends = (SearchFilter, OrderingFilter) |
|||
search_fields = ("name",) |
|||
ordering_fields = ("id",) |
|||
authentication_classes = (JSONWebTokenAuthentication,) |
|||
permission_classes = (RbacPermission,) |
|||
|
|||
def get_serializer_class(self): |
|||
if self.action == "list": |
|||
return RoleListSerializer |
|||
return RoleModifySerializer |
|||
|
|||
def get_queryset(self): |
|||
""" |
|||
后台角色列表 |
|||
返回后台角色(label=2)与前台默认角色(label=1,companyMid==None) |
|||
""" |
|||
label = self.request.query_params.get('label', None) # 1-前台默认角色 2-后台角色列表 |
|||
queryset = Role.objects.all() |
|||
if not label or int(label) not in (1, 2): |
|||
return queryset |
|||
if 1 == int(label): |
|||
queryset = queryset.filter(label=1, companyMid__isnull=True) |
|||
else: |
|||
queryset = queryset.filter(label=2) |
|||
return queryset |
|||
|
|||
def list(self, request, *args, **kwargs): |
|||
try: |
|||
queryset = self.filter_queryset(self.get_queryset()) |
|||
page = self.paginate_queryset(queryset) |
|||
if page is not None: |
|||
serializer = self.get_serializer(page, many=True) |
|||
return self.get_paginated_response(serializer.data) |
|||
|
|||
serializer = self.get_serializer(queryset, many=True) |
|||
return CCAIResponse(data=serializer.data) |
|||
except Exception as e: |
|||
logger.error("user: %s, get role list failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("get list failed: \n %s" % e, SERVER_ERROR) |
|||
File diff suppressed because it is too large
@ -0,0 +1,3 @@ |
|||
from django.contrib import admin |
|||
|
|||
# Register your models here. |
|||
@ -0,0 +1,5 @@ |
|||
from django.apps import AppConfig |
|||
|
|||
|
|||
class StaffConfig(AppConfig): |
|||
name = 'staff' |
|||
@ -0,0 +1,177 @@ |
|||
# Generated by Django 3.1.4 on 2024-03-08 08:44 |
|||
|
|||
from django.db import migrations, models |
|||
import django.db.models.deletion |
|||
|
|||
|
|||
class Migration(migrations.Migration): |
|||
|
|||
initial = True |
|||
|
|||
dependencies = [ |
|||
] |
|||
|
|||
operations = [ |
|||
migrations.CreateModel( |
|||
name='Accrued', |
|||
fields=[ |
|||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('staffId', models.IntegerField(blank=True, help_text='员工id', null=True, verbose_name='员工id')), |
|||
('accruedDate', models.DateField(blank=True, help_text='预提年月 # 2024-01', null=True, verbose_name='预提年月')), |
|||
('accruedEndDate', models.DateField(blank=True, help_text='预提终止年月 # 2024-01', null=True, verbose_name='预提终止年月')), |
|||
('accruedAmount', models.DecimalField(blank=True, decimal_places=2, help_text='预提金额', max_digits=18, null=True, verbose_name='预提金额')), |
|||
('accruedType', models.IntegerField(blank=True, help_text='预提类型 # (1:工资,2:奖金,3:福利)', null=True, verbose_name='预提类型')), |
|||
('amountType', models.IntegerField(blank=True, help_text='预提金额类型id # 用户自定义的奖金与福利类型', null=True, verbose_name='预提金额类型id')), |
|||
('status', models.IntegerField(blank=True, default=0, help_text='状态 # 0:未审核 1:已审核保存 2:已添加凭证', null=True, verbose_name='状态')), |
|||
('remark', models.CharField(blank=True, help_text='备注信息', max_length=200, null=True, verbose_name='备注信息')), |
|||
('CreateBy', models.CharField(blank=True, max_length=36, null=True, verbose_name='创建人')), |
|||
('UpdateBy', models.CharField(blank=True, max_length=36, null=True, verbose_name='更新人')), |
|||
('CreateByUid', models.IntegerField(blank=True, null=True, verbose_name='创建人ID')), |
|||
('UpdateByUid', models.IntegerField(blank=True, null=True, verbose_name='更新人ID')), |
|||
('CreateDateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), |
|||
('UpdateDateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), |
|||
('companyMid', models.CharField(blank=True, max_length=36, null=True, verbose_name='公司全局id')), |
|||
], |
|||
options={ |
|||
'verbose_name': '预提记录', |
|||
'verbose_name_plural': '预提记录', |
|||
'ordering': ['id'], |
|||
}, |
|||
), |
|||
migrations.CreateModel( |
|||
name='Attendance', |
|||
fields=[ |
|||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('staffId', models.IntegerField(blank=True, help_text='被考勤人员id', null=True, verbose_name='被考勤人员id')), |
|||
('attendanceDate', models.DateField(blank=True, help_text='考勤年月 # 2024-01', null=True, verbose_name='考勤年月')), |
|||
('attendanceDays', models.FloatField(blank=True, help_text='月出勤天数', null=True, verbose_name='月出勤天数')), |
|||
('restDay', models.CharField(blank=True, help_text='休息日', max_length=200, null=True, verbose_name='休息日')), |
|||
('rndDay', models.FloatField(blank=True, help_text='研发天数', null=True, verbose_name='研发天数')), |
|||
('status', models.IntegerField(blank=True, default=0, help_text='状态 # 0:未审核 1:已审核保存 2:已添加凭证', null=True, verbose_name='状态')), |
|||
('CreateBy', models.CharField(blank=True, max_length=36, null=True, verbose_name='创建人')), |
|||
('UpdateBy', models.CharField(blank=True, max_length=36, null=True, verbose_name='更新人')), |
|||
('CreateByUid', models.IntegerField(blank=True, null=True, verbose_name='创建人ID')), |
|||
('UpdateByUid', models.IntegerField(blank=True, null=True, verbose_name='更新人ID')), |
|||
('CreateDateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), |
|||
('UpdateDateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), |
|||
('companyMid', models.CharField(blank=True, max_length=36, null=True, verbose_name='公司全局id')), |
|||
], |
|||
options={ |
|||
'verbose_name': '考勤记录', |
|||
'verbose_name_plural': '考勤记录', |
|||
'ordering': ['id'], |
|||
}, |
|||
), |
|||
migrations.CreateModel( |
|||
name='Reward', |
|||
fields=[ |
|||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('staffId', models.IntegerField(blank=True, help_text='员工id', null=True, verbose_name='员工id')), |
|||
('startDate', models.DateField(blank=True, help_text='起始年月# 2024-01', null=True, verbose_name='起始年月')), |
|||
('endDate', models.DateField(blank=True, help_text='终止年月 # 2024-01', null=True, verbose_name='终止年月')), |
|||
('amount', models.DecimalField(blank=True, decimal_places=2, help_text='金额', max_digits=18, null=True, verbose_name='金额')), |
|||
('type', models.IntegerField(blank=True, help_text='类型', null=True, verbose_name='类型')), |
|||
('amountType', models.IntegerField(blank=True, help_text='金额类型 # 用户自定义的奖金与福利类型id', null=True, verbose_name='金额类型id')), |
|||
('status', models.IntegerField(blank=True, default=0, help_text='状态 # 0:未审核 1:已审核保存 2:已添加凭证', null=True, verbose_name='状态')), |
|||
('remark', models.CharField(blank=True, help_text='备注信息', max_length=200, null=True, verbose_name='备注信息')), |
|||
('CreateBy', models.CharField(blank=True, max_length=36, null=True, verbose_name='创建人')), |
|||
('UpdateBy', models.CharField(blank=True, max_length=36, null=True, verbose_name='更新人')), |
|||
('CreateByUid', models.IntegerField(blank=True, null=True, verbose_name='创建人ID')), |
|||
('UpdateByUid', models.IntegerField(blank=True, null=True, verbose_name='更新人ID')), |
|||
('CreateDateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), |
|||
('UpdateDateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), |
|||
('companyMid', models.CharField(blank=True, max_length=36, null=True, verbose_name='公司全局id')), |
|||
], |
|||
options={ |
|||
'verbose_name': '奖金福利管理', |
|||
'verbose_name_plural': '奖金福利管理', |
|||
'ordering': ['id'], |
|||
}, |
|||
), |
|||
migrations.CreateModel( |
|||
name='Salary', |
|||
fields=[ |
|||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('staffId', models.IntegerField(blank=True, help_text='员工id', null=True, verbose_name='员工id')), |
|||
('salaryDate', models.DateField(blank=True, help_text='薪资记录年月 # 2024-01', null=True, verbose_name='薪资记录年月')), |
|||
('salaryAmount', models.DecimalField(blank=True, decimal_places=2, help_text='工资', max_digits=18, null=True, verbose_name='工资')), |
|||
('accumulation', models.DecimalField(blank=True, decimal_places=2, help_text='公积金', max_digits=18, null=True, verbose_name='公积金')), |
|||
('endowment', models.DecimalField(blank=True, decimal_places=2, help_text='养老', max_digits=18, null=True, verbose_name='养老')), |
|||
('medical', models.DecimalField(blank=True, decimal_places=2, help_text='医疗', max_digits=18, null=True, verbose_name='医疗')), |
|||
('unemployment', models.DecimalField(blank=True, decimal_places=2, help_text='失业', max_digits=18, null=True, verbose_name='失业')), |
|||
('workInjury', models.DecimalField(blank=True, decimal_places=2, help_text='工伤', max_digits=18, null=True, verbose_name='工伤')), |
|||
('maternity', models.DecimalField(blank=True, decimal_places=2, help_text='生育', max_digits=18, null=True, verbose_name='生育')), |
|||
('illness', models.DecimalField(blank=True, decimal_places=2, help_text='大病医疗', max_digits=18, null=True, verbose_name='大病医疗')), |
|||
('type', models.IntegerField(blank=True, help_text='1:工资SA(salary), 2:公积金AC(accumulation)', null=True, verbose_name='类型')), |
|||
('status', models.IntegerField(blank=True, default=0, help_text='状态 # 0:未审核 1:已审核保存 2:已添加凭证', null=True, verbose_name='状态')), |
|||
('CreateBy', models.CharField(blank=True, max_length=36, null=True, verbose_name='创建人')), |
|||
('UpdateBy', models.CharField(blank=True, max_length=36, null=True, verbose_name='更新人')), |
|||
('CreateByUid', models.IntegerField(blank=True, null=True, verbose_name='创建人ID')), |
|||
('UpdateByUid', models.IntegerField(blank=True, null=True, verbose_name='更新人ID')), |
|||
('CreateDateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), |
|||
('UpdateDateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), |
|||
('companyMid', models.CharField(blank=True, max_length=36, null=True, verbose_name='公司全局id')), |
|||
], |
|||
options={ |
|||
'verbose_name': '薪资记录', |
|||
'verbose_name_plural': '薪资记录', |
|||
'ordering': ['id'], |
|||
}, |
|||
), |
|||
migrations.CreateModel( |
|||
name='Staff', |
|||
fields=[ |
|||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('name', models.CharField(blank=True, help_text='姓名', max_length=20, null=True, verbose_name='姓名')), |
|||
('gender', models.IntegerField(blank=True, default=1, help_text='性别', null=True, verbose_name='性别')), |
|||
('idCode', models.CharField(blank=True, help_text='身份唯一识别码', max_length=200, null=True, verbose_name='身份唯一识别码')), |
|||
('category', models.IntegerField(blank=True, help_text='人员类别 # 1.研发人员 2.技术人员 3.辅助人员 4.非研发人员', null=True, verbose_name='人员类别')), |
|||
('dept', models.IntegerField(blank=True, help_text='所属部门id', null=True, verbose_name='所属部门id')), |
|||
('education', models.IntegerField(blank=True, help_text='学历 # 1.高中及以下 2.大专 3.本科 4.硕士 5.博士', null=True, verbose_name='学历')), |
|||
('employmentMethod', models.IntegerField(blank=True, help_text='聘用方式 # 1:正式 2:临时 3.兼职', null=True, verbose_name='聘用方式')), |
|||
('isActive', models.BooleanField(blank=True, default=True, help_text='用户是否可用 # 用户锁定与激活用户', null=True, verbose_name='用户是否可用')), |
|||
('duties', models.CharField(blank=True, help_text='职务', max_length=200, null=True, verbose_name='职务')), |
|||
('major', models.CharField(blank=True, help_text='专业', max_length=200, null=True, verbose_name='专业')), |
|||
('isAbroad', models.BooleanField(blank=True, default=False, null=True, verbose_name='是否海归')), |
|||
('isForeign', models.BooleanField(blank=True, default=False, null=True, verbose_name='是否外籍人员')), |
|||
('birthday', models.DateField(blank=True, help_text='出生日期', null=True, verbose_name='出生日期')), |
|||
('entryTime', models.DateField(blank=True, help_text='入职时间', null=True, verbose_name='入职时间')), |
|||
('leaveTime', models.DateField(blank=True, help_text='离职时间', null=True, verbose_name='离职时间')), |
|||
('changeTime', models.DateField(blank=True, help_text='变动时间', null=True, verbose_name='变动时间')), |
|||
('status', models.PositiveSmallIntegerField(blank=True, default=0, help_text='状态 # 0:未离职 1:离职', null=True, verbose_name='状态')), |
|||
('CreateBy', models.CharField(blank=True, max_length=36, null=True, verbose_name='创建人')), |
|||
('UpdateBy', models.CharField(blank=True, max_length=36, null=True, verbose_name='更新人')), |
|||
('CreateByUid', models.IntegerField(blank=True, null=True, verbose_name='创建人ID')), |
|||
('UpdateByUid', models.IntegerField(blank=True, null=True, verbose_name='更新人ID')), |
|||
('CreateDateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), |
|||
('UpdateDateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), |
|||
('companyMid', models.CharField(blank=True, max_length=36, null=True, verbose_name='公司全局id')), |
|||
], |
|||
options={ |
|||
'verbose_name': '员工信息', |
|||
'verbose_name_plural': '员工信息', |
|||
'ordering': ['id'], |
|||
}, |
|||
), |
|||
migrations.CreateModel( |
|||
name='Dept', |
|||
fields=[ |
|||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), |
|||
('name', models.CharField(blank=True, help_text='名称', max_length=20, null=True, verbose_name='名称')), |
|||
('is_rnd', models.BooleanField(blank=True, default=False, help_text='是否研发部门,默认否', null=True, verbose_name='是否研发部门,默认否')), |
|||
('CreateBy', models.CharField(blank=True, max_length=36, null=True, verbose_name='创建人')), |
|||
('UpdateBy', models.CharField(blank=True, max_length=36, null=True, verbose_name='更新人')), |
|||
('CreateByUid', models.IntegerField(blank=True, null=True, verbose_name='创建人ID')), |
|||
('UpdateByUid', models.IntegerField(blank=True, null=True, verbose_name='更新人ID')), |
|||
('CreateDateTime', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), |
|||
('UpdateDateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')), |
|||
('companyMid', models.CharField(blank=True, max_length=36, null=True, verbose_name='公司全局id')), |
|||
('pid', models.ForeignKey(blank=True, help_text='上级部门id', null=True, on_delete=django.db.models.deletion.SET_NULL, to='staff.dept', verbose_name='上级部门id')), |
|||
], |
|||
options={ |
|||
'verbose_name': '部门信息', |
|||
'verbose_name_plural': '部门信息', |
|||
'ordering': ['id'], |
|||
}, |
|||
), |
|||
] |
|||
@ -0,0 +1,650 @@ |
|||
from django.db import models |
|||
|
|||
|
|||
# 员工 |
|||
class Staff(models.Model): |
|||
""" |
|||
员工 |
|||
""" |
|||
|
|||
name = models.CharField(max_length=20, null=True, blank=True, verbose_name="姓名", help_text="姓名") |
|||
gender = models.IntegerField(null=True, blank=True, default=1, verbose_name="性别", help_text="性别") # 1:男 2:女 |
|||
idCode = models.CharField(max_length=200, null=True, blank=True, verbose_name="身份唯一识别码", |
|||
help_text="身份唯一识别码") |
|||
category = models.IntegerField(null=True, blank=True, verbose_name="人员类别", |
|||
help_text="人员类别 # 1.研发人员 2.技术人员 3.辅助人员 4.非研发人员") |
|||
dept = models.IntegerField(null=True, blank=True, verbose_name="所属部门id", help_text="所属部门id") |
|||
education = models.IntegerField(null=True, blank=True, verbose_name="学历", |
|||
help_text="学历 # 1.高中(中专)及以下 2.大专 3.本科 4.硕士 5.博士") |
|||
employmentMethod = models.IntegerField(null=True, blank=True, verbose_name="聘用方式", |
|||
help_text="聘用方式 # 1:正式 2:临时 3.兼职") |
|||
isActive = models.BooleanField(null=True, blank=True, default=True, verbose_name="用户是否可用", |
|||
help_text="用户是否可用 # 用户锁定与激活用户") |
|||
education = models.IntegerField(null=True, blank=True, verbose_name="学历", help_text="学历 # 1.高中(中专)及以下 2.大专 3.本科 4.硕士 5.博士") |
|||
employmentMethod = models.IntegerField(null=True, blank=True, verbose_name="聘用方式", help_text="聘用方式 # 1:正式 2:临时 3.兼职") |
|||
isActive = models.BooleanField(null=True, blank=True, default=True, verbose_name="用户是否可用", help_text="用户是否可用 # 用户锁定与激活用户") |
|||
duties = models.CharField(null=True, blank=True, max_length=200, verbose_name="职务", help_text="职务") |
|||
major = models.CharField(null=True, blank=True, max_length=200, verbose_name="专业", help_text="专业") |
|||
isAbroad = models.BooleanField(default=False, null=True, blank=True, verbose_name="是否海归") |
|||
isForeign = models.BooleanField(default=False, null=True, blank=True, verbose_name="是否外籍人员") |
|||
birthday = models.DateField(null=True, blank=True, verbose_name="出生日期", help_text="出生日期") |
|||
entryTime = models.DateField(null=True, blank=True, verbose_name="入职时间", help_text="入职时间") |
|||
leaveTime = models.DateField(null=True, blank=True, verbose_name="离职时间", help_text="离职时间") |
|||
changeTime = models.DateField(null=True, blank=True, verbose_name="变动时间", help_text="变动时间") |
|||
status = models.PositiveSmallIntegerField(null=True, blank=True, default=0, verbose_name="状态", |
|||
help_text="状态 # 0:未离职 1:离职") |
|||
# relCount = models.IntegerField(default=0, verbose_name="关联员工的数量,relCount>0时不可删除", help_text="关联员工的数量,relCount>0时不可删除") |
|||
|
|||
CreateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="创建人") |
|||
UpdateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="更新人") |
|||
CreateByUid = models.IntegerField(null=True, blank=True, verbose_name="创建人ID") |
|||
UpdateByUid = models.IntegerField(null=True, blank=True, verbose_name="更新人ID") |
|||
CreateDateTime = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") |
|||
UpdateDateTime = models.DateTimeField(auto_now=True, verbose_name="更新时间") |
|||
companyMid = models.CharField(null=True, blank=True, max_length=36, verbose_name="公司全局id") # 用于绑定公司 |
|||
|
|||
professionalCertificate = models.CharField(null=True, blank=True, max_length=200, verbose_name="专业证书") |
|||
personalHonor = models.CharField(null=True, blank=True, max_length=200, verbose_name="个人荣誉") |
|||
talentQualification = models.CharField(null=True, blank=True, max_length=10, verbose_name="人才资质", |
|||
help_text="1.国家高层次人才 2.地方高层次人才 3.区高层次人才") |
|||
graduationSchool = models.CharField(null=True, blank=True, max_length=200, verbose_name="毕业院校") |
|||
|
|||
class Meta: |
|||
verbose_name = "员工信息" |
|||
verbose_name_plural = verbose_name |
|||
ordering = ["id"] |
|||
|
|||
def __str__(self): |
|||
return self.name |
|||
|
|||
|
|||
class Dept(models.Model): |
|||
""" |
|||
部门 |
|||
""" |
|||
|
|||
name = models.CharField(max_length=20, null=True, blank=True, verbose_name="名称", help_text="名称") |
|||
# type = models.CharField(max_length=50, null=True, blank=True, verbose_name="类型", help_text="类型") |
|||
is_rnd = models.BooleanField(default=False, null=True, blank=True, verbose_name="是否研发部门,默认否", |
|||
help_text="是否研发部门,默认否") |
|||
pid = models.ForeignKey("self", null=True, blank=True, on_delete=models.SET_NULL, verbose_name="上级部门id", |
|||
help_text="上级部门id") |
|||
# relCount = models.IntegerField(default=0, verbose_name="关联部门的数量,relCount>0时不可删除", help_text="关联部门的数量,relCount>0时不可删除") |
|||
|
|||
CreateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="创建人") |
|||
UpdateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="更新人") |
|||
CreateByUid = models.IntegerField(null=True, blank=True, verbose_name="创建人ID") |
|||
UpdateByUid = models.IntegerField(null=True, blank=True, verbose_name="更新人ID") |
|||
CreateDateTime = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") |
|||
UpdateDateTime = models.DateTimeField(auto_now=True, verbose_name="更新时间") |
|||
companyMid = models.CharField(null=True, blank=True, max_length=36, verbose_name="公司全局id") # 用于绑定公司 |
|||
|
|||
class Meta: |
|||
verbose_name = "部门信息" |
|||
verbose_name_plural = verbose_name |
|||
ordering = ["id"] |
|||
|
|||
def __str__(self): |
|||
return self.name |
|||
|
|||
|
|||
# 考勤 |
|||
class Attendance(models.Model): |
|||
""" |
|||
考勤记录 |
|||
""" |
|||
|
|||
staffId = models.IntegerField(null=True, blank=True, verbose_name="被考勤人员id", help_text="被考勤人员id") |
|||
attendanceDate = models.DateField(null=True, blank=True, verbose_name="考勤年月", help_text="考勤年月 # 2024-01") |
|||
attendanceDays = models.FloatField(null=True, blank=True, verbose_name="月出勤天数", help_text="月出勤天数") |
|||
restDay = models.CharField(max_length=200, null=True, blank=True, verbose_name="休息日", help_text="休息日") |
|||
rndDay = models.FloatField(null=True, blank=True, verbose_name="研发天数", help_text="研发天数") |
|||
status = models.IntegerField(null=True, default=0, blank=True, verbose_name="状态", |
|||
help_text="状态 # 0:未审核 1:已审核保存 1+:已添加凭证(已记账)") |
|||
|
|||
CreateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="创建人") |
|||
UpdateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="更新人") |
|||
CreateByUid = models.IntegerField(null=True, blank=True, verbose_name="创建人ID") |
|||
UpdateByUid = models.IntegerField(null=True, blank=True, verbose_name="更新人ID") |
|||
CreateDateTime = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") |
|||
UpdateDateTime = models.DateTimeField(auto_now=True, verbose_name="更新时间") |
|||
companyMid = models.CharField(null=True, blank=True, max_length=36, verbose_name="公司全局id") # 用于绑定公司 |
|||
|
|||
class Meta: |
|||
verbose_name = "考勤记录" |
|||
verbose_name_plural = verbose_name |
|||
ordering = ["id"] |
|||
|
|||
def save(self, *args, **kwargs): |
|||
# 强制确保日期为每月的第一天 |
|||
if self.attendanceDate: |
|||
self.attendanceDate = self.attendanceDate.replace(day=1) |
|||
super().save(*args, **kwargs) |
|||
|
|||
|
|||
# 实发工资 |
|||
class Wages(models.Model): |
|||
""" |
|||
工资记录 费用编码 1.1 外聘劳务费 1.3 |
|||
""" |
|||
|
|||
staffId = models.IntegerField(null=True, blank=True, verbose_name="员工id", help_text="员工id") |
|||
date = models.DateField(null=True, blank=True, verbose_name="薪资入账年月", help_text="薪资入账年月 # 2024-01") |
|||
startDate = models.DateField(null=True, blank=True, verbose_name="薪资费用发生开始年月", help_text="薪资费用发生开始年月 # 2024-01") |
|||
endDate = models.DateField(null=True, blank=True, verbose_name="薪资费用发生结束年月", help_text="薪资费用发生结束年月 # 2024-01") |
|||
amount = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="工资", help_text="工资") |
|||
subjectId = models.IntegerField(null=True, blank=True, verbose_name="对应科目id", help_text="对应科目id") |
|||
status = models.IntegerField(null=True, default=0, blank=True, verbose_name="状态", help_text="状态 # 0:未审核 1:已审核保存 1+:已添加凭证") |
|||
attendanceStatus = models.BooleanField(default=0, verbose_name="考勤状态是否完成,0-未完成", help_text="考勤状态是否完成,0-未完成") |
|||
|
|||
CreateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="创建人") |
|||
UpdateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="更新人") |
|||
CreateByUid = models.IntegerField(null=True, blank=True, verbose_name="创建人ID") |
|||
UpdateByUid = models.IntegerField(null=True, blank=True, verbose_name="更新人ID") |
|||
CreateDateTime = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") |
|||
UpdateDateTime = models.DateTimeField(auto_now=True, verbose_name="更新时间") |
|||
companyMid = models.CharField(null=True, blank=True, max_length=36, verbose_name="公司全局id") # 用于绑定公司 |
|||
|
|||
class Meta: |
|||
db_table = "staff_wages" # 不可删除 |
|||
verbose_name = "工资记录" |
|||
verbose_name_plural = verbose_name |
|||
ordering = ["id"] |
|||
|
|||
def save(self, *args, **kwargs): |
|||
# 强制确保日期为每月的第一天 |
|||
if self.date: |
|||
self.date = self.date.replace(day=1) |
|||
super().save(*args, **kwargs) |
|||
|
|||
|
|||
# 实发社保 |
|||
class SocialInsurance(models.Model): |
|||
""" |
|||
社保记录 费用编码 1.2.1(多种社保,具体类型由用户自定义的社保科目类型决定) |
|||
""" |
|||
|
|||
staffId = models.IntegerField(null=True, blank=True, verbose_name="员工id", help_text="员工id") |
|||
date = models.DateField(null=True, blank=True, verbose_name="薪资入账年月", help_text="薪资入账年月 # 2024-01") |
|||
startDate = models.DateField(null=True, blank=True, verbose_name="薪资费用发生开始年月", |
|||
help_text="薪资费用发生开始年月 # 2024-01") |
|||
endDate = models.DateField(null=True, blank=True, verbose_name="薪资费用发生结束年月", |
|||
help_text="薪资费用发生结束年月 # 2024-01") |
|||
amount = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="金额", |
|||
help_text="金额") |
|||
subjectId = models.IntegerField(null=True, blank=True, verbose_name="对应科目id", help_text="对应科目id") |
|||
status = models.IntegerField(null=True, default=0, blank=True, verbose_name="状态", |
|||
help_text="状态 # 0:未审核 1:已审核保存 1+:已添加凭证") |
|||
attendanceStatus = models.BooleanField(default=0, verbose_name="考勤状态是否完成,0-未完成", |
|||
help_text="考勤状态是否完成,0-未完成") |
|||
# endowment = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="养老", help_text="养老") |
|||
# medical = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="医疗", help_text="医疗") |
|||
# unemployment = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="失业", help_text="失业") |
|||
# workInjury = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="工伤", help_text="工伤") |
|||
# maternity = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="生育", help_text="生育") |
|||
# illness = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="大病医疗", help_text="大病医疗") |
|||
|
|||
CreateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="创建人") |
|||
UpdateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="更新人") |
|||
CreateByUid = models.IntegerField(null=True, blank=True, verbose_name="创建人ID") |
|||
UpdateByUid = models.IntegerField(null=True, blank=True, verbose_name="更新人ID") |
|||
CreateDateTime = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") |
|||
UpdateDateTime = models.DateTimeField(auto_now=True, verbose_name="更新时间") |
|||
companyMid = models.CharField(null=True, blank=True, max_length=36, verbose_name="公司全局id") # 用于绑定公司 |
|||
|
|||
class Meta: |
|||
db_table = "staff_social_insurance" |
|||
verbose_name = "社保记录" |
|||
verbose_name_plural = verbose_name |
|||
ordering = ["id"] |
|||
|
|||
def save(self, *args, **kwargs): |
|||
# 强制确保日期为每月的第一天 |
|||
if self.date: |
|||
self.date = self.date.replace(day=1) |
|||
super().save(*args, **kwargs) |
|||
|
|||
|
|||
# 实发公积金 |
|||
class Accumulation(models.Model): |
|||
""" |
|||
公积金记录 费用编码 1.2.2 |
|||
""" |
|||
|
|||
staffId = models.IntegerField(null=True, blank=True, verbose_name="员工id", help_text="员工id") |
|||
date = models.DateField(null=True, blank=True, verbose_name="薪资入账年月", help_text="薪资入账年月 # 2024-01") |
|||
startDate = models.DateField(null=True, blank=True, verbose_name="薪资费用发生开始年月", |
|||
help_text="薪资费用发生开始年月 # 2024-01") |
|||
endDate = models.DateField(null=True, blank=True, verbose_name="薪资费用发生结束年月", |
|||
help_text="薪资费用发生结束年月 # 2024-01") |
|||
amount = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="金额", |
|||
help_text="金额") |
|||
subjectId = models.IntegerField(null=True, blank=True, verbose_name="对应科目id", help_text="对应科目id") |
|||
status = models.IntegerField(null=True, default=0, blank=True, verbose_name="状态", |
|||
help_text="状态 # 0:未审核 1:已审核保存 1+:已添加凭证") |
|||
attendanceStatus = models.BooleanField(default=0, verbose_name="考勤状态是否完成,0-未完成", |
|||
help_text="考勤状态是否完成,0-未完成") |
|||
# accumulation = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="公积金", help_text="公积金") |
|||
|
|||
CreateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="创建人") |
|||
UpdateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="更新人") |
|||
CreateByUid = models.IntegerField(null=True, blank=True, verbose_name="创建人ID") |
|||
UpdateByUid = models.IntegerField(null=True, blank=True, verbose_name="更新人ID") |
|||
CreateDateTime = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") |
|||
UpdateDateTime = models.DateTimeField(auto_now=True, verbose_name="更新时间") |
|||
companyMid = models.CharField(null=True, blank=True, max_length=36, verbose_name="公司全局id") # 用于绑定公司 |
|||
|
|||
class Meta: |
|||
db_table = "staff_accumulation" |
|||
verbose_name = "公积金记录" |
|||
verbose_name_plural = verbose_name |
|||
ordering = ["id"] |
|||
|
|||
def save(self, *args, **kwargs): |
|||
# 强制确保日期为每月的第一天 |
|||
if self.date: |
|||
self.date = self.date.replace(day=1) |
|||
super().save(*args, **kwargs) |
|||
|
|||
|
|||
# 实发奖金 |
|||
class Bonus(models.Model): |
|||
""" |
|||
奖金管理 费用编码 1.4 |
|||
""" |
|||
|
|||
staffId = models.IntegerField(null=True, blank=True, verbose_name="员工id", help_text="员工id") |
|||
date = models.DateField(null=True, blank=True, verbose_name="薪资入账年月", help_text="薪资入账年月 # 2024-01") |
|||
startDate = models.DateField(null=True, blank=True, verbose_name="薪资费用发生开始年月", |
|||
help_text="薪资费用发生开始年月 # 2024-01") |
|||
endDate = models.DateField(null=True, blank=True, verbose_name="薪资费用发生结束年月", |
|||
help_text="薪资费用发生结束年月 # 2024-01") |
|||
amount = models.DecimalField(max_digits=18, decimal_places=2, null=True, blank=True, verbose_name="金额", |
|||
help_text="金额") |
|||
subjectId = models.IntegerField(null=True, blank=True, verbose_name="对应科目id", help_text="对应科目id") |
|||
status = models.IntegerField(null=True, default=0, blank=True, verbose_name="状态", |
|||
help_text="状态 # 0:未审核 1:已审核保存 1+:已添加凭证") |
|||
attendanceStatus = models.BooleanField(default=0, verbose_name="考勤状态是否完成,0-未完成", |
|||
help_text="考勤状态是否完成,0-未完成") |
|||
remark = models.CharField(max_length=200, null=True, blank=True, verbose_name="备注信息", help_text="备注信息") |
|||
|
|||
CreateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="创建人") |
|||
UpdateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="更新人") |
|||
CreateByUid = models.IntegerField(null=True, blank=True, verbose_name="创建人ID") |
|||
UpdateByUid = models.IntegerField(null=True, blank=True, verbose_name="更新人ID") |
|||
CreateDateTime = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") |
|||
UpdateDateTime = models.DateTimeField(auto_now=True, verbose_name="更新时间") |
|||
companyMid = models.CharField(null=True, blank=True, max_length=36, verbose_name="公司全局id") # 用于绑定公司 |
|||
|
|||
class Meta: |
|||
db_table = "staff_bonus" |
|||
verbose_name = "奖金管理" |
|||
verbose_name_plural = verbose_name |
|||
ordering = ["id"] |
|||
|
|||
def save(self, *args, **kwargs): |
|||
# 强制确保日期为每月的第一天 |
|||
if self.date: |
|||
self.date = self.date.replace(day=1) |
|||
super().save(*args, **kwargs) |
|||
|
|||
|
|||
# 实发福利 |
|||
class Welfare(models.Model): |
|||
""" |
|||
福利管理 费用编码 7.9.3 |
|||
""" |
|||
|
|||
staffId = models.IntegerField(null=True, blank=True, verbose_name="员工id", help_text="员工id") |
|||
date = models.DateField(null=True, blank=True, verbose_name="薪资入账年月", help_text="薪资入账年月 # 2024-01") |
|||
startDate = models.DateField(null=True, blank=True, verbose_name="薪资费用发生开始年月", |
|||
help_text="薪资费用发生开始年月 # 2024-01") |
|||
endDate = models.DateField(null=True, blank=True, verbose_name="薪资费用发生结束年月", |
|||
help_text="薪资费用发生结束年月 # 2024-01") |
|||
amount = models.DecimalField(max_digits=18, decimal_places=2, null=True, blank=True, verbose_name="金额", |
|||
help_text="金额") |
|||
subjectId = models.IntegerField(null=True, blank=True, verbose_name="对应科目id", help_text="对应科目id") |
|||
status = models.IntegerField(null=True, default=0, blank=True, verbose_name="状态", |
|||
help_text="状态 # 0:未审核 1:已审核保存 1+:已添加凭证") |
|||
attendanceStatus = models.BooleanField(default=0, verbose_name="考勤状态是否完成,0-未完成", |
|||
help_text="考勤状态是否完成,0-未完成") |
|||
remark = models.CharField(max_length=200, null=True, blank=True, verbose_name="备注信息", help_text="备注信息") |
|||
|
|||
CreateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="创建人") |
|||
UpdateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="更新人") |
|||
CreateByUid = models.IntegerField(null=True, blank=True, verbose_name="创建人ID") |
|||
UpdateByUid = models.IntegerField(null=True, blank=True, verbose_name="更新人ID") |
|||
CreateDateTime = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") |
|||
UpdateDateTime = models.DateTimeField(auto_now=True, verbose_name="更新时间") |
|||
companyMid = models.CharField(null=True, blank=True, max_length=36, verbose_name="公司全局id") # 用于绑定公司 |
|||
|
|||
class Meta: |
|||
db_table = "staff_welfare" |
|||
verbose_name = "福利管理" |
|||
verbose_name_plural = verbose_name |
|||
ordering = ["id"] |
|||
|
|||
def save(self, *args, **kwargs): |
|||
# 强制确保日期为每月的第一天 |
|||
if self.date: |
|||
self.date = self.date.replace(day=1) |
|||
super().save(*args, **kwargs) |
|||
|
|||
|
|||
# |
|||
# 以下为预提管理的model |
|||
# |
|||
|
|||
# 预提工资 |
|||
class AccruedWages(models.Model): |
|||
""" |
|||
预提工资记录 费用编码 1.1 外聘劳务费 1.3 |
|||
""" |
|||
|
|||
staffId = models.IntegerField(null=True, blank=True, verbose_name="员工id", help_text="员工id") |
|||
date = models.DateField(null=True, blank=True, verbose_name="薪资入账年月", help_text="薪资入账年月 # 2024-01") |
|||
startDate = models.DateField(null=True, blank=True, verbose_name="薪资费用发生开始年月", |
|||
help_text="薪资费用发生开始年月 # 2024-01") |
|||
endDate = models.DateField(null=True, blank=True, verbose_name="薪资费用发生结束年月", |
|||
help_text="薪资费用发生结束年月 # 2024-01") |
|||
amount = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="工资", |
|||
help_text="工资") |
|||
subjectId = models.IntegerField(null=True, blank=True, verbose_name="对应科目id", help_text="对应科目id") |
|||
status = models.IntegerField(null=True, default=0, blank=True, verbose_name="状态", |
|||
help_text="状态 # 0:未审核 1:已审核保存 1+:已添加凭证") |
|||
attendanceStatus = models.BooleanField(default=0, verbose_name="考勤状态是否完成,0-未完成", |
|||
help_text="考勤状态是否完成,0-未完成") |
|||
|
|||
CreateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="创建人") |
|||
UpdateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="更新人") |
|||
CreateByUid = models.IntegerField(null=True, blank=True, verbose_name="创建人ID") |
|||
UpdateByUid = models.IntegerField(null=True, blank=True, verbose_name="更新人ID") |
|||
CreateDateTime = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") |
|||
UpdateDateTime = models.DateTimeField(auto_now=True, verbose_name="更新时间") |
|||
companyMid = models.CharField(null=True, blank=True, max_length=36, verbose_name="公司全局id") # 用于绑定公司 |
|||
|
|||
class Meta: |
|||
db_table = "staff_accrued_wages" |
|||
verbose_name = "工资记录" |
|||
verbose_name_plural = verbose_name |
|||
ordering = ["id"] |
|||
|
|||
def save(self, *args, **kwargs): |
|||
# 强制确保日期为每月的第一天 |
|||
if self.date: |
|||
self.date = self.date.replace(day=1) |
|||
super().save(*args, **kwargs) |
|||
|
|||
|
|||
# 预提社保 |
|||
class AccruedSocialInsurance(models.Model): |
|||
""" |
|||
预提社保记录 费用编码 1.2.1(多种社保,具体类型由用户自定义的社保科目类型决定) |
|||
""" |
|||
|
|||
staffId = models.IntegerField(null=True, blank=True, verbose_name="员工id", help_text="员工id") |
|||
date = models.DateField(null=True, blank=True, verbose_name="薪资入账年月", help_text="薪资入账年月 # 2024-01") |
|||
startDate = models.DateField(null=True, blank=True, verbose_name="薪资费用发生开始年月", |
|||
help_text="薪资费用发生开始年月 # 2024-01") |
|||
endDate = models.DateField(null=True, blank=True, verbose_name="薪资费用发生结束年月", |
|||
help_text="薪资费用发生结束年月 # 2024-01") |
|||
amount = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="金额", |
|||
help_text="金额") |
|||
subjectId = models.IntegerField(null=True, blank=True, verbose_name="对应科目id", help_text="对应科目id") |
|||
status = models.IntegerField(null=True, default=0, blank=True, verbose_name="状态", |
|||
help_text="状态 # 0:未审核 1:已审核保存 1+:已添加凭证") |
|||
attendanceStatus = models.BooleanField(default=0, verbose_name="考勤状态是否完成,0-未完成", |
|||
help_text="考勤状态是否完成,0-未完成") |
|||
# endowment = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="养老", help_text="养老") |
|||
# medical = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="医疗", help_text="医疗") |
|||
# unemployment = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="失业", help_text="失业") |
|||
# workInjury = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="工伤", help_text="工伤") |
|||
# maternity = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="生育", help_text="生育") |
|||
# illness = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="大病医疗", help_text="大病医疗") |
|||
|
|||
CreateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="创建人") |
|||
UpdateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="更新人") |
|||
CreateByUid = models.IntegerField(null=True, blank=True, verbose_name="创建人ID") |
|||
UpdateByUid = models.IntegerField(null=True, blank=True, verbose_name="更新人ID") |
|||
CreateDateTime = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") |
|||
UpdateDateTime = models.DateTimeField(auto_now=True, verbose_name="更新时间") |
|||
companyMid = models.CharField(null=True, blank=True, max_length=36, verbose_name="公司全局id") # 用于绑定公司 |
|||
|
|||
class Meta: |
|||
db_table = "staff_accrued_social_insurance" |
|||
verbose_name = "社保记录" |
|||
verbose_name_plural = verbose_name |
|||
ordering = ["id"] |
|||
|
|||
def save(self, *args, **kwargs): |
|||
# 强制确保日期为每月的第一天 |
|||
if self.date: |
|||
self.date = self.date.replace(day=1) |
|||
super().save(*args, **kwargs) |
|||
|
|||
|
|||
# 预提公积金 |
|||
class AccruedAccumulation(models.Model): |
|||
""" |
|||
预提公积金记录 费用编码 1.2.2 |
|||
""" |
|||
|
|||
staffId = models.IntegerField(null=True, blank=True, verbose_name="员工id", help_text="员工id") |
|||
date = models.DateField(null=True, blank=True, verbose_name="薪资入账年月", help_text="薪资入账年月 # 2024-01") |
|||
startDate = models.DateField(null=True, blank=True, verbose_name="薪资费用发生开始年月", |
|||
help_text="薪资费用发生开始年月 # 2024-01") |
|||
endDate = models.DateField(null=True, blank=True, verbose_name="薪资费用发生结束年月", |
|||
help_text="薪资费用发生结束年月 # 2024-01") |
|||
amount = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="金额", |
|||
help_text="金额") |
|||
subjectId = models.IntegerField(null=True, blank=True, verbose_name="对应科目id", help_text="对应科目id") |
|||
status = models.IntegerField(null=True, default=0, blank=True, verbose_name="状态", |
|||
help_text="状态 # 0:未审核 1:已审核保存 1+:已添加凭证") |
|||
attendanceStatus = models.BooleanField(default=0, verbose_name="考勤状态是否完成,0-未完成", |
|||
help_text="考勤状态是否完成,0-未完成") |
|||
# accumulation = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="公积金", help_text="公积金") |
|||
|
|||
CreateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="创建人") |
|||
UpdateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="更新人") |
|||
CreateByUid = models.IntegerField(null=True, blank=True, verbose_name="创建人ID") |
|||
UpdateByUid = models.IntegerField(null=True, blank=True, verbose_name="更新人ID") |
|||
CreateDateTime = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") |
|||
UpdateDateTime = models.DateTimeField(auto_now=True, verbose_name="更新时间") |
|||
companyMid = models.CharField(null=True, blank=True, max_length=36, verbose_name="公司全局id") # 用于绑定公司 |
|||
|
|||
class Meta: |
|||
db_table = "staff_accrued_accumulation" |
|||
verbose_name = "公积金记录" |
|||
verbose_name_plural = verbose_name |
|||
ordering = ["id"] |
|||
|
|||
def save(self, *args, **kwargs): |
|||
# 强制确保日期为每月的第一天 |
|||
if self.date: |
|||
self.date = self.date.replace(day=1) |
|||
super().save(*args, **kwargs) |
|||
|
|||
|
|||
# 预提奖金 |
|||
class AccruedBonus(models.Model): |
|||
""" |
|||
预提奖金管理 费用编码 1.4 |
|||
""" |
|||
|
|||
staffId = models.IntegerField(null=True, blank=True, verbose_name="员工id", help_text="员工id") |
|||
date = models.DateField(null=True, blank=True, verbose_name="薪资入账年月", help_text="薪资入账年月 # 2024-01") |
|||
startDate = models.DateField(null=True, blank=True, verbose_name="薪资费用发生开始年月", |
|||
help_text="薪资费用发生开始年月 # 2024-01") |
|||
endDate = models.DateField(null=True, blank=True, verbose_name="薪资费用发生结束年月", |
|||
help_text="薪资费用发生结束年月 # 2024-01") |
|||
amount = models.DecimalField(max_digits=18, decimal_places=2, null=True, blank=True, verbose_name="金额", |
|||
help_text="金额") |
|||
subjectId = models.IntegerField(null=True, blank=True, verbose_name="对应科目id", help_text="对应科目id") |
|||
status = models.IntegerField(null=True, default=0, blank=True, verbose_name="状态", |
|||
help_text="状态 # 0:未审核 1:已审核保存 1+:已添加凭证") |
|||
attendanceStatus = models.BooleanField(default=0, verbose_name="考勤状态是否完成,0-未完成", |
|||
help_text="考勤状态是否完成,0-未完成") |
|||
remark = models.CharField(max_length=200, null=True, blank=True, verbose_name="备注信息", help_text="备注信息") |
|||
|
|||
CreateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="创建人") |
|||
UpdateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="更新人") |
|||
CreateByUid = models.IntegerField(null=True, blank=True, verbose_name="创建人ID") |
|||
UpdateByUid = models.IntegerField(null=True, blank=True, verbose_name="更新人ID") |
|||
CreateDateTime = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") |
|||
UpdateDateTime = models.DateTimeField(auto_now=True, verbose_name="更新时间") |
|||
companyMid = models.CharField(null=True, blank=True, max_length=36, verbose_name="公司全局id") # 用于绑定公司 |
|||
|
|||
class Meta: |
|||
db_table = "staff_accrued_bonus" |
|||
verbose_name = "奖金管理" |
|||
verbose_name_plural = verbose_name |
|||
ordering = ["id"] |
|||
|
|||
def save(self, *args, **kwargs): |
|||
# 强制确保日期为每月的第一天 |
|||
if self.date: |
|||
self.date = self.date.replace(day=1) |
|||
super().save(*args, **kwargs) |
|||
|
|||
|
|||
# 预提福利 |
|||
class AccruedWelfare(models.Model): |
|||
""" |
|||
预提福利管理 费用编码 7.9.3 |
|||
""" |
|||
|
|||
staffId = models.IntegerField(null=True, blank=True, verbose_name="员工id", help_text="员工id") |
|||
date = models.DateField(null=True, blank=True, verbose_name="薪资入账年月", help_text="薪资入账年月 # 2024-01") |
|||
startDate = models.DateField(null=True, blank=True, verbose_name="薪资费用发生开始年月", |
|||
help_text="薪资费用发生开始年月 # 2024-01") |
|||
endDate = models.DateField(null=True, blank=True, verbose_name="薪资费用发生结束年月", |
|||
help_text="薪资费用发生结束年月 # 2024-01") |
|||
amount = models.DecimalField(max_digits=18, decimal_places=2, null=True, blank=True, verbose_name="金额", |
|||
help_text="金额") |
|||
subjectId = models.IntegerField(null=True, blank=True, verbose_name="对应科目id", help_text="对应科目id") |
|||
status = models.IntegerField(null=True, default=0, blank=True, verbose_name="状态", |
|||
help_text="状态 # 0:未审核 1:已审核保存 1+:已添加凭证") |
|||
attendanceStatus = models.BooleanField(default=0, verbose_name="考勤状态是否完成,0-未完成", |
|||
help_text="考勤状态是否完成,0-未完成") |
|||
remark = models.CharField(max_length=200, null=True, blank=True, verbose_name="备注信息", help_text="备注信息") |
|||
|
|||
CreateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="创建人") |
|||
UpdateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="更新人") |
|||
CreateByUid = models.IntegerField(null=True, blank=True, verbose_name="创建人ID") |
|||
UpdateByUid = models.IntegerField(null=True, blank=True, verbose_name="更新人ID") |
|||
CreateDateTime = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") |
|||
UpdateDateTime = models.DateTimeField(auto_now=True, verbose_name="更新时间") |
|||
companyMid = models.CharField(null=True, blank=True, max_length=36, verbose_name="公司全局id") # 用于绑定公司 |
|||
|
|||
class Meta: |
|||
db_table = "staff_accrued_welfare" |
|||
verbose_name = "福利管理" |
|||
verbose_name_plural = verbose_name |
|||
ordering = ["id"] |
|||
|
|||
def save(self, *args, **kwargs): |
|||
# 强制确保日期为每月的第一天 |
|||
if self.date: |
|||
self.date = self.date.replace(day=1) |
|||
super().save(*args, **kwargs) |
|||
|
|||
|
|||
|
|||
class Salary(models.Model): |
|||
""" |
|||
薪资记录(工资,公积金) |
|||
""" |
|||
|
|||
staffId = models.IntegerField(null=True, blank=True, verbose_name="员工id", help_text="员工id") |
|||
salaryDate = models.DateField(null=True, blank=True, verbose_name="薪资记录年月", help_text="薪资记录年月 # 2024-01") |
|||
salaryAmount = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="工资", help_text="工资") |
|||
accumulation = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="公积金", help_text="公积金") |
|||
endowment = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="养老", help_text="养老") |
|||
medical = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="医疗", help_text="医疗") |
|||
unemployment = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="失业", help_text="失业") |
|||
workInjury = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="工伤", help_text="工伤") |
|||
maternity = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="生育", help_text="生育") |
|||
illness = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="大病医疗", help_text="大病医疗") |
|||
# type = models.IntegerField(null=True, blank=True, verbose_name="类型", help_text="1:工资SA(salary), 2:公积金AC(accumulation)") |
|||
status = models.IntegerField(null=True, default=0, blank=True, verbose_name="状态", help_text="状态 # 0:未审核 1:已审核保存 1+:已添加凭证") |
|||
attendanceStatus = models.BooleanField(default=0, verbose_name="考勤状态是否完成,0-未完成", help_text="考勤状态是否完成,0-未完成") |
|||
|
|||
CreateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="创建人") |
|||
UpdateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="更新人") |
|||
CreateByUid = models.IntegerField(null=True, blank=True, verbose_name="创建人ID") |
|||
UpdateByUid = models.IntegerField(null=True, blank=True, verbose_name="更新人ID") |
|||
CreateDateTime = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") |
|||
UpdateDateTime = models.DateTimeField(auto_now=True, verbose_name="更新时间") |
|||
companyMid = models.CharField(null=True, blank=True, max_length=36, verbose_name="公司全局id") # 用于绑定公司 |
|||
|
|||
class Meta: |
|||
verbose_name = "薪资记录" |
|||
verbose_name_plural = verbose_name |
|||
ordering = ["id"] |
|||
|
|||
|
|||
class Accrued(models.Model): |
|||
""" |
|||
预提记录(工资,奖金,福利) |
|||
""" |
|||
|
|||
staffId = models.IntegerField(null=True, blank=True, verbose_name="员工id", help_text="员工id") |
|||
accruedDate = models.DateField(null=True, blank=True, verbose_name="预提年月", help_text="预提年月 # 2024-01") |
|||
accruedEndDate = models.DateField(null=True, blank=True, verbose_name="预提终止年月", help_text="预提终止年月 # 2024-01") |
|||
accruedAmount = models.DecimalField(max_digits=18, decimal_places=2, null=True, blank=True, verbose_name="预提金额", help_text="预提金额") |
|||
accruedType = models.IntegerField(null=True, blank=True, verbose_name="预提类型", help_text="预提类型 # (1:工资&社保公积金,2:奖金,3:福利)") |
|||
amountType = models.IntegerField(null=True, blank=True, verbose_name="预提金额类型id", help_text="预提金额类型id # 用户自定义的奖金与福利类型") |
|||
attendanceStatus = models.BooleanField(default=0, verbose_name="考勤状态是否完成,0-未完成", help_text="考勤状态是否完成,0-未完成") |
|||
status = models.IntegerField(null=True, default=0, blank=True, verbose_name="状态", help_text="状态 # 0:未审核 1:已审核保存 2:已添加凭证") |
|||
remark = models.CharField(max_length=200, null=True, blank=True, verbose_name="备注信息", help_text="备注信息") |
|||
accumulation = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="公积金", help_text="公积金") |
|||
endowment = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="养老", help_text="养老") |
|||
medical = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="医疗", help_text="医疗") |
|||
unemployment = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="失业", help_text="失业") |
|||
workInjury = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="工伤", help_text="工伤") |
|||
maternity = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="生育", help_text="生育") |
|||
illness = models.DecimalField(max_digits=18, decimal_places=2, default=0, blank=True, verbose_name="大病医疗", help_text="大病医疗") |
|||
|
|||
|
|||
CreateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="创建人") |
|||
UpdateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="更新人") |
|||
CreateByUid = models.IntegerField(null=True, blank=True, verbose_name="创建人ID") |
|||
UpdateByUid = models.IntegerField(null=True, blank=True, verbose_name="更新人ID") |
|||
CreateDateTime = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") |
|||
UpdateDateTime = models.DateTimeField(auto_now=True, verbose_name="更新时间") |
|||
companyMid = models.CharField(null=True, blank=True, max_length=36, verbose_name="公司全局id") # 用于绑定公司 |
|||
|
|||
class Meta: |
|||
verbose_name = "预提记录" |
|||
verbose_name_plural = verbose_name |
|||
ordering = ["id"] |
|||
|
|||
|
|||
class Reward(models.Model): |
|||
""" |
|||
奖金福利管理 |
|||
""" |
|||
|
|||
staffId = models.IntegerField(null=True, blank=True, verbose_name="员工id", help_text="员工id") |
|||
startDate = models.DateField(null=True, blank=True, verbose_name="起始年月", help_text="起始年月# 2024-01") |
|||
endDate = models.DateField(null=True, blank=True, verbose_name="终止年月", help_text="终止年月 # 2024-01") # 2024-01 |
|||
amount = models.DecimalField(max_digits=18, decimal_places=2, null=True, blank=True, verbose_name="金额", help_text="金额") |
|||
type = models.IntegerField(null=True, blank=True, verbose_name="类型", help_text="类型") # 1:奖金 2:福利 |
|||
amountType = models.IntegerField(null=True, blank=True, verbose_name="金额类型id", help_text="金额类型 # 用户自定义的奖金与福利类型id") |
|||
attendanceStatus = models.BooleanField(default=False, verbose_name="是否完成对应时间的考勤,0-未完成", help_text="是否完成对应时间的考勤,0-未完成") |
|||
status = models.IntegerField(null=True, default=0, blank=True, verbose_name="状态", help_text="状态 # 0:未审核 1:已审核保存 2:已添加凭证") |
|||
remark = models.CharField(max_length=200, null=True, blank=True, verbose_name="备注信息", help_text="备注信息") |
|||
|
|||
CreateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="创建人") |
|||
UpdateBy = models.CharField(max_length=36, null=True, blank=True, verbose_name="更新人") |
|||
CreateByUid = models.IntegerField(null=True, blank=True, verbose_name="创建人ID") |
|||
UpdateByUid = models.IntegerField(null=True, blank=True, verbose_name="更新人ID") |
|||
CreateDateTime = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") |
|||
UpdateDateTime = models.DateTimeField(auto_now=True, verbose_name="更新时间") |
|||
companyMid = models.CharField(null=True, blank=True, max_length=36, verbose_name="公司全局id") # 用于绑定公司 |
|||
|
|||
class Meta: |
|||
verbose_name = "奖金福利管理" |
|||
verbose_name_plural = verbose_name |
|||
ordering = ["id"] |
|||
|
|||
@ -0,0 +1,259 @@ |
|||
from rest_framework import serializers |
|||
|
|||
from staff.models import * |
|||
from staff.utils import vaild_attendance |
|||
from utils.custom import MyCustomError |
|||
|
|||
|
|||
class DeptSerializer(serializers.ModelSerializer): |
|||
""" |
|||
部门序列化 |
|||
""" |
|||
|
|||
class Meta: |
|||
model = Dept |
|||
fields = "__all__" |
|||
|
|||
def validate_name(self, value): |
|||
# 当修改与创建时,检查idCode是否已存在 |
|||
if self.instance: # Update case |
|||
existing_dept = Dept.objects.exclude(id=self.instance.id).filter(name=value, companyMid=self.initial_data.get('companyMid')) |
|||
else: # Create case |
|||
existing_dept = Dept.objects.filter(name=value, companyMid=self.initial_data.get('companyMid')) |
|||
|
|||
if existing_dept.exists(): |
|||
raise MyCustomError("部门已存在") |
|||
|
|||
return value |
|||
|
|||
|
|||
class DeptModifySerializer(serializers.ModelSerializer): |
|||
""" |
|||
部门修改序列化 |
|||
""" |
|||
|
|||
class Meta: |
|||
model = Dept |
|||
fields = "__all__" |
|||
|
|||
def validate_name(self, value): |
|||
# 当修改与创建时,检查idCode是否已存在 |
|||
if self.instance: # Update case |
|||
existing_dept = Dept.objects.exclude(id=self.instance.id).filter(name=value, companyMid=self.initial_data.get('companyMid')) |
|||
else: # Create case |
|||
existing_dept = Dept.objects.filter(name=value, companyMid=self.initial_data.get('companyMid')) |
|||
|
|||
if existing_dept.exists(): |
|||
raise MyCustomError("部门已存在") |
|||
|
|||
return value |
|||
|
|||
def validate(self, data): |
|||
pid = data.get('pid') |
|||
id = self.instance if self.instance else None |
|||
|
|||
if pid == id: |
|||
raise serializers.ValidationError("pid字段不能等于id字段") |
|||
|
|||
return data |
|||
|
|||
|
|||
class AttendanceSerializer(serializers.ModelSerializer): |
|||
""" |
|||
考勤序列化 |
|||
""" |
|||
|
|||
class Meta: |
|||
model = Attendance |
|||
fields = "__all__" |
|||
|
|||
def validate(self, data): |
|||
attendance = self.Meta.model(**data) or self.instance |
|||
staffTime = Staff.objects.filter(id=attendance.staffId if attendance.staffId else self.instance.staffId).values( |
|||
'id', 'entryTime', 'leaveTime').first() |
|||
if not staffTime: |
|||
raise MyCustomError("员工不存在") |
|||
try: |
|||
vaild_attendance(attendance, entryTime=staffTime['entryTime'], leaveTime=staffTime['leaveTime']) |
|||
data['restDay'] = attendance.restDay |
|||
except Exception as e: |
|||
raise e |
|||
return data |
|||
|
|||
|
|||
class WagesSerializer(serializers.ModelSerializer): |
|||
""" |
|||
工资记录序列化 |
|||
""" |
|||
|
|||
class Meta: |
|||
model = Wages |
|||
fields = "__all__" |
|||
|
|||
|
|||
class SocialInsuranceSerializer(serializers.ModelSerializer): |
|||
""" |
|||
社保记录序列化 |
|||
""" |
|||
|
|||
class Meta: |
|||
model = SocialInsurance |
|||
fields = "__all__" |
|||
|
|||
|
|||
class AccumulationSerializer(serializers.ModelSerializer): |
|||
""" |
|||
公积金记录序列化 |
|||
""" |
|||
|
|||
class Meta: |
|||
model = Accumulation |
|||
fields = "__all__" |
|||
|
|||
|
|||
class BonusSerializer(serializers.ModelSerializer): |
|||
""" |
|||
奖金记录序列化 |
|||
""" |
|||
|
|||
class Meta: |
|||
model = Accumulation |
|||
fields = "__all__" |
|||
|
|||
|
|||
class WelfareSerializer(serializers.ModelSerializer): |
|||
""" |
|||
福利记录序列化 |
|||
""" |
|||
|
|||
class Meta: |
|||
model = Welfare |
|||
fields = "__all__" |
|||
|
|||
|
|||
# 预提管理序列化 |
|||
|
|||
class AccruedWagesSerializer(serializers.ModelSerializer): |
|||
""" |
|||
预提工资记录序列化 |
|||
""" |
|||
|
|||
class Meta: |
|||
model = AccruedWages |
|||
fields = "__all__" |
|||
|
|||
|
|||
class AccruedSocialInsuranceSerializer(serializers.ModelSerializer): |
|||
""" |
|||
预提社保记录序列化 |
|||
""" |
|||
|
|||
class Meta: |
|||
model = AccruedSocialInsurance |
|||
fields = "__all__" |
|||
|
|||
|
|||
class AccruedAccumulationSerializer(serializers.ModelSerializer): |
|||
""" |
|||
预提公积金序列化 |
|||
""" |
|||
|
|||
class Meta: |
|||
model = AccruedAccumulation |
|||
fields = "__all__" |
|||
|
|||
|
|||
class AccruedBonusSerializer(serializers.ModelSerializer): |
|||
""" |
|||
预提奖金序列化 |
|||
""" |
|||
|
|||
class Meta: |
|||
model = AccruedBonus |
|||
fields = "__all__" |
|||
|
|||
|
|||
class AccruedWelfareSerializer(serializers.ModelSerializer): |
|||
""" |
|||
预提福利序列化 |
|||
""" |
|||
|
|||
class Meta: |
|||
model = AccruedWelfare |
|||
fields = "__all__" |
|||
|
|||
|
|||
|
|||
class SalarySerializer(serializers.ModelSerializer): |
|||
""" |
|||
薪资记录序列化 |
|||
""" |
|||
|
|||
class Meta: |
|||
model = Salary |
|||
fields = "__all__" |
|||
|
|||
def validate_salaryDate(self, value): |
|||
# 从context中获取companyMid |
|||
# companyMid = self.context.get('companyMid') |
|||
# 当修改与创建时,检查idCode是否已存在 |
|||
Year, Month = value.year, value.month |
|||
if self.instance: # Update case |
|||
# existing_staff = Salary.objects.exclude(id=self.instance.id).filter(salaryDate__year=Year, salaryDate__month=Month, type=self.instance.type, staffId=self.initial_data.get('staffId'), companyMid=self.initial_data.get('companyMid')) |
|||
existing_staff = Salary.objects.exclude(id=self.instance.id).filter(salaryDate__year=Year, salaryDate__month=Month, staffId=self.initial_data.get('staffId'), companyMid=self.initial_data.get('companyMid')) |
|||
else: # Create case |
|||
# existing_staff = Salary.objects.filter(salaryDate__year=Year, salaryDate__month=Month, type=self.initial_data.get('type'), staffId=self.initial_data.get('staffId'), companyMid=self.initial_data.get('companyMid')) |
|||
existing_staff = Salary.objects.filter(salaryDate__year=Year, salaryDate__month=Month, staffId=self.initial_data.get('staffId'), companyMid=self.initial_data.get('companyMid')) |
|||
|
|||
if existing_staff.exists(): |
|||
raise MyCustomError("该员工该月已存在薪资记录") |
|||
|
|||
return value |
|||
|
|||
|
|||
class AccruedSerializer(serializers.ModelSerializer): |
|||
""" |
|||
预提记录序列化 |
|||
""" |
|||
|
|||
class Meta: |
|||
model = Accrued |
|||
fields = "__all__" |
|||
|
|||
def validate_accruedDate(self, value): |
|||
# 从context中获取companyMid |
|||
# companyMid = self.context.get('companyMid') |
|||
# 当修改与创建时,检查idCode是否已存在 |
|||
Year, Month = value.year, value.month |
|||
existing_accrued = None |
|||
if self.instance: # Update case |
|||
if 1 == self.initial_data.get('accruedType'): # 仅类型-工资社保公积金需要限制每月一条记录 |
|||
existing_accrued = Accrued.objects.exclude(id=self.instance.id).filter(accruedDate__year=Year, |
|||
accruedDate__month=Month, |
|||
accruedType=self.initial_data.get('accruedType'), |
|||
staffId=self.initial_data.get( |
|||
'staffId'), |
|||
companyMid=self.initial_data.get( |
|||
'companyMid')) |
|||
else: # Create case |
|||
if 1 == self.initial_data.get('accruedType'): |
|||
existing_accrued = Accrued.objects.filter(accruedDate__year=Year, accruedDate__month=Month, |
|||
accruedType=self.initial_data.get('accruedType'), |
|||
staffId=self.initial_data.get('staffId'), |
|||
companyMid=self.initial_data.get('companyMid')) |
|||
|
|||
if existing_accrued and existing_accrued.exists(): |
|||
raise MyCustomError("该员工该月已存在预提薪资记录") |
|||
|
|||
return value |
|||
|
|||
|
|||
class RewardSerializer(serializers.ModelSerializer): |
|||
""" |
|||
奖金福利序列化 |
|||
""" |
|||
|
|||
class Meta: |
|||
model = Reward |
|||
fields = "__all__" |
|||
|
|||
@ -0,0 +1,28 @@ |
|||
from rest_framework import serializers |
|||
|
|||
from staff.models import Staff |
|||
from utils.custom import MyCustomError |
|||
|
|||
|
|||
class StaffSerializer(serializers.ModelSerializer): |
|||
""" |
|||
员工序列化 |
|||
""" |
|||
|
|||
class Meta: |
|||
model = Staff |
|||
fields = "__all__" |
|||
|
|||
def validate_idCode(self, value): |
|||
# 从context中获取companyMid |
|||
# companyMid = self.context.get('companyMid') |
|||
# 当修改与创建时,检查idCode是否已存在 |
|||
if self.instance: # Update case |
|||
existing_staff = Staff.objects.exclude(id=self.instance.id).filter(idCode=value, companyMid=self.initial_data.get('companyMid')) |
|||
else: # Create case |
|||
existing_staff = Staff.objects.filter(idCode=value, companyMid=self.initial_data.get('companyMid')) |
|||
|
|||
if existing_staff.exists(): |
|||
raise MyCustomError("该身份码已存在") |
|||
|
|||
return value |
|||
@ -0,0 +1,3 @@ |
|||
from django.test import TestCase |
|||
|
|||
# Create your tests here. |
|||
@ -0,0 +1,9 @@ |
|||
from django.urls import path, include |
|||
from rest_framework import routers |
|||
|
|||
|
|||
router = routers.SimpleRouter() |
|||
|
|||
urlpatterns = [ |
|||
path(r"api/staff/", include(router.urls)), |
|||
] |
|||
@ -0,0 +1,301 @@ |
|||
import calendar |
|||
import logging |
|||
import re |
|||
import traceback |
|||
from datetime import datetime |
|||
import json |
|||
import datetime as oneDatetime |
|||
|
|||
from staff.models import Attendance |
|||
from utils.custom import MyCustomError |
|||
|
|||
|
|||
def vaild_restDays(restDays): |
|||
""" |
|||
校验输入的休息日期是否合法 |
|||
""" |
|||
try: |
|||
if isinstance(restDays, float) and restDays.is_integer(): |
|||
restDays = str(int(restDays)) |
|||
else: |
|||
restDays = str(restDays).strip() |
|||
check = [0] * 31 # 用于检验休息日,不存在重复休息同一时间的情况 1-上午 2-下午 3-全天 |
|||
restDayList = restDays.split(',') |
|||
for string in restDayList: |
|||
pattern = r'^(1[0-9]|2[0-9]|3[0-1]|[1-9])(am|pm)?$' |
|||
match = re.match(pattern, string) |
|||
if match: |
|||
num = int(match.group(1)) |
|||
am_pm = match.group(2) |
|||
if am_pm: # 是半天的情况 |
|||
if 'am' == am_pm: |
|||
check[num-1] += 1 |
|||
elif 'pm' == am_pm: |
|||
check[num-1] += 2 |
|||
else: |
|||
return False |
|||
else: # 完整一天的情况 |
|||
check[num-1] += 3 |
|||
# 检验是否已出错 |
|||
if check[num-1] > 3: |
|||
return False |
|||
else: |
|||
return False |
|||
return True |
|||
except Exception as e: |
|||
logging.getLogger('error').error("vaild_restDays failed: \n%s" % (traceback.format_exc())) |
|||
raise Exception('校验日期字符串失败') |
|||
|
|||
|
|||
def vaild_attendance(attendance: Attendance, entryTime, leaveTime): |
|||
""" |
|||
校验考勤数据 |
|||
1. 研发天数 >= 0 |
|||
2. 休息日 + 月出勤天数 = 本月天数(减去本月入职离职时间) |
|||
3. 研发天数 <= 出勤天数 |
|||
""" |
|||
try: |
|||
if isinstance(attendance.attendanceDate, str): |
|||
attendance.attendanceDate = datetime.strptime(attendance.attendanceDate, "%Y-%m-%d") |
|||
_, days = calendar.monthrange(attendance.attendanceDate.year, attendance.attendanceDate.month) |
|||
monthAbleDay = days # 本月可用总天数 |
|||
# 判断处理入职与离职时间 |
|||
attendance_month = attendance.attendanceDate.strftime('%Y-%m') |
|||
entry_month = entryTime.strftime('%Y-%m') |
|||
if attendance_month == entry_month: |
|||
monthAbleDay = days - (entryTime.day - 1) |
|||
if leaveTime: |
|||
leave_month = leaveTime.strftime('%Y-%m') |
|||
if attendance_month == leave_month: |
|||
monthAbleDay -= (days - leaveTime.day) |
|||
|
|||
# 计算休息日count |
|||
rest_count = 0 |
|||
rest_day_list = [] |
|||
if attendance.restDay: |
|||
if attendance_month == entry_month: # 过滤无效休息日 |
|||
for day in attendance.restDay.split(','): |
|||
dayInt = int(re.search(r'\d+', day).group()) |
|||
if dayInt < entryTime.day: |
|||
continue |
|||
if 'am' in day or 'pm' in day: |
|||
rest_count += 0.5 |
|||
else: |
|||
rest_count += 1 |
|||
rest_day_list.append(day) |
|||
elif leaveTime and attendance_month == leave_month: # 过滤无效休息日 |
|||
for day in attendance.restDay.split(','): |
|||
dayInt = int(re.search(r'\d+', day).group()) |
|||
if dayInt > leaveTime.day: |
|||
continue |
|||
if 'am' in day or 'pm' in day: |
|||
rest_count += 0.5 |
|||
else: |
|||
rest_count += 1 |
|||
rest_day_list.append(day) |
|||
else: |
|||
for day in attendance.restDay.split(','): |
|||
if 'am' in day or 'pm' in day: |
|||
rest_count += 0.5 |
|||
else: |
|||
rest_count += 1 |
|||
rest_day_list.append(day) |
|||
attendance.restDay = ','.join(rest_day_list) |
|||
# 1.研发天数 >= 0 |
|||
if attendance.rndDay < 0: |
|||
raise MyCustomError('研发天数不能小于0') |
|||
# 2.休息日 + 月出勤天数 = 本月天数 |
|||
if rest_count + attendance.attendanceDays != monthAbleDay: |
|||
raise MyCustomError('休息日加月出勤天数应等于本月天数减去入职或离职时间') |
|||
# 3.研发天数 <= 出勤天数 |
|||
if attendance.rndDay > attendance.attendanceDays: |
|||
raise MyCustomError('研发天数不能大于出勤天数') |
|||
return True |
|||
except Exception as e: |
|||
raise e |
|||
|
|||
|
|||
def check_entry_leave_time(attendance: Attendance, entryTime: datetime, leaveTime: datetime) -> bool: |
|||
""" |
|||
检查考勤休息日是否与入职离职时间冲突,不冲突则返回True,冲突返回False |
|||
""" |
|||
try: |
|||
attendance_month = attendance.attendanceDate.strftime('%Y-%m') |
|||
# entry_month, leave_month = map(lambda x: x.strftime('%Y-%m'), [entryTime, leaveTime]).list() |
|||
entry_month = entryTime.strftime('%Y-%m') |
|||
leave_month = leaveTime.strftime('%Y-%m') |
|||
|
|||
def check(attendance, checkDate, type=None, checkDateExtra=None): |
|||
# 将该月休息日反选,查看是否存在工作日与离职或入职时间冲突 |
|||
# checkDateExtra仅一种情况,即该月入职且该月离职 |
|||
restDays = attendance.restDay |
|||
days = calendar.monthrange(attendance.attendanceDate.year, attendance.attendanceDate.month)[1] |
|||
if restDays and restDays != '': |
|||
restDaySet = set(re.sub(r'(am|pm)', restDays).split(',')) |
|||
workDaySet = {day for day in range(1,days+1) if day not in restDaySet} |
|||
else: |
|||
workDaySet = {day for day in range(1, days+1)} |
|||
checkDay = checkDate.day |
|||
if 1 == type: # 入职时间 |
|||
return not any(day < checkDay for day in workDaySet) |
|||
elif 2 == type: # 离职时间 |
|||
return not any(day > checkDay for day in workDaySet) |
|||
else: # 当月入职且离职 |
|||
checkDayExtra = checkDateExtra.day # 离职日 |
|||
return not any(day < checkDay or day > checkDayExtra for day in workDaySet) |
|||
|
|||
# 判断是否与入职离职时间处于同一月 |
|||
if entry_month != leave_month: |
|||
if attendance_month == entry_month: |
|||
check(attendance, entryTime, 1) |
|||
elif attendance_month == leave_month: |
|||
check(attendance, entryTime, 2) |
|||
else: # 不可能冲突 |
|||
return True |
|||
else: # 如果当月入职且当月离职 |
|||
if attendance_month == entry_month: |
|||
check(attendance, entryTime) |
|||
else: |
|||
return True |
|||
|
|||
except Exception as e: |
|||
raise e |
|||
|
|||
|
|||
def supplyRestDays(attendance: Attendance, entryTime: datetime, leaveTime: datetime) -> bool: |
|||
""" |
|||
判断是否当月入职离职,自动补充入职前与离职后的时间为休息日 |
|||
""" |
|||
try: |
|||
attendance_month = attendance.attendanceDate.strftime('%Y-%m') |
|||
# entry_month, leave_month = map(lambda x: x.strftime('%Y-%m'), [entryTime, leaveTime]).list() |
|||
entry_month = entryTime.strftime('%Y-%m') |
|||
leave_month = leaveTime.strftime('%Y-%m') |
|||
|
|||
days_in_month = calendar.monthrange(attendance.attendanceDate.year, attendance.attendanceDate.month)[1] |
|||
restDays = attendance.restDay |
|||
|
|||
# 解析休息日 |
|||
if restDays and restDays != '': |
|||
restDaySet = set(map(int, re.findall(r'\d+', restDays))) |
|||
else: |
|||
restDaySet = set() |
|||
|
|||
def update_rest_days(start, end): |
|||
for day in range(start, end + 1): |
|||
restDaySet.add(day) |
|||
# 判断是否与入职离职时间处于同一月 |
|||
if attendance_month == entry_month: |
|||
update_rest_days(1, entryTime.day - 1) |
|||
elif attendance_month == leave_month: |
|||
update_rest_days(leaveTime.day + 1, days_in_month) |
|||
# 更新休息日字符串 |
|||
attendance.restDay = ','.join(map(str, sorted(restDaySet))) |
|||
except Exception as e: |
|||
raise e |
|||
|
|||
def replace_or_add(original, target, addition): |
|||
if target in original: |
|||
# 如果目标子字符串存在,则替换它 |
|||
return original.replace(target, addition) |
|||
else: |
|||
# 如果目标子字符串不存在,则添加它 |
|||
return original + addition |
|||
|
|||
def get_reset_dates(year, month, file_path): |
|||
try: |
|||
# 获取当月的所有日期 |
|||
day_range = range(1, calendar.monthrange(year, month)[1] + 1) |
|||
|
|||
# 初始化一个空列表来存储周末日期 |
|||
weekend_dates = set() |
|||
|
|||
# 从本地读取 media/chinese_holiday/{year}.json |
|||
restData = {} |
|||
|
|||
try: |
|||
with open(file_path, 'r', encoding='utf-8') as file: |
|||
restData = json.load(file) |
|||
except Exception as e: |
|||
raise e |
|||
|
|||
# 遍历每一天,检查是否是周末 |
|||
for day in day_range: |
|||
current_date = oneDatetime.date(year, month, day) |
|||
if current_date.weekday() >= 5: # 星期六是5,星期日是6 |
|||
weekend_dates.add(current_date) |
|||
restDayStr = str(str(month).zfill(2) + '-' + str(day).zfill(2)) |
|||
restDayData = restData['holiday'].get(restDayStr, None) |
|||
if restDayData: |
|||
if restDayData['holiday']: |
|||
weekend_dates.add(current_date) |
|||
if not restDayData['holiday']: |
|||
weekend_dates.remove(current_date) |
|||
|
|||
return sorted(weekend_dates) |
|||
except Exception as e: |
|||
raise e |
|||
|
|||
def setDateListStrFrontDate(dates, joinDate, leaveDate): |
|||
setDates = [] |
|||
if leaveDate: |
|||
for date in dates: |
|||
# 如果离职时间和入职时间不在同一个月 |
|||
# 1入职前的时间不算进去,离职后的时间不算进去 |
|||
if joinDate.year != leaveDate.year and joinDate.month != leaveDate.month: |
|||
if joinDate.year == date.year and joinDate.month == date.month and date.day >= joinDate.day: |
|||
setDates.append(str(date.day)) |
|||
continue |
|||
if leaveDate.year == date.year and leaveDate.month == date.month and date.day <= leaveDate.day: |
|||
setDates.append(str(date.day)) |
|||
continue |
|||
elif leaveDate.year != date.year or leaveDate.month != date.month: |
|||
setDates.append(str(date.day)) |
|||
# 如果离职时间和入职时间在同一个月 |
|||
# 1入职前的时间不算进去,离职后的时间不算进去 |
|||
elif joinDate.year == leaveDate.year and joinDate.month == leaveDate.month: |
|||
if joinDate.year == date.year and joinDate.month == date.month and (date.day >= joinDate.day and date.day <= leaveDate.day): |
|||
setDates.append(str(date.day)) |
|||
continue |
|||
elif leaveDate.year != date.year or leaveDate.month != date.month: |
|||
setDates.append(str(date.day)) |
|||
|
|||
else: |
|||
for date in dates: |
|||
if joinDate.year == date.year and joinDate.month == date.month and date.day >= joinDate.day: |
|||
setDates.append(str(date.day)) |
|||
elif joinDate.year != date.year or joinDate.month != date.month: |
|||
setDates.append(str(date.day)) |
|||
datesStr = ','.join(setDates) |
|||
return datesStr |
|||
|
|||
def has_nonzero_decimal_part(num): |
|||
# 将浮点数转换为字符串 |
|||
num_str = f"{num:.16f}".rstrip('0').rstrip('.') # 保留足够多的小数位,并去除末尾的零和点 |
|||
# 检查字符串中是否包含小数点,并且小数点后是否有字符 |
|||
# 注意:由于我们使用了rstrip去除了末尾的零,所以这里的检查是有效的 |
|||
has_decimal = '.' in num_str and num_str.split('.')[1] != '' |
|||
# 如果小数点后有字符,再检查这些字符是否全不是'0' |
|||
nonzero_decimal = has_decimal and not num_str.split('.')[1].isdigit() and '0' not in num_str.split('.')[1] or ( |
|||
has_decimal and any(char != '0' for char in num_str.split('.')[1])) |
|||
# 但是上面的逻辑有点复杂,我们可以简化它:只需要检查小数点后是否至少有一个非零字符 |
|||
# 下面的逻辑更加直接和清晰: |
|||
nonzero_decimal_simplified = '.' in num_str and any(char != '0' for char in num_str.split('.')[1]) |
|||
|
|||
# 注意:由于浮点数精度问题,直接使用==来判断两个浮点数是否相等通常是不安全的 |
|||
# 因此,我们在这里不检查num_str是否等于去掉小数部分后的字符串,而是直接检查小数点后是否有非零字符 |
|||
|
|||
return nonzero_decimal_simplified |
|||
|
|||
def transform_staffCodeToStr(idCode): |
|||
if isinstance(idCode, str): |
|||
staffIdCode = idCode.strip() # 身份码 |
|||
elif isinstance(idCode, float) or isinstance(idCode, int): |
|||
zeroTag = has_nonzero_decimal_part(idCode) |
|||
if zeroTag: |
|||
staffIdCode = str(idCode).strip() |
|||
else: |
|||
staffIdCode = str(int(idCode)).strip() |
|||
else: |
|||
staffIdCode = str(idCode).strip() # 身份码 |
|||
return staffIdCode |
|||
@ -0,0 +1,3 @@ |
|||
from django.shortcuts import render |
|||
|
|||
# Create your views here. |
|||
@ -0,0 +1,566 @@ |
|||
import datetime |
|||
import logging |
|||
import os |
|||
import traceback |
|||
from collections import Counter |
|||
|
|||
import xlrd3 |
|||
from django.apps import apps |
|||
from django.core.exceptions import ObjectDoesNotExist |
|||
from django.db import transaction |
|||
from django.db.models import Q, Count |
|||
from django.http import FileResponse |
|||
from django_filters.rest_framework import DjangoFilterBackend |
|||
from rest_framework.decorators import action |
|||
from rest_framework.filters import SearchFilter, OrderingFilter |
|||
from rest_framework.generics import get_object_or_404 |
|||
from rest_framework.permissions import IsAuthenticated |
|||
from rest_framework_jwt.authentication import JSONWebTokenAuthentication |
|||
|
|||
from ChaCeRndTrans.basic import CCAIResponse |
|||
from ChaCeRndTrans.code import SERVER_ERROR, BAD, OK, NOT_FOUND |
|||
from ChaCeRndTrans.settings import MEDIA_ROOT, SHOW_UPLOAD_PATH, FILE_PATH |
|||
from rbac.models import Company |
|||
from staff.models import Staff, Dept |
|||
from staff.serializers.Serializer import DeptSerializer, DeptModifySerializer |
|||
from utils.custom import CustomViewBase, RbacPermission, CommonPagination, req_operate_by_user, asyncDeleteFile, \ |
|||
MyCustomError |
|||
|
|||
logger = logging.getLogger('error') |
|||
|
|||
|
|||
class DeptCustomView(CustomViewBase): |
|||
''' |
|||
部门管理,增删改查 |
|||
''' |
|||
perms_map = ( |
|||
{"*": "admin"}, {"*": "comadmin"}, {"*": "dept_all"}, {"get": "dept_list"}, |
|||
{"post": "dept_create"}, |
|||
{"put": "dept_edit"}, {"delete": "dept_delete"} |
|||
) |
|||
queryset = Dept.objects.all() |
|||
serializer_class = DeptSerializer |
|||
pagination_class = CommonPagination |
|||
filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter) |
|||
search_fields = ("name",) |
|||
ordering_fields = ("id",) |
|||
authentication_classes = (JSONWebTokenAuthentication,) |
|||
permission_classes = (RbacPermission,) |
|||
|
|||
# 关联部门的模型 |
|||
modelNameList = {'Equipment': "设备", 'Building': "建筑物", 'Inassets': "无形资产", 'Longterm': "长期待摊", 'Rentequip': "租赁设备", 'Rentbuild': "租赁建筑"} |
|||
remark = [{'app': 'asset', 'model': modelName, 'info': info} for modelName, info in modelNameList.items()] |
|||
remark.append({'app': 'staff', 'model': 'Staff', 'info': '员工'}) |
|||
|
|||
def get_serializer_class(self): |
|||
# 根据请求类型动态变更serializer |
|||
if self.action == "create": |
|||
return DeptSerializer |
|||
elif self.action == "list": |
|||
return DeptSerializer |
|||
return DeptModifySerializer |
|||
|
|||
def get_queryset(self): |
|||
# query = self.request.query_params.get('q', None) # q为实际的搜索词 |
|||
# field = self.request.query_params.get('field', None) # field为具体字段 |
|||
companyMid = self.request.query_params.get('companyMid', None) # field为具体字段 |
|||
|
|||
queryset = Dept.objects.all() |
|||
|
|||
# if query and field: |
|||
# # 根据传递的字段决定搜索的字段 |
|||
# if field == 'subjectCode': |
|||
# queryset = queryset.filter(subjectCode__icontains=query) |
|||
# elif field == 'subjectName': |
|||
# queryset = queryset.filter(subjectName__icontains=query) |
|||
if companyMid: |
|||
queryset = queryset.filter(companyMid=companyMid) |
|||
return queryset |
|||
|
|||
def list(self, request, format=None): |
|||
""" |
|||
列表 |
|||
""" |
|||
pagination = {} |
|||
try: |
|||
params = request.GET |
|||
keyword = params.get('keyword') # 关键词 |
|||
is_rnd = params.get('is_rnd') # 是否研发部 |
|||
|
|||
companyMid = params.get('companyMid') |
|||
page_size = params.get('size') |
|||
page = params.get('page') |
|||
sort = params.get('sort') |
|||
param = [] |
|||
order_by = 'N.id desc' |
|||
if sort: |
|||
order_by = sort |
|||
if page is None: |
|||
page = 1 |
|||
if page_size is None: |
|||
page_size = 10 |
|||
start_index = (int(page) - 1) * int(page_size) |
|||
|
|||
where = 'WHERE 1=1 ' |
|||
if not companyMid: |
|||
return CCAIResponse('公司信息缺失', BAD) |
|||
if companyMid: |
|||
where += 'AND N.companyMid = %s ' |
|||
param.append(companyMid) |
|||
if is_rnd: |
|||
where += 'AND N.is_rnd = %s ' |
|||
param.append(is_rnd) |
|||
if keyword: |
|||
where += 'AND N.name like %s ' |
|||
param.append('%' + keyword + '%') |
|||
|
|||
sql = 'SELECT N.id, name, pid_id, (SELECT name from chace_rnd.staff_dept where id = N.pid_id) AS parentName, ' \ |
|||
'(SELECT COUNT(id) FROM chace_rnd.staff_staff where dept = N.id) AS count, is_rnd, ' \ |
|||
'CreateBy, UpdateBy, CreateByUid, UpdateByUid, CreateDateTime, UpdateDateTime, companyMid ' \ |
|||
'FROM chace_rnd.staff_dept AS N ' \ |
|||
'%s ORDER BY %s LIMIT %s,%s ' % (where, order_by, start_index, page_size) |
|||
query_rows = Dept.objects.raw(sql, param) |
|||
|
|||
count_sql = """ select N.id, count(N.id) as count |
|||
from chace_rnd.staff_dept AS N %s """ % where |
|||
count_result = Dept.objects.raw(count_sql, param) |
|||
|
|||
count = 0 |
|||
if len(count_result) > 0: |
|||
count = count_result[0].count |
|||
rows = [] |
|||
for item in query_rows: |
|||
item.__dict__.pop('_state') |
|||
item.__dict__['CreateDateTime'] = item.__dict__['CreateDateTime'].strftime('%Y-%m-%d') |
|||
item.__dict__['UpdateDateTime'] = item.__dict__['UpdateDateTime'].strftime('%Y-%m-%d') |
|||
rows.append(item.__dict__) |
|||
|
|||
pagination = { |
|||
"page": page, |
|||
"page_size": page_size |
|||
} |
|||
|
|||
return CCAIResponse(rows, count=count, pagination=pagination) |
|||
|
|||
except Exception as e: |
|||
logger.error("user: %s, get dept list failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("获取部门管理列表失败", SERVER_ERROR) |
|||
|
|||
def create(self, request, *args, **kwargs): |
|||
try: |
|||
data = req_operate_by_user(request) |
|||
companyMid = request.query_params.get('companyMid', None) |
|||
if not companyMid: |
|||
return CCAIResponse("Missing company information", BAD) |
|||
is_com_exist = Company.objects.filter(MainId=companyMid).exists() |
|||
if is_com_exist: |
|||
data['companyMid'] = companyMid |
|||
serializer = self.get_serializer(data=data) |
|||
serializer.is_valid(raise_exception=True) |
|||
self.perform_create(serializer) |
|||
# headers = self.get_success_headers(serializer.data) |
|||
return CCAIResponse(data="success") |
|||
else: |
|||
return CCAIResponse("Company information error", BAD) |
|||
except MyCustomError as e: |
|||
return CCAIResponse(e.message, BAD) |
|||
except Exception as e: |
|||
logger.error("user: %s, staff create failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("create failed", SERVER_ERROR) |
|||
|
|||
def update(self, request, *args, **kwargs): |
|||
from rest_framework.exceptions import ValidationError |
|||
try: |
|||
companyMid = request.query_params.get('companyMid', None) |
|||
data = req_operate_by_user(request) |
|||
data['companyMid'] = companyMid |
|||
partial = kwargs.pop('partial', False) # True:所有字段全部更新, False:仅更新提供的字段 |
|||
instance = self.get_object() |
|||
|
|||
children_ids = get_all_children(instance.id) |
|||
param_pid = data.get('pid') |
|||
if param_pid and (param_pid in children_ids or param_pid == instance.id): |
|||
return CCAIResponse("不可选自身与自身的下级部门作为上级部门", BAD) |
|||
|
|||
serializer = self.get_serializer(instance, data=data, partial=partial) |
|||
serializer.is_valid(raise_exception=True) |
|||
self.perform_update(serializer) |
|||
|
|||
if getattr(instance, '_prefetched_objects_cache', None): |
|||
# If 'prefetch_related' has been applied to a queryset, we need to |
|||
# forcibly invalidate the prefetch cache on the instance. |
|||
instance._prefetched_objects_cache = {} |
|||
return CCAIResponse(data="success") |
|||
except ValidationError as e: |
|||
logger.error( |
|||
"user: %s, update dept cause:can not set self pid \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("can not set self as parent", BAD) |
|||
except MyCustomError as e: |
|||
return CCAIResponse(e.message, BAD) |
|||
except Exception as e: |
|||
logger.error("user: %s, update dept failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("update dept failed", SERVER_ERROR) |
|||
|
|||
def destroy(self, request, *args, **kwargs): |
|||
try: |
|||
instance = self.get_object() |
|||
# 查询是否存在关联 |
|||
is_association = self.check_association(deptId=instance.id) |
|||
if is_association: |
|||
return CCAIResponse(is_association, BAD) |
|||
self.perform_destroy(instance) |
|||
return CCAIResponse(data="delete resource success") |
|||
except Exception as e: |
|||
logger.error("user: %s, delete dept failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("delete dept failed", SERVER_ERROR) |
|||
|
|||
def check_association(self, deptId): |
|||
""" |
|||
检查部门关联的状态,遍历查询所有与部门关联的表 |
|||
return False-不存在关联 True-存在关联 |
|||
""" |
|||
try: |
|||
isExist = False # 不存在关联 |
|||
info = None |
|||
code = None # 存在关联的人员或设备的编码 |
|||
for item in self.remark: # 遍历查询是否存在关联,一旦存在关联就不给删除 |
|||
Model = apps.get_model(app_label=item.get('app'), model_name=item.get('model')) |
|||
if not isExist: |
|||
if 'staff' == item.get('app'): |
|||
isExist = Model.objects.filter(dept=deptId).first() |
|||
else: |
|||
isExist = Model.objects.filter(departmentId=deptId).first() |
|||
|
|||
if isExist: |
|||
info = item.get('info') |
|||
code = getattr(isExist, 'code' if 'staff' != item.get('app') else 'idCode') |
|||
else: |
|||
break |
|||
if isExist: |
|||
return f'{info}:{code} 与部门存在关联!' |
|||
else: |
|||
return False |
|||
except Exception as e: |
|||
logger.error("check staff association failed: \n%s" % traceback.format_exc()) |
|||
raise Exception('检查员工关联状态失败') |
|||
|
|||
@action(methods=['delete'], detail=False) |
|||
def multiple_delete(self, request, *args, **kwargs): |
|||
try: |
|||
delete_id = request.query_params.get('ids', None) |
|||
if not delete_id: |
|||
return CCAIResponse("参数不对啊!", NOT_FOUND) |
|||
for i in delete_id.split(','): |
|||
# 查询是否存在关联 |
|||
is_association = self.check_association(deptId=i) |
|||
if not is_association: # 不存在关联,才删除 |
|||
get_object_or_404(self.queryset, pk=int(i)).delete() |
|||
else: # 存在关联,不删除,跳过 |
|||
return CCAIResponse(is_association, BAD) |
|||
return CCAIResponse("批量删除成功", OK) |
|||
except Exception as e: |
|||
logger.error("multiple delete dept failed: \n%s" % traceback.format_exc()) |
|||
return CCAIResponse("批量删除失败", SERVER_ERROR) |
|||
|
|||
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated], |
|||
url_name='download', url_path='download') |
|||
def download(self, request, *args, **kwargs): |
|||
"""导入模板下载""" |
|||
try: |
|||
file = open(MEDIA_ROOT + '/部门批量导入模板.xlsx', 'rb') |
|||
response = FileResponse(file) |
|||
response['Content-Type'] = 'application/octet-stream' |
|||
response['Content-Disposition'] = 'attachment;filename="部门批量导入模板.xlsx"' |
|||
return response |
|||
except Exception as e: |
|||
logger.error("user: %s, get dept template file failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("下载部门批量导入模板失败", SERVER_ERROR) |
|||
|
|||
|
|||
@action(methods=["post"], detail=False, permission_classes=[IsAuthenticated], |
|||
url_path="import_excel", url_name="import_excel") |
|||
def import_excel(self, request): |
|||
"""导入excel表数据""" |
|||
try: |
|||
params = request.data |
|||
companyMid = request.query_params.get('companyMid', None) |
|||
file_name = params.get('file_name') |
|||
|
|||
if not companyMid or not file_name: |
|||
return CCAIResponse('Missing params', BAD) |
|||
|
|||
file_paths = file_name.split(",") |
|||
if len(file_paths) >= 2: |
|||
return CCAIResponse("部门数据暂不支持同时多文件上传", BAD) |
|||
excel_file = file_paths[0].replace(SHOW_UPLOAD_PATH, FILE_PATH) |
|||
with xlrd3.open_workbook(excel_file) as data: |
|||
sheets = list(data.sheets()) |
|||
if len(sheets) > 1: |
|||
return CCAIResponse("部门数据暂不支持同时多表格上传", BAD) |
|||
|
|||
table = sheets[0] # 只读取第一张表 |
|||
rows = table.nrows |
|||
check_value = table.row_values(0) |
|||
if (check_value[0] != '部门名称' and check_value[1] != '是否研发部门' and check_value[2] != '上级部门') or (len(check_value) != 3): |
|||
return CCAIResponse("文件不符合模板标准", SERVER_ERROR) |
|||
|
|||
# 查询该公司已存在的部门记录 |
|||
dept_tree_list = [] |
|||
db_depts = self.get_queryset().order_by('pid') |
|||
deptId_dept_map = {d.id: d for d in db_depts} |
|||
db_depts_name = {x.name: deptid for deptid, x in deptId_dept_map.items()} |
|||
for d in db_depts: |
|||
if not d.pid: |
|||
dept_tree_list.append(d) |
|||
|
|||
dept_col_data = [str(dname).strip() for dname in table.col_values(0)[1:] if dname and str(dname).strip()] # 获取部门列数据 |
|||
deptCounter = Counter(dept_col_data) |
|||
error_dept_name_set = [item for item, count in deptCounter.items() if count > 1] |
|||
if error_dept_name_set: |
|||
return CCAIResponse(f"部门名称列存在重复{','.join(error_dept_name_set)}", BAD) |
|||
dept_parent_col_data = [str(pName).strip() for pName in table.col_values(2)[1:] if pName and str(pName).strip()] # 获取上级部门列数据 |
|||
error_dept_name_set = set(db_depts_name.keys()) - (set(db_depts_name.keys()) - set(dept_col_data)) |
|||
if error_dept_name_set: |
|||
return CCAIResponse(f"部门已存在:{','.join(error_dept_name_set)}", BAD) |
|||
error_dept_name_set = set(dept_parent_col_data) - (set(db_depts_name.keys()) | set(dept_col_data)) |
|||
if error_dept_name_set: |
|||
return CCAIResponse(f"未知部门:{','.join(error_dept_name_set)}", BAD) |
|||
# 数据库事务 |
|||
with transaction.atomic(): |
|||
try: |
|||
for row in range(1, rows): |
|||
row_values = table.row_values(row) # 每一行的数据 |
|||
|
|||
# 校验必填项 |
|||
if not row_values[0] or str(row_values[0]).strip() == '': |
|||
raise MyCustomError("部门名称必填!") |
|||
if len(str(row_values[0]).strip()) > 20: |
|||
raise MyCustomError("部门名称过长,请控制在20字符内!") |
|||
|
|||
is_rnd = False |
|||
if row_values[1]: |
|||
is_rnd_str = str(row_values[1]).strip() |
|||
if is_rnd_str and is_rnd_str not in ['是', '否', '不是']: |
|||
raise MyCustomError(f"部门研发属性:{str(row_values[1])}错误,请在「'是', '否', '不是'」中选择!") |
|||
if '是' == is_rnd_str: |
|||
is_rnd = True |
|||
|
|||
parent_dept_name = None |
|||
parent_id = None |
|||
if row_values[2] and str(row_values[2]).strip(): |
|||
parent_dept_name = str(row_values[2]).strip() |
|||
if parent_dept_name: |
|||
if parent_dept_name == str(row_values[0]).strip(): |
|||
raise MyCustomError(f"部门:{parent_dept_name},不可以以自身为上级") |
|||
parent_id = db_depts_name.get(parent_dept_name) |
|||
|
|||
dept = Dept() |
|||
dept.name = str(row_values[0]).strip() |
|||
dept.is_rnd = is_rnd |
|||
dept.CreateBy = request.user.name |
|||
dept.CreateByUid = request.user.id |
|||
dept.CreateDateTime = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') |
|||
dept.companyMid = companyMid |
|||
if parent_dept_name: |
|||
dept.parent_dept_name = parent_dept_name |
|||
if parent_id: |
|||
dept.pid = deptId_dept_map.get(parent_id) |
|||
dept.save() |
|||
deptId_dept_map[dept.id] = dept |
|||
db_depts_name[dept.name] = dept.id |
|||
if not parent_dept_name: |
|||
dept_tree_list.append(dept) |
|||
|
|||
# 查看是否存在未设置pid的部门 |
|||
for deptId, d in deptId_dept_map.items(): |
|||
if hasattr(d, 'parent_dept_name') and not d.pid: |
|||
# if d.pid.id not in deptId_dept_map: |
|||
if d.parent_dept_name not in db_depts_name: |
|||
raise MyCustomError("数据有误!") |
|||
d.pid = deptId_dept_map.get(db_depts_name.get(d.parent_dept_name)) |
|||
d.save() |
|||
if check_circular_association(request, list(deptId_dept_map.values())): |
|||
raise MyCustomError("存在循环上下级关系!") |
|||
except MyCustomError as e: |
|||
transaction.set_rollback(True) |
|||
return CCAIResponse(e.message, BAD) |
|||
# 异步删除文件 |
|||
try: |
|||
if os.path.exists(MEDIA_ROOT + '/' + excel_file): # 如果文件存在 |
|||
# 删除文件,可使用以下两种方法。 |
|||
os.remove(MEDIA_ROOT + '/' + excel_file) |
|||
except Exception as e: |
|||
logger.error( |
|||
"user: %s, export ccw user xlsx file failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
|
|||
return CCAIResponse(data="success") |
|||
except Exception as e: |
|||
logger.error("user: %s, import dept file failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("导入部门记录失败", SERVER_ERROR) |
|||
|
|||
@classmethod |
|||
def changeCount(cls, method, deptId): |
|||
""" |
|||
改变部门关联数量 |
|||
method: 1-新增关联 2-取消关联 |
|||
return: True操作成功 False操作失败 |
|||
""" |
|||
if not deptId or not method: |
|||
return False |
|||
try: |
|||
dept = Dept.objects.get(id=deptId) |
|||
except Dept.DoesNotExist: |
|||
logger.error("dept do not exist: \n%s" % (traceback.format_exc(),)) |
|||
return False |
|||
else: |
|||
try: |
|||
if 1 == method: # 新增关联 |
|||
dept.relCount += 1 |
|||
elif 2 == method: |
|||
if dept.relCount > 0: # 大于0才取消关联 |
|||
dept.relCount -= 1 |
|||
else: |
|||
return False |
|||
dept.save() |
|||
return True |
|||
except Exception as e: |
|||
logger.error("change dept relCount failed: \n%s" % (traceback.format_exc(),)) |
|||
return False |
|||
|
|||
@action(methods=["get"], detail=False, permission_classes=[IsAuthenticated], |
|||
url_path="getDeptMap", url_name="getDeptMap") |
|||
def getDeptMap(self, request): |
|||
try: |
|||
params = request.GET |
|||
companyMid = params.get('companyMid') |
|||
if not companyMid: |
|||
return CCAIResponse("Missing company information", BAD) |
|||
if companyMid: |
|||
query = Q(companyMid=companyMid) |
|||
dept = Dept.objects.filter(query).values('id', 'name') |
|||
return CCAIResponse(dept) |
|||
except Exception as e: |
|||
logger.error("user: %s, getDeptMap failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("getDeptMap failed", SERVER_ERROR) |
|||
|
|||
@action(methods=["get"], detail=False, permission_classes=[IsAuthenticated], |
|||
url_path="tree", url_name="tree") |
|||
def tree(self, request, format=None): |
|||
""" |
|||
树状列表 |
|||
""" |
|||
params = request.GET |
|||
companyMid = params.get('companyMid') |
|||
if not companyMid: |
|||
return CCAIResponse("Missing company information", BAD) |
|||
# 统计员工表不同部门人数 |
|||
sql = 'SELECT id, dept, COUNT(id) AS count FROM chace_rnd.staff_staff WHERE companyMid = %s and status = 0 GROUP BY dept ' |
|||
result = Staff.objects.raw(sql, [companyMid]) |
|||
# result = Staff.objects.filter(companyMid=companyMid, status=0).values('dept').annotate(count=Count('id')) |
|||
deptCountDict = {item.dept: item.count for item in result} |
|||
|
|||
queryset = self.filter_queryset(self.get_queryset()) |
|||
page = self.paginate_queryset(queryset) |
|||
serializer = self.get_serializer(queryset, many=True) |
|||
tree_dict = {} |
|||
tree_data = [] |
|||
try: |
|||
# 初始赋值字典与count |
|||
for item in serializer.data: |
|||
if item['id'] in deptCountDict: |
|||
item['count'] = deptCountDict[item['id']] |
|||
else: |
|||
item['count'] = 0 |
|||
tree_dict[item["id"]] = item |
|||
# 设置树状结构 |
|||
for i in tree_dict: |
|||
if tree_dict[i]["pid"]: |
|||
pid = tree_dict[i]["pid"] |
|||
parent = tree_dict[pid] |
|||
parent.setdefault("children", []).append(tree_dict[i]) |
|||
else: |
|||
tree_data.append(tree_dict[i]) |
|||
# 统计部门人数 1:递归 2:多层遍历 |
|||
for dept in tree_data: # 第一层部门 |
|||
if 'children' in dept: # 若存在下级部门,调用方法递归累加最终数量 |
|||
self.getCountFromTree(dept) |
|||
|
|||
results = tree_data |
|||
except KeyError: |
|||
results = serializer.data |
|||
if page is not None: |
|||
return self.get_paginated_response(results) |
|||
return CCAIResponse(results) |
|||
|
|||
def getCountFromTree(self, parentDept): |
|||
""" |
|||
(递归)设置并返回当前本部门的下级部门的总人数 |
|||
""" |
|||
sum = 0 # 子部门人数统计 |
|||
for dept in parentDept['children']: |
|||
if 'children' in dept: # 存在下级部门,递归调用,设置当前部门人数,并返回上级,本部门人数 |
|||
deptCount = self.getCountFromTree(dept) |
|||
sum += deptCount |
|||
else: # 最底层部门不需要设置,只需要累计到上层 |
|||
sum += dept['count'] |
|||
parentDept['count'] += sum # 本部门 + 子级部门 |
|||
return parentDept['count'] |
|||
|
|||
# def getCountFromTree(self, parentDept): |
|||
# """ |
|||
# (模拟栈)设置并返回当前本部门的下级部门的总人数 |
|||
# """ |
|||
# stack = [parentDept] # 使用栈来模拟递归的过程 |
|||
# # results = [] # 用于保存每次调用的结果 |
|||
# while stack: |
|||
# parentDept = stack.pop() |
|||
# sum = 0 # 子部门人数统计 |
|||
# for dept in parentDept['children']: |
|||
# if 'children' in dept: # 存在下级部门,递归调用,设置当前部门人数,并返回上级,本部门人数 |
|||
# stack.append(dept) |
|||
# # deptCount = self.getCountFromTree(dept) |
|||
# # sum += deptCount |
|||
# else: # 最底层部门不需要设置,只需要累计到上层 |
|||
# sum += dept['count'] |
|||
# parentDept['count'] += sum # 本部门 + 子级部门 |
|||
# # results.append(parentDept['count']) |
|||
|
|||
|
|||
def get_all_children(deptId, max_depth=50, current_depth=0): |
|||
# 如果设置了最大深度并且当前深度已经达到或超过最大深度,则返回空列表 |
|||
if current_depth >= max_depth: |
|||
return [] |
|||
|
|||
children = [] |
|||
sub_depts = Dept.objects.filter(pid=deptId) |
|||
for sub_dept in sub_depts: |
|||
children.append(sub_dept.id) |
|||
# 在递归调用时增加当前深度 |
|||
children.extend(get_all_children(sub_dept.id, max_depth, current_depth + 1)) |
|||
return children |
|||
|
|||
def check_circular_association(request, dept_list): |
|||
""" |
|||
检查部门循环上下级, 存在循环问题会返回True,否则返回False |
|||
deptId_dept_map: [ Node1, Node2, ... ] |
|||
""" |
|||
try: |
|||
if not dept_list or 0 == len(dept_list): |
|||
return False |
|||
|
|||
for node in dept_list: |
|||
nodeList = [node, ] |
|||
n = 0 |
|||
while node.pid: |
|||
n += 1 |
|||
if n >= 50: |
|||
return True |
|||
if node.pid in nodeList: |
|||
return True |
|||
node = node.pid |
|||
nodeList.append(node) |
|||
|
|||
return False |
|||
except Exception as e: |
|||
logger.error("user: %s, check_circular_association failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse("check_circular_association failed", SERVER_ERROR) |
|||
@ -0,0 +1 @@ |
|||
default_app_config = "tasks.apps.TasksConfig" |
|||
@ -0,0 +1,3 @@ |
|||
from django.contrib import admin |
|||
|
|||
# Register your models here. |
|||
@ -0,0 +1,5 @@ |
|||
from django.apps import AppConfig |
|||
|
|||
|
|||
class TasksConfig(AppConfig): |
|||
name = 'tasks' |
|||
@ -0,0 +1,20 @@ |
|||
from django.db import models |
|||
|
|||
|
|||
# Create your models here. |
|||
class TasksConsloe(models.Model): |
|||
""" |
|||
任务输出 |
|||
""" |
|||
task = models.CharField(max_length=500, null=True, blank=True, verbose_name="任务") |
|||
content = models.CharField(max_length=500, null=True, blank=True, verbose_name="内容") |
|||
start_time = models.DateTimeField(auto_now_add=True, verbose_name="执行时间") |
|||
finish_time = models.DateTimeField(auto_now_add=True, verbose_name="结束时间") |
|||
|
|||
class Meta: |
|||
verbose_name = "任务输出" |
|||
verbose_name_plural = verbose_name |
|||
ordering = ["id"] |
|||
|
|||
def __str__(self): |
|||
return self.task |
|||
@ -0,0 +1,18 @@ |
|||
import datetime |
|||
import json |
|||
import logging |
|||
import traceback |
|||
import uuid |
|||
|
|||
from django.db import connections, transaction |
|||
from django_redis import get_redis_connection |
|||
|
|||
|
|||
logger = logging.getLogger('error') |
|||
logger_info = logging.getLogger("info") |
|||
|
|||
|
|||
class TasksFactory(object): |
|||
|
|||
def crontest(self): |
|||
logger_info.info('测试定时任务管理cron表达式') |
|||
@ -0,0 +1,13 @@ |
|||
from django.urls import path |
|||
from tasks.views import TaskAddView, TaskUpdateView, TaskDeleteView, TaskListView, TaskSearchView, TaskStartView, \ |
|||
TaskStopView |
|||
|
|||
urlpatterns = [ |
|||
path(r"api/tasks/add/", TaskAddView.as_view(), name="task_add"), |
|||
path(r"api/tasks/update/", TaskUpdateView.as_view(), name="task_update"), |
|||
path(r"api/tasks/delete/", TaskDeleteView.as_view(), name="task_delete"), |
|||
path(r"api/tasks/list/", TaskListView.as_view(), name="task_list"), |
|||
path(r"api/tasks/search/", TaskSearchView.as_view(), name="task_search"), |
|||
path(r"api/tasks/stop/", TaskStopView.as_view(), name="task_stop"), |
|||
path(r"api/tasks/start/", TaskStartView.as_view(), name="task_start"), |
|||
] |
|||
@ -0,0 +1,256 @@ |
|||
import datetime |
|||
import logging |
|||
import traceback |
|||
|
|||
from apscheduler.executors.pool import ThreadPoolExecutor |
|||
from apscheduler.schedulers.background import BackgroundScheduler |
|||
from django.db import connection |
|||
from django_apscheduler.jobstores import register_events |
|||
from rest_framework.permissions import IsAuthenticated |
|||
from rest_framework.views import APIView |
|||
from rest_framework_jwt.authentication import JSONWebTokenAuthentication |
|||
|
|||
from ChaCeRndTrans import settings |
|||
from ChaCeRndTrans.basic import CCAIResponse |
|||
from ChaCeRndTrans.code import * |
|||
from tasks.tasks import TasksFactory |
|||
from utils.custom import is_connection_usable |
|||
|
|||
logger = logging.getLogger('error') |
|||
|
|||
MYSQL_URL = "mysql://{user}:{password}@{host}:{port}/{dbname}".format( |
|||
user=settings.DATABASES.get('default').get('USER'), |
|||
password=settings.DATABASES.get('default').get('PASSWORD'), |
|||
host=settings.DATABASES.get('default').get('HOST'), |
|||
port=settings.DATABASES.get('default').get('PORT'), |
|||
dbname=settings.DATABASES.get('default').get('NAME'), |
|||
) |
|||
|
|||
executors = { |
|||
'default': ThreadPoolExecutor(20) # 最多20个线程同时执行 |
|||
} |
|||
|
|||
# 实例化调度器 |
|||
scheduler = BackgroundScheduler(executors=executors) |
|||
# 调度器使用默认的DjangoJobStore() |
|||
scheduler.add_jobstore(jobstore='sqlalchemy', url=MYSQL_URL, |
|||
engine_options={'pool_pre_ping': True, 'pool_recycle': 3200}) |
|||
# 注册定时任务并开始 |
|||
|
|||
register_events(scheduler) |
|||
scheduler.start() |
|||
|
|||
|
|||
class TaskListView(APIView): |
|||
authentication_classes = (JSONWebTokenAuthentication,) |
|||
permission_classes = (IsAuthenticated,) |
|||
|
|||
def get(self, request, *args, **kwargs): |
|||
list = [] |
|||
for item in scheduler.get_jobs(): |
|||
next_run_time = item.next_run_time |
|||
if item.next_run_time: |
|||
next_run_time = item.next_run_time.strftime('%Y-%m-%d %H:%M:%S') |
|||
task = {'id': item.id, 'name': item.name, 'args': item.args, 'next_run_time': next_run_time} |
|||
list.append(task) |
|||
return CCAIResponse(list, status=OK) |
|||
|
|||
|
|||
class TaskAddView(APIView): |
|||
authentication_classes = (JSONWebTokenAuthentication,) |
|||
permission_classes = (IsAuthenticated,) |
|||
|
|||
def post(self, request, *args, **kwargs): |
|||
if not is_connection_usable(): |
|||
connection.close() |
|||
|
|||
try: |
|||
start_time = request.data.get('start_time') # 用户输入的任务开始时间, '10:00:00' |
|||
args = request.data.get('args', '1') # 接收执行任务的各种参数 |
|||
task_name = request.data.get('task_name') |
|||
task_type = request.data.get('task_type') |
|||
if task_type == 'interval': |
|||
start_time = start_time.split(':') |
|||
hour = int(start_time[0]) |
|||
minute = int(start_time[1]) |
|||
second = int(start_time[2]) |
|||
if task_type == 'cron': |
|||
if ':' in start_time: |
|||
start_time_list = start_time.split(':') |
|||
hour = int(start_time_list[0]) |
|||
minute = int(start_time_list[1]) |
|||
second = int(start_time_list[2]) |
|||
else: |
|||
croncode = start_time.split() |
|||
if len(croncode) != 5: |
|||
return CCAIResponse('cron表达式错误', BAD) |
|||
for index, p in enumerate(croncode): |
|||
if p == '*': |
|||
croncode[index] = None |
|||
second, minute, hour, day, month = croncode # 0 30 1 1 1 0 30 1 1 * |
|||
|
|||
# 创建任务 |
|||
if task_type == 'date': |
|||
start_time = datetime.datetime.strptime(start_time, "%Y-%m-%d %H:%M:%S") |
|||
scheduler.add_job(getattr(TasksFactory, task_name), task_type, max_instances=10, run_date=start_time, |
|||
args=[args]) |
|||
if task_type == 'interval': |
|||
scheduler.add_job(getattr(TasksFactory, task_name), task_type, max_instances=10, hours=hour, |
|||
minutes=minute, seconds=second, args=[args]) |
|||
if task_type == 'cron': |
|||
if ':' in start_time: |
|||
scheduler.add_job(getattr(TasksFactory, task_name), task_type, max_instances=10, hour=hour, |
|||
minute=minute, second=second, args=[args]) |
|||
else: |
|||
scheduler.add_job(getattr(TasksFactory, task_name), 'cron', second=second, minute=minute, hour=hour, |
|||
day=day, month=month, args=[args]) |
|||
return CCAIResponse('创建任务成功', status=OK) |
|||
except Exception as e: |
|||
logger.error("user: %s, add task failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse('创建任务失败', status=BAD) |
|||
|
|||
|
|||
class TaskStopView(APIView): |
|||
authentication_classes = (JSONWebTokenAuthentication,) |
|||
permission_classes = (IsAuthenticated,) |
|||
|
|||
def post(self, request, *args, **kwargs): |
|||
if not is_connection_usable(): |
|||
connection.close() |
|||
|
|||
try: |
|||
task_name = request.data.get('task_name') |
|||
scheduler.pause_job(task_name) |
|||
|
|||
return CCAIResponse('停止任务成功', status=OK) |
|||
except Exception as e: |
|||
logger.error("user: %s, stop task failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse('停止任务失败', status=BAD) |
|||
|
|||
|
|||
class TaskStartView(APIView): |
|||
authentication_classes = (JSONWebTokenAuthentication,) |
|||
permission_classes = (IsAuthenticated,) |
|||
|
|||
def post(self, request, *args, **kwargs): |
|||
if not is_connection_usable(): |
|||
connection.close() |
|||
|
|||
try: |
|||
task_name = request.data.get('task_name') |
|||
scheduler.resume_job(task_name) |
|||
|
|||
return CCAIResponse('启动任务成功', status=OK) |
|||
except Exception as e: |
|||
logger.error("user: %s, start task failed: \n%s" % (request.user.id, traceback.format_exc())) |
|||
return CCAIResponse('启动任务失败', status=BAD) |
|||
|
|||
|
|||
class TaskUpdateView(APIView): |
|||
authentication_classes = (JSONWebTokenAuthentication,) |
|||
permission_classes = (IsAuthenticated,) |
|||
|
|||
def post(self, request, *args, **kwargs): |
|||
if not is_connection_usable(): |
|||
connection.close() |
|||
|
|||
if request.user.id is not None: |
|||
task_id = request.data.get("task_id") |
|||
task_name = request.data.get("task_name") |
|||
task_type = request.data.get("task_type") |
|||
start_time = request.data.get("start_time") |
|||
|
|||
args = request.data.get('args') # 接收执行任务的各种参数 |
|||
if task_type == 'interval': |
|||
start_time = start_time.split(':') |
|||
hour = int(start_time[0]) |
|||
minute = int(start_time[1]) |
|||
second = int(start_time[2]) |
|||
|
|||
if task_type == 'cron': |
|||
if ':' in start_time: |
|||
start_time_list = start_time.split(':') |
|||
hour = int(start_time_list[0]) |
|||
minute = int(start_time_list[1]) |
|||
second = int(start_time_list[2]) |
|||
else: |
|||
croncode = start_time.split() |
|||
if len(croncode) != 5: |
|||
return CCAIResponse('cron表达式错误', BAD) |
|||
for index, p in enumerate(croncode): |
|||
if p == '*': |
|||
croncode[index] = None |
|||
second, minute, hour, day, month = croncode # 0 30 1 1 1 |
|||
|
|||
# 创建任务 |
|||
if task_type == 'date': |
|||
temp_dict = {"run_date": start_time} |
|||
temp_trigger = scheduler._create_trigger(trigger='date', trigger_args=temp_dict) |
|||
result = scheduler.modify_job(job_id=task_id, max_instances=10, trigger=temp_trigger) |
|||
|
|||
if task_type == 'interval': |
|||
temp_dict = {'hours': hour, 'minutes': minute, "seconds": second} |
|||
temp_trigger = scheduler._create_trigger(trigger='interval', trigger_args=temp_dict) |
|||
result = scheduler.modify_job(job_id=task_id, max_instances=10, trigger=temp_trigger) |
|||
|
|||
if task_type == 'cron': |
|||
if ':' in start_time: |
|||
temp_dict = {'hour': hour, 'minute': minute, "second": second} |
|||
temp_trigger = scheduler._create_trigger(trigger='cron', trigger_args=temp_dict) |
|||
result = scheduler.modify_job(job_id=task_id, max_instances=10, trigger=temp_trigger) |
|||
else: |
|||
job = scheduler.get_job(job_id=task_id) |
|||
task_name = job.name |
|||
task_name = task_name.split('.')[1] |
|||
scheduler.remove_job(job_id=task_id) |
|||
scheduler.add_job(getattr(TasksFactory, task_name), 'cron', second=second, minute=minute, hour=hour, |
|||
day=day, month=month, args=[1]) |
|||
|
|||
return CCAIResponse("更新任务成功!", status=OK) |
|||
else: |
|||
return CCAIResponse("更新任务失败!", status=BAD) |
|||
|
|||
|
|||
class TaskDeleteView(APIView): |
|||
authentication_classes = (JSONWebTokenAuthentication,) |
|||
permission_classes = (IsAuthenticated,) |
|||
|
|||
def post(self, request, *args, **kwargs): |
|||
""" |
|||
删除任务 |
|||
:param task_ids: 任务id的list |
|||
:return: |
|||
""" |
|||
if not is_connection_usable(): |
|||
connection.close() |
|||
|
|||
if request.user is not None: |
|||
task_name = request.data.get("task_name") |
|||
scheduler.remove_job(task_name) |
|||
return CCAIResponse("删除任务成功!", status=OK) |
|||
else: |
|||
return CCAIResponse("删除任务失败!", status=BAD) |
|||
|
|||
|
|||
class TaskSearchView(APIView): |
|||
authentication_classes = (JSONWebTokenAuthentication,) |
|||
permission_classes = (IsAuthenticated,) |
|||
|
|||
def post(self, request, *args, **kwargs): |
|||
""" |
|||
任务查询 |
|||
:param task_name: 任务名称 |
|||
:param task_queue: 任务队列 |
|||
:return: task_name任务名称、task_queue任务队列、task_args任务参数、task_class任务执行类、task_cron任务定时的表达式 |
|||
""" |
|||
if not is_connection_usable(): |
|||
connection.close() |
|||
|
|||
# 查询目前满足条件的所有周期性任务 |
|||
if request.user is not None: |
|||
task_name = request.data.get("task_name") |
|||
task_queue = request.data.get("task_queue") |
|||
|
|||
# return CCAIResponse(data) |
|||
else: |
|||
return CCAIResponse("查询任务失败!", status=BAD) |
|||
@ -0,0 +1,22 @@ |
|||
#!/usr/bin/env python |
|||
"""Django's command-line utility for administrative tasks.""" |
|||
import os |
|||
import sys |
|||
|
|||
|
|||
def main(): |
|||
"""Run administrative tasks.""" |
|||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ChaCeRndTrans.settings') |
|||
try: |
|||
from django.core.management import execute_from_command_line |
|||
except ImportError as exc: |
|||
raise ImportError( |
|||
"Couldn't import Django. Are you sure it's installed and " |
|||
"available on your PYTHONPATH environment variable? Did you " |
|||
"forget to activate a virtual environment?" |
|||
) from exc |
|||
execute_from_command_line(sys.argv) |
|||
|
|||
|
|||
if __name__ == '__main__': |
|||
main() |
|||
@ -0,0 +1,121 @@ |
|||
讨论项目进展与研发团队 |
|||
评审技术方案参与 |
|||
协助新产品功能测试 |
|||
编写文档技术 |
|||
优化系统架构现有 |
|||
参与审查代码 |
|||
设计模块新功能 |
|||
分析反馈用户并改进产品 |
|||
研究应用新技术 |
|||
开发测试工具自动化 |
|||
参与计划产品发布 |
|||
沟通客户技术需求 |
|||
改进设计用户界面 |
|||
进行测试性能 |
|||
解决故障技术 |
|||
参与培训技术 |
|||
编写接口文档API |
|||
设计结构数据库 |
|||
测试项目 |
|||
参与规划项目 |
|||
与团队研发讨论进展项目 |
|||
参与评审方案技术 |
|||
协助测试功能新产品 |
|||
编写技术的文档 |
|||
优化架构系统现有 |
|||
参与代码的审查 |
|||
设计功能模块新 |
|||
分析用户的反馈并改进产品 |
|||
研究新技术的应用 |
|||
开发自动化的测试工具 |
|||
参与发布计划产品 |
|||
与客户沟通需求技术 |
|||
改进用户的界面设计 |
|||
进行性能的测试 |
|||
解决故障的技术 |
|||
参与培训的技术 |
|||
编写API的接口文档 |
|||
设计数据库的结构 |
|||
测试的项目 |
|||
参与项目的规划 |
|||
讨论研发团队项目进展 |
|||
参与技术评审方案 |
|||
协助功能测试新产品 |
|||
编写文档技术 |
|||
优化架构现有系统 |
|||
参与代码审查 |
|||
设计模块功能新 |
|||
分析反馈用户改进产品 |
|||
研究应用技术新 |
|||
开发工具自动化测试 |
|||
参与计划发布产品 |
|||
沟通技术需求客户 |
|||
改进界面设计用户 |
|||
进行测试性能 |
|||
解决故障技术 |
|||
参与培训技术 |
|||
编写文档接口API |
|||
设计结构数据库 |
|||
测试项目 |
|||
参与规划项目 |
|||
研发团队讨论进展项目 |
|||
评审参与方案技术 |
|||
协助测试新功能产品 |
|||
编写技术文档 |
|||
优化系统架构现有 |
|||
代码参与审查 |
|||
设计新模块功能 |
|||
分析用户反馈改进产品 |
|||
研究新应用技术 |
|||
开发自动化工具测试 |
|||
参与发布产品计划 |
|||
客户沟通需求技术 |
|||
改进用户设计界面 |
|||
进行性能测试 |
|||
解决技术故障 |
|||
参与技术培训 |
|||
编写接口API文档 |
|||
设计数据库结构 |
|||
项目测试 |
|||
规划参与项目 |
|||
讨论项目进展与团队研发 |
|||
参与方案评审技术 |
|||
协助测试功能新产品 |
|||
编写文档的技术 |
|||
优化架构的现有系统 |
|||
参与审查的代码 |
|||
设计功能的新模块 |
|||
分析反馈的用户并改进产品 |
|||
研究应用的新技术 |
|||
开发工具的 |
|||
自动化测试 |
|||
参与计划的产品发布 |
|||
沟通需求的客户技术 |
|||
改进设计的用户界面 |
|||
进行测试的性能 |
|||
解决技术的故障 |
|||
参与培训的技术 |
|||
编写文档的API接口 |
|||
设计结构的数据库 |
|||
测试的项目 |
|||
参与规划的项目 |
|||
研发团队进展项目讨论 |
|||
方案技术评审参与 |
|||
功能新产品测试协助 |
|||
文档技术编写 |
|||
架构现有系统优化 |
|||
审查代码参与 |
|||
模块新功能设计 |
|||
用户反馈分析改进产品 |
|||
新技术应用研究 |
|||
自动化测试工具开发 |
|||
产品发布计划参与 |
|||
技术需求客户沟通 |
|||
用户界面设计改进 |
|||
性能测试进行 |
|||
技术故障解决 |
|||
技术培训参与 |
|||
API接口文档编写 |
|||
数据库结构设计 |
|||
项目测试 |
|||
项目规划参与 |
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
@ -0,0 +1,83 @@ |
|||
# _*_ coding: utf-8 _*_ |
|||
# @Time : 2021/5/27 14:58 |
|||
# @File : funcs.py |
|||
# @Software: PyCharm |
|||
|
|||
""" |
|||
此文件存放一些公用的工具函数 |
|||
""" |
|||
import datetime |
|||
import random |
|||
from random import shuffle |
|||
from django_redis import get_redis_connection |
|||
|
|||
from utils.custom import MyCustomError |
|||
|
|||
|
|||
def generate_random_str(randomlength=16): |
|||
""" |
|||
生成一个指定长度的随机字符串 |
|||
""" |
|||
random_str = '' |
|||
base_str = 'abcdefghigklmnopqrstuvwxyz0123456789' |
|||
length = len(base_str) - 1 |
|||
for i in range(randomlength): |
|||
random_str += base_str[random.randint(0, length)] |
|||
return random_str |
|||
|
|||
|
|||
def generate_random_str_16_system(randomlength=16): |
|||
""" |
|||
生成一个指定长度的随机字符串, 这个字符串看起来像16进制的 |
|||
""" |
|||
random_str = '' |
|||
base_str = '0123456789abcdef' |
|||
length = len(base_str) - 1 |
|||
for i in range(randomlength): |
|||
random_str += base_str[random.randint(0, length)] |
|||
return random_str |
|||
|
|||
|
|||
# 获取乱序字符串 |
|||
def shuffle_str(s): |
|||
# 将字符串转换成列表 |
|||
str_list = list(s) |
|||
# 调用random模块的shuffle函数打乱列表 |
|||
shuffle(str_list) |
|||
# 将列表转字符串 |
|||
return ''.join(str_list) |
|||
|
|||
|
|||
# 获取原来字符串在新字符串中的位置 |
|||
def str_map(origin, new_str): |
|||
result_str = [] |
|||
new_str_list = list(new_str) |
|||
for each in origin: |
|||
index = new_str_list.index(each) |
|||
result_str.append(index) |
|||
# 生成轨迹 |
|||
track = random.sample(range(0, len(result_str)), len(result_str)) |
|||
return result_str, track |
|||
|
|||
|
|||
# 定义尝试的日期格式列表 |
|||
sys_formats = ["%Y-%m-%d", "%Y/%m/%d", "%Y年%m月%d日", "%Y%m%d"] |
|||
|
|||
|
|||
def parse_date(date_string, formats=sys_formats): |
|||
""" |
|||
尝试按照提供的格式解析日期字符串, 返回日期date |
|||
@param date_string: 日期字符串 |
|||
@param formats: 尝试格式 |
|||
@return: |
|||
""" |
|||
if not isinstance(formats, list): |
|||
if not isinstance(formats, str) or formats not in sys_formats: |
|||
raise MyCustomError("日期格式不支持") |
|||
formats = [formats, ] |
|||
for fmt in formats: |
|||
try: |
|||
return datetime.datetime.strptime(date_string, fmt).date() |
|||
except ValueError: |
|||
pass |
|||
raise MyCustomError("无法解析日期字符串") |
|||
@ -0,0 +1,56 @@ |
|||
import requests, json |
|||
import time |
|||
import hashlib |
|||
from ChaCeRndTrans import settings |
|||
|
|||
|
|||
# 发送短信接口 |
|||
def send_msg(phone, content): |
|||
url = settings.URL |
|||
account = settings.ACCOUNT |
|||
pwd = settings.PWD |
|||
mttime = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time())) |
|||
# import md5 #Python2里的引用 |
|||
# s.encode()#变成bytes类型才能加密 |
|||
m = hashlib.md5((pwd + mttime).encode()) |
|||
md = m.hexdigest() |
|||
postData = "name=%s&pwd=%s&phone=%s&content=%s&mttime=%s&rpttype=1" % (account, md, phone, content, mttime) |
|||
|
|||
headers = { |
|||
'Content-Type': 'application/x-www-form-urlencoded', |
|||
'Cookie': 'secure' |
|||
} |
|||
response = requests.request("POST", url, headers=headers, data=postData.encode(encoding='UTF-8', errors='strict')) |
|||
# result = json.loads(response.text) |
|||
return response |
|||
|
|||
|
|||
if __name__ == '__main__': |
|||
mttime = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time())) |
|||
print(mttime) |
|||
url = settings.URL |
|||
Account = settings.ACCOUNT |
|||
Pwd = settings.PWD |
|||
m = hashlib.md5((Pwd + mttime).encode()) |
|||
md = m.hexdigest() |
|||
phone = '13580505142' |
|||
content = '【查策网】验证码:123456' |
|||
# params = u'{"name":"szccwl","pwd":"md","address":"bz","phone":"13000000000"}' |
|||
postData = "name=szccwl&pwd=%s&phone=%s&content=%s&mttime=%s&rpttype=1" % (md, phone, content, mttime) |
|||
# payload = 'name=szccwl&pwd=7e1ae2114ac4238d02e919a72b41026b&phone=18819470249&content=123456&mttime=20210106174609&rpttype=1' |
|||
headers = { |
|||
'Content-Type': 'application/x-www-form-urlencoded', |
|||
'Cookie': 'secure' |
|||
} |
|||
|
|||
response = requests.request("POST", url, headers=headers, data=postData.encode(encoding='UTF-8', errors='strict')) |
|||
|
|||
print(response.text) |
|||
|
|||
# r = requests.post(url, data=postData) |
|||
# key = json.loads(r.text) |
|||
# print(key) |
|||
# 这一步将返回值转成json |
|||
# key = json.loads(r.text) |
|||
# __business_id = uuid.uuid1() |
|||
# print(__business_id) |
|||
@ -0,0 +1,128 @@ |
|||
import base64 |
|||
import http |
|||
import json |
|||
import logging |
|||
import threading |
|||
import uuid |
|||
import traceback |
|||
|
|||
from django.utils.deprecation import MiddlewareMixin |
|||
from rest_framework import response as rest_response |
|||
from utils.custom import get_ip |
|||
|
|||
local = threading.local() # 获取当前线程对象 |
|||
|
|||
logger = logging.getLogger("info") |
|||
error_logger = logging.getLogger("error") |
|||
|
|||
|
|||
class RequestLogFilter(logging.Filter): |
|||
""" |
|||
日志过滤器,将当前请求线程的request信息保存到日志的record上下文 |
|||
record带有formater需要的信息。 |
|||
""" |
|||
|
|||
def filter(self, record): |
|||
record.method = getattr(local, 'method', "none") |
|||
record.path = getattr(local, 'path', "none") |
|||
record.request_id = getattr(local, 'request_id', "none") |
|||
record.params = getattr(local, 'params', "none") |
|||
record.body = getattr(local, 'body', "none") |
|||
record.user = getattr(local, 'user', "none") |
|||
return True |
|||
|
|||
|
|||
class RequestLogMiddleware(MiddlewareMixin): |
|||
""" |
|||
将request的信息记录在当前的请求线程上。 |
|||
""" |
|||
def process_request(self, request): |
|||
# 以下逻辑是 实现请求进来打印 相关请求参数 |
|||
local.request_id = str(uuid.uuid1()) # 线程对象里面加入uuid |
|||
body = {} |
|||
try: |
|||
body = request.body.decode() |
|||
if body: |
|||
body = json.loads(body) |
|||
except: |
|||
pass |
|||
token = request.META.get("HTTP_AUTHORIZATION") |
|||
local.user = "guest" |
|||
if token: |
|||
try: |
|||
user_base64 = token.split(".")[1] |
|||
missing_padding = 4 - len(user_base64) % 4 |
|||
if missing_padding: |
|||
user_base64 += '=' * missing_padding |
|||
|
|||
local.user = str(base64.b64decode(bytes(user_base64, 'utf-8')), encoding='utf-8') |
|||
except: |
|||
local.user = token |
|||
pass |
|||
|
|||
local.method = request.method |
|||
local.params = request.GET.dict() |
|||
local.path = request.path_info |
|||
local.body = json.dumps(body) |
|||
local.ip = get_ip(request) |
|||
|
|||
request_info = { |
|||
"ip": get_ip(request), |
|||
"request_id": local.request_id, |
|||
'method': request.method, |
|||
'path': request.path_info, |
|||
'params': request.GET.dict(), |
|||
'body': body, |
|||
'user': local.user, |
|||
} |
|||
|
|||
logger.info(f'requests: {json.dumps(request_info, ensure_ascii=False)}') |
|||
|
|||
def process_response(self, request, response): |
|||
""" |
|||
当请求是媒体请求时,需要设置对应的响应头以方便浏览器可以缓存媒体的当前播放时间,从而解决拉进度条会回弹到原点的bug |
|||
""" |
|||
if request.path.endswith(".mp3"): |
|||
response["Accept-Ranges"] = "bytes" |
|||
token = request.META.get("HTTP_AUTHORIZATION") |
|||
local.user = "guest" |
|||
if token: |
|||
try: |
|||
user_base64 = token.split(".")[1] |
|||
missing_padding = 4 - len(user_base64) % 4 |
|||
if missing_padding: |
|||
user_base64 += '=' * missing_padding |
|||
|
|||
local.user = str(base64.b64decode(bytes(user_base64, 'utf-8')), encoding='utf-8') |
|||
except Exception as e: |
|||
local.user = token |
|||
error_logger.error( |
|||
"process_response failed: \n%s" % (traceback.format_exc())) |
|||
pass |
|||
data = response.status_code |
|||
# if response.data: |
|||
# data = response.data |
|||
local.method = request.method |
|||
local.params = request.GET.dict() |
|||
local.path = request.path_info |
|||
local.ip = get_ip(request) |
|||
local.response = data |
|||
|
|||
request_info = { |
|||
"ip": get_ip(request), |
|||
"request_id": local.request_id, |
|||
'method': request.method, |
|||
'path': request.path_info, |
|||
'params': request.GET.dict(), |
|||
'user': local.user, |
|||
"response": data, |
|||
} |
|||
|
|||
logger.info('response: %r' % request_info) |
|||
return response |
|||
|
|||
|
|||
class ExceptionLoggingMiddleware(MiddlewareMixin): |
|||
def process_exception(self, request, exception): |
|||
import traceback |
|||
error_logger.error(traceback.format_exc()) |
|||
@ -0,0 +1,147 @@ |
|||
import logging |
|||
import traceback |
|||
|
|||
from aliyunsdkcore.acs_exception.exceptions import ServerException, ClientException |
|||
from aliyunsdkcore.client import AcsClient |
|||
from aliyunsdkcore.request import CommonRequest |
|||
|
|||
from ChaCeRndTrans import settings |
|||
|
|||
error_logger = logging.getLogger("error") |
|||
|
|||
|
|||
def get_client(): |
|||
access_key_id = settings.ACCESS_KEY_ID |
|||
access_secret = settings.ACCESS_SECRET |
|||
client = AcsClient(access_key_id, access_secret, 'cn-hangzhou') |
|||
return client |
|||
|
|||
|
|||
# 获取reques对象 |
|||
# method_type 请求方式 get/post |
|||
# protocol_type https/http请求 |
|||
# action_name 方法名 |
|||
def get_request(method_type, protocol_type, action_name): |
|||
request = CommonRequest() |
|||
request.set_accept_format('json') |
|||
request.set_domain('dysmsapi.aliyuncs.com') |
|||
request.set_method(method_type) # get | POST |
|||
request.set_protocol_type(protocol_type) # https | http |
|||
request.set_version('2017-05-25') |
|||
request.set_action_name(action_name) # 方法名 |
|||
return request |
|||
|
|||
|
|||
# 发送短信接口 |
|||
def send_msg(phone_number, sign_name, template_code, template_param): |
|||
request = get_request('post', 'https', 'SendSms') |
|||
client = get_client() |
|||
# 调用发送短信接口 |
|||
request.add_query_param('RegionId', "cn-hangzhou") |
|||
request.add_query_param('PhoneNumbers', phone_number) |
|||
request.add_query_param('SignName', sign_name) # 签名 |
|||
request.add_query_param('TemplateCode', template_code) |
|||
request.add_query_param('TemplateParam', template_param) |
|||
try: |
|||
response = client.do_action_with_exception(request) |
|||
except ServerException as e: |
|||
# ServerException的处理逻辑 |
|||
error_logger.error(traceback.format_exc()) |
|||
except ClientException as e: |
|||
# ClientException的处理逻辑 |
|||
error_logger.error(traceback.format_exc()) |
|||
print(str(response, encoding='utf-8')) |
|||
return response |
|||
|
|||
|
|||
# source_url:原始链接地址,不超过1000个字符长度。 |
|||
# short_url_name:短链服务名称,不超过12个字符长度。 |
|||
# effective_days:短链服务使用有效期,以天为单位,不超过30天。 |
|||
def add_short_url(source_url, short_url_name, effective_days): |
|||
request = get_request('post', 'https', 'AddShortUrl') |
|||
client = get_client() |
|||
request.add_query_param('RegionId', "cn-hangzhou") |
|||
request.add_query_param('SourceUrl', source_url) |
|||
request.add_query_param('ShortUrlName', short_url_name) |
|||
request.add_query_param('EffectiveDays', effective_days) |
|||
try: |
|||
response = client.do_action_with_exception(request) |
|||
except ServerException as e: |
|||
error_logger.error(traceback.format_exc()) |
|||
except ClientException as e: |
|||
error_logger.error(traceback.format_exc()) |
|||
print(str(response, encoding='utf-8')) |
|||
return response |
|||
|
|||
|
|||
# 添加模板接口 |
|||
def add_template(template_type, template_name, template_content, remark): |
|||
request = get_request('post', 'https', 'AddSmsTemplate') |
|||
client = get_client() |
|||
request.add_query_param('RegionId', "cn-hangzhou") |
|||
request.add_query_param('TemplateType', template_type) |
|||
request.add_query_param('TemplateName', template_name) |
|||
request.add_query_param('TemplateContent', template_content) |
|||
request.add_query_param('Remark', remark) |
|||
try: |
|||
response = client.do_action_with_exception(request) |
|||
except ServerException as e: |
|||
error_logger.error(traceback.format_exc()) |
|||
except ClientException as e: |
|||
error_logger.error(traceback.format_exc()) |
|||
print(str(response, encoding='utf-8')) |
|||
return response |
|||
|
|||
|
|||
# 删除模板接口 |
|||
def delete_template(template_code): |
|||
request = get_request('post', 'https', 'DeleteSmsTemplate') |
|||
client = get_client() |
|||
|
|||
request.add_query_param('RegionId', "cn-hangzhou") |
|||
request.add_query_param('TemplateCode', template_code) |
|||
try: |
|||
response = client.do_action_with_exception(request) |
|||
except ServerException as e: |
|||
error_logger.error(traceback.format_exc()) |
|||
except ClientException as e: |
|||
error_logger.error(traceback.format_exc()) |
|||
print(str(response, encoding='utf-8')) |
|||
return response |
|||
|
|||
|
|||
# 修改模板接口 |
|||
def edit_template(template_type, template_name, template_content, remark, template_code): |
|||
request = get_request('post', 'https', 'ModifySmsTemplate') |
|||
client = get_client() |
|||
request.add_query_param('RegionId', "cn-hangzhou") |
|||
request.add_query_param('TemplateType', template_type) |
|||
request.add_query_param('TemplateName', template_name) |
|||
request.add_query_param('TemplateCode', template_code) |
|||
request.add_query_param('TemplateContent', template_content) |
|||
request.add_query_param('Remark', remark) |
|||
try: |
|||
response = client.do_action_with_exception(request) |
|||
except ServerException as e: |
|||
error_logger.error(traceback.format_exc()) |
|||
except ClientException as e: |
|||
error_logger.error(traceback.format_exc()) |
|||
print(str(response, encoding='utf-8')) |
|||
return response |
|||
|
|||
|
|||
# 查询模板接口 |
|||
def query_template(template_code): |
|||
request = get_request('post', 'https', 'QuerySmsTemplate') |
|||
client = get_client() |
|||
|
|||
request.add_query_param('RegionId', "cn-hangzhou") |
|||
request.add_query_param('TemplateCode', template_code) |
|||
try: |
|||
response = client.do_action_with_exception(request) |
|||
except ServerException as e: |
|||
error_logger.error(traceback.format_exc()) |
|||
except ClientException as e: |
|||
error_logger.error(traceback.format_exc()) |
|||
print(str(response, encoding='utf-8')) |
|||
return response |
|||
@ -0,0 +1,123 @@ |
|||
import math |
|||
import sys |
|||
import time |
|||
from concurrent.futures import ThreadPoolExecutor |
|||
import threading |
|||
|
|||
|
|||
class ThreadPool: |
|||
def __init__(self, max_thread_num=5): |
|||
# 记录全部线程是否已经结束 |
|||
self.over = False |
|||
# 记录所有的子线程完成后的返回值 |
|||
self.results = [] |
|||
|
|||
# 子线程函数体 |
|||
self.func = None |
|||
# 需要传进子线程的参数,数组中每一个元素都是一个元组 |
|||
# 例如有一个函数定义add(a,b),返回a和b的和 |
|||
# 则数组表现为[(1,2),(3,10),...] |
|||
# 可以依据数组中的每一个元组建立一个线程 |
|||
self.args_list = None |
|||
# 需要完成的任务的数量,获取自参数数组的长度 |
|||
self.task_num = 0 |
|||
# 线程池同时容纳的最大线程数,默认为5 |
|||
self.max_thread_num = max_thread_num |
|||
# 初始化线程池 |
|||
self.pool = ThreadPoolExecutor(max_workers=max_thread_num) |
|||
self.cond = threading.Condition() |
|||
|
|||
# 设置线程池中执行任务的各项参数 |
|||
def set_tasks(self, func, args_list): |
|||
# 需要完成的任务的数量,获取自参数数组的长度 |
|||
self.task_num = len(args_list) |
|||
# 参数数组 |
|||
self.args_list = args_list |
|||
# 线程中执行的函数体 |
|||
self.func = func |
|||
|
|||
# 显示进度条,用以查看所有任务的完成进度 |
|||
@staticmethod |
|||
def show_process(desc_text, curr, total): |
|||
proc = math.ceil(curr / total * 100) |
|||
show_line = '\r' + desc_text + ':' + '>' * proc \ |
|||
+ ' ' * (100 - proc) + '[%s%%]' % proc \ |
|||
+ '[%s/%s]' % (curr, total) |
|||
sys.stdout.write(show_line) |
|||
sys.stdout.flush() |
|||
time.sleep(0.1) |
|||
|
|||
# 线程完成后的回调,功能有3 |
|||
# 1:监控所有任务的完成进度 |
|||
# 2:收集任务完成后的结果 |
|||
# 3.继续向线程池中添加新的任务 |
|||
def get_result(self, future): |
|||
# 监控线程完成进度 |
|||
self.show_process('任务完成进度', self.task_num - len(self.args_list), self.task_num) |
|||
# 将函数处理的返回值添加到结果集合当中,若没有返回值,则future.result()的值是None |
|||
self.results.append(future.result()) |
|||
# 若参数数组中含有元素,则说明还有后续的任务 |
|||
if len(self.args_list): |
|||
# 提取出将要执行的一个任务的参数 |
|||
args = self.args_list.pop() |
|||
# 向线程池中提交一个新任务,第一个参数是函数体,第二个参数是执行函数时所需要的各项参数 |
|||
task = self.pool.submit(self.func, *args) |
|||
# 绑定任务完成后的回调 |
|||
task.add_done_callback(self.get_result) |
|||
else: |
|||
# 若结果的数量与任务的数量相等,则说明所有的任务已经完成 |
|||
if self.task_num == len(self.results): |
|||
print('\n', '任务完成') |
|||
# 获取锁 |
|||
self.cond.acquire() |
|||
# 通知 |
|||
self.cond.notify() |
|||
# 释放锁 |
|||
self.cond.release() |
|||
return |
|||
|
|||
def _start_tasks(self): |
|||
# 向线程池中添加到最大数量的线程 |
|||
for i in range(self.max_thread_num): |
|||
# 作出所有任务是否已经完成的判断,原因如下: |
|||
# 如果直接向线程池提交巨大数量的任务,线程池会创建任务队列,占用大量内存 |
|||
# 为减少创建任务队列的巨大开销,本类中所有子线程在完成后的回调中,会向线程池中提交新的任务 |
|||
# 循环往复,直到所有任务全部完成,而任务队列几乎不存在 |
|||
# 1:当提交的任务数量小于线程池容纳的最大线程数,在本循环中,必会出现所有任务已经提交的情况 |
|||
# 2:当函数执行速度非常快的时候,也会出现所有任务已经提交的情况 |
|||
|
|||
# 如果参数数组中还有元素,则说明没有到达线程池的上限 |
|||
if len(self.args_list): |
|||
# 取出一组参数,同时删除该任务 |
|||
args = self.args_list.pop() |
|||
# 向线程池中提交新的任务 |
|||
task = self.pool.submit(self.func, *args) |
|||
# 绑定任务完成后的回调 |
|||
task.add_done_callback(self.get_result) |
|||
# 所有任务已经全部提交,跳出循环 |
|||
else: |
|||
break |
|||
|
|||
# 获取最终所有线程完成后的处理结果 |
|||
def final_results(self): |
|||
# 开始执行所有任务 |
|||
self._start_tasks() |
|||
# 获取结果时,会有两种情况 |
|||
# 所有的任务都已经完成了,直接返回结果就行 |
|||
if self.task_num == len(self.results): |
|||
return self.results |
|||
# 线程池中还有未完成的线程,只有当线程池中的任务全部结束才能够获取到最终的结果 |
|||
# 这种情况会在线程池容量过大或者线程极度耗时时才会出现 |
|||
else: |
|||
# 获取锁 |
|||
self.cond.acquire() |
|||
# 阻塞当前线程,等待通知 |
|||
self.cond.wait() |
|||
# 已经获取到通知,释放锁 |
|||
self.cond.release() |
|||
# 返回结果集 |
|||
return self.results |
|||
|
|||
|
|||
# 创建线程池,最大线程数量为10 |
|||
tp = ThreadPool(10) |
|||
@ -0,0 +1,109 @@ |
|||
import logging |
|||
import traceback |
|||
|
|||
from django_redis import get_redis_connection |
|||
from rest_framework.throttling import ScopedRateThrottle |
|||
|
|||
import ChaCeRndTrans |
|||
from ChaCeRndTrans.basic import CCAIResponse |
|||
|
|||
err_logger = logging.getLogger('error') |
|||
|
|||
class CustomThrottle(ScopedRateThrottle): |
|||
cache_format = 'throttle_%(path)s_%(scope)s_%(ident)s' |
|||
# cache = get_redis_connection('db2') |
|||
cache = get_redis_connection('default') |
|||
|
|||
def parse_rate(self, rate): |
|||
""" |
|||
Given the request rate string, return a two tuple of: |
|||
<allowed number of requests>, <period of time in seconds> |
|||
""" |
|||
if rate is None: |
|||
return None |
|||
num, period = rate[:-1], rate[-1] |
|||
num = int(num) |
|||
duration = num * {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]] |
|||
return duration |
|||
|
|||
def allow_request(self, request, view): |
|||
try: |
|||
# We can only determine the scope once we're called by the view. |
|||
self.scope = getattr(view, self.scope_attr, None) |
|||
|
|||
# If a view does not have a `throttle_scope` always allow the request |
|||
if not self.scope: |
|||
return True |
|||
|
|||
# Determine the allowed request rate as we normally would during |
|||
# the `__init__` call. |
|||
self.rate = self.get_rate() |
|||
self.duration = self.parse_rate(self.rate) |
|||
|
|||
if self.rate is None: |
|||
return True |
|||
|
|||
# 无缓存 |
|||
self.key = self.get_cache_key(request, view) |
|||
if self.key is None: |
|||
return True |
|||
|
|||
# self.history = self.cache.get(self.cache.make_key(self.key), None) |
|||
tmp_history = self.cache.get(self.key) |
|||
self.history = float(tmp_history) if tmp_history else None |
|||
self.now = self.timer() |
|||
# Drop any requests from the history which have now passed the |
|||
# throttle duration |
|||
if self.history is None: |
|||
return self.throttle_success() |
|||
if self.history >= self.now - self.duration: |
|||
return self.throttle_failure() |
|||
return self.throttle_success() |
|||
except Exception as e: |
|||
err_logger.error("keys: %s throttle failed: \n%s" % (self.key or None, traceback.format_exc(),)) |
|||
raise Exception("限流器错误") |
|||
|
|||
def throttle_success(self): |
|||
""" |
|||
Inserts the current request's timestamp along with the key |
|||
into the cache. |
|||
""" |
|||
|
|||
self.history = self.now |
|||
self.cache.set(self.key, self.history, self.duration) |
|||
return True |
|||
|
|||
# def throttle_failure(self): |
|||
# """ |
|||
# Called when a request to the API has failed due to throttling. |
|||
# """ |
|||
# return False |
|||
|
|||
def get_cache_key(self, request, view): |
|||
""" |
|||
If `view.throttle_scope` is not set, don't apply this throttle. |
|||
|
|||
Otherwise generate the unique cache key by concatenating the user id |
|||
with the '.throttle_scope` property of the view. |
|||
""" |
|||
if request.user.is_authenticated: |
|||
ident = request.user.pk |
|||
else: |
|||
ident = self.get_ident(request) |
|||
|
|||
# return self.cache.make_key(self.cache_format % { |
|||
# 'scope': self.scope, |
|||
# 'ident': ident |
|||
# }) |
|||
return self.cache_format % { |
|||
'path': request.path, |
|||
'scope': self.scope, |
|||
'ident': ident |
|||
} |
|||
|
|||
def wait(self): |
|||
if self.history: |
|||
seconds = self.duration - (self.now - self.history) |
|||
else: |
|||
seconds = self.duration |
|||
return seconds |
|||
Loading…
Reference in new issue