语法pep8修正
This commit is contained in:
parent
d1c79e516a
commit
2a9c70eee5
|
@ -4,4 +4,3 @@ from django.apps import AppConfig
|
|||
class AuthConfig(AppConfig):
|
||||
name = 'apps.auth1'
|
||||
verbose_name = "认证"
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
|
||||
class LoginSerializer(serializers.Serializer):
|
||||
username = serializers.CharField(label="用户名")
|
||||
password = serializers.CharField(label="密码")
|
|
@ -1,6 +1,5 @@
|
|||
|
||||
from rest_framework.exceptions import ParseError, ValidationError
|
||||
from django.shortcuts import render
|
||||
from rest_framework.exceptions import ParseError
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
|
@ -14,6 +13,7 @@ from apps.auth1.serializers import LoginSerializer
|
|||
|
||||
# Create your views here.
|
||||
|
||||
|
||||
class TokenBlackView(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
|
@ -26,6 +26,7 @@ class TokenBlackView(APIView):
|
|||
"""
|
||||
return Response(status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class LoginView(CreateAPIView):
|
||||
"""
|
||||
Session登录
|
||||
|
@ -48,6 +49,7 @@ class LoginView(CreateAPIView):
|
|||
return Response()
|
||||
raise ParseError(**USERNAME_OR_PASSWORD_WRONG)
|
||||
|
||||
|
||||
class LogoutView(APIView):
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
from distutils import command
|
||||
from rest_framework.response import Response
|
||||
from celery import shared_task
|
||||
import os
|
||||
import subprocess
|
||||
from server.settings import DATABASES, BACKUP_PATH, SH_PATH, SD_PWD
|
||||
|
||||
|
||||
@shared_task
|
||||
def backup_database():
|
||||
"""
|
||||
|
@ -23,22 +21,23 @@ def backup_database():
|
|||
completed = subprocess.run(command, shell=True, capture_output=True, text=True)
|
||||
return completed
|
||||
|
||||
|
||||
@shared_task
|
||||
def reload_server_git():
|
||||
command = 'bash {}/git_server.sh'.format(SH_PATH)
|
||||
completed = subprocess.run(command, shell=True, capture_output=True, text=True)
|
||||
return completed
|
||||
|
||||
|
||||
@shared_task
|
||||
def reload_server_only():
|
||||
command = 'echo "{}" | sudo -S service supervisor reload'.format(SD_PWD)
|
||||
completed = subprocess.run(command, shell=True, capture_output=True, text=True)
|
||||
return completed
|
||||
|
||||
|
||||
@shared_task
|
||||
def backup_media():
|
||||
command = 'bash {}/backup_media.sh'.format(SH_PATH)
|
||||
completed = subprocess.run(command, shell=True, capture_output=True, text=True)
|
||||
return completed
|
||||
|
||||
|
|
@ -1,7 +1,4 @@
|
|||
from django.db.models import base
|
||||
from rest_framework import urlpatterns
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from django.urls import path
|
||||
from apps.develop.views import BackupDatabase, BackupMedia, ReloadServerGit, ReloadServerOnly
|
||||
|
||||
API_BASE_URL = 'api/develop/'
|
||||
|
@ -13,4 +10,3 @@ urlpatterns = [
|
|||
path(API_BASE_URL + 'backup_database/', BackupDatabase.as_view()),
|
||||
path(API_BASE_URL + 'backup_media/', BackupMedia.as_view())
|
||||
]
|
||||
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
from django.db import transaction
|
||||
from django.shortcuts import render
|
||||
from rest_framework.decorators import permission_classes
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.permissions import IsAdminUser, AllowAny
|
||||
from rest_framework.permissions import IsAdminUser
|
||||
from rest_framework.response import Response
|
||||
from apps.develop.tasks import backup_database, backup_media, reload_server_git, reload_server_only
|
||||
from rest_framework.exceptions import APIException
|
||||
|
@ -11,6 +8,7 @@ from rest_framework.exceptions import APIException
|
|||
|
||||
class ReloadServerGit(APIView):
|
||||
permission_classes = [IsAdminUser]
|
||||
|
||||
def post(self, request):
|
||||
"""
|
||||
拉取后端代码并重启服务
|
||||
|
@ -23,8 +21,10 @@ class ReloadServerGit(APIView):
|
|||
else:
|
||||
raise APIException(completed.stdout)
|
||||
|
||||
|
||||
class ReloadServerOnly(APIView):
|
||||
permission_classes = [IsAdminUser]
|
||||
|
||||
def post(self, request):
|
||||
"""
|
||||
仅重启服务
|
||||
|
@ -40,6 +40,7 @@ class ReloadServerOnly(APIView):
|
|||
|
||||
class BackupDatabase(APIView):
|
||||
permission_classes = [IsAdminUser]
|
||||
|
||||
def post(self, request):
|
||||
"""
|
||||
备份数据库到指定位置
|
||||
|
@ -52,8 +53,10 @@ class BackupDatabase(APIView):
|
|||
else:
|
||||
raise APIException(completed.stdout)
|
||||
|
||||
|
||||
class BackupMedia(APIView):
|
||||
permission_classes = [IsAdminUser]
|
||||
|
||||
def post(self, request):
|
||||
"""
|
||||
备份资源到指定位置
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class HrmConfig(AppConfig):
|
||||
name = 'apps.hrm'
|
||||
verbose_name = '人力资源管理'
|
||||
|
||||
def ready(self):
|
||||
import apps.hrm.signals
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
from enum import Enum
|
||||
|
||||
|
||||
NO_NEED_LEVEL_REMARK = {"code": "no_need_level_remark", "detail": "无需填写离岗说明"}
|
||||
PHONE_F_WRONG = {"code": "phone_f_wrong", "detail": "手机号格式错误"}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
from django_filters import rest_framework as filters
|
||||
from apps.hrm.models import ClockRecord, Employee, NotWorkRemark
|
||||
|
||||
|
||||
class ClockRecordFilterSet(filters.FilterSet):
|
||||
create_time_start = filters.DateFilter(field_name="create_time", lookup_expr='gte')
|
||||
create_time_end = filters.DateFilter(field_name="create_time", lookup_expr='lte')
|
||||
year = filters.NumberFilter(method='filter_year')
|
||||
month = filters.NumberFilter(method='filter_month')
|
||||
|
||||
class Meta:
|
||||
model = ClockRecord
|
||||
fields = ['create_by', 'create_time_start', 'create_time_end', 'year', 'month']
|
||||
|
@ -16,6 +18,7 @@ class ClockRecordFilterSet(filters.FilterSet):
|
|||
def filter_month(self, queryset, name, value):
|
||||
return queryset.filter(create_time_date__month=value)
|
||||
|
||||
|
||||
class EmployeeFilterSet(filters.FilterSet):
|
||||
|
||||
class Meta:
|
||||
|
@ -26,6 +29,7 @@ class EmployeeFilterSet(filters.FilterSet):
|
|||
class NotWorkRemarkFilterSet(filters.FilterSet):
|
||||
year = filters.NumberFilter(method='filter_year')
|
||||
month = filters.NumberFilter(method='filter_month')
|
||||
|
||||
class Meta:
|
||||
model = NotWorkRemark
|
||||
fields = ['year', 'month', 'user']
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
from django.db import models
|
||||
from django.db.models.query import QuerySet
|
||||
from apps.system.models import User
|
||||
|
||||
from apps.utils.models import CommonADModel, CommonAModel, CommonBModel
|
||||
|
||||
|
||||
|
||||
|
||||
class Employee(CommonBModel):
|
||||
"""
|
||||
员工信息
|
||||
|
@ -37,6 +34,7 @@ class Employee(CommonBModel):
|
|||
last_check_time = models.DateTimeField('打卡时间', null=True, blank=True)
|
||||
not_work_remark = models.CharField('当前未打卡说明', null=True, blank=True, max_length=200)
|
||||
third_info = models.JSONField('三方信息', default=dict, null=False, blank=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = '员工补充信息'
|
||||
verbose_name_plural = verbose_name
|
||||
|
@ -53,7 +51,6 @@ class Employee(CommonBModel):
|
|||
# CARD_LOCATION = 20
|
||||
|
||||
|
||||
|
||||
class NotWorkRemark(CommonAModel):
|
||||
"""
|
||||
离岗说明
|
||||
|
@ -62,6 +59,7 @@ class NotWorkRemark(CommonAModel):
|
|||
user = models.ForeignKey(User, verbose_name='用户', on_delete=models.CASCADE)
|
||||
remark = models.CharField('未打卡说明', null=True, blank=True, max_length=200)
|
||||
|
||||
|
||||
class ClockRecord(CommonADModel):
|
||||
"""
|
||||
打卡记录
|
||||
|
@ -71,5 +69,3 @@ class ClockRecord(CommonADModel):
|
|||
(ClOCK_WORK1, '上班打卡'),
|
||||
)
|
||||
type = models.PositiveSmallIntegerField('打卡类型', choices=type_choice, default=ClOCK_WORK1)
|
||||
|
||||
|
||||
|
|
|
@ -1,20 +1,18 @@
|
|||
from apps.hrm.errors import DH_PHOTO_FALI, PHONE_EXIST, PHONE_F_WRONG
|
||||
from apps.system.models import Dept, User
|
||||
from apps.hrm.errors import PHONE_F_WRONG
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework import serializers
|
||||
|
||||
from apps.utils.serializers import CustomModelSerializer
|
||||
from apps.utils.constants import EXCLUDE_FIELDS
|
||||
from apps.utils.tools import rannum, ranstr
|
||||
from .models import ClockRecord, Employee, NotWorkRemark
|
||||
from apps.utils.tools import rannum
|
||||
from apps.hrm.models import ClockRecord, Employee, NotWorkRemark
|
||||
from apps.system.serializers import DeptSimpleSerializer, UserSimpleSerializer
|
||||
from django.db import transaction
|
||||
from apps.third.clients import dhClient
|
||||
from apps.third.tapis import dhapis
|
||||
import re
|
||||
from server.settings import DEBUG
|
||||
from rest_framework.exceptions import ParseError
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class EmployeeBaseSerializer(CustomModelSerializer):
|
||||
def save(self, **kwargs):
|
||||
|
@ -26,13 +24,15 @@ class EmployeeBaseSerializer(CustomModelSerializer):
|
|||
|
||||
|
||||
def phone_check(phone):
|
||||
re_phone = '^1[358]\d{9}$|^147\d{8}$|^176\d{8}$'
|
||||
re_phone = r'^1[358]\d{9}$|^147\d{8}$|^176\d{8}$'
|
||||
if not re.match(re_phone, phone):
|
||||
raise serializers.ValidationError(**PHONE_F_WRONG)
|
||||
return phone
|
||||
|
||||
|
||||
class EmployeeCreateUpdateSerializer(EmployeeBaseSerializer):
|
||||
phone = serializers.CharField(label="手机号", validators=[phone_check])
|
||||
|
||||
class Meta:
|
||||
model = Employee
|
||||
exclude = EXCLUDE_FIELDS + ['face_data',
|
||||
|
@ -181,31 +181,35 @@ class EmployeeCreateUpdateSerializer(EmployeeBaseSerializer):
|
|||
instance.save()
|
||||
return instance
|
||||
|
||||
|
||||
class ChannelAuthoritySerializer(serializers.Serializer):
|
||||
pks = serializers.ListField(child=serializers.CharField(max_length=20), label="员工ID列表")
|
||||
channels = serializers.ListField(child=serializers.CharField(max_length=20), label="门通道ID列表")
|
||||
|
||||
|
||||
class EmployeeSerializer(EmployeeBaseSerializer):
|
||||
belong_dept_ = DeptSimpleSerializer(source='belong_dept', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Employee
|
||||
exclude = ['face_data']
|
||||
read_only_fields = ['is_atwork', 'last_check_time', 'not_work_remark']
|
||||
|
||||
|
||||
|
||||
|
||||
class EmployeeNotWorkRemarkSerializer(ModelSerializer):
|
||||
class Meta:
|
||||
model = Employee
|
||||
fields = ['not_work_remark']
|
||||
|
||||
|
||||
class ClockRecordListSerializer(serializers.ModelSerializer):
|
||||
create_by_ = UserSimpleSerializer(source='create_by', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ClockRecord
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class NotWorkRemarkListSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = NotWorkRemark
|
||||
|
|
|
@ -2,7 +2,7 @@ from django.db.models.signals import post_save
|
|||
from apps.system.models import User
|
||||
from django.dispatch import receiver
|
||||
from apps.hrm.models import Employee
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
def updateEmployee(sender, instance, created, **kwargs):
|
||||
|
@ -13,4 +13,3 @@ def updateEmployee(sender, instance, created, **kwargs):
|
|||
if ep.belong_dept and ep.belong_dept != instance.belong_dept:
|
||||
ep.belong_dept = instance.belong_dept
|
||||
ep.save()
|
||||
|
||||
|
|
|
@ -10,5 +10,3 @@ def update_all_employee_not_atwork():
|
|||
将所有员工设为非在岗状态
|
||||
"""
|
||||
Employee.objects.all().update(is_atwork=False, last_check_time=None, not_work_remark=None)
|
||||
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
from rest_framework import urlpatterns
|
||||
from apps.hrm.views import ClockRecordViewSet, EmployeeViewSet, NotWorkRemarkViewSet
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
@ -14,4 +12,3 @@ router.register('not_work_remark', NotWorkRemarkViewSet, basename='not_work_ream
|
|||
urlpatterns = [
|
||||
path(API_BASE_URL, include(router.urls)),
|
||||
]
|
||||
|
||||
|
|
|
@ -1,18 +1,13 @@
|
|||
from datetime import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
from django.shortcuts import render
|
||||
from django.utils import timezone
|
||||
from rest_framework import exceptions, serializers, status
|
||||
from rest_framework import serializers
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import ParseError
|
||||
from rest_framework.generics import CreateAPIView
|
||||
from rest_framework.mixins import (CreateModelMixin, ListModelMixin,
|
||||
RetrieveModelMixin, UpdateModelMixin)
|
||||
from rest_framework.mixins import ListModelMixin
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.response import Response
|
||||
from rest_framework_simplejwt.tokens import RefreshToken
|
||||
|
||||
from apps.hrm.errors import NO_NEED_LEVEL_REMARK
|
||||
from apps.hrm.filters import (ClockRecordFilterSet, EmployeeFilterSet,
|
||||
|
@ -24,11 +19,9 @@ from apps.hrm.serializers import (ChannelAuthoritySerializer,
|
|||
EmployeeNotWorkRemarkSerializer,
|
||||
EmployeeSerializer,
|
||||
NotWorkRemarkListSerializer)
|
||||
from apps.system.models import User
|
||||
from apps.system.serializers import UserSimpleSerializer
|
||||
|
||||
from apps.third.clients import dhClient
|
||||
from apps.third.tapis import dhapis
|
||||
from apps.utils.serializers import PkSerializer
|
||||
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
|
||||
|
||||
|
||||
|
@ -47,8 +40,8 @@ class EmployeeViewSet(CustomModelViewSet):
|
|||
search_fields = ['name', 'number', 'user__username']
|
||||
ordering = ['-pk']
|
||||
|
||||
@action(methods=['post'], detail=True, perms_map={'post': 'employee:notworkremark'}
|
||||
, serializer_class=EmployeeNotWorkRemarkSerializer)
|
||||
@action(methods=['post'], detail=True, perms_map={'post': 'employee:notworkremark'},
|
||||
serializer_class=EmployeeNotWorkRemarkSerializer)
|
||||
def not_work_remark(self, request, pk=None):
|
||||
"""
|
||||
填写离岗说明
|
||||
|
@ -79,8 +72,8 @@ class EmployeeViewSet(CustomModelViewSet):
|
|||
raise ParseError(**NO_NEED_LEVEL_REMARK)
|
||||
|
||||
@transaction.atomic
|
||||
@action(methods=['post'], detail=False, perms_map={'post': 'employee:channel_authority'}
|
||||
, serializer_class=ChannelAuthoritySerializer)
|
||||
@action(methods=['post'], detail=False, perms_map={'post': 'employee:channel_authority'},
|
||||
serializer_class=ChannelAuthoritySerializer)
|
||||
def channel_authority(self, request, pk=None):
|
||||
"""门通道授权
|
||||
|
||||
|
@ -114,8 +107,8 @@ class EmployeeViewSet(CustomModelViewSet):
|
|||
return Response()
|
||||
|
||||
@transaction.atomic
|
||||
@action(methods=['post'], detail=False, perms_map={'post': 'employee:face_bind_1'}
|
||||
, serializer_class=serializers.Serializer)
|
||||
@action(methods=['post'], detail=False, perms_map={'post': 'employee:face_bind_1'},
|
||||
serializer_class=serializers.Serializer)
|
||||
def face_bind_1(self, request, pk=None):
|
||||
"""同步人脸库1
|
||||
|
||||
|
@ -153,8 +146,8 @@ class EmployeeViewSet(CustomModelViewSet):
|
|||
dhClient.request(**dhapis['face_bind'], json=json_data)
|
||||
return Response()
|
||||
|
||||
@action(methods=['get'], detail=False, perms_map={'get': 'employee:face_bind_1'}
|
||||
, serializer_class=serializers.Serializer)
|
||||
@action(methods=['get'], detail=False, perms_map={'get': 'employee:face_bind_1'},
|
||||
serializer_class=serializers.Serializer)
|
||||
def face_status_1(self, request, pk=None):
|
||||
"""人像下发状态
|
||||
|
||||
|
@ -165,7 +158,6 @@ class EmployeeViewSet(CustomModelViewSet):
|
|||
return Response(res)
|
||||
|
||||
|
||||
|
||||
class ClockRecordViewSet(ListModelMixin, CustomGenericViewSet):
|
||||
"""
|
||||
打卡记录
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import imp
|
||||
import json
|
||||
from channels.generic.websocket import AsyncWebsocketConsumer
|
||||
|
||||
|
||||
class MonitorConsumer(AsyncWebsocketConsumer):
|
||||
async def connect(self):
|
||||
self.room_name = self.scope['url_route']['kwargs']['room_name']
|
||||
|
|
|
@ -3,6 +3,7 @@ from django.db import models
|
|||
|
||||
from apps.utils.models import BaseModel
|
||||
|
||||
|
||||
class DrfRequestLog(BaseModel):
|
||||
"""Logs Django rest framework API requests"""
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from django.urls import path, include
|
||||
from rest_framework import routers
|
||||
from django.urls import path
|
||||
from .views import DrfRequestLogViewSet, ServerInfoView, LogView, LogDetailView, index, room, video
|
||||
|
||||
API_BASE_URL = 'api/monitor/'
|
||||
|
|
|
@ -4,10 +4,9 @@ import psutil
|
|||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.viewsets import ViewSet
|
||||
from django.conf import settings
|
||||
import os
|
||||
from rest_framework import serializers, status
|
||||
from rest_framework import serializers
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from rest_framework.exceptions import NotFound
|
||||
|
@ -28,12 +27,14 @@ def room(request, room_name):
|
|||
'room_name': room_name
|
||||
})
|
||||
|
||||
|
||||
def video(request):
|
||||
return render(request, 'monitor/video.html')
|
||||
|
||||
|
||||
class ServerInfoView(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""
|
||||
获取服务器当前状态
|
||||
|
@ -124,11 +125,13 @@ class LogDetailView(APIView):
|
|||
except:
|
||||
raise NotFound(**LOG_NOT_FONED)
|
||||
|
||||
|
||||
class DrfRequestLogSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = DrfRequestLog
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class DrfRequestLogViewSet(ListModelMixin, CustomGenericViewSet):
|
||||
"""请求日志
|
||||
|
||||
|
@ -138,5 +141,3 @@ class DrfRequestLogViewSet(ListModelMixin, CustomGenericViewSet):
|
|||
queryset = DrfRequestLog.objects.all()
|
||||
list_serializer_class = DrfRequestLogSerializer
|
||||
ordering = ['-requested_at']
|
||||
|
||||
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
import json
|
||||
import re
|
||||
|
||||
from django_celery_beat.models import PeriodicTask, CrontabSchedule, IntervalSchedule
|
||||
from rest_framework import serializers
|
||||
from django_celery_results.models import TaskResult
|
||||
from apps.system.errors import ROLE_CODE_EXIST, ROLE_NAME_EXIST, USERNAME_EXIST
|
||||
from apps.system.errors import USERNAME_EXIST
|
||||
from apps.utils.serializers import CustomModelSerializer
|
||||
from apps.utils.constants import EXCLUDE_FIELDS, EXCLUDE_FIELDS_BASE
|
||||
from .models import (Dictionary, DictType, File, Dept, Permission, Post,
|
||||
Role, User, UserPost)
|
||||
from rest_framework.exceptions import ParseError, APIException
|
||||
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.contrib.auth.hashers import make_password
|
||||
|
||||
|
||||
class IntervalSerializer(CustomModelSerializer):
|
||||
class Meta:
|
||||
|
@ -72,6 +71,7 @@ class DictTypeSerializer(CustomModelSerializer):
|
|||
model = DictType
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class DictTypeCreateUpdateSerializer(CustomModelSerializer):
|
||||
class Meta:
|
||||
model = DictType
|
||||
|
@ -87,6 +87,7 @@ class DictSerializer(CustomModelSerializer):
|
|||
model = Dictionary
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class DictCreateUpdateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
数据字典序列化
|
||||
|
@ -96,6 +97,7 @@ class DictCreateUpdateSerializer(CustomModelSerializer):
|
|||
model = Dictionary
|
||||
exclude = EXCLUDE_FIELDS
|
||||
|
||||
|
||||
class PostSerializer(CustomModelSerializer):
|
||||
"""
|
||||
岗位序列化
|
||||
|
@ -105,6 +107,7 @@ class PostSerializer(CustomModelSerializer):
|
|||
model = Post
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class PostCreateUpdateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
岗位序列化
|
||||
|
@ -145,10 +148,12 @@ class RoleCreateUpdateSerializer(CustomModelSerializer):
|
|||
UniqueValidator(queryset=Role.objects.all(), message='已存在相同名称的角色')])
|
||||
code = serializers.CharField(label="标识", validators=[
|
||||
UniqueValidator(queryset=Role.objects.all(), message='已存在相同标识的角色')])
|
||||
|
||||
class Meta:
|
||||
model = Role
|
||||
exclude = EXCLUDE_FIELDS
|
||||
|
||||
|
||||
class PermissionSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
权限序列化
|
||||
|
@ -158,6 +163,7 @@ class PermissionSerializer(serializers.ModelSerializer):
|
|||
model = Permission
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class PermissionCreateUpdateSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
权限序列化
|
||||
|
@ -167,6 +173,7 @@ class PermissionCreateUpdateSerializer(serializers.ModelSerializer):
|
|||
model = Permission
|
||||
exclude = EXCLUDE_FIELDS_BASE
|
||||
|
||||
|
||||
class DeptSimpleSerializer(CustomModelSerializer):
|
||||
class Meta:
|
||||
model = Dept
|
||||
|
@ -184,11 +191,13 @@ class DeptSerializer(CustomModelSerializer):
|
|||
model = Dept
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class DeptCreateUpdateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
部门序列化
|
||||
"""
|
||||
parent = serializers.PrimaryKeyRelatedField(queryset=Dept.objects.all(), required=True)
|
||||
|
||||
class Meta:
|
||||
model = Dept
|
||||
exclude = EXCLUDE_FIELDS + ['third_info']
|
||||
|
@ -207,7 +216,6 @@ class DeptCreateUpdateSerializer(CustomModelSerializer):
|
|||
validated_data['third_info'] = third_info
|
||||
return super().create(validated_data)
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def update(self, instance, validated_data):
|
||||
from apps.third.clients import dhClient
|
||||
|
@ -233,6 +241,7 @@ class DeptCreateUpdateSerializer(CustomModelSerializer):
|
|||
dhClient.request(**dhapis['dept_update'], json=data)
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
|
||||
class UserSimpleSerializer(CustomModelSerializer):
|
||||
class Meta:
|
||||
model = User
|
||||
|
@ -260,11 +269,13 @@ class UserListSerializer(CustomModelSerializer):
|
|||
# if User.objects.filter(phone=phone).exists():
|
||||
# raise serializers.ValidationError(**PHONE_EXIST)
|
||||
|
||||
|
||||
def user_exist(username):
|
||||
if User.objects.filter(username=username).exists():
|
||||
raise serializers.ValidationError(**USERNAME_EXIST)
|
||||
return username
|
||||
|
||||
|
||||
class UserUpdateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
用户编辑序列化
|
||||
|
@ -280,8 +291,6 @@ class UserUpdateSerializer(CustomModelSerializer):
|
|||
return super().update(instance, validated_data)
|
||||
|
||||
|
||||
|
||||
|
||||
class UserCreateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
创建用户序列化
|
||||
|
@ -293,12 +302,12 @@ class UserCreateSerializer(CustomModelSerializer):
|
|||
fields = ['username', 'name', 'avatar', 'is_active']
|
||||
|
||||
|
||||
|
||||
class PasswordChangeSerializer(serializers.Serializer):
|
||||
old_password = serializers.CharField(label="原密码")
|
||||
new_password1 = serializers.CharField(label="新密码1")
|
||||
new_password2 = serializers.CharField(label="新密码2")
|
||||
|
||||
|
||||
class PTaskResultSerializer(CustomModelSerializer):
|
||||
class Meta:
|
||||
model = TaskResult
|
||||
|
@ -317,6 +326,7 @@ class UserPostSerializer(CustomModelSerializer):
|
|||
model = UserPost
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class UserPostCreateSerializer(CustomModelSerializer):
|
||||
class Meta:
|
||||
model = UserPost
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
from django.urls import path, include
|
||||
from .views import FileViewSet, PTaskResultViewSet, TaskList, UserPostViewSet, UserViewSet, DeptViewSet, PermissionViewSet, RoleViewSet, PostViewSet, DictTypeViewSet, DictViewSet, PTaskViewSet
|
||||
from .views import FileViewSet, PTaskResultViewSet, TaskList, \
|
||||
UserPostViewSet, UserViewSet, DeptViewSet, \
|
||||
PermissionViewSet, RoleViewSet, PostViewSet, \
|
||||
DictTypeViewSet, DictViewSet, PTaskViewSet
|
||||
from rest_framework import routers
|
||||
|
||||
API_BASE_URL = 'api/system/'
|
||||
|
|
|
@ -17,14 +17,15 @@ from apps.system.errors import OLD_PASSWORD_WRONG, PASSWORD_NOT_SAME, SCHEDULE_W
|
|||
from apps.utils.mixins import (CustomCreateModelMixin)
|
||||
from django.conf import settings
|
||||
from apps.utils.permission import ALL_PERMS, get_user_perms_map
|
||||
from apps.utils.queryset import get_child_queryset2
|
||||
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
|
||||
from server.celery import app as celery_app
|
||||
from .filters import UserFilter
|
||||
from .models import (Dept, Dictionary, DictType, File, Permission, Post, Role, User,
|
||||
UserPost)
|
||||
from .serializers import (DeptCreateUpdateSerializer, DeptSerializer, DictCreateUpdateSerializer, DictSerializer, DictTypeCreateUpdateSerializer, DictTypeSerializer,
|
||||
FileSerializer, PasswordChangeSerializer, PermissionCreateUpdateSerializer, PermissionSerializer, PostCreateUpdateSerializer, PostSerializer,
|
||||
from .serializers import (DeptCreateUpdateSerializer, DeptSerializer, DictCreateUpdateSerializer,
|
||||
DictSerializer, DictTypeCreateUpdateSerializer, DictTypeSerializer,
|
||||
FileSerializer, PasswordChangeSerializer, PermissionCreateUpdateSerializer,
|
||||
PermissionSerializer, PostCreateUpdateSerializer, PostSerializer,
|
||||
PTaskCreateUpdateSerializer, PTaskResultSerializer,
|
||||
PTaskSerializer, RoleCreateUpdateSerializer, RoleSerializer,
|
||||
UserCreateSerializer, UserListSerializer, UserPostCreateSerializer,
|
||||
|
|
|
@ -2,7 +2,6 @@ from rest_framework.exceptions import ParseError, APIException
|
|||
from apps.third.tapis import dhapis, xxapis
|
||||
from apps.third.erros import TAPI_CODE_WRONG
|
||||
from apps.third.clients import dhClient
|
||||
from apps.utils.errors import XX_REQUEST_ERROR
|
||||
from apps.utils.mixins import MyLoggingMixin
|
||||
from apps.third.clients import xxClient
|
||||
from rest_framework.response import Response
|
||||
|
@ -51,6 +50,8 @@ class XxTestView(APIView):
|
|||
raise APIException(**res)
|
||||
|
||||
# 寻息事件订阅
|
||||
|
||||
|
||||
class XxListener(stomp.ConnectionListener):
|
||||
def on_error(self, frame):
|
||||
print('received an error "%s"' % frame.body)
|
||||
|
@ -65,6 +66,7 @@ if settings.XX_ENABLED:
|
|||
c.connect(settings.XX_USERNAME, settings.XX_LICENCE)
|
||||
c.subscribe(settings.XX_QUEUE, id='')
|
||||
|
||||
|
||||
class XxCommonViewSet(CreateModelMixin, CustomGenericViewSet):
|
||||
"""
|
||||
寻息通用调用接口
|
||||
|
@ -189,7 +191,7 @@ class DhCommonViewSet(CreateModelMixin, CustomGenericViewSet):
|
|||
data = request.data
|
||||
method = data['method']
|
||||
subsystem = data.get('subsystem', None)
|
||||
info = data.get('info', {})
|
||||
# info = data.get('info', {})
|
||||
if method == 'department.update':
|
||||
pass
|
||||
elif method == 'person.update':
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
import time
|
||||
from threading import Thread
|
||||
import traceback
|
||||
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from rest_framework.exceptions import APIException, ParseError
|
||||
|
||||
from apps.utils.errors import DH_REQUEST_ERROR
|
||||
from apps.utils.tools import print_roundtrip
|
||||
from django.conf import settings
|
||||
import json
|
||||
import time
|
||||
from rest_framework.exceptions import APIException, ParseError
|
||||
import os
|
||||
requests.packages.urllib3.disable_warnings()
|
||||
|
||||
|
||||
class DhClient:
|
||||
"""
|
||||
大华
|
||||
"""
|
||||
|
||||
def __init__(self, client_id= settings.DAHUA_CLIENTID
|
||||
, client_secret = settings.DAHUA_SECRET) -> None:
|
||||
def __init__(self, client_id=settings.DAHUA_CLIENTID,
|
||||
client_secret=settings.DAHUA_SECRET) -> None:
|
||||
if not settings.DAHUA_ENABLED:
|
||||
return None
|
||||
self.client_id = client_id
|
||||
|
@ -34,7 +34,8 @@ class DhClient:
|
|||
'client_id': self.client_id,
|
||||
'client_secret': self.client_secret
|
||||
}
|
||||
r = requests.post(params=params, url=settings.DAHUA_BASE_URL + '/evo-apigw/evo-oauth/oauth/token', verify=False)
|
||||
r = requests.post(params=params,
|
||||
url=settings.DAHUA_BASE_URL + '/evo-apigw/evo-oauth/oauth/token', verify=False)
|
||||
ret = r.json()
|
||||
if ret['success']:
|
||||
self.headers['Authorization'] = 'bearer ' + ret['data']['access_token']
|
||||
|
@ -66,7 +67,8 @@ class DhClient:
|
|||
self.isRuning = False
|
||||
# self.t.join()
|
||||
|
||||
def request(self, url:str, method:str, params=dict(), json=dict(), timeout=10, file_path_rela=None, raise_exception=True):
|
||||
def request(self, url: str, method: str, params=dict(), json=dict(), timeout=10,
|
||||
file_path_rela=None, raise_exception=True):
|
||||
if self.isGetingToken:
|
||||
req_num = 0
|
||||
while True:
|
||||
|
@ -83,8 +85,9 @@ class DhClient:
|
|||
try:
|
||||
if params:
|
||||
url = url.format(**params)
|
||||
r = getattr(requests, method)('{}{}'.format(settings.DAHUA_BASE_URL, url)
|
||||
, headers = self.headers, params=params, json=json, timeout=timeout, files=files, verify=False)
|
||||
r = getattr(requests, method)('{}{}'.format(settings.DAHUA_BASE_URL, url),
|
||||
headers=self.headers, params=params, json=json,
|
||||
timeout=timeout, files=files, verify=False)
|
||||
except:
|
||||
if raise_exception:
|
||||
raise APIException(**DH_REQUEST_ERROR)
|
||||
|
@ -92,16 +95,15 @@ class DhClient:
|
|||
# if settings.DEBUG:
|
||||
# print_roundtrip(r)
|
||||
if r.status_code == 200:
|
||||
"""
|
||||
请求成功
|
||||
"""
|
||||
ret = r.json()
|
||||
if ret.get('code') == '27001007':
|
||||
self.get_token() # 重新获取token
|
||||
self.request(url, method, params, json, timeout, file_path_rela, raise_exception)
|
||||
else:
|
||||
if ret['code'] not in ['0', '100', '00000', '1000', 0, 100, 1000]:
|
||||
detail = '大华错误:' + '{}|{}{}{}'.format(str(ret['code']), ret.get('errMsg',''), ret.get('desc', ''), str(ret.get('data', '')))
|
||||
detail = '大华错误:' + \
|
||||
'{}|{}{}{}'.format(str(ret['code']), ret.get('errMsg', ''),
|
||||
ret.get('desc', ''), str(ret.get('data', '')))
|
||||
err_detail = dict(detail=detail, code='dh_'+str(ret['code']))
|
||||
if raise_exception:
|
||||
raise ParseError(**err_detail)
|
||||
|
@ -111,4 +113,5 @@ class DhClient:
|
|||
raise APIException(**DH_REQUEST_ERROR)
|
||||
return 'error', DH_REQUEST_ERROR
|
||||
|
||||
|
||||
dhClient = DhClient()
|
|
@ -1,12 +1,13 @@
|
|||
import traceback
|
||||
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import Http404
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import exceptions
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import set_rollback
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
import traceback
|
||||
from server.settings import myLogger
|
||||
|
||||
|
||||
def custom_exception_hander(exc, context):
|
||||
"""
|
||||
自定义异常处理
|
||||
|
@ -34,6 +35,6 @@ def custom_exception_hander(exc, context):
|
|||
set_rollback()
|
||||
data['request_id'] = request_id
|
||||
return Response(data, status=exc.status_code, headers=headers)
|
||||
err_str = traceback.format_exc()
|
||||
myLogger.error('{}-{}'.format(request_id, err_str))
|
||||
args = (request_id, traceback.format_exc())
|
||||
myLogger.error(f"{args[0]}-{args[1]}")
|
||||
return Response(data={'err_code': 'server_error', 'err_msg': '服务器错误', 'request_id': request_id}, status=500)
|
|
@ -1,6 +1,5 @@
|
|||
import uuid
|
||||
from django.db.models.query import QuerySet
|
||||
from rest_framework.mixins import CreateModelMixin, UpdateModelMixin, ListModelMixin, RetrieveModelMixin, DestroyModelMixin
|
||||
from rest_framework.mixins import CreateModelMixin, UpdateModelMixin, DestroyModelMixin
|
||||
import ast
|
||||
import ipaddress
|
||||
import traceback
|
||||
|
@ -8,41 +7,48 @@ from apps.monitor.models import DrfRequestLog
|
|||
from server.settings import myLogger
|
||||
from django.db import connection
|
||||
from django.utils.timezone import now
|
||||
from apps.utils.snowflake import idWorker
|
||||
from user_agents import parse
|
||||
|
||||
|
||||
class CreateUpdateModelAMixin:
|
||||
"""
|
||||
业务用基本表A用
|
||||
"""
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(create_by=self.request.user)
|
||||
|
||||
def perform_update(self, serializer):
|
||||
serializer.save(update_by=self.request.user)
|
||||
|
||||
|
||||
class CreateUpdateModelBMixin:
|
||||
"""
|
||||
业务用基本表B用
|
||||
"""
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(create_by=self.request.user, belong_dept=self.request.user.dept)
|
||||
|
||||
def perform_update(self, serializer):
|
||||
serializer.save(update_by=self.request.user)
|
||||
|
||||
|
||||
class CreateUpdateCustomMixin:
|
||||
"""
|
||||
整合
|
||||
"""
|
||||
|
||||
def perform_create(self, serializer):
|
||||
if hasattr(self.queryset.model, 'belong_dept'):
|
||||
serializer.save(create_by=self.request.user, belong_dept=self.request.user.dept)
|
||||
else:
|
||||
serializer.save(create_by=self.request.user)
|
||||
|
||||
def perform_update(self, serializer):
|
||||
serializer.save(update_by=self.request.user)
|
||||
|
||||
|
||||
class CustomCreateModelMixin(CreateModelMixin):
|
||||
|
||||
def perform_create(self, serializer):
|
||||
|
@ -51,11 +57,13 @@ class CustomCreateModelMixin(CreateModelMixin):
|
|||
else:
|
||||
serializer.save(create_by=self.request.user)
|
||||
|
||||
|
||||
class CustomUpdateModelMixin(UpdateModelMixin):
|
||||
|
||||
def perform_update(self, serializer):
|
||||
serializer.save(update_by=self.request.user)
|
||||
|
||||
|
||||
class CustomDestoryModelMixin(DestroyModelMixin):
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
|
@ -114,7 +122,8 @@ class MyLoggingMixin(object):
|
|||
)
|
||||
|
||||
if should_log(request, response):
|
||||
if (connection.settings_dict.get("ATOMIC_REQUESTS") and getattr(response, "exception", None) and connection.in_atomic_block):
|
||||
if (connection.settings_dict.get("ATOMIC_REQUESTS") and
|
||||
getattr(response, "exception", None) and connection.in_atomic_block):
|
||||
# response with exception (HTTP status like: 401, 404, etc)
|
||||
# pointwise disable atomic block for handle log (TransactionManagementError)
|
||||
connection.set_rollback(True)
|
||||
|
@ -277,4 +286,3 @@ class MyLoggingMixin(object):
|
|||
if key.lower() in SENSITIVE_FIELDS:
|
||||
data[key] = self.CLEANED_SUBSTITUTE
|
||||
return data
|
||||
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
from copy import copy
|
||||
import django.utils.timezone as timezone
|
||||
from django.db import models
|
||||
from django.db.models.query import QuerySet
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from apps.utils.snowflake import IdWorker, idWorker
|
||||
from typing import Iterable, Optional
|
||||
from apps.utils.snowflake import idWorker
|
||||
|
||||
# 自定义软删除查询基类
|
||||
|
||||
|
@ -101,9 +98,11 @@ class CommonAModel(SoftModel):
|
|||
业务用基本表A,包含create_by, update_by字段
|
||||
"""
|
||||
create_by = models.ForeignKey(
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='创建人', related_name='%(class)s_create_by')
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL,
|
||||
verbose_name='创建人', related_name='%(class)s_create_by')
|
||||
update_by = models.ForeignKey(
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='最后编辑人', related_name='%(class)s_update_by')
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL,
|
||||
verbose_name='最后编辑人', related_name='%(class)s_update_by')
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
@ -114,11 +113,14 @@ class CommonBModel(SoftModel):
|
|||
业务用基本表B,包含create_by, update_by, belong_dept字段
|
||||
"""
|
||||
create_by = models.ForeignKey(
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='创建人', related_name='%(class)s_create_by')
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL,
|
||||
verbose_name='创建人', related_name='%(class)s_create_by')
|
||||
update_by = models.ForeignKey(
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='最后编辑人', related_name='%(class)s_update_by')
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL,
|
||||
verbose_name='最后编辑人', related_name='%(class)s_update_by')
|
||||
belong_dept = models.ForeignKey(
|
||||
'system.dept', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='所属部门', related_name='%(class)s_belong_dept')
|
||||
'system.dept', null=True, blank=True, on_delete=models.SET_NULL,
|
||||
verbose_name='所属部门', related_name='%(class)s_belong_dept')
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
@ -129,9 +131,11 @@ class CommonADModel(BaseModel):
|
|||
业务用基本表A, 物理删除, 包含create_by, update_by字段
|
||||
"""
|
||||
create_by = models.ForeignKey(
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='创建人', related_name='%(class)s_create_by')
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL,
|
||||
verbose_name='创建人', related_name='%(class)s_create_by')
|
||||
update_by = models.ForeignKey(
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='最后编辑人', related_name='%(class)s_update_by')
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL,
|
||||
verbose_name='最后编辑人', related_name='%(class)s_update_by')
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
@ -142,11 +146,14 @@ class CommonBDModel(BaseModel):
|
|||
业务用基本表B, 物理删除, 包含create_by, update_by, belong_dept字段
|
||||
"""
|
||||
create_by = models.ForeignKey(
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='创建人', related_name='%(class)s_create_by')
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL,
|
||||
verbose_name='创建人', related_name='%(class)s_create_by')
|
||||
update_by = models.ForeignKey(
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='最后编辑人', related_name='%(class)s_update_by')
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL,
|
||||
verbose_name='最后编辑人', related_name='%(class)s_update_by')
|
||||
belong_dept = models.ForeignKey(
|
||||
'system.organzation', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='所属部门', related_name='%(class)s_belong_dept')
|
||||
'system.organzation', null=True, blank=True, on_delete=models.SET_NULL,
|
||||
verbose_name='所属部门', related_name='%(class)s_belong_dept')
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
from rest_framework.pagination import PageNumberPagination
|
||||
from rest_framework.exceptions import ParseError
|
||||
|
||||
|
||||
class MyPagination(PageNumberPagination):
|
||||
"""
|
||||
自定义分页/传入page为0则不分页
|
||||
"""
|
||||
page_size = 10
|
||||
page_size_query_param = 'page_size'
|
||||
|
||||
def paginate_queryset(self, queryset, request, view):
|
||||
def paginate_queryset(self, queryset, request, view=None):
|
||||
if request.query_params.get('pageoff', None) or request.query_params.get('page', None) == '0':
|
||||
if queryset.count() < 500:
|
||||
return None
|
||||
elif queryset.count()>=500:
|
||||
raise ParseError('单次请求数据量大,请分页获取')
|
||||
return super().paginate_queryset(queryset, request, view=view)
|
|
@ -1,14 +1,14 @@
|
|||
from django.core.cache import cache
|
||||
from rest_framework.permissions import BasePermission, DjangoModelPermissions
|
||||
from rest_framework.permissions import BasePermission
|
||||
from apps.utils.queryset import get_child_queryset2
|
||||
from apps.system.models import Dept, Permission, Post, Role, UserPost
|
||||
from django.db.models import Q
|
||||
from apps.system.models import Dept, Permission, Post, UserPost
|
||||
from django.db.models.query import QuerySet
|
||||
|
||||
ALL_PERMS = [
|
||||
|
||||
]
|
||||
|
||||
|
||||
def get_user_perms_map(user):
|
||||
"""
|
||||
获取权限字典,可用redis存取
|
||||
|
@ -65,6 +65,7 @@ class RbacPermission(BasePermission):
|
|||
return False
|
||||
return False
|
||||
|
||||
|
||||
class RbacDataMixin:
|
||||
"""
|
||||
数据权限控权返回的queryset
|
||||
|
@ -128,4 +129,3 @@ class RbacDataMixin:
|
|||
else:
|
||||
return queryset.none()
|
||||
return queryset
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
from django.db import models
|
||||
from django.apps import apps
|
||||
|
||||
|
||||
|
@ -41,6 +40,7 @@ def get_child_queryset(name, pk, hasParent=True):
|
|||
child_queryset = cls.objects.filter(parent__in=child_queryset)
|
||||
return queryset
|
||||
|
||||
|
||||
def get_child_queryset2(obj, hasParent=True):
|
||||
'''
|
||||
获取所有子集
|
||||
|
@ -59,6 +59,7 @@ def get_child_queryset2(obj, hasParent=True):
|
|||
child_queryset = cls.objects.filter(parent__in=child_queryset)
|
||||
return queryset
|
||||
|
||||
|
||||
def get_parent_queryset(obj, hasSelf=True):
|
||||
cls = type(obj)
|
||||
ids = []
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import json
|
||||
from user_agents import parse
|
||||
|
||||
|
||||
def get_request_ip(request):
|
||||
"""
|
||||
获取请求IP
|
||||
|
@ -11,6 +13,7 @@ def get_request_ip(request):
|
|||
ip = request.META.get('REMOTE_ADDR', '') or getattr(request, 'request_ip', None)
|
||||
return ip or 'unknown'
|
||||
|
||||
|
||||
def get_request_data(request):
|
||||
"""
|
||||
获取请求参数
|
||||
|
@ -55,6 +58,7 @@ def get_request_path(request, *args, **kwargs):
|
|||
path = path.replace('/' + value, '/' + '{id}')
|
||||
return path
|
||||
|
||||
|
||||
def get_browser(request, ):
|
||||
"""
|
||||
获取浏览器名
|
||||
|
|
|
@ -3,14 +3,20 @@ from rest_framework import serializers
|
|||
from django_restql.mixins import DynamicFieldsMixin
|
||||
from rest_framework.fields import empty
|
||||
from rest_framework.request import Request
|
||||
|
||||
|
||||
class PkSerializer(serializers.Serializer):
|
||||
pks = serializers.ListField(child=serializers.CharField(max_length=20), label="主键ID列表")
|
||||
|
||||
|
||||
class GenSignatureSerializer(serializers.Serializer):
|
||||
path = serializers.CharField(label="图片地址")
|
||||
|
||||
class CustomModelSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
|
||||
|
||||
class CustomModelSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
|
||||
"""
|
||||
自定义serializer/包含创建和新增字段处理
|
||||
"""
|
||||
def __init__(self, instance=None, data=empty, request=None, **kwargs):
|
||||
super().__init__(instance, data, **kwargs)
|
||||
self.request: Request = request or self.context.get('request', None)
|
||||
|
@ -19,7 +25,7 @@ class CustomModelSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
|
|||
if self.request:
|
||||
if getattr(self.request, 'user', None):
|
||||
validated_data['create_by'] = self.request.user
|
||||
if 'belong_dept' in validated_data: # 如果指定了数据归属部门
|
||||
if 'belong_dept' in validated_data:
|
||||
pass
|
||||
elif getattr(self.request.user, 'belong_dept', None):
|
||||
if hasattr(self.Meta.model, 'belong_dept'):
|
||||
|
|
|
@ -4,12 +4,14 @@
|
|||
import time
|
||||
from server.settings import SNOW_DATACENTER_ID, SNOW_WORKER_ID
|
||||
|
||||
|
||||
class InvalidSystemClock(Exception):
|
||||
"""
|
||||
时钟回拨异常
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class Constant(object):
|
||||
# 64位ID的划分
|
||||
WORKER_ID_BITS = 5
|
||||
|
@ -84,7 +86,8 @@ class IdWorker(object):
|
|||
|
||||
self.last_timestamp = timestamp
|
||||
|
||||
new_id = ((timestamp - Constant.TWEPOCH) << Constant.TIMESTAMP_LEFT_SHIFT) | (self.datacenter_id << Constant.DATACENTER_ID_SHIFT) | \
|
||||
new_id = ((timestamp - Constant.TWEPOCH) << Constant.TIMESTAMP_LEFT_SHIFT
|
||||
) | (self.datacenter_id << Constant.DATACENTER_ID_SHIFT) | \
|
||||
(self.worker_id << Constant.WOKER_ID_SHIFT) | self.sequence
|
||||
return new_id
|
||||
|
||||
|
@ -97,6 +100,7 @@ class IdWorker(object):
|
|||
timestamp = self._gen_timestamp()
|
||||
return timestamp
|
||||
|
||||
|
||||
idWorker = IdWorker(SNOW_DATACENTER_ID, SNOW_WORKER_ID)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -2,8 +2,9 @@ import textwrap
|
|||
import random
|
||||
import string
|
||||
|
||||
|
||||
def print_roundtrip(response, *args, **kwargs):
|
||||
format_headers = lambda d: '\n'.join(f'{k}: {v}' for k, v in d.items())
|
||||
def format_headers(d): return '\n'.join(f'{k}: {v}' for k, v in d.items())
|
||||
print(textwrap.dedent('''
|
||||
---------------- request ----------------
|
||||
{req.method} {req.url}
|
||||
|
@ -22,10 +23,12 @@ def print_roundtrip(response, *args, **kwargs):
|
|||
reshdrs=format_headers(response.headers),
|
||||
))
|
||||
|
||||
|
||||
def ranstr(num):
|
||||
salt = ''.join(random.sample(string.ascii_lowercase + string.digits, num))
|
||||
return salt
|
||||
|
||||
|
||||
def rannum(num):
|
||||
salt = ''.join(random.sample(string.digits, num))
|
||||
return salt
|
|
@ -1,4 +1,4 @@
|
|||
from rest_framework.views import APIView
|
||||
|
||||
import os
|
||||
import cv2
|
||||
from apps.utils.errors import SIGN_MAKE_FAIL
|
||||
|
@ -10,6 +10,7 @@ from apps.utils.viewsets import CustomGenericViewSet
|
|||
from apps.utils.mixins import CustomCreateModelMixin
|
||||
from apps.utils.serializers import GenSignatureSerializer
|
||||
|
||||
|
||||
class SignatureViewSet(CustomCreateModelMixin, CustomGenericViewSet):
|
||||
authentication_classes = ()
|
||||
permission_classes = ()
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
from django.core.cache import cache
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.mixins import (CreateModelMixin, ListModelMixin,
|
||||
RetrieveModelMixin, UpdateModelMixin)
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
|
||||
from apps.system.models import Dept, Post
|
||||
from apps.utils.errors import PKS_ERROR
|
||||
from apps.utils.mixins import CustomDestoryModelMixin, MyLoggingMixin
|
||||
from apps.utils.permission import ALL_PERMS, RbacPermission, get_user_perms_map
|
||||
from apps.utils.queryset import get_child_queryset2
|
||||
from apps.utils.serializers import PkSerializer
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.mixins import RetrieveModelMixin, ListModelMixin, CreateModelMixin, UpdateModelMixin
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from django.core.cache import cache
|
||||
|
||||
|
||||
class CustomGenericViewSet(MyLoggingMixin, GenericViewSet):
|
||||
|
@ -95,27 +97,29 @@ class CustomGenericViewSet(MyLoggingMixin, GenericViewSet):
|
|||
return queryset.none()
|
||||
return queryset
|
||||
|
||||
class CustomModelViewSet(CreateModelMixin
|
||||
, UpdateModelMixin, ListModelMixin, RetrieveModelMixin
|
||||
, CustomDestoryModelMixin, CustomGenericViewSet):
|
||||
|
||||
class CustomModelViewSet(CreateModelMixin, UpdateModelMixin, ListModelMixin,
|
||||
RetrieveModelMixin, CustomDestoryModelMixin, CustomGenericViewSet):
|
||||
"""
|
||||
增强的ModelViewSet
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
super().__init__(**kwargs)
|
||||
# 增加默认权限标识
|
||||
if not self.perms_map:
|
||||
basename = self.basename
|
||||
self.perms_map = {'get':'*', 'post':'{}:create'.format(basename)
|
||||
,'put':'{}:update'.format(basename)
|
||||
,'patch':'{}:update'.format(basename)
|
||||
,'delete':'{}:delete'.format(basename)}
|
||||
self.perms_map = {'get': '*', 'post': '{}:create'.format(basename), 'put': '{}:update'.format(
|
||||
basename), 'patch': '{}:update'.format(basename), 'delete': '{}:delete'.format(basename)}
|
||||
for k, v in self.perms_map.items():
|
||||
if v not in ALL_PERMS and v != '*':
|
||||
ALL_PERMS.append(v)
|
||||
|
||||
@action(methods=['post'], detail=False, serializer_class=PkSerializer)
|
||||
def deletes(self, request, *args, **kwargs):
|
||||
"""
|
||||
批量删除
|
||||
"""
|
||||
request_data = request.data
|
||||
pks = request_data.get('pks', None)
|
||||
if pks:
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
from threading import Thread
|
||||
import requests
|
||||
import json
|
||||
from apps.utils.errors import XX_REQUEST_ERROR
|
||||
from apps.utils.tools import print_roundtrip
|
||||
from django.conf import settings
|
||||
|
@ -9,11 +8,11 @@ from rest_framework.exceptions import APIException, ParseError
|
|||
requests.packages.urllib3.disable_warnings()
|
||||
|
||||
|
||||
|
||||
class XxClient:
|
||||
"""
|
||||
寻息
|
||||
"""
|
||||
|
||||
def __init__(self, licence=settings.XX_LICENCE, username=settings.XX_USERNAME) -> None:
|
||||
if not settings.XX_ENABLED:
|
||||
return None
|
||||
|
@ -72,8 +71,8 @@ class XxClient:
|
|||
if req_num > 4:
|
||||
break
|
||||
else:
|
||||
r = getattr(requests, method)('{}{}'.format(settings.XX_BASE_URL, url)
|
||||
, params=params, json=json, timeout=timeout, verify=False)
|
||||
r = getattr(requests, method)('{}{}'.format(settings.XX_BASE_URL, url),
|
||||
params=params, json=json, timeout=timeout, verify=False)
|
||||
# if settings.DEBUG:
|
||||
# print_roundtrip(r)
|
||||
ret = r.json()
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class WfConfig(AppConfig):
|
||||
name = 'apps.wf'
|
||||
verbose_name = '工作流管理'
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from django_filters import rest_framework as filters
|
||||
from .models import Ticket
|
||||
|
||||
|
||||
class TicketFilterSet(filters.FilterSet):
|
||||
start_create = filters.DateFilter(field_name="create_time", lookup_expr='gte')
|
||||
end_create = filters.DateFilter(field_name="create_time", lookup_expr='lte')
|
||||
|
@ -14,11 +16,14 @@ class TicketFilterSet(filters.FilterSet):
|
|||
if value == 'owner': # 我的
|
||||
queryset = queryset.filter(create_by=user)
|
||||
elif value == 'duty': # 待办
|
||||
queryset = queryset.filter(participant__contains=user.id).exclude(act_state__in=[Ticket.TICKET_ACT_STATE_FINISH, Ticket.TICKET_ACT_STATE_CLOSED])
|
||||
queryset = queryset.filter(participant__contains=user.id).exclude(
|
||||
act_state__in=[Ticket.TICKET_ACT_STATE_FINISH, Ticket.TICKET_ACT_STATE_CLOSED])
|
||||
elif value == 'worked': # 处理过的
|
||||
queryset = queryset.filter(ticketflow_ticket__participant=user).exclude(create_by=user).order_by('-update_time').distinct()
|
||||
queryset = queryset.filter(ticketflow_ticket__participant=user).exclude(
|
||||
create_by=user).order_by('-update_time').distinct()
|
||||
elif value == 'cc': # 抄送我的
|
||||
queryset = queryset.filter(ticketflow_ticket__participant_cc__contains=user.id).exclude(create_by=user).order_by('-update_time').distinct()
|
||||
queryset = queryset.filter(ticketflow_ticket__participant_cc__contains=user.id).exclude(
|
||||
create_by=user).order_by('-update_time').distinct()
|
||||
elif value == 'all':
|
||||
pass
|
||||
else:
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
from random import choice
|
||||
from django.db import models
|
||||
from django.db.models.base import Model
|
||||
import django.utils.timezone as timezone
|
||||
from django.db.models.query import QuerySet
|
||||
from apps.utils.models import CommonAModel
|
||||
from apps.system.models import Dept, User, File
|
||||
from apps.utils.models import SoftModel, BaseModel
|
||||
from simple_history.models import HistoricalRecords
|
||||
from apps.system.models import User
|
||||
from apps.utils.models import BaseModel
|
||||
|
||||
|
||||
class Workflow(CommonAModel):
|
||||
|
@ -18,10 +13,15 @@ class Workflow(CommonAModel):
|
|||
sn_prefix = models.CharField('流水号前缀', max_length=50, default='hb')
|
||||
description = models.CharField('描述', max_length=200, null=True, blank=True)
|
||||
view_permission_check = models.BooleanField('查看权限校验', default=True, help_text='开启后,只允许工单的关联人(创建人、曾经的处理人)有权限查看工单')
|
||||
limit_expression = models.JSONField('限制表达式', default=dict, blank=True, 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的用户提交工单)')
|
||||
display_form_str = models.JSONField('展现表单字段', default=list, blank=True, help_text='默认"[]",用于用户只有对应工单查看权限时显示哪些字段,field_key的list的json,如["days","sn"],内置特殊字段participant_info.participant_name:当前处理人信息(部门名称、角色名称),state.state_name:当前状态的状态名,workflow.workflow_name:工作流名称')
|
||||
title_template = models.CharField('标题模板', max_length=50, default='{title}', null=True, blank=True, help_text='工单字段的值可以作为参数写到模板中,格式如:你有一个待办工单:{title}')
|
||||
content_template = models.CharField('内容模板', max_length=1000, default='标题:{title}, 创建时间:{create_time}', null=True, blank=True, help_text='工单字段的值可以作为参数写到模板中,格式如:标题:{title}, 创建时间:{create_time}')
|
||||
limit_expression = models.JSONField(
|
||||
'限制表达式', default=dict, blank=True, 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的用户提交工单)')
|
||||
display_form_str = models.JSONField('展现表单字段', default=list, blank=True,
|
||||
help_text='默认"[]",用于用户只有对应工单查看权限时显示哪些字段,field_key的list的json,如["days","sn"],内置特殊字段participant_info.participant_name:当前处理人信息(部门名称、角色名称),state.state_name:当前状态的状态名,workflow.workflow_name:工作流名称')
|
||||
title_template = models.CharField(
|
||||
'标题模板', max_length=50, default='{title}', null=True, blank=True, help_text='工单字段的值可以作为参数写到模板中,格式如:你有一个待办工单:{title}')
|
||||
content_template = models.CharField(
|
||||
'内容模板', max_length=1000, default='标题:{title}, 创建时间:{create_time}', null=True, blank=True, help_text='工单字段的值可以作为参数写到模板中,格式如:标题:{title}, 创建时间:{create_time}')
|
||||
|
||||
|
||||
class State(CommonAModel):
|
||||
"""
|
||||
|
@ -80,15 +80,22 @@ class State(CommonAModel):
|
|||
workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, verbose_name='所属工作流')
|
||||
is_hidden = models.BooleanField('是否隐藏', default=False, help_text='设置为True时,获取工单步骤api中不显示此状态(当前处于此状态时除外)')
|
||||
sort = models.IntegerField('状态顺序', default=0, help_text='用于工单步骤接口时,step上状态的顺序(因为存在网状情况,所以需要人为设定顺序),值越小越靠前')
|
||||
type = models.IntegerField('状态类型', default=0, choices=type_choices, help_text='0.普通类型 1.初始状态(用于新建工单时,获取对应的字段必填及transition信息) 2.结束状态(此状态下的工单不得再处理,即没有对应的transition)')
|
||||
type = models.IntegerField('状态类型', default=0, choices=type_choices,
|
||||
help_text='0.普通类型 1.初始状态(用于新建工单时,获取对应的字段必填及transition信息) 2.结束状态(此状态下的工单不得再处理,即没有对应的transition)')
|
||||
enable_retreat = models.BooleanField('允许撤回', default=False, help_text='开启后允许工单创建人在此状态直接撤回工单到初始状态')
|
||||
participant_type = models.IntegerField('参与者类型', choices=state_participanttype_choices, default=1, blank=True, help_text='0.无处理人,1.个人,2.多人,3.部门,4.角色,5.变量(支持工单创建人,创建人的leader),6.脚本,7.工单的字段内容(如表单中的"测试负责人",需要为用户名或者逗号隔开的多个用户名),8.父工单的字段内容。 初始状态请选择类型5,参与人填create_by')
|
||||
participant = models.JSONField('参与者', default=list, blank=True, help_text='可以为空(无处理人的情况,如结束状态)、userid、userid列表\部门id\角色id\变量(create_by,create_by_tl)\脚本记录的id等,包含子工作流的需要设置处理人为loonrobot')
|
||||
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:工作流名称') # json格式存储,包括读写属性1:只读,2:必填,3:可选,4:不显示, 字典的字典
|
||||
distribute_type = models.IntegerField('分配方式', default=1, choices=state_distribute_choices, help_text='1.主动接单(如果当前处理人实际为多人的时候,需要先接单才能处理) 2.直接处理(即使当前处理人实际为多人,也可以直接处理) 3.随机分配(如果实际为多人,则系统会随机分配给其中一个人) 4.全部处理(要求所有参与人都要处理一遍,才能进入下一步)')
|
||||
participant_type = models.IntegerField('参与者类型', choices=state_participanttype_choices, default=1, blank=True,
|
||||
help_text='0.无处理人,1.个人,2.多人,3.部门,4.角色,5.变量(支持工单创建人,创建人的leader),6.脚本,7.工单的字段内容(如表单中的"测试负责人",需要为用户名或者逗号隔开的多个用户名),8.父工单的字段内容。 初始状态请选择类型5,参与人填create_by')
|
||||
participant = models.JSONField('参与者', default=list, blank=True,
|
||||
help_text='可以为空(无处理人的情况,如结束状态)、userid、userid列表\部门id\角色id\变量(create_by,create_by_tl)\脚本记录的id等,包含子工作流的需要设置处理人为loonrobot')
|
||||
# json格式存储,包括读写属性1:只读,2:必填,3:可选,4:不显示, 字典的字典
|
||||
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:工作流名称')
|
||||
distribute_type = models.IntegerField('分配方式', default=1, choices=state_distribute_choices,
|
||||
help_text='1.主动接单(如果当前处理人实际为多人的时候,需要先接单才能处理) 2.直接处理(即使当前处理人实际为多人,也可以直接处理) 3.随机分配(如果实际为多人,则系统会随机分配给其中一个人) 4.全部处理(要求所有参与人都要处理一遍,才能进入下一步)')
|
||||
filter_policy = models.IntegerField('参与人过滤策略', default=0, choices=state_filter_choices)
|
||||
participant_cc = models.JSONField('抄送给', default=list, blank=True, help_text='抄送给(userid列表)')
|
||||
|
||||
|
||||
class Transition(CommonAModel):
|
||||
"""
|
||||
工作流流转,定时器,条件(允许跳过), 条件流转与定时器不可同时存在
|
||||
|
@ -131,11 +138,16 @@ class Transition(CommonAModel):
|
|||
name = models.CharField('操作', max_length=50)
|
||||
workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, verbose_name='所属工作流')
|
||||
timer = models.IntegerField('定时器(单位秒)', default=0, help_text='单位秒。处于源状态X秒后如果状态都没有过变化则自动流转到目标状态。设置时间有效')
|
||||
source_state = models.ForeignKey(State, on_delete=models.CASCADE, verbose_name='源状态', related_name='sstate_transition')
|
||||
destination_state = models.ForeignKey(State, on_delete=models.CASCADE, verbose_name='目的状态', related_name='dstate_transition')
|
||||
condition_expression = models.JSONField('条件表达式', max_length=1000, default=list, help_text='流转条件表达式,根据表达式中的条件来确定流转的下个状态,格式为[{"expression":"{days} > 3 and {days}<10", "target_state":11}] 其中{}用于填充工单的字段key,运算时会换算成实际的值,当符合条件下个状态将变为target_state_id中的值,表达式只支持简单的运算或datetime/time运算.loonflow会以首次匹配成功的条件为准,所以多个条件不要有冲突' )
|
||||
attribute_type = models.IntegerField('属性类型', default=1, choices=attribute_type_choices, help_text='属性类型,1.同意,2.拒绝,3.其他')
|
||||
field_require_check = models.BooleanField('是否校验必填项', default=True, help_text='默认在用户点击操作的时候需要校验工单表单的必填项,如果设置为否则不检查。用于如"退回"属性的操作,不需要填写表单内容')
|
||||
source_state = models.ForeignKey(State, on_delete=models.CASCADE,
|
||||
verbose_name='源状态', related_name='sstate_transition')
|
||||
destination_state = models.ForeignKey(State, on_delete=models.CASCADE,
|
||||
verbose_name='目的状态', related_name='dstate_transition')
|
||||
condition_expression = models.JSONField('条件表达式', max_length=1000, default=list,
|
||||
help_text='流转条件表达式,根据表达式中的条件来确定流转的下个状态,格式为[{"expression":"{days} > 3 and {days}<10", "target_state":11}] 其中{}用于填充工单的字段key,运算时会换算成实际的值,当符合条件下个状态将变为target_state_id中的值,表达式只支持简单的运算或datetime/time运算.loonflow会以首次匹配成功的条件为准,所以多个条件不要有冲突')
|
||||
attribute_type = models.IntegerField(
|
||||
'属性类型', default=1, choices=attribute_type_choices, help_text='属性类型,1.同意,2.拒绝,3.其他')
|
||||
field_require_check = models.BooleanField(
|
||||
'是否校验必填项', default=True, help_text='默认在用户点击操作的时候需要校验工单表单的必填项,如果设置为否则不检查。用于如"退回"属性的操作,不需要填写表单内容')
|
||||
|
||||
|
||||
class CustomField(CommonAModel):
|
||||
|
@ -163,9 +175,12 @@ class CustomField(CommonAModel):
|
|||
help_text='string, int, float, date, datetime, radio, checkbox, select, selects, cascader, cascaders, select_dg, select_dgs,textarea, file')
|
||||
field_key = models.CharField('字段标识', max_length=50, help_text='字段类型请尽量特殊,避免与系统中关键字冲突')
|
||||
field_name = models.CharField('字段名称', max_length=50)
|
||||
sort = models.IntegerField('排序', default=0, help_text='工单基础字段在表单中排序为:流水号0,标题20,状态id40,状态名41,创建人80,创建时间100,更新时间120.前端展示工单信息的表单可以根据这个id顺序排列')
|
||||
default_value = models.CharField('默认值', null=True, blank=True, max_length=100, help_text='前端展示时,可以将此内容作为表单中的该字段的默认值')
|
||||
description = models.CharField('描述', max_length=100, blank=True, null=True, help_text='字段的描述信息,可用于显示在字段的下方对该字段的详细描述')
|
||||
sort = models.IntegerField(
|
||||
'排序', default=0, help_text='工单基础字段在表单中排序为:流水号0,标题20,状态id40,状态名41,创建人80,创建时间100,更新时间120.前端展示工单信息的表单可以根据这个id顺序排列')
|
||||
default_value = models.CharField('默认值', null=True, blank=True, max_length=100,
|
||||
help_text='前端展示时,可以将此内容作为表单中的该字段的默认值')
|
||||
description = models.CharField('描述', max_length=100, blank=True, null=True,
|
||||
help_text='字段的描述信息,可用于显示在字段的下方对该字段的详细描述')
|
||||
placeholder = models.CharField('占位符', max_length=100, blank=True, null=True, help_text='用户工单详情表单中作为字段的占位符显示')
|
||||
field_template = models.TextField('文本域模板', null=True, blank=True, help_text='文本域类型字段前端显示时可以将此内容作为字段的placeholder')
|
||||
boolean_field_display = models.JSONField('布尔类型显示名', default=dict, blank=True,
|
||||
|
@ -178,6 +193,7 @@ class CustomField(CommonAModel):
|
|||
# hook = models.CharField('hook', max_length=1000, default='', help_text='获取下拉选项用于动态选项值')
|
||||
is_hidden = models.BooleanField('是否隐藏', default=False, help_text='可用于携带不需要用户查看的字段信息')
|
||||
|
||||
|
||||
class Ticket(CommonAModel):
|
||||
"""
|
||||
工单
|
||||
|
@ -209,12 +225,15 @@ class Ticket(CommonAModel):
|
|||
sn = models.CharField('流水号', max_length=25, help_text="工单的流水号")
|
||||
state = models.ForeignKey(State, on_delete=models.CASCADE, verbose_name='当前状态', related_name='ticket_state')
|
||||
parent = models.ForeignKey('self', null=True, blank=True, on_delete=models.CASCADE, verbose_name='父工单')
|
||||
parent_state = models.ForeignKey(State, null=True, blank=True, on_delete=models.CASCADE, verbose_name='父工单状态', related_name='ticket_parent_state')
|
||||
parent_state = models.ForeignKey(State, null=True, blank=True, on_delete=models.CASCADE,
|
||||
verbose_name='父工单状态', related_name='ticket_parent_state')
|
||||
ticket_data = models.JSONField('工单数据', default=dict, help_text='工单自定义字段内容')
|
||||
in_add_node = models.BooleanField('加签状态中', default=False, help_text='是否处于加签状态下')
|
||||
add_node_man = models.ForeignKey(User, verbose_name='加签人', on_delete=models.SET_NULL, null=True, blank=True, help_text='加签操作的人,工单当前处理人处理完成后会回到该处理人,当处于加签状态下才有效')
|
||||
add_node_man = models.ForeignKey(User, verbose_name='加签人', on_delete=models.SET_NULL,
|
||||
null=True, blank=True, help_text='加签操作的人,工单当前处理人处理完成后会回到该处理人,当处于加签状态下才有效')
|
||||
script_run_last_result = models.BooleanField('脚本最后一次执行结果', default=True)
|
||||
participant_type = models.IntegerField('当前处理人类型', default=0, help_text='0.无处理人,1.个人,2.多人', choices=State.state_participanttype_choices)
|
||||
participant_type = models.IntegerField(
|
||||
'当前处理人类型', default=0, help_text='0.无处理人,1.个人,2.多人', choices=State.state_participanttype_choices)
|
||||
participant = models.JSONField('当前处理人', default=list, blank=True, help_text='可以为空(无处理人的情况,如结束状态)、userid、userid列表')
|
||||
act_state = models.IntegerField('进行状态', default=1, help_text='当前工单的进行状态', choices=act_state_choices)
|
||||
multi_all_person = models.JSONField('全部处理的结果', default=dict, blank=True, help_text='需要当前状态处理人全部处理时实际的处理结果,json格式')
|
||||
|
@ -225,13 +244,15 @@ class TicketFlow(BaseModel):
|
|||
工单流转日志
|
||||
"""
|
||||
ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE, verbose_name='关联工单', related_name='ticketflow_ticket')
|
||||
transition = models.ForeignKey(Transition, verbose_name='流转id', help_text='与worklow.Transition关联, 为空时表示认为干预的操作', on_delete=models.CASCADE, null=True, blank=True)
|
||||
transition = models.ForeignKey(Transition, verbose_name='流转id',
|
||||
help_text='与worklow.Transition关联, 为空时表示认为干预的操作', on_delete=models.CASCADE, null=True, blank=True)
|
||||
suggestion = models.CharField('处理意见', max_length=10000, default='', blank=True)
|
||||
participant_type = models.IntegerField('处理人类型', default=0, help_text='0.无处理人,1.个人,2.多人等', choices=State.state_participanttype_choices)
|
||||
participant = models.ForeignKey(User, verbose_name='处理人', on_delete=models.SET_NULL, null=True, blank=True, related_name='ticketflow_participant')
|
||||
participant_type = models.IntegerField(
|
||||
'处理人类型', default=0, help_text='0.无处理人,1.个人,2.多人等', choices=State.state_participanttype_choices)
|
||||
participant = models.ForeignKey(User, verbose_name='处理人', on_delete=models.SET_NULL,
|
||||
null=True, blank=True, related_name='ticketflow_participant')
|
||||
participant_str = models.CharField('处理人', max_length=200, null=True, blank=True, help_text='非人工处理的处理人相关信息')
|
||||
state = models.ForeignKey(State, verbose_name='当前状态', default=0, blank=True, on_delete=models.CASCADE)
|
||||
ticket_data = models.JSONField('工单数据', default=dict, blank=True, help_text='可以用于记录当前表单数据,json格式')
|
||||
intervene_type = models.IntegerField('干预类型', default=0, help_text='流转类型', choices=Transition.intervene_type_choices)
|
||||
participant_cc = models.JSONField('抄送给', default=list, blank=True, help_text='抄送给(userid列表)')
|
||||
|
||||
|
|
|
@ -10,16 +10,13 @@ class GetParticipants:
|
|||
{'func': 'get_create_by', 'name': '获取工单创建人'}
|
||||
]
|
||||
|
||||
# def all_funcs(self):
|
||||
# # return list(filter(lambda x: x.startswith('get_') and callable(getattr(self, x)), dir(self)))
|
||||
# return [(func, getattr(self, func).__doc__) for func in dir(self) if callable(getattr(self, func)) and func.startswith('get_')]
|
||||
|
||||
@classmethod
|
||||
def get_create_by(cls, state: dict = {}, ticket: dict = {}, new_ticket_data: dict = {}, handler: User = {}):
|
||||
"""工单创建人"""
|
||||
participant = ticket.create_by.id
|
||||
return participant
|
||||
|
||||
|
||||
class HandleScripts:
|
||||
"""
|
||||
任务处理脚本
|
||||
|
@ -28,9 +25,9 @@ class HandleScripts:
|
|||
{'func': 'handle_something', 'name': '处理一些工作'}
|
||||
]
|
||||
|
||||
|
||||
@classmethod
|
||||
def to_next(cls, ticket:Ticket, by_timer:bool=False, by_task:bool=False, by_hook:bool=False, script_str:str=''):
|
||||
def to_next(cls, ticket: Ticket, by_timer: bool = False,
|
||||
by_task: bool = False, by_hook: bool = False, script_str: str = ''):
|
||||
# 获取信息
|
||||
transition_obj = Transition.objects.filter(source_state=ticket.state, is_deleted=False).first()
|
||||
|
||||
|
@ -41,7 +38,8 @@ class HandleScripts:
|
|||
from .services import WfService
|
||||
|
||||
# 自动执行流转
|
||||
WfService.handle_ticket(ticket=ticket, transition=transition_obj, new_ticket_data=ticket.ticket_data, by_task=True)
|
||||
WfService.handle_ticket(ticket=ticket, transition=transition_obj,
|
||||
new_ticket_data=ticket.ticket_data, by_task=True)
|
||||
|
||||
return ticket
|
||||
|
||||
|
@ -50,7 +48,5 @@ class HandleScripts:
|
|||
"""处理一些工作"""
|
||||
# 任务处理代码区
|
||||
|
||||
|
||||
# 调用自动流转
|
||||
ticket = cls.to_next(ticket=ticket, by_task=True, script_str='handle_something')
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
from apps.system.models import Dept, User
|
||||
from apps.system.serializers import UserSimpleSerializer
|
||||
import rest_framework
|
||||
from rest_framework import serializers
|
||||
|
||||
from .models import State, Ticket, TicketFlow, Workflow, Transition, CustomField
|
||||
|
@ -11,33 +10,40 @@ class WorkflowSerializer(serializers.ModelSerializer):
|
|||
model = Workflow
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class StateSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = State
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class WorkflowSimpleSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Workflow
|
||||
fields = ['id', 'name']
|
||||
|
||||
|
||||
class StateSimpleSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = State
|
||||
fields = ['id', 'name', 'type', 'distribute_type', 'enable_retreat']
|
||||
|
||||
|
||||
class TransitionSerializer(serializers.ModelSerializer):
|
||||
source_state_ = StateSimpleSerializer(source='source_state', read_only=True)
|
||||
destination_state_ = StateSimpleSerializer(source='destination_state', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Transition
|
||||
fields = '__all__'
|
||||
|
||||
@staticmethod
|
||||
def setup_eager_loading(queryset):
|
||||
""" Perform necessary eager loading of data. """
|
||||
queryset = queryset.select_related('source_state', 'destination_state')
|
||||
return queryset
|
||||
|
||||
|
||||
class AllField(serializers.Field):
|
||||
def to_representation(self, value):
|
||||
return value
|
||||
|
@ -45,6 +51,7 @@ class AllField(serializers.Field):
|
|||
def to_internal_value(self, data):
|
||||
return data
|
||||
|
||||
|
||||
class FieldChoiceSerializer(serializers.Serializer):
|
||||
id = AllField(label='ID')
|
||||
name = serializers.CharField(label='名称')
|
||||
|
@ -55,9 +62,11 @@ class CustomFieldSerializer(serializers.ModelSerializer):
|
|||
model = CustomField
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class CustomFieldCreateUpdateSerializer(serializers.ModelSerializer):
|
||||
|
||||
field_choice = FieldChoiceSerializer(label='选项列表', many=True, required=False)
|
||||
|
||||
class Meta:
|
||||
model = CustomField
|
||||
fields = ['workflow', 'field_type', 'field_key', 'field_name',
|
||||
|
@ -70,9 +79,11 @@ class TicketSimpleSerializer(serializers.ModelSerializer):
|
|||
model = Ticket
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class TicketCreateSerializer(serializers.ModelSerializer):
|
||||
transition = serializers.PrimaryKeyRelatedField(queryset=Transition.objects.all(), write_only=True)
|
||||
title = serializers.CharField(allow_blank=True, required=False)
|
||||
|
||||
class Meta:
|
||||
model = Ticket
|
||||
fields = ['title', 'workflow', 'ticket_data', 'transition']
|
||||
|
@ -80,6 +91,7 @@ class TicketCreateSerializer(serializers.ModelSerializer):
|
|||
def create(self, validated_data):
|
||||
return super().create(validated_data)
|
||||
|
||||
|
||||
class TicketSerializer(serializers.ModelSerializer):
|
||||
workflow_ = WorkflowSimpleSerializer(source='workflow', read_only=True)
|
||||
state_ = StateSimpleSerializer(source='state', read_only=True)
|
||||
|
@ -93,23 +105,27 @@ class TicketSerializer(serializers.ModelSerializer):
|
|||
queryset = queryset.select_related('workflow', 'state')
|
||||
return queryset
|
||||
|
||||
|
||||
class TicketListSerializer(serializers.ModelSerializer):
|
||||
workflow_ = WorkflowSimpleSerializer(source='workflow', read_only=True)
|
||||
state_ = StateSimpleSerializer(source='state', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Ticket
|
||||
fields = ['id', 'title', 'sn', 'workflow', 'workflow_', 'state', 'state_', 'act_state', 'create_time', 'update_time', 'participant_type', 'create_by']
|
||||
fields = ['id', 'title', 'sn', 'workflow', 'workflow_', 'state', 'state_',
|
||||
'act_state', 'create_time', 'update_time', 'participant_type', 'create_by']
|
||||
|
||||
@staticmethod
|
||||
def setup_eager_loading(queryset):
|
||||
queryset = queryset.select_related('workflow', 'state')
|
||||
return queryset
|
||||
|
||||
|
||||
class TicketDetailSerializer(serializers.ModelSerializer):
|
||||
workflow_ = WorkflowSimpleSerializer(source='workflow', read_only=True)
|
||||
state_ = StateSimpleSerializer(source='state', read_only=True)
|
||||
ticket_data_ = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Ticket
|
||||
fields = '__all__'
|
||||
|
@ -132,12 +148,14 @@ class TicketDetailSerializer(serializers.ModelSerializer):
|
|||
if i['field_value']:
|
||||
if 'sys_user' in i['label']:
|
||||
if isinstance(i['field_value'], list):
|
||||
i['field_display'] = ','.join(list(User.objects.filter(id__in=i['field_value']).values_list('name', flat=True)))
|
||||
i['field_display'] = ','.join(list(User.objects.filter(
|
||||
id__in=i['field_value']).values_list('name', flat=True)))
|
||||
else:
|
||||
i['field_display'] = User.objects.get(id=i['field_value']).name
|
||||
elif 'deptSelect' in i['label']:
|
||||
if isinstance(i['field_value'], list):
|
||||
i['field_display'] = ','.join(list(Dept.objects.filter(id__in=i['field_value']).values_list('name', flat=True)))
|
||||
i['field_display'] = ','.join(list(Dept.objects.filter(
|
||||
id__in=i['field_value']).values_list('name', flat=True)))
|
||||
else:
|
||||
i['field_display'] = Dept.objects.get(id=i['field_value']).name
|
||||
elif i['field_type'] in ['radio', 'select']:
|
||||
|
@ -156,16 +174,20 @@ class TicketDetailSerializer(serializers.ModelSerializer):
|
|||
if item['id'] == field_value:
|
||||
return
|
||||
|
||||
|
||||
class TicketFlowSerializer(serializers.ModelSerializer):
|
||||
participant_ = UserSimpleSerializer(source='participant', read_only=True)
|
||||
state_ = StateSimpleSerializer(source='state', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = TicketFlow
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class TicketFlowSimpleSerializer(serializers.ModelSerializer):
|
||||
participant_ = UserSimpleSerializer(source='participant', read_only=True)
|
||||
state_ = StateSimpleSerializer(source='state', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = TicketFlow
|
||||
exclude = ['ticket_data']
|
||||
|
@ -176,18 +198,23 @@ class TicketHandleSerializer(serializers.Serializer):
|
|||
ticket_data = serializers.JSONField(label="表单数据json")
|
||||
suggestion = serializers.CharField(label="处理意见", required=False, allow_blank=True)
|
||||
|
||||
|
||||
class TicketRetreatSerializer(serializers.Serializer):
|
||||
suggestion = serializers.CharField(label="撤回原因", required=False)
|
||||
|
||||
|
||||
class TicketCloseSerializer(serializers.Serializer):
|
||||
suggestion = serializers.CharField(label="关闭原因", required=False)
|
||||
|
||||
|
||||
class TicketAddNodeSerializer(serializers.Serializer):
|
||||
suggestion = serializers.CharField(label="加签说明", required=False)
|
||||
toadd_user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), label='发送给谁去加签')
|
||||
|
||||
|
||||
class TicketAddNodeEndSerializer(serializers.Serializer):
|
||||
suggestion = serializers.CharField(label="加签意见", required=False)
|
||||
|
||||
|
||||
class TicketDestorySerializer(serializers.Serializer):
|
||||
ids = serializers.ListField(child=serializers.PrimaryKeyRelatedField(queryset=Ticket.objects.all()), label='工单ID列表')
|
|
@ -1,6 +1,4 @@
|
|||
from apps.wf.serializers import CustomFieldSerializer
|
||||
from apps.wf.serializers import TicketSerializer, TicketSimpleSerializer
|
||||
from typing import Tuple
|
||||
from apps.wf.serializers import TicketSimpleSerializer
|
||||
from apps.system.models import User
|
||||
from apps.wf.models import CustomField, State, Ticket, TicketFlow, Transition, Workflow
|
||||
from rest_framework.exceptions import APIException, PermissionDenied, ValidationError
|
||||
|
@ -10,6 +8,7 @@ import random
|
|||
from .scripts import GetParticipants, HandleScripts
|
||||
from apps.utils.queryset import get_parent_queryset
|
||||
|
||||
|
||||
class WfService(object):
|
||||
@staticmethod
|
||||
def get_worlflow_states(workflow: Workflow):
|
||||
|
@ -59,7 +58,8 @@ class WfService(object):
|
|||
"""
|
||||
获取工单字段key List
|
||||
"""
|
||||
return list(CustomField.objects.filter(is_deleted=False, workflow=workflow).order_by('sort').values_list('field_key', flat=True))
|
||||
return list(CustomField.objects.filter(is_deleted=False,
|
||||
workflow=workflow).order_by('sort').values_list('field_key', flat=True))
|
||||
|
||||
@classmethod
|
||||
def get_ticket_transitions(cls, ticket: Ticket):
|
||||
|
@ -107,17 +107,16 @@ class WfService(object):
|
|||
now = timezone.now()
|
||||
today = str(now)[:10]+' 00:00:00'
|
||||
next_day = str(now+timedelta(days=1))[:10]+' 00:00:00'
|
||||
ticket_day_count_new = Ticket.objects.filter(create_time__gte=today, create_time__lte=next_day, workflow=workflow).count()+1
|
||||
ticket_day_count_new = Ticket.objects.filter(
|
||||
create_time__gte=today, create_time__lte=next_day, workflow=workflow).count()+1
|
||||
return '%s_%04d%02d%02d%04d' % (workflow.sn_prefix, now.year, now.month, now.day, ticket_day_count_new)
|
||||
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_next_state_by_transition_and_ticket_info(cls, ticket:Ticket, transition: Transition, new_ticket_data:dict={})->object:
|
||||
def get_next_state_by_transition_and_ticket_info(cls, ticket: Ticket,
|
||||
transition: Transition, new_ticket_data: dict = {}) -> object:
|
||||
"""
|
||||
获取下个节点状态
|
||||
"""
|
||||
source_state = ticket.state
|
||||
destination_state = transition.destination_state
|
||||
ticket_all_value = cls.get_ticket_all_field_value(ticket)
|
||||
ticket_all_value.update(**new_ticket_data)
|
||||
|
@ -127,14 +126,16 @@ class WfService(object):
|
|||
if transition.condition_expression:
|
||||
for i in transition.condition_expression:
|
||||
expression = i['expression'].format(**ticket_all_value)
|
||||
import datetime, time # 用于支持条件表达式中对时间的操作
|
||||
import datetime
|
||||
import time # 用于支持条件表达式中对时间的操作
|
||||
if eval(expression, {'__builtins__': None}, {'datetime': datetime, 'time': time}):
|
||||
destination_state = State.objects.get(pk=i['target_state'])
|
||||
return destination_state
|
||||
return destination_state
|
||||
|
||||
@classmethod
|
||||
def get_ticket_state_participant_info(cls, state:State, ticket:Ticket, new_ticket_data:dict={}, handler:User=None):
|
||||
def get_ticket_state_participant_info(cls, state: State,
|
||||
ticket: Ticket, new_ticket_data: dict = {}, handler: User = None):
|
||||
"""
|
||||
获取工单目标状态实际的处理人, 处理人类型
|
||||
"""
|
||||
|
@ -155,15 +156,16 @@ class WfService(object):
|
|||
multi_all_person_dict = {}
|
||||
destination_participant_type, destination_participant = state.participant_type, state.participant
|
||||
if destination_participant_type == State.PARTICIPANT_TYPE_FIELD:
|
||||
destination_participant = new_ticket_data.get(destination_participant, 0) if destination_participant in new_ticket_data \
|
||||
else Ticket.ticket_data.get(destination_participant, 0)
|
||||
destination_participant = new_ticket_data.get(destination_participant, 0) if destination_participant \
|
||||
in new_ticket_data else Ticket.ticket_data.get(destination_participant, 0)
|
||||
|
||||
elif destination_participant_type == State.PARTICIPANT_TYPE_FORMCODE: # 代码获取
|
||||
destination_participant = getattr(GetParticipants, destination_participant)(
|
||||
state=state, ticket=ticket, new_ticket_data=new_ticket_data, hander=handler)
|
||||
|
||||
elif destination_participant_type == State.PARTICIPANT_TYPE_DEPT: # 部门
|
||||
destination_participant = list(User.objects.filter(dept__in=destination_participant).values_list('id', flat=True))
|
||||
destination_participant = list(User.objects.filter(
|
||||
dept__in=destination_participant).values_list('id', flat=True))
|
||||
|
||||
elif destination_participant_type == State.PARTICIPANT_TYPE_ROLE: # 角色
|
||||
user_queryset = User.objects.filter(roles__in=destination_participant)
|
||||
|
@ -209,7 +211,8 @@ class WfService(object):
|
|||
if participant_type == State.PARTICIPANT_TYPE_PERSONAL:
|
||||
if user.id != participant:
|
||||
return dict(permission=False, msg="非当前处理人", need_accept=False)
|
||||
elif participant_type in [State.PARTICIPANT_TYPE_MULTI, State.PARTICIPANT_TYPE_DEPT, State.PARTICIPANT_TYPE_ROLE]:
|
||||
elif participant_type in [State.PARTICIPANT_TYPE_MULTI,
|
||||
State.PARTICIPANT_TYPE_DEPT, State.PARTICIPANT_TYPE_ROLE]:
|
||||
if user.id not in participant:
|
||||
return dict(permission=False, msg="非当前处理人", need_accept=False)
|
||||
current_participant_count = len(participant)
|
||||
|
@ -260,7 +263,8 @@ class WfService(object):
|
|||
|
||||
@classmethod
|
||||
def handle_ticket(cls, ticket: Ticket, transition: Transition, new_ticket_data: dict = {}, handler: User = None,
|
||||
suggestion:str='', created:bool=False, by_timer:bool=False, by_task:bool=False, by_hook:bool=False):
|
||||
suggestion: str = '', created: bool = False, by_timer: bool = False,
|
||||
by_task: bool = False, by_hook: bool = False):
|
||||
|
||||
source_state = ticket.state
|
||||
source_ticket_data = ticket.ticket_data
|
||||
|
@ -284,7 +288,8 @@ class WfService(object):
|
|||
multi_all_person[handler.id] = dict(transition=transition.id)
|
||||
# 判断所有人处理结果是否一致
|
||||
if WfService.check_dict_has_all_same_value(multi_all_person):
|
||||
participant_info = WfService.get_ticket_state_participant_info(destination_state, ticket, new_ticket_data)
|
||||
participant_info = WfService.get_ticket_state_participant_info(
|
||||
destination_state, ticket, new_ticket_data)
|
||||
destination_participant_type = participant_info.get('destination_participant_type', 0)
|
||||
destination_participant = participant_info.get('destination_participant', 0)
|
||||
multi_all_person = {}
|
||||
|
@ -329,7 +334,8 @@ class WfService(object):
|
|||
|
||||
# 更新工单流转记录
|
||||
if not by_task:
|
||||
TicketFlow.objects.create(ticket=ticket, state=source_state, ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||
TicketFlow.objects.create(ticket=ticket, state=source_state,
|
||||
ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL,
|
||||
participant=handler, transition=transition)
|
||||
|
||||
|
@ -350,4 +356,3 @@ class WfService(object):
|
|||
getattr(HandleScripts, destination_state.participant)(ticket)
|
||||
|
||||
return ticket
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
from django.db.models import base
|
||||
from rest_framework import urlpatterns
|
||||
from apps.wf.views import CustomFieldViewSet, FromCodeListView, StateViewSet, TicketFlowViewSet, TicketViewSet, TransitionViewSet, WorkflowViewSet
|
||||
from apps.wf.views import CustomFieldViewSet, FromCodeListView, StateViewSet, TicketFlowViewSet, \
|
||||
TicketViewSet, TransitionViewSet, WorkflowViewSet
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
|
@ -18,4 +17,3 @@ urlpatterns = [
|
|||
path(API_BASE_URL + 'participant_from_code', FromCodeListView.as_view()),
|
||||
path(API_BASE_URL, include(router.urls)),
|
||||
]
|
||||
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
from django.utils import timezone
|
||||
from django.db import transaction
|
||||
from django.db.models import query
|
||||
from rest_framework.utils import serializer_helpers
|
||||
from rest_framework.views import APIView
|
||||
from apps.system.models import User
|
||||
from apps.utils.viewsets import CustomModelViewSet
|
||||
from apps.wf.filters import TicketFilterSet
|
||||
from django.core.exceptions import AppRegistryNotReady
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import serializers
|
||||
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin
|
||||
from apps.wf.serializers import CustomFieldCreateUpdateSerializer, CustomFieldSerializer, StateSerializer, TicketAddNodeEndSerializer, TicketAddNodeSerializer, TicketCloseSerializer, TicketCreateSerializer, TicketDestorySerializer, TicketFlowSerializer, TicketFlowSimpleSerializer, TicketHandleSerializer, TicketRetreatSerializer, TicketSerializer, TransitionSerializer, WorkflowSerializer, TicketListSerializer, TicketDetailSerializer
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModelMixin, \
|
||||
RetrieveModelMixin, UpdateModelMixin
|
||||
from apps.wf.serializers import CustomFieldCreateUpdateSerializer, CustomFieldSerializer, StateSerializer, \
|
||||
TicketAddNodeEndSerializer, TicketAddNodeSerializer, TicketCloseSerializer, \
|
||||
TicketCreateSerializer, TicketDestorySerializer, TicketFlowSerializer, \
|
||||
TicketHandleSerializer, TicketRetreatSerializer, \
|
||||
TicketSerializer, TransitionSerializer, WorkflowSerializer, \
|
||||
TicketListSerializer, TicketDetailSerializer
|
||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||
from rest_framework.decorators import action, api_view
|
||||
from rest_framework.decorators import action
|
||||
from apps.wf.models import CustomField, Ticket, Workflow, State, Transition, TicketFlow
|
||||
from apps.utils.mixins import CreateUpdateCustomMixin, CreateUpdateModelAMixin
|
||||
from apps.wf.services import WfService
|
||||
from rest_framework.exceptions import APIException, PermissionDenied
|
||||
from rest_framework.exceptions import APIException
|
||||
from rest_framework import status
|
||||
from django.db.models import Count
|
||||
from .scripts import GetParticipants, HandleScripts
|
||||
from .scripts import GetParticipants
|
||||
|
||||
|
||||
# Create your views here.
|
||||
|
@ -31,6 +31,7 @@ class FromCodeListView(APIView):
|
|||
"""
|
||||
return Response(GetParticipants.all_funcs)
|
||||
|
||||
|
||||
class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet):
|
||||
perms_map = {'get': '*', 'post': 'workflow:create',
|
||||
'put': 'workflow:update', 'delete': 'workflow:delete'}
|
||||
|
@ -41,7 +42,8 @@ class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet):
|
|||
ordering_fields = ['create_time']
|
||||
ordering = ['-create_time']
|
||||
|
||||
@action(methods=['get'], detail=True, perms_map={'get':'workflow:update'}, pagination_class=None, serializer_class=StateSerializer)
|
||||
@action(methods=['get'], detail=True, perms_map={'get': 'workflow:update'},
|
||||
pagination_class=None, serializer_class=StateSerializer)
|
||||
def states(self, request, pk=None):
|
||||
"""
|
||||
工作流下的状态节点
|
||||
|
@ -50,7 +52,8 @@ class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet):
|
|||
serializer = self.serializer_class(instance=WfService.get_worlflow_states(wf), many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(methods=['get'], detail=True, perms_map={'get':'workflow:update'}, pagination_class=None, serializer_class=TransitionSerializer)
|
||||
@action(methods=['get'], detail=True, perms_map={'get': 'workflow:update'},
|
||||
pagination_class=None, serializer_class=TransitionSerializer)
|
||||
def transitions(self, request, pk=None):
|
||||
"""
|
||||
工作流下的流转规则
|
||||
|
@ -59,13 +62,15 @@ class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet):
|
|||
serializer = self.serializer_class(instance=WfService.get_workflow_transitions(wf), many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(methods=['get'], detail=True, perms_map={'get':'workflow:update'}, pagination_class=None, serializer_class=CustomFieldSerializer)
|
||||
@action(methods=['get'], detail=True, perms_map={'get': 'workflow:update'},
|
||||
pagination_class=None, serializer_class=CustomFieldSerializer)
|
||||
def customfields(self, request, pk=None):
|
||||
"""
|
||||
工作流下的自定义字段
|
||||
"""
|
||||
wf = self.get_object()
|
||||
serializer = self.serializer_class(instance=CustomField.objects.filter(workflow=wf, is_deleted=False).order_by('sort'), many=True)
|
||||
serializer = self.serializer_class(instance=CustomField.objects.filter(
|
||||
workflow=wf, is_deleted=False).order_by('sort'), many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(methods=['get'], detail=True, perms_map={'get': 'workflow:init'})
|
||||
|
@ -88,6 +93,7 @@ class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet):
|
|||
ret['field_list'] = field_list
|
||||
return Response(ret)
|
||||
|
||||
|
||||
class StateViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet):
|
||||
perms_map = {'get': '*', 'post': 'workflow:update',
|
||||
'put': 'workflow:update', 'delete': 'workflow:update'}
|
||||
|
@ -97,6 +103,7 @@ class StateViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, Destr
|
|||
filterset_fields = ['workflow']
|
||||
ordering = ['sort']
|
||||
|
||||
|
||||
class TransitionViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet):
|
||||
perms_map = {'get': '*', 'post': 'workflow:update',
|
||||
'put': 'workflow:update', 'delete': 'workflow:update'}
|
||||
|
@ -106,6 +113,7 @@ class TransitionViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin,
|
|||
filterset_fields = ['workflow']
|
||||
ordering = ['id']
|
||||
|
||||
|
||||
class CustomFieldViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet):
|
||||
perms_map = {'get': '*', 'post': 'workflow:update',
|
||||
'put': 'workflow:update', 'delete': 'workflow:update'}
|
||||
|
@ -120,6 +128,7 @@ class CustomFieldViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin,
|
|||
return CustomFieldCreateUpdateSerializer
|
||||
return super().get_serializer_class()
|
||||
|
||||
|
||||
class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, RetrieveModelMixin, GenericViewSet):
|
||||
perms_map = {'get': '*', 'post': 'ticket:create'}
|
||||
queryset = Ticket.objects.all()
|
||||
|
@ -216,10 +225,10 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
|||
new_ticket_data.update(**vdata['ticket_data'])
|
||||
|
||||
ticket = WfService.handle_ticket(ticket=ticket, transition=vdata['transition'],
|
||||
new_ticket_data=new_ticket_data, handler=request.user, suggestion=vdata['suggestion'])
|
||||
new_ticket_data=new_ticket_data, handler=request.user,
|
||||
suggestion=vdata['suggestion'])
|
||||
return Response(TicketSerializer(instance=ticket).data)
|
||||
|
||||
|
||||
@action(methods=['get'], detail=True, perms_map={'get': '*'})
|
||||
def flowsteps(self, request, pk=None):
|
||||
"""
|
||||
|
@ -261,8 +270,10 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
|||
ticket.save()
|
||||
# 接单日志
|
||||
# 更新工单流转记录
|
||||
TicketFlow.objects.create(ticket=ticket, state=ticket.state, ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||
suggestion='', participant_type=State.PARTICIPANT_TYPE_PERSONAL, intervene_type=Transition.TRANSITION_ATTRIBUTE_TYPE_ACCEPT,
|
||||
TicketFlow.objects.create(ticket=ticket, state=ticket.state,
|
||||
ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||
suggestion='', participant_type=State.PARTICIPANT_TYPE_PERSONAL,
|
||||
intervene_type=Transition.TRANSITION_ATTRIBUTE_TYPE_ACCEPT,
|
||||
participant=request.user, transition=None)
|
||||
return Response()
|
||||
else:
|
||||
|
@ -286,8 +297,10 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
|||
ticket.save()
|
||||
# 更新流转记录
|
||||
suggestion = request.data.get('suggestion', '') # 撤回原因
|
||||
TicketFlow.objects.create(ticket=ticket, state=ticket.state, ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_RETREAT,
|
||||
TicketFlow.objects.create(ticket=ticket, state=ticket.state,
|
||||
ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL,
|
||||
intervene_type=Transition.TRANSITION_INTERVENE_TYPE_RETREAT,
|
||||
participant=request.user, transition=None)
|
||||
return Response()
|
||||
|
||||
|
@ -306,8 +319,10 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
|||
ticket.save()
|
||||
# 更新流转记录
|
||||
suggestion = request.data.get('suggestion', '') # 加签说明
|
||||
TicketFlow.objects.create(ticket=ticket, state=ticket.state, ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_ADD_NODE,
|
||||
TicketFlow.objects.create(ticket=ticket, state=ticket.state,
|
||||
ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL,
|
||||
intervene_type=Transition.TRANSITION_INTERVENE_TYPE_ADD_NODE,
|
||||
participant=request.user, transition=None)
|
||||
return Response()
|
||||
|
||||
|
@ -324,13 +339,15 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
|||
ticket.save()
|
||||
# 更新流转记录
|
||||
suggestion = request.data.get('suggestion', '') # 加签意见
|
||||
TicketFlow.objects.create(ticket=ticket, state=ticket.state, ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_ADD_NODE_END,
|
||||
TicketFlow.objects.create(ticket=ticket, state=ticket.state,
|
||||
ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL,
|
||||
intervene_type=Transition.TRANSITION_INTERVENE_TYPE_ADD_NODE_END,
|
||||
participant=request.user, transition=None)
|
||||
return Response()
|
||||
|
||||
|
||||
@action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=TicketCloseSerializer)
|
||||
@action(methods=['post'], detail=True, perms_map={'post': '*'},
|
||||
serializer_class=TicketCloseSerializer)
|
||||
def close(self, request, pk=None):
|
||||
"""
|
||||
关闭工单(创建人在初始状态)
|
||||
|
@ -345,14 +362,17 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
|||
ticket.save()
|
||||
# 更新流转记录
|
||||
suggestion = request.data.get('suggestion', '') # 关闭原因
|
||||
TicketFlow.objects.create(ticket=ticket, state=ticket.state, ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_CLOSE,
|
||||
TicketFlow.objects.create(ticket=ticket, state=ticket.state,
|
||||
ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL,
|
||||
intervene_type=Transition.TRANSITION_INTERVENE_TYPE_CLOSE,
|
||||
participant=request.user, transition=None)
|
||||
return Response()
|
||||
else:
|
||||
return Response('工单不可关闭', status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@action(methods=['post'], detail=False, perms_map={'post':'ticket:destorys'}, serializer_class=TicketDestorySerializer)
|
||||
@action(methods=['post'], detail=False, perms_map={'post': 'ticket:destorys'},
|
||||
serializer_class=TicketDestorySerializer)
|
||||
def destorys(self, request, pk=None):
|
||||
"""
|
||||
批量物理删除
|
||||
|
@ -361,7 +381,6 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
|||
return Response()
|
||||
|
||||
|
||||
|
||||
class TicketFlowViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet):
|
||||
"""
|
||||
工单日志
|
||||
|
|
|
@ -61,4 +61,3 @@ urlpatterns = [
|
|||
] + \
|
||||
static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + \
|
||||
static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
|
||||
|
|
Loading…
Reference in New Issue