Browse Source

first commit

master
崔伟栋_28095 10 months ago
commit
a7bd6e0f6b
  1. 12
      .gitignore
  2. 0
      ChaCeRndTrans/__init__.py
  3. 16
      ChaCeRndTrans/asgi.py
  4. 45
      ChaCeRndTrans/basic.py
  5. 85
      ChaCeRndTrans/code.py
  6. 44
      ChaCeRndTrans/database_router.py
  7. 458
      ChaCeRndTrans/settings.py
  8. 50
      ChaCeRndTrans/urls.py
  9. 16
      ChaCeRndTrans/wsgi.py
  10. 0
      apps/__init__.py
  11. 0
      apps/common/__init__.py
  12. 3
      apps/common/admin.py
  13. 6
      apps/common/apps.py
  14. 123
      apps/common/migrations/0001_initial.py
  15. 0
      apps/common/migrations/__init__.py
  16. 215
      apps/common/models.py
  17. 0
      apps/common/serializers/__init__.py
  18. 13
      apps/common/serializers/archive_serializer.py
  19. 57
      apps/common/serializers/dict_serializer.py
  20. 3
      apps/common/tests.py
  21. 25
      apps/common/urls.py
  22. 3
      apps/common/views.py
  23. 0
      apps/common/views/__init__.py
  24. 959
      apps/common/views/archive.py
  25. 72
      apps/common/views/area.py
  26. 413
      apps/common/views/dict.py
  27. 88
      apps/common/views/history.py
  28. 15
      apps/common/views/index.py
  29. 197
      apps/common/views/upload_file.py
  30. 0
      apps/rbac/__init__.py
  31. 3
      apps/rbac/admin.py
  32. 6
      apps/rbac/apps.py
  33. 121
      apps/rbac/migrations/0001_initial.py
  34. 0
      apps/rbac/migrations/__init__.py
  35. 129
      apps/rbac/models.py
  36. 0
      apps/rbac/serializers/__init__.py
  37. 13
      apps/rbac/serializers/batch_serializer.py
  38. 13
      apps/rbac/serializers/menu_serializer.py
  39. 12
      apps/rbac/serializers/permission_serializer.py
  40. 31
      apps/rbac/serializers/role_serializer.py
  41. 127
      apps/rbac/serializers/user_serializer.py
  42. 14
      apps/rbac/signals.py
  43. 3
      apps/rbac/tests.py
  44. 30
      apps/rbac/urls.py
  45. 3
      apps/rbac/views.py
  46. 96
      apps/rbac/views/FrontRole.py
  47. 286
      apps/rbac/views/Slider_Verification.py
  48. 393
      apps/rbac/views/SubAccount.py
  49. 0
      apps/rbac/views/__init__.py
  50. 560
      apps/rbac/views/company.py
  51. 138
      apps/rbac/views/menu.py
  52. 105
      apps/rbac/views/message.py
  53. 95
      apps/rbac/views/permission.py
  54. 73
      apps/rbac/views/role.py
  55. 1163
      apps/rbac/views/user.py
  56. 0
      apps/staff/__init__.py
  57. 3
      apps/staff/admin.py
  58. 5
      apps/staff/apps.py
  59. 177
      apps/staff/migrations/0001_initial.py
  60. 0
      apps/staff/migrations/__init__.py
  61. 650
      apps/staff/models.py
  62. 259
      apps/staff/serializers/Serializer.py
  63. 0
      apps/staff/serializers/__init__.py
  64. 28
      apps/staff/serializers/staffSerializer.py
  65. 3
      apps/staff/tests.py
  66. 9
      apps/staff/urls.py
  67. 301
      apps/staff/utils.py
  68. 3
      apps/staff/views.py
  69. 0
      apps/staff/views/__init__.py
  70. 566
      apps/staff/views/dept.py
  71. 1
      apps/tasks/__init__.py
  72. 3
      apps/tasks/admin.py
  73. 5
      apps/tasks/apps.py
  74. 0
      apps/tasks/migrations/__init__.py
  75. 20
      apps/tasks/models.py
  76. 18
      apps/tasks/tasks.py
  77. 0
      apps/tasks/tests.py
  78. 13
      apps/tasks/urls.py
  79. 256
      apps/tasks/views.py
  80. 22
      manage.py
  81. 121
      media/research_descriptions.txt
  82. BIN
      media/人员工时生成表.xlsx
  83. BIN
      media/人员工资导入模板.xlsx
  84. BIN
      media/场景一模版.zip
  85. BIN
      requirements.txt
  86. BIN
      requirementstest.txt
  87. 0
      utils/__init__.py
  88. 1017
      utils/custom.py
  89. 83
      utils/funcs.py
  90. 56
      utils/http_sms_mt.py
  91. 128
      utils/middleware.py
  92. 147
      utils/sms_client.py
  93. 123
      utils/thread_pool.py
  94. 109
      utils/throttles.py

