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