任务调度celery使用优化

This commit is contained in:
曹前明 2022-05-10 21:10:38 +08:00
parent f3631e70ee
commit bea318dc53
15 changed files with 392 additions and 33 deletions

View File

@ -2,6 +2,8 @@ SCHEDULE_WRONG = {"code": "schedule_wrong", "detail": "时间策略有误"}
PASSWORD_NOT_SAME = {"code": "password_not_same", "detail": "新旧密码不一致"}
OLD_PASSWORD_WRONG = {"code": "old_password_wrong", "detail": "旧密码错误"}
FUNC_ERROR = {"code": "func_error", "detail": "执行方法有误"}
USERNAME_EXIST = {"code": "username_exist", "detail": "账户已存在"}
ROLE_NAME_EXIST = {"code": "role_name_exist", "detail": "角色名已存在"}
ROLE_CODE_EXIST = {"code": "role_code_exist", "detail": "角色标识已存在"}

View File

@ -11,6 +11,40 @@ from rest_framework.exceptions import ParseError
from django.db import transaction
from apps.third.tapis import dhapis
from rest_framework.validators import UniqueValidator
# from django_q.models import Task as QTask, Schedule as QSchedule
# class QScheduleSerializer(CustomModelSerializer):
# success = serializers.SerializerMethodField()
# class Meta:
# model = QSchedule
# fields = '__all__'
# def get_success(self, obj):
# return obj.success()
# class QTaskResultSerializer(CustomModelSerializer):
# args = serializers.SerializerMethodField()
# kwargs = serializers.SerializerMethodField()
# result = serializers.SerializerMethodField()
# class Meta:
# model = QTask
# fields = '__all__'
# def get_args(self, obj):
# return obj.args
# def get_kwargs(self, obj):
# return obj.kwargs
# def get_result(self, obj):
# return obj.result
class TaskRunSerializer(serializers.Serializer):
sync = serializers.BooleanField(default=True)
class IntervalSerializer(CustomModelSerializer):
@ -56,6 +90,12 @@ class PTaskSerializer(CustomModelSerializer):
return 'interval'
class PTaskResultSerializer(CustomModelSerializer):
class Meta:
model = TaskResult
fields = '__all__'
class FileSerializer(CustomModelSerializer):
class Meta:
model = File
@ -308,12 +348,6 @@ class PasswordChangeSerializer(serializers.Serializer):
new_password2 = serializers.CharField(label="新密码2")
class PTaskResultSerializer(CustomModelSerializer):
class Meta:
model = TaskResult
fields = '__all__'
class UserPostSerializer(CustomModelSerializer):
"""
用户-岗位序列化

View File

@ -1,9 +1,12 @@
# Create your tasks here
from __future__ import absolute_import, unicode_literals
from random import random, randint
from apps.utils.task import CustomTask
from celery import shared_task
from django.core.mail import send_mail
@shared_task
@shared_task(base=CustomTask)
def show():
print('ok')
x = randint(0, 5)
print(40/x)

View File

@ -1,8 +1,8 @@
from django.urls import path, include
from .views import FileViewSet, PTaskResultViewSet, TaskList, \
from .views import FileViewSet, PTaskViewSet, PTaskResultViewSet, TaskList, \
UserPostViewSet, UserViewSet, DeptViewSet, \
PermissionViewSet, RoleViewSet, PostViewSet, \
DictTypeViewSet, DictViewSet, PTaskViewSet
DictTypeViewSet, DictViewSet
from rest_framework import routers
API_BASE_URL = 'api/system/'
@ -18,6 +18,8 @@ router.register('dicttype', DictTypeViewSet, basename="dicttype")
router.register('dict', DictViewSet, basename="dict")
router.register('ptask', PTaskViewSet, basename="ptask")
router.register('ptask_result', PTaskResultViewSet, basename="ptask_result")
# router.register('qschedule', QScheduleViewSet, basename="qschedule")
# router.register('qtask_result', QTaskResultViewSet, basename="qtask_result")
router.register('user_post', UserPostViewSet, basename='user_post')
router2 = routers.DefaultRouter()

View File

@ -1,3 +1,5 @@
import importlib
import json
from django.contrib.auth.hashers import check_password, make_password
from django.db import transaction
from django_celery_beat.models import (CrontabSchedule, IntervalSchedule,
@ -12,8 +14,8 @@ from rest_framework.parsers import (JSONParser,
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from apps.system.errors import OLD_PASSWORD_WRONG, PASSWORD_NOT_SAME, SCHEDULE_WRONG
from apps.system.errors import FUNC_ERROR, OLD_PASSWORD_WRONG, PASSWORD_NOT_SAME, SCHEDULE_WRONG
# from django_q.models import Task as QTask, Schedule as QSchedule
from apps.utils.mixins import (CustomCreateModelMixin)
from django.conf import settings
from apps.utils.permission import ALL_PERMS, get_user_perms_map
@ -26,8 +28,8 @@ from .serializers import (DeptCreateUpdateSerializer, DeptSerializer, DictCreate
DictSerializer, DictTypeCreateUpdateSerializer, DictTypeSerializer,
FileSerializer, PasswordChangeSerializer, PermissionCreateUpdateSerializer,
PermissionSerializer, PostCreateUpdateSerializer, PostSerializer,
PTaskCreateUpdateSerializer, PTaskResultSerializer,
PTaskSerializer, RoleCreateUpdateSerializer, RoleSerializer,
PTaskSerializer, PTaskCreateUpdateSerializer, PTaskResultSerializer,
RoleCreateUpdateSerializer, RoleSerializer, TaskRunSerializer,
UserCreateSerializer, UserListSerializer, UserPostCreateSerializer,
UserPostSerializer, UserUpdateSerializer)
@ -40,7 +42,7 @@ from .serializers import (DeptCreateUpdateSerializer, DeptSerializer, DictCreate
class TaskList(APIView):
permission_classes = [IsAuthenticated]
def get(self, requests):
def get(self, request):
"""获取注册任务列表
获取注册任务列表
@ -50,6 +52,53 @@ class TaskList(APIView):
return Response(tasks)
# class QScheduleViewSet(CustomModelViewSet):
# """
# list:定时任务列表
# 定时任务列表
# retrieve:定时任务详情
# 定时任务详情
# """
# queryset = QSchedule.objects.all()
# serializer_class = QScheduleSerializer
# search_fields = ['name', 'func']
# filterset_fields = ['schedule_type']
# ordering = ['-pk']
# @action(methods=['get'], detail=True, perms_map={'post': 'qschedule:run_once'})
# def run_once(self, request, pk=None):
# """同步执行一次
# 同步执行一次
# """
# obj = self.get_object()
# module, func = obj.func.rsplit(".", 1)
# m = importlib.import_module(module)
# f = getattr(m, func)
# f(*obj.args.split(','), **eval(f"dict({obj.kwargs})"))
# return Response()
# class QTaskResultViewSet(ListModelMixin, RetrieveModelMixin, CustomGenericViewSet):
# """
# list:任务执行结果列表
# 任务执行结果列表
# retrieve:任务执行结果详情
# 任务执行结果详情
# """
# perms_map = {'get': '*'}
# filterset_fields = ['func']
# queryset = QTask.objects.all()
# serializer_class = QTaskResultSerializer
# ordering = ['-started']
# lookup_field = 'id'
class PTaskViewSet(CustomModelViewSet):
"""
list:定时任务列表
@ -68,7 +117,25 @@ class PTaskViewSet(CustomModelViewSet):
search_fields = ['name', 'task']
filterset_fields = ['enabled']
select_related_fields = ['interval', 'crontab']
ordering = ['-create_time']
ordering = ['-id']
@action(methods=['post'], detail=True, perms_map={'get': 'qtask:run_once'},
serializer_class=TaskRunSerializer)
def run_once(self, request, pk=None):
"""执行一次
执行一次
"""
obj = self.get_object()
module, func = obj.task.rsplit(".", 1)
m = importlib.import_module(module)
f = getattr(m, func)
if request.data.get('sync', True):
f(*json.loads(obj.args), **json.loads(obj.kwargs))
return Response()
else:
task_obj = f.delay(*json.loads(obj.args), **json.loads(obj.kwargs))
return Response({'task_id': task_obj.id})
@action(methods=['put'], detail=True, perms_map={'put': 'ptask:update'})
def toggle(self, request, pk=None):

View File

@ -14,5 +14,5 @@ urlpatterns = [
path(API_BASE_URL, include(router.urls)),
path(API_BASE_URL + 'dahua/test/', DahuaTestView.as_view()),
path(API_BASE_URL + 'xunxi/test/', XxTestView.as_view()),
path(API_BASE_URL + 'speaker/test/', SpTestView.as_view()),
path(API_BASE_URL + 'speaker/test/', SpTestView.as_view())
]

View File

@ -82,13 +82,15 @@ class XxListener(stomp.ConnectionListener):
class XxCommonViewSet(CreateModelMixin, CustomGenericViewSet):
"""
寻息通用调用接口
"""
perms_map = {'post': '*'}
serializer_class = RequestCommonSerializer
def create(self, request, *args, **kwargs):
"""
寻息通用调用接口
寻息通用调用接口
"""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data
@ -116,13 +118,15 @@ class XxCommonViewSet(CreateModelMixin, CustomGenericViewSet):
class DhCommonViewSet(CreateModelMixin, CustomGenericViewSet):
"""
大华通用调用接口
"""
perms_map = {'post': '*'}
serializer_class = RequestCommonSerializer
def create(self, request, *args, **kwargs):
"""
大华通用调用接口
大华通用调用接口
"""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data

View File

@ -3,24 +3,53 @@ from apps.third.serializers import BindAreaSerializer, LabelLocationSerializer
from apps.utils.viewsets import CustomGenericViewSet
from rest_framework.mixins import ListModelMixin
from apps.third.clients import xxClient, dhClient, spClient
from apps.third.tapis import xxapis, dhapis
from apps.third.tapis import xxapis, dhapis, spapis
from rest_framework.response import Response
from rest_framework.serializers import Serializer
from rest_framework.decorators import action
from apps.am.models import Area
from rest_framework.views import APIView
class TDeviceViewSet(CustomGenericViewSet):
"""
视频接口
三方设备接口
"""
@action(methods=['post'], detail=False, perms_map={'post': '*'},
serializer_class=Serializer)
def vchannel(self, request):
"""
视频通道列表
视频通道列表
"""
request.data.update({'channelTypeList': ["1"]})
_, res = dhClient.request(**dhapis['channel_list'], json=request.data)
return Response(res)
@action(methods=['post'], detail=False, perms_map={'post': '*'},
serializer_class=Serializer)
def speaker(self, request):
"""
喇叭列表
喇叭列表
"""
_, res = spClient.request(**spapis['device_list'], params=request.data)
return Response(res)
@action(methods=['post'], detail=False, perms_map={'post': '*'},
serializer_class=Serializer)
def dchannel(self, request):
"""
闸机通道列表
闸机通道列表
"""
request.data.update({'channelTypeList': ["7"]})
_, res = dhClient.request(**dhapis['channel_list'], json=request.data)
return Response(res)
@action(methods=['post'], detail=False, perms_map={'post': 'tdevice:label_location'},
serializer_class=LabelLocationSerializer)
def label_location(self, request):

View File

@ -92,8 +92,8 @@ class SpClient:
if raise_exception:
raise APIException(**SP_REQUEST_ERROR)
return 'error', SP_REQUEST_ERROR
if settings.DEBUG:
print_roundtrip(r)
# if settings.DEBUG:
# print_roundtrip(r)
if r.status_code == 200:
ret = r.json()
if 'code' in ret and ret['code'] not in ['0', '100', '00000', '1000', 0, 100, 1000]:

11
apps/utils/task.py Normal file
View File

@ -0,0 +1,11 @@
from celery import Task
from server.settings import myLogger
class CustomTask(Task):
"""
自定义的任务回调
"""
def on_failure(self, exc, task_id, args, kwargs, einfo):
myLogger.error('{0!r} failed: {1!r}'.format(task_id, exc))
return super().on_failure(exc, task_id, args, kwargs, einfo)

View File

@ -0,0 +1,174 @@
# Generated by Django 3.2.12 on 2022-05-10 02:06
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='State',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('name', models.CharField(max_length=50, verbose_name='名称')),
('is_hidden', models.BooleanField(default=False, help_text='设置为True时,获取工单步骤api中不显示此状态(当前处于此状态时除外)', verbose_name='是否隐藏')),
('sort', models.IntegerField(default=0, help_text='用于工单步骤接口时step上状态的顺序(因为存在网状情况,所以需要人为设定顺序),值越小越靠前', verbose_name='状态顺序')),
('type', models.IntegerField(choices=[(0, '普通'), (1, '开始'), (2, '结束')], default=0, help_text='0.普通类型 1.初始状态(用于新建工单时,获取对应的字段必填及transition信息) 2.结束状态(此状态下的工单不得再处理即没有对应的transition)', verbose_name='状态类型')),
('enable_retreat', models.BooleanField(default=False, help_text='开启后允许工单创建人在此状态直接撤回工单到初始状态', verbose_name='允许撤回')),
('participant_type', models.IntegerField(blank=True, choices=[(0, '无处理人'), (1, '个人'), (2, '多人'), (4, '角色'), (6, '脚本'), (7, '工单的字段'), (9, '代码获取')], default=1, help_text='0.无处理人,1.个人,2.多人,3.部门,4.角色,5.变量(支持工单创建人,创建人的leader),6.脚本,7.工单的字段内容(如表单中的"测试负责人",需要为用户名或者逗号隔开的多个用户名),8.父工单的字段内容。 初始状态请选择类型5参与人填create_by', verbose_name='参与者类型')),
('participant', models.JSONField(blank=True, default=list, help_text='可以为空(无处理人的情况,如结束状态)、userid、userid列表\\部门id\\角色id\\变量(create_by,create_by_tl)\\脚本记录的id等包含子工作流的需要设置处理人为loonrobot', verbose_name='参与者')),
('state_fields', models.JSONField(default=dict, help_text='json格式字典存储,包括读写属性1只读2必填3可选, 4:隐藏 示例:{"create_time":1,"title":2, "sn":1}, 内置特殊字段participant_info.participant_name:当前处理人信息(部门名称、角色名称)state.state_name:当前状态的状态名,workflow.workflow_name:工作流名称', verbose_name='表单字段')),
('distribute_type', models.IntegerField(choices=[(1, '主动接单'), (2, '直接处理'), (3, '随机分配'), (4, '全部处理')], default=1, help_text='1.主动接单(如果当前处理人实际为多人的时候,需要先接单才能处理) 2.直接处理(即使当前处理人实际为多人,也可以直接处理) 3.随机分配(如果实际为多人,则系统会随机分配给其中一个人) 4.全部处理(要求所有参与人都要处理一遍,才能进入下一步)', verbose_name='分配方式')),
('filter_policy', models.IntegerField(choices=[(0, ''), (1, '和工单同属一及上级部门'), (2, '和创建人同属一及上级部门'), (3, '和上步处理人同属一及上级部门')], default=0, verbose_name='参与人过滤策略')),
('participant_cc', models.JSONField(blank=True, default=list, help_text='抄送给(userid列表)', verbose_name='抄送给')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='state_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='state_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Ticket',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('title', models.CharField(blank=True, help_text='工单标题', max_length=500, null=True, verbose_name='标题')),
('sn', models.CharField(help_text='工单的流水号', max_length=25, verbose_name='流水号')),
('ticket_data', models.JSONField(default=dict, help_text='工单自定义字段内容', verbose_name='工单数据')),
('in_add_node', models.BooleanField(default=False, help_text='是否处于加签状态下', verbose_name='加签状态中')),
('script_run_last_result', models.BooleanField(default=True, verbose_name='脚本最后一次执行结果')),
('participant_type', models.IntegerField(choices=[(0, '无处理人'), (1, '个人'), (2, '多人'), (4, '角色'), (6, '脚本'), (7, '工单的字段'), (9, '代码获取')], default=0, help_text='0.无处理人,1.个人,2.多人', verbose_name='当前处理人类型')),
('participant', models.JSONField(blank=True, default=list, help_text='可以为空(无处理人的情况,如结束状态)、userid、userid列表', verbose_name='当前处理人')),
('act_state', models.IntegerField(choices=[(0, '草稿中'), (1, '进行中'), (2, '被退回'), (3, '被撤回'), (4, '已完成'), (5, '已关闭')], default=1, help_text='当前工单的进行状态', verbose_name='进行状态')),
('multi_all_person', models.JSONField(blank=True, default=dict, help_text='需要当前状态处理人全部处理时实际的处理结果json格式', verbose_name='全部处理的结果')),
('add_node_man', models.ForeignKey(blank=True, help_text='加签操作的人,工单当前处理人处理完成后会回到该处理人,当处于加签状态下才有效', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='加签人')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='ticket_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='wf.ticket', verbose_name='父工单')),
('parent_state', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='ticket_parent_state', to='wf.state', verbose_name='父工单状态')),
('state', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ticket_state', to='wf.state', verbose_name='当前状态')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='ticket_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Workflow',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('name', models.CharField(max_length=50, verbose_name='名称')),
('key', models.CharField(blank=True, max_length=20, null=True, unique=True, verbose_name='工作流标识')),
('sn_prefix', models.CharField(default='hb', max_length=50, verbose_name='流水号前缀')),
('description', models.CharField(blank=True, max_length=200, null=True, verbose_name='描述')),
('view_permission_check', models.BooleanField(default=True, help_text='开启后,只允许工单的关联人(创建人、曾经的处理人)有权限查看工单', verbose_name='查看权限校验')),
('limit_expression', models.JSONField(blank=True, default=dict, help_text='限制周期({"period":24} 24小时), 限制次数({"count":1}在限制周期内只允许提交1次), 限制级别({"level":1} 针对(1单个用户 2全局)限制周期限制次数,默认特定用户);允许特定人员提交({"allow_persons":"zhangsan,lisi"}只允许张三提交工单,{"allow_depts":"1,2"}只允许部门id为1和2的用户提交工单{"allow_roles":"1,2"}只允许角色id为1和2的用户提交工单)', verbose_name='限制表达式')),
('display_form_str', models.JSONField(blank=True, default=list, help_text='默认"[]",用于用户只有对应工单查看权限时显示哪些字段,field_key的list的json,如["days","sn"],内置特殊字段participant_info.participant_name:当前处理人信息(部门名称、角色名称)state.state_name:当前状态的状态名,workflow.workflow_name:工作流名称', verbose_name='展现表单字段')),
('title_template', models.CharField(blank=True, default='{title}', help_text='工单字段的值可以作为参数写到模板中,格式如:你有一个待办工单:{title}', max_length=50, null=True, verbose_name='标题模板')),
('content_template', models.CharField(blank=True, default='标题:{title}, 创建时间:{create_time}', help_text='工单字段的值可以作为参数写到模板中,格式如:标题:{title}, 创建时间:{create_time}', max_length=1000, null=True, verbose_name='内容模板')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='workflow_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='workflow_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Transition',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('name', models.CharField(max_length=50, verbose_name='操作')),
('timer', models.IntegerField(default=0, help_text='单位秒。处于源状态X秒后如果状态都没有过变化则自动流转到目标状态。设置时间有效', verbose_name='定时器(单位秒)')),
('condition_expression', models.JSONField(default=list, help_text='流转条件表达式,根据表达式中的条件来确定流转的下个状态,格式为[{"expression":"{days} > 3 and {days}<10", "target_state":11}] 其中{}用于填充工单的字段key,运算时会换算成实际的值当符合条件下个状态将变为target_state_id中的值,表达式只支持简单的运算或datetime/time运算.loonflow会以首次匹配成功的条件为准所以多个条件不要有冲突', max_length=1000, verbose_name='条件表达式')),
('attribute_type', models.IntegerField(choices=[(1, '同意'), (2, '拒绝'), (3, '其他')], default=1, help_text='属性类型1.同意2.拒绝3.其他', verbose_name='属性类型')),
('field_require_check', models.BooleanField(default=True, help_text='默认在用户点击操作的时候需要校验工单表单的必填项,如果设置为否则不检查。用于如"退回"属性的操作,不需要填写表单内容', verbose_name='是否校验必填项')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transition_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('destination_state', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='dstate_transition', to='wf.state', verbose_name='目的状态')),
('source_state', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sstate_transition', to='wf.state', verbose_name='源状态')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transition_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
('workflow', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wf.workflow', verbose_name='所属工作流')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='TicketFlow',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('suggestion', models.CharField(blank=True, default='', max_length=10000, verbose_name='处理意见')),
('participant_type', models.IntegerField(choices=[(0, '无处理人'), (1, '个人'), (2, '多人'), (4, '角色'), (6, '脚本'), (7, '工单的字段'), (9, '代码获取')], default=0, help_text='0.无处理人,1.个人,2.多人等', verbose_name='处理人类型')),
('participant_str', models.CharField(blank=True, help_text='非人工处理的处理人相关信息', max_length=200, null=True, verbose_name='处理人')),
('ticket_data', models.JSONField(blank=True, default=dict, help_text='可以用于记录当前表单数据json格式', verbose_name='工单数据')),
('intervene_type', models.IntegerField(choices=[(0, '正常处理'), (1, '转交'), (2, '加签'), (3, '加签处理完成'), (4, '接单'), (5, '评论'), (6, '删除'), (7, '强制关闭'), (8, '强制修改状态'), (9, 'hook操作'), (10, '撤回'), (11, '抄送')], default=0, help_text='流转类型', verbose_name='干预类型')),
('participant_cc', models.JSONField(blank=True, default=list, help_text='抄送给(userid列表)', verbose_name='抄送给')),
('participant', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='ticketflow_participant', to=settings.AUTH_USER_MODEL, verbose_name='处理人')),
('state', models.ForeignKey(blank=True, default=0, on_delete=django.db.models.deletion.CASCADE, to='wf.state', verbose_name='当前状态')),
('ticket', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ticketflow_ticket', to='wf.ticket', verbose_name='关联工单')),
('transition', models.ForeignKey(blank=True, help_text='与worklow.Transition关联 为空时表示认为干预的操作', null=True, on_delete=django.db.models.deletion.CASCADE, to='wf.transition', verbose_name='流转id')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='ticket',
name='workflow',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wf.workflow', verbose_name='关联工作流'),
),
migrations.AddField(
model_name='state',
name='workflow',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wf.workflow', verbose_name='所属工作流'),
),
migrations.CreateModel(
name='CustomField',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('field_type', models.CharField(choices=[('string', '字符串'), ('int', '整型'), ('float', '浮点'), ('boolean', '布尔'), ('date', '日期'), ('datetime', '日期时间'), ('radio', '单选'), ('checkbox', '多选'), ('select', '单选下拉'), ('selects', '多选下拉'), ('cascader', '单选级联'), ('cascaders', '多选级联'), ('select_dg', '弹框单选'), ('select_dgs', '弹框多选'), ('textarea', '文本域'), ('file', '附件')], help_text='string, int, float, date, datetime, radio, checkbox, select, selects, cascader, cascaders, select_dg, select_dgs,textarea, file', max_length=50, verbose_name='类型')),
('field_key', models.CharField(help_text='字段类型请尽量特殊,避免与系统中关键字冲突', max_length=50, verbose_name='字段标识')),
('field_name', models.CharField(max_length=50, verbose_name='字段名称')),
('sort', models.IntegerField(default=0, help_text='工单基础字段在表单中排序为:流水号0,标题20,状态id40,状态名41,创建人80,创建时间100,更新时间120.前端展示工单信息的表单可以根据这个id顺序排列', verbose_name='排序')),
('default_value', models.CharField(blank=True, help_text='前端展示时,可以将此内容作为表单中的该字段的默认值', max_length=100, null=True, verbose_name='默认值')),
('description', models.CharField(blank=True, help_text='字段的描述信息,可用于显示在字段的下方对该字段的详细描述', max_length=100, null=True, verbose_name='描述')),
('placeholder', models.CharField(blank=True, help_text='用户工单详情表单中作为字段的占位符显示', max_length=100, null=True, verbose_name='占位符')),
('field_template', models.TextField(blank=True, help_text='文本域类型字段前端显示时可以将此内容作为字段的placeholder', null=True, verbose_name='文本域模板')),
('boolean_field_display', models.JSONField(blank=True, default=dict, help_text='当为布尔类型时候,可以支持自定义显示形式。{"1":"","0":""}或{"1":"需要","0":"不需要"},注意数字也需要引号', verbose_name='布尔类型显示名')),
('field_choice', models.JSONField(blank=True, default=list, help_text='选项值格式为list, 例["id":1, "name":"张三"]', verbose_name='选项值')),
('label', models.CharField(default='', help_text='处理特殊逻辑使用,比如sys_user用于获取用户作为选项', max_length=1000, verbose_name='标签')),
('is_hidden', models.BooleanField(default=False, help_text='可用于携带不需要用户查看的字段信息', verbose_name='是否隐藏')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='customfield_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='customfield_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
('workflow', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='wf.workflow', verbose_name='所属工作流')),
],
options={
'abstract': False,
},
),
]

View File

View File

@ -1,7 +1,7 @@
celery==5.2.3
Django==3.2.12
django-celery-beat==2.2.1
django-celery-results==2.3.0
django-celery-results==2.3.1
django-cors-headers==3.11.0
django-filter==21.1
djangorestframework==3.13.1

View File

@ -19,4 +19,4 @@ app.autodiscover_tasks()
@app.task(bind=True)
def debug_task(self):
print(f'Request: {self.request!r}')
print(f'Request: {self.request!r}')

View File

@ -45,7 +45,8 @@ INSTALLED_APPS = [
'django_celery_results',
'drf_yasg',
'rest_framework',
"django_filters",
'django_filters',
# 'django_q',
'apps.develop',
'apps.utils',
'apps.third',
@ -148,6 +149,15 @@ STATICFILES_DIRS = (
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# 邮箱配置
EMAIL_HOST = conf.EMAIL_HOST
EMAIL_PORT = conf.EMAIL_PORT
EMAIL_HOST_USER = conf.EMAIL_HOST_USER
EMAIL_HOST_PASSWORD = conf.EMAIL_HOST_PASSWORD
EMAIL_SUBJECT_PREFIX = conf.EMAIL_SUBJECT_PREFIX
EMAIL_USE_TLS = conf.EMAIL_USE_TLS
# 默认主键
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# 雪花ID生成配置
@ -183,7 +193,7 @@ REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'apps.utils.exceptions.custom_exception_hander',
'DEFAULT_THROTTLE_RATES': {
'anon': '1/second',
'user': '1/second'
'user': '2/second'
}
}
# simplejwt配置
@ -214,11 +224,34 @@ AUTHENTICATION_BACKENDS = (
# }
# celery配置,celery正常运行必须安装redis
CELERY_BROKER_URL = "redis://redis:6379/0" # 任务存储
CELERY_BROKER_URL = "redis://127.0.0.1:6379/1" # 任务存储
CELERYD_MAX_TASKS_PER_CHILD = 100 # 每个worker最多执行100个任务就会被销毁可防止内存泄露
CELERY_TIMEZONE = 'Asia/Shanghai' # 设置时区
CELERY_ENABLE_UTC = True # 启动时区设置
CELERY_RESULT_BACKEND = 'django-db'
CELERY_CACHE_BACKEND = 'django-cache'
CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler'
CELERY_ACCEPT_CONTENT = ['application/json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TASK_TRACK_STARTED = True
CELERY_CACHE_BACKEND = 'default'
CELERYD_SOFT_TIME_LIMIT = 60*10
CELERY_TASK_TRACK_STARTED = True
# django_q配置
# Q_CLUSTER = {
# 'name': 'ehs',
# # 'workers': 8,
# 'recycle': 500,
# 'timeout': 60,
# 'compress': True,
# 'save_limit': 250,
# 'queue_limit': 500,
# 'cpu_affinity': 1,
# 'label': 'Django Q',
# 'orm': 'default'
# }
# swagger配置
SWAGGER_SETTINGS = {