12
.gitignore

@ -0,0 +1,12 @@
.vscode/
.vs/
.idea
.vscode
.DS_Store
logs/
celecelerybeat.*
celerybeat-schedule.*
*/__pycache__
__pycache__/
.pytest_cache/
.idea/

0
ChaCeRndTrans/__init__.py

16
ChaCeRndTrans/asgi.py

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

45
ChaCeRndTrans/basic.py

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

85
ChaCeRndTrans/code.py

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

44
ChaCeRndTrans/database_router.py

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

458
ChaCeRndTrans/settings.py

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

50
ChaCeRndTrans/urls.py

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

16
ChaCeRndTrans/wsgi.py

@ -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
apps/__init__.py

0
apps/common/__init__.py

3
apps/common/admin.py

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

6
apps/common/apps.py

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

123
apps/common/migrations/0001_initial.py

@ -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
apps/common/migrations/__init__.py

215
apps/common/models.py

@ -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
apps/common/serializers/__init__.py

13
apps/common/serializers/archive_serializer.py

@ -0,0 +1,13 @@
from rest_framework import serializers
from common.models import Archive
class ArchiveSerializer(serializers.ModelSerializer):
"""
用户上传资料管理序列化
"""
class Meta:
model = Archive
fields = "__all__"

57
apps/common/serializers/dict_serializer.py

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

3
apps/common/tests.py

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

25
apps/common/urls.py

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

3
apps/common/views.py

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

0
apps/common/views/__init__.py

959
apps/common/views/archive.py

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

72
apps/common/views/area.py

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

413
apps/common/views/dict.py

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

88
apps/common/views/history.py

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

15
apps/common/views/index.py

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

197
apps/common/views/upload_file.py

@ -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
apps/rbac/__init__.py

3
apps/rbac/admin.py

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

6
apps/rbac/apps.py

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

121
apps/rbac/migrations/0001_initial.py

@ -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
apps/rbac/migrations/__init__.py

129
apps/rbac/models.py

@ -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
apps/rbac/serializers/__init__.py

13
apps/rbac/serializers/batch_serializer.py

@ -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": "必须填写批次名"}}}

13
apps/rbac/serializers/menu_serializer.py

@ -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": "必须填写菜单名"}}}

12
apps/rbac/serializers/permission_serializer.py

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

31
apps/rbac/serializers/role_serializer.py

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

127
apps/rbac/serializers/user_serializer.py

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

14
apps/rbac/signals.py

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

3
apps/rbac/tests.py

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

30
apps/rbac/urls.py

@ -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()), # 微信绑定接口
]

3
apps/rbac/views.py

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

96
apps/rbac/views/FrontRole.py

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

286
apps/rbac/views/Slider_Verification.py

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

393
apps/rbac/views/SubAccount.py

@ -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
apps/rbac/views/__init__.py

560
apps/rbac/views/company.py

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

138
apps/rbac/views/menu.py

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

105
apps/rbac/views/message.py

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

95
apps/rbac/views/permission.py

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

73
apps/rbac/views/role.py

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

1163
apps/rbac/views/user.py

File diff suppressed because it is too large

0
apps/staff/__init__.py

3
apps/staff/admin.py

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

5
apps/staff/apps.py

@ -0,0 +1,5 @@
from django.apps import AppConfig
class StaffConfig(AppConfig):
name = 'staff'

177
apps/staff/migrations/0001_initial.py

@ -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
apps/staff/migrations/__init__.py

650
apps/staff/models.py

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

259
apps/staff/serializers/Serializer.py

@ -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
apps/staff/serializers/__init__.py

28
apps/staff/serializers/staffSerializer.py

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

3
apps/staff/tests.py

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

9
apps/staff/urls.py

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

301
apps/staff/utils.py

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

3
apps/staff/views.py

@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

0
apps/staff/views/__init__.py

566
apps/staff/views/dept.py

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

1
apps/tasks/__init__.py

@ -0,0 +1 @@
default_app_config = "tasks.apps.TasksConfig"

3
apps/tasks/admin.py

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

5
apps/tasks/apps.py

@ -0,0 +1,5 @@
from django.apps import AppConfig
class TasksConfig(AppConfig):
name = 'tasks'

0
apps/tasks/migrations/__init__.py

20
apps/tasks/models.py

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

18
apps/tasks/tasks.py

@ -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
apps/tasks/tests.py

13
apps/tasks/urls.py

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

256
apps/tasks/views.py

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

22
manage.py

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

121
media/research_descriptions.txt

@ -0,0 +1,121 @@
讨论项目进展与研发团队
评审技术方案参与
协助新产品功能测试
编写文档技术
优化系统架构现有
参与审查代码
设计模块新功能
分析反馈用户并改进产品
研究应用新技术
开发测试工具自动化
参与计划产品发布
沟通客户技术需求
改进设计用户界面
进行测试性能
解决故障技术
参与培训技术
编写接口文档API
设计结构数据库
测试项目
参与规划项目
与团队研发讨论进展项目
参与评审方案技术
协助测试功能新产品
编写技术的文档
优化架构系统现有
参与代码的审查
设计功能模块新
分析用户的反馈并改进产品
研究新技术的应用
开发自动化的测试工具
参与发布计划产品
与客户沟通需求技术
改进用户的界面设计
进行性能的测试
解决故障的技术
参与培训的技术
编写API的接口文档
设计数据库的结构
测试的项目
参与项目的规划
讨论研发团队项目进展
参与技术评审方案
协助功能测试新产品
编写文档技术
优化架构现有系统
参与代码审查
设计模块功能新
分析反馈用户改进产品
研究应用技术新
开发工具自动化测试
参与计划发布产品
沟通技术需求客户
改进界面设计用户
进行测试性能
解决故障技术
参与培训技术
编写文档接口API
设计结构数据库
测试项目
参与规划项目
研发团队讨论进展项目
评审参与方案技术
协助测试新功能产品
编写技术文档
优化系统架构现有
代码参与审查
设计新模块功能
分析用户反馈改进产品
研究新应用技术
开发自动化工具测试
参与发布产品计划
客户沟通需求技术
改进用户设计界面
进行性能测试
解决技术故障
参与技术培训
编写接口API文档
设计数据库结构
项目测试
规划参与项目
讨论项目进展与团队研发
参与方案评审技术
协助测试功能新产品
编写文档的技术
优化架构的现有系统
参与审查的代码
设计功能的新模块
分析反馈的用户并改进产品
研究应用的新技术
开发工具的
自动化测试
参与计划的产品发布
沟通需求的客户技术
改进设计的用户界面
进行测试的性能
解决技术的故障
参与培训的技术
编写文档的API接口
设计结构的数据库
测试的项目
参与规划的项目
研发团队进展项目讨论
方案技术评审参与
功能新产品测试协助
文档技术编写
架构现有系统优化
审查代码参与
模块新功能设计
用户反馈分析改进产品
新技术应用研究
自动化测试工具开发
产品发布计划参与
技术需求客户沟通
用户界面设计改进
性能测试进行
技术故障解决
技术培训参与
API接口文档编写
数据库结构设计
项目测试
项目规划参与

BIN
media/人员工时生成表.xlsx

Binary file not shown.

BIN
media/人员工资导入模板.xlsx

Binary file not shown.

BIN
media/场景一模版.zip

Binary file not shown.

BIN
requirements.txt

Binary file not shown.

BIN
requirementstest.txt

Binary file not shown.

0
utils/__init__.py

1017
utils/custom.py

File diff suppressed because it is too large

83
utils/funcs.py

@ -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("无法解析日期字符串")

56
utils/http_sms_mt.py

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

128
utils/middleware.py

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

147
utils/sms_client.py

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

123
utils/thread_pool.py

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

109
utils/throttles.py

@ -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…
Cancel
Save