语法pep8修正

This commit is contained in:
caoqianming 2022-05-04 16:06:02 +08:00
parent d1c79e516a
commit 2a9c70eee5
65 changed files with 821 additions and 703 deletions

0
apps/__init__.py Normal file
View File

View File

@ -4,4 +4,3 @@ from django.apps import AppConfig
class AuthConfig(AppConfig): class AuthConfig(AppConfig):
name = 'apps.auth1' name = 'apps.auth1'
verbose_name = "认证" verbose_name = "认证"

View File

@ -1,2 +1,2 @@
USERNAME_OR_PASSWORD_WRONG = {"code":"username_or_password_wrong", "detail":"账户名或密码错误"} USERNAME_OR_PASSWORD_WRONG = {"code": "username_or_password_wrong", "detail": "账户名或密码错误"}

View File

@ -1,5 +1,6 @@
from rest_framework import serializers from rest_framework import serializers
class LoginSerializer(serializers.Serializer): class LoginSerializer(serializers.Serializer):
username = serializers.CharField(label="用户名") username = serializers.CharField(label="用户名")
password = serializers.CharField(label="密码") password = serializers.CharField(label="密码")

View File

@ -1,6 +1,5 @@
from rest_framework.exceptions import ParseError, ValidationError from rest_framework.exceptions import ParseError
from django.shortcuts import render
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import status from rest_framework import status
@ -14,6 +13,7 @@ from apps.auth1.serializers import LoginSerializer
# Create your views here. # Create your views here.
class TokenBlackView(APIView): class TokenBlackView(APIView):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
@ -26,6 +26,7 @@ class TokenBlackView(APIView):
""" """
return Response(status=status.HTTP_200_OK) return Response(status=status.HTTP_200_OK)
class LoginView(CreateAPIView): class LoginView(CreateAPIView):
""" """
Session登录 Session登录
@ -41,13 +42,14 @@ class LoginView(CreateAPIView):
serializer = self.get_serializer(data=request.data) serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data vdata = serializer.validated_data
user = authenticate(username = vdata.get('username'), user = authenticate(username=vdata.get('username'),
password = vdata.get('password')) password=vdata.get('password'))
if user is not None: if user is not None:
login(request, user) login(request, user)
return Response() return Response()
raise ParseError(**USERNAME_OR_PASSWORD_WRONG) raise ParseError(**USERNAME_OR_PASSWORD_WRONG)
class LogoutView(APIView): class LogoutView(APIView):
authentication_classes = [] authentication_classes = []
permission_classes = [] permission_classes = []

View File

@ -1,11 +1,9 @@
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
from distutils import command
from rest_framework.response import Response
from celery import shared_task from celery import shared_task
import os
import subprocess import subprocess
from server.settings import DATABASES, BACKUP_PATH, SH_PATH, SD_PWD from server.settings import DATABASES, BACKUP_PATH, SH_PATH, SD_PWD
@shared_task @shared_task
def backup_database(): def backup_database():
""" """
@ -23,22 +21,23 @@ def backup_database():
completed = subprocess.run(command, shell=True, capture_output=True, text=True) completed = subprocess.run(command, shell=True, capture_output=True, text=True)
return completed return completed
@shared_task @shared_task
def reload_server_git(): def reload_server_git():
command = 'bash {}/git_server.sh'.format(SH_PATH) command = 'bash {}/git_server.sh'.format(SH_PATH)
completed = subprocess.run(command, shell=True, capture_output=True, text=True) completed = subprocess.run(command, shell=True, capture_output=True, text=True)
return completed return completed
@shared_task @shared_task
def reload_server_only(): def reload_server_only():
command = 'echo "{}" | sudo -S service supervisor reload'.format(SD_PWD) command = 'echo "{}" | sudo -S service supervisor reload'.format(SD_PWD)
completed = subprocess.run(command, shell=True, capture_output=True, text=True) completed = subprocess.run(command, shell=True, capture_output=True, text=True)
return completed return completed
@shared_task @shared_task
def backup_media(): def backup_media():
command = 'bash {}/backup_media.sh'.format(SH_PATH) command = 'bash {}/backup_media.sh'.format(SH_PATH)
completed = subprocess.run(command, shell=True, capture_output=True, text=True) completed = subprocess.run(command, shell=True, capture_output=True, text=True)
return completed return completed

View File

@ -1,7 +1,4 @@
from django.db.models import base from django.urls import path
from rest_framework import urlpatterns
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from apps.develop.views import BackupDatabase, BackupMedia, ReloadServerGit, ReloadServerOnly from apps.develop.views import BackupDatabase, BackupMedia, ReloadServerGit, ReloadServerOnly
API_BASE_URL = 'api/develop/' API_BASE_URL = 'api/develop/'
@ -13,4 +10,3 @@ urlpatterns = [
path(API_BASE_URL + 'backup_database/', BackupDatabase.as_view()), path(API_BASE_URL + 'backup_database/', BackupDatabase.as_view()),
path(API_BASE_URL + 'backup_media/', BackupMedia.as_view()) path(API_BASE_URL + 'backup_media/', BackupMedia.as_view())
] ]

View File

@ -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.views import APIView
from rest_framework.permissions import IsAdminUser, AllowAny from rest_framework.permissions import IsAdminUser
from rest_framework.response import Response from rest_framework.response import Response
from apps.develop.tasks import backup_database, backup_media, reload_server_git, reload_server_only from apps.develop.tasks import backup_database, backup_media, reload_server_git, reload_server_only
from rest_framework.exceptions import APIException from rest_framework.exceptions import APIException
@ -11,6 +8,7 @@ from rest_framework.exceptions import APIException
class ReloadServerGit(APIView): class ReloadServerGit(APIView):
permission_classes = [IsAdminUser] permission_classes = [IsAdminUser]
def post(self, request): def post(self, request):
""" """
拉取后端代码并重启服务 拉取后端代码并重启服务
@ -23,8 +21,10 @@ class ReloadServerGit(APIView):
else: else:
raise APIException(completed.stdout) raise APIException(completed.stdout)
class ReloadServerOnly(APIView): class ReloadServerOnly(APIView):
permission_classes = [IsAdminUser] permission_classes = [IsAdminUser]
def post(self, request): def post(self, request):
""" """
仅重启服务 仅重启服务
@ -40,6 +40,7 @@ class ReloadServerOnly(APIView):
class BackupDatabase(APIView): class BackupDatabase(APIView):
permission_classes = [IsAdminUser] permission_classes = [IsAdminUser]
def post(self, request): def post(self, request):
""" """
备份数据库到指定位置 备份数据库到指定位置
@ -52,8 +53,10 @@ class BackupDatabase(APIView):
else: else:
raise APIException(completed.stdout) raise APIException(completed.stdout)
class BackupMedia(APIView): class BackupMedia(APIView):
permission_classes = [IsAdminUser] permission_classes = [IsAdminUser]
def post(self, request): def post(self, request):
""" """
备份资源到指定位置 备份资源到指定位置

View File

@ -1,9 +1,9 @@
from django.apps import AppConfig from django.apps import AppConfig
class HrmConfig(AppConfig): class HrmConfig(AppConfig):
name = 'apps.hrm' name = 'apps.hrm'
verbose_name = '人力资源管理' verbose_name = '人力资源管理'
def ready(self): def ready(self):
import apps.hrm.signals import apps.hrm.signals

View File

@ -1,7 +1,5 @@
from enum import Enum
NO_NEED_LEVEL_REMARK = {"code": "no_need_level_remark", "detail": "无需填写离岗说明"}
NO_NEED_LEVEL_REMARK = {"code":"no_need_level_remark", "detail":"无需填写离岗说明"} PHONE_F_WRONG = {"code": "phone_f_wrong", "detail": "手机号格式错误"}
PHONE_F_WRONG = {"code":"phone_f_wrong", "detail":"手机号格式错误"} PHONE_EXIST = {"code": "phone_exist", "detail": "手机号已存在"}
PHONE_EXIST = {"code":"phone_exist", "detail":"手机号已存在"} DH_PHOTO_FALI = {"code": "dh_photo_fail", "detail": "大华照片人脸提取失败"}
DH_PHOTO_FALI = {"code":"dh_photo_fail", "detail":"大华照片人脸提取失败"}

View File

@ -1,11 +1,13 @@
from django_filters import rest_framework as filters from django_filters import rest_framework as filters
from apps.hrm.models import ClockRecord, Employee, NotWorkRemark from apps.hrm.models import ClockRecord, Employee, NotWorkRemark
class ClockRecordFilterSet(filters.FilterSet): class ClockRecordFilterSet(filters.FilterSet):
create_time_start = filters.DateFilter(field_name="create_time", lookup_expr='gte') create_time_start = filters.DateFilter(field_name="create_time", lookup_expr='gte')
create_time_end = filters.DateFilter(field_name="create_time", lookup_expr='lte') create_time_end = filters.DateFilter(field_name="create_time", lookup_expr='lte')
year = filters.NumberFilter(method='filter_year') year = filters.NumberFilter(method='filter_year')
month = filters.NumberFilter(method='filter_month') month = filters.NumberFilter(method='filter_month')
class Meta: class Meta:
model = ClockRecord model = ClockRecord
fields = ['create_by', 'create_time_start', 'create_time_end', 'year', 'month'] 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): def filter_month(self, queryset, name, value):
return queryset.filter(create_time_date__month=value) return queryset.filter(create_time_date__month=value)
class EmployeeFilterSet(filters.FilterSet): class EmployeeFilterSet(filters.FilterSet):
class Meta: class Meta:
@ -26,6 +29,7 @@ class EmployeeFilterSet(filters.FilterSet):
class NotWorkRemarkFilterSet(filters.FilterSet): class NotWorkRemarkFilterSet(filters.FilterSet):
year = filters.NumberFilter(method='filter_year') year = filters.NumberFilter(method='filter_year')
month = filters.NumberFilter(method='filter_month') month = filters.NumberFilter(method='filter_month')
class Meta: class Meta:
model = NotWorkRemark model = NotWorkRemark
fields = ['year', 'month', 'user'] fields = ['year', 'month', 'user']

View File

@ -1,12 +1,9 @@
from django.db import models from django.db import models
from django.db.models.query import QuerySet
from apps.system.models import User from apps.system.models import User
from apps.utils.models import CommonADModel, CommonAModel, CommonBModel from apps.utils.models import CommonADModel, CommonAModel, CommonBModel
class Employee(CommonBModel): class Employee(CommonBModel):
""" """
员工信息 员工信息
@ -37,6 +34,7 @@ class Employee(CommonBModel):
last_check_time = models.DateTimeField('打卡时间', null=True, blank=True) last_check_time = models.DateTimeField('打卡时间', null=True, blank=True)
not_work_remark = models.CharField('当前未打卡说明', null=True, blank=True, max_length=200) not_work_remark = models.CharField('当前未打卡说明', null=True, blank=True, max_length=200)
third_info = models.JSONField('三方信息', default=dict, null=False, blank=True) third_info = models.JSONField('三方信息', default=dict, null=False, blank=True)
class Meta: class Meta:
verbose_name = '员工补充信息' verbose_name = '员工补充信息'
verbose_name_plural = verbose_name verbose_name_plural = verbose_name
@ -53,7 +51,6 @@ class Employee(CommonBModel):
# CARD_LOCATION = 20 # CARD_LOCATION = 20
class NotWorkRemark(CommonAModel): class NotWorkRemark(CommonAModel):
""" """
离岗说明 离岗说明
@ -62,6 +59,7 @@ class NotWorkRemark(CommonAModel):
user = models.ForeignKey(User, verbose_name='用户', on_delete=models.CASCADE) user = models.ForeignKey(User, verbose_name='用户', on_delete=models.CASCADE)
remark = models.CharField('未打卡说明', null=True, blank=True, max_length=200) remark = models.CharField('未打卡说明', null=True, blank=True, max_length=200)
class ClockRecord(CommonADModel): class ClockRecord(CommonADModel):
""" """
打卡记录 打卡记录
@ -71,5 +69,3 @@ class ClockRecord(CommonADModel):
(ClOCK_WORK1, '上班打卡'), (ClOCK_WORK1, '上班打卡'),
) )
type = models.PositiveSmallIntegerField('打卡类型', choices=type_choice, default=ClOCK_WORK1) type = models.PositiveSmallIntegerField('打卡类型', choices=type_choice, default=ClOCK_WORK1)

View File

@ -1,20 +1,18 @@
from apps.hrm.errors import DH_PHOTO_FALI, PHONE_EXIST, PHONE_F_WRONG from apps.hrm.errors import PHONE_F_WRONG
from apps.system.models import Dept, User
from rest_framework.serializers import ModelSerializer from rest_framework.serializers import ModelSerializer
from rest_framework import serializers from rest_framework import serializers
from apps.utils.serializers import CustomModelSerializer from apps.utils.serializers import CustomModelSerializer
from apps.utils.constants import EXCLUDE_FIELDS from apps.utils.constants import EXCLUDE_FIELDS
from apps.utils.tools import rannum, ranstr from apps.utils.tools import rannum
from .models import ClockRecord, Employee, NotWorkRemark from apps.hrm.models import ClockRecord, Employee, NotWorkRemark
from apps.system.serializers import DeptSimpleSerializer,UserSimpleSerializer from apps.system.serializers import DeptSimpleSerializer, UserSimpleSerializer
from django.db import transaction from django.db import transaction
from apps.third.clients import dhClient from apps.third.clients import dhClient
from apps.third.tapis import dhapis from apps.third.tapis import dhapis
import re import re
from server.settings import DEBUG from datetime import datetime
from rest_framework.exceptions import ParseError
from datetime import datetime, timedelta
class EmployeeBaseSerializer(CustomModelSerializer): class EmployeeBaseSerializer(CustomModelSerializer):
def save(self, **kwargs): def save(self, **kwargs):
@ -26,13 +24,15 @@ class EmployeeBaseSerializer(CustomModelSerializer):
def phone_check(phone): 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): if not re.match(re_phone, phone):
raise serializers.ValidationError(**PHONE_F_WRONG) raise serializers.ValidationError(**PHONE_F_WRONG)
return phone return phone
class EmployeeCreateUpdateSerializer(EmployeeBaseSerializer): class EmployeeCreateUpdateSerializer(EmployeeBaseSerializer):
phone = serializers.CharField(label="手机号", validators=[phone_check]) phone = serializers.CharField(label="手机号", validators=[phone_check])
class Meta: class Meta:
model = Employee model = Employee
exclude = EXCLUDE_FIELDS + ['face_data', exclude = EXCLUDE_FIELDS + ['face_data',
@ -59,7 +59,7 @@ class EmployeeCreateUpdateSerializer(EmployeeBaseSerializer):
except: except:
pass pass
json_data = { json_data = {
"service":"ehs", "service": "ehs",
"id": personId, "id": personId,
"name": instance.name, "name": instance.name,
"code": instance.number, "code": instance.number,
@ -75,10 +75,10 @@ class EmployeeCreateUpdateSerializer(EmployeeBaseSerializer):
dh_photo = res["fileUrl"] dh_photo = res["fileUrl"]
json_data.update( json_data.update(
{ {
"biosignatureTypeList":[3], "biosignatureTypeList": [3],
"personBiosignatures":[{ "personBiosignatures": [{
"type":3, "type": 3,
"index":1, "index": 1,
"path": dh_photo "path": dh_photo
}] }]
} }
@ -98,13 +98,13 @@ class EmployeeCreateUpdateSerializer(EmployeeBaseSerializer):
"category": 0, "category": 0,
"cardType": 0, "cardType": 0,
"personId": personId, "personId": personId,
"departmentId":departmentId, "departmentId": departmentId,
"startDate": startDate, "startDate": startDate,
"endDate": endDate "endDate": endDate
} }
_, res = dhClient.request(**dhapis['card_add'], json=json_data) _, res = dhClient.request(**dhapis['card_add'], json=json_data)
instance.third_info = {'dh_id':personId, instance.third_info = {'dh_id': personId,
'dh_photo':dh_photo, 'dh_face_card':res['id']} 'dh_photo': dh_photo, 'dh_face_card': res['id']}
instance.save() instance.save()
return instance return instance
@ -123,7 +123,7 @@ class EmployeeCreateUpdateSerializer(EmployeeBaseSerializer):
dh_id = instance.third_info['dh_id'] dh_id = instance.third_info['dh_id']
dh_photo = third_info['dh_photo'] dh_photo = third_info['dh_photo']
json_data = { json_data = {
"service":"ehs", "service": "ehs",
"id": dh_id, "id": dh_id,
"name": instance.name, "name": instance.name,
"code": instance.number, "code": instance.number,
@ -134,10 +134,10 @@ class EmployeeCreateUpdateSerializer(EmployeeBaseSerializer):
"phone": instance.phone, "phone": instance.phone,
"email": instance.email, "email": instance.email,
"sex": 1 if instance.gender == '' else 2, "sex": 1 if instance.gender == '' else 2,
"biosignatureTypeList":[3], "biosignatureTypeList": [3],
"personBiosignatures":[{ "personBiosignatures": [{
"type":3, "type": 3,
"index":1, "index": 1,
"path": third_info['dh_photo'] "path": third_info['dh_photo']
}] }]
} }
@ -146,10 +146,10 @@ class EmployeeCreateUpdateSerializer(EmployeeBaseSerializer):
dh_photo = res["fileUrl"] dh_photo = res["fileUrl"]
json_data.update( json_data.update(
{ {
"biosignatureTypeList":[3], "biosignatureTypeList": [3],
"personBiosignatures":[{ "personBiosignatures": [{
"type":3, "type": 3,
"index":1, "index": 1,
"path": dh_photo "path": dh_photo
}] }]
} }
@ -172,7 +172,7 @@ class EmployeeCreateUpdateSerializer(EmployeeBaseSerializer):
"category": 0, "category": 0,
"cardType": 0, "cardType": 0,
"personId": third_info['dh_id'], "personId": third_info['dh_id'],
"departmentId":departmentId, "departmentId": departmentId,
"startDate": startDate, "startDate": startDate,
"endDate": endDate "endDate": endDate
} }
@ -181,31 +181,35 @@ class EmployeeCreateUpdateSerializer(EmployeeBaseSerializer):
instance.save() instance.save()
return instance return instance
class ChannelAuthoritySerializer(serializers.Serializer): class ChannelAuthoritySerializer(serializers.Serializer):
pks = serializers.ListField(child=serializers.CharField(max_length=20), label="员工ID列表") pks = serializers.ListField(child=serializers.CharField(max_length=20), label="员工ID列表")
channels = serializers.ListField(child=serializers.CharField(max_length=20), label="门通道ID列表") channels = serializers.ListField(child=serializers.CharField(max_length=20), label="门通道ID列表")
class EmployeeSerializer(EmployeeBaseSerializer): class EmployeeSerializer(EmployeeBaseSerializer):
belong_dept_ = DeptSimpleSerializer(source='belong_dept', read_only=True) belong_dept_ = DeptSimpleSerializer(source='belong_dept', read_only=True)
class Meta: class Meta:
model = Employee model = Employee
exclude = ['face_data'] exclude = ['face_data']
read_only_fields = ['is_atwork', 'last_check_time', 'not_work_remark'] read_only_fields = ['is_atwork', 'last_check_time', 'not_work_remark']
class EmployeeNotWorkRemarkSerializer(ModelSerializer): class EmployeeNotWorkRemarkSerializer(ModelSerializer):
class Meta: class Meta:
model = Employee model = Employee
fields = ['not_work_remark'] fields = ['not_work_remark']
class ClockRecordListSerializer(serializers.ModelSerializer): class ClockRecordListSerializer(serializers.ModelSerializer):
create_by_ = UserSimpleSerializer(source='create_by', read_only=True) create_by_ = UserSimpleSerializer(source='create_by', read_only=True)
class Meta: class Meta:
model = ClockRecord model = ClockRecord
fields = '__all__' fields = '__all__'
class NotWorkRemarkListSerializer(serializers.ModelSerializer): class NotWorkRemarkListSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = NotWorkRemark model = NotWorkRemark

View File

@ -2,7 +2,7 @@ from django.db.models.signals import post_save
from apps.system.models import User from apps.system.models import User
from django.dispatch import receiver from django.dispatch import receiver
from apps.hrm.models import Employee from apps.hrm.models import Employee
from django.conf import settings
@receiver(post_save, sender=User) @receiver(post_save, sender=User)
def updateEmployee(sender, instance, created, **kwargs): 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: if ep.belong_dept and ep.belong_dept != instance.belong_dept:
ep.belong_dept = instance.belong_dept ep.belong_dept = instance.belong_dept
ep.save() ep.save()

View File

@ -9,6 +9,4 @@ def update_all_employee_not_atwork():
""" """
将所有员工设为非在岗状态 将所有员工设为非在岗状态
""" """
Employee.objects.all().update(is_atwork=False, last_check_time = None, not_work_remark=None) Employee.objects.all().update(is_atwork=False, last_check_time=None, not_work_remark=None)

View File

@ -1,5 +1,3 @@
from rest_framework import urlpatterns
from apps.hrm.views import ClockRecordViewSet, EmployeeViewSet, NotWorkRemarkViewSet from apps.hrm.views import ClockRecordViewSet, EmployeeViewSet, NotWorkRemarkViewSet
from django.urls import path, include from django.urls import path, include
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
@ -14,4 +12,3 @@ router.register('not_work_remark', NotWorkRemarkViewSet, basename='not_work_ream
urlpatterns = [ urlpatterns = [
path(API_BASE_URL, include(router.urls)), path(API_BASE_URL, include(router.urls)),
] ]

View File

@ -1,18 +1,13 @@
from datetime import datetime
from django.conf import settings from django.conf import settings
from django.db import transaction from django.db import transaction
from django.shortcuts import render
from django.utils import timezone 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.decorators import action
from rest_framework.exceptions import ParseError from rest_framework.exceptions import ParseError
from rest_framework.generics import CreateAPIView from rest_framework.mixins import ListModelMixin
from rest_framework.mixins import (CreateModelMixin, ListModelMixin,
RetrieveModelMixin, UpdateModelMixin)
from rest_framework.permissions import AllowAny from rest_framework.permissions import AllowAny
from rest_framework.response import Response 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.errors import NO_NEED_LEVEL_REMARK
from apps.hrm.filters import (ClockRecordFilterSet, EmployeeFilterSet, from apps.hrm.filters import (ClockRecordFilterSet, EmployeeFilterSet,
@ -24,11 +19,9 @@ from apps.hrm.serializers import (ChannelAuthoritySerializer,
EmployeeNotWorkRemarkSerializer, EmployeeNotWorkRemarkSerializer,
EmployeeSerializer, EmployeeSerializer,
NotWorkRemarkListSerializer) NotWorkRemarkListSerializer)
from apps.system.models import User
from apps.system.serializers import UserSimpleSerializer
from apps.third.clients import dhClient from apps.third.clients import dhClient
from apps.third.tapis import dhapis from apps.third.tapis import dhapis
from apps.utils.serializers import PkSerializer
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
@ -47,8 +40,8 @@ class EmployeeViewSet(CustomModelViewSet):
search_fields = ['name', 'number', 'user__username'] search_fields = ['name', 'number', 'user__username']
ordering = ['-pk'] ordering = ['-pk']
@action(methods=['post'], detail=True, perms_map={'post': 'employee:notworkremark'} @action(methods=['post'], detail=True, perms_map={'post': 'employee:notworkremark'},
, serializer_class=EmployeeNotWorkRemarkSerializer) serializer_class=EmployeeNotWorkRemarkSerializer)
def not_work_remark(self, request, pk=None): def not_work_remark(self, request, pk=None):
""" """
填写离岗说明 填写离岗说明
@ -62,13 +55,13 @@ class EmployeeViewSet(CustomModelViewSet):
obj.save() obj.save()
now = timezone.now() now = timezone.now()
instance, created = NotWorkRemark.objects.get_or_create( instance, created = NotWorkRemark.objects.get_or_create(
not_work_date = now.date(), not_work_date=now.date(),
user = obj.user, user=obj.user,
defaults={ defaults={
"not_work_date":now.date(), "not_work_date": now.date(),
"user":obj.user, "user": obj.user,
"remark":remark, "remark": remark,
"create_by":request.user, "create_by": request.user,
} }
) )
if not created: if not created:
@ -79,8 +72,8 @@ class EmployeeViewSet(CustomModelViewSet):
raise ParseError(**NO_NEED_LEVEL_REMARK) raise ParseError(**NO_NEED_LEVEL_REMARK)
@transaction.atomic @transaction.atomic
@action(methods=['post'], detail=False, perms_map={'post': 'employee:channel_authority'} @action(methods=['post'], detail=False, perms_map={'post': 'employee:channel_authority'},
, serializer_class=ChannelAuthoritySerializer) serializer_class=ChannelAuthoritySerializer)
def channel_authority(self, request, pk=None): def channel_authority(self, request, pk=None):
"""门通道授权 """门通道授权
@ -110,12 +103,12 @@ class EmployeeViewSet(CustomModelViewSet):
dhClient.request(**dhapis['card_door_authority'], json=json_data) dhClient.request(**dhapis['card_door_authority'], json=json_data)
for i in objs: for i in objs:
i.third_info['dh_channels'] = vdata['channels'] i.third_info['dh_channels'] = vdata['channels']
Employee.objects.bulk_update(objs, fields = ['third_info']) Employee.objects.bulk_update(objs, fields=['third_info'])
return Response() return Response()
@transaction.atomic @transaction.atomic
@action(methods=['post'], detail=False, perms_map={'post': 'employee:face_bind_1'} @action(methods=['post'], detail=False, perms_map={'post': 'employee:face_bind_1'},
, serializer_class=serializers.Serializer) serializer_class=serializers.Serializer)
def face_bind_1(self, request, pk=None): def face_bind_1(self, request, pk=None):
"""同步人脸库1 """同步人脸库1
@ -123,10 +116,10 @@ class EmployeeViewSet(CustomModelViewSet):
""" """
# 获取设备 # 获取设备
json_data = { json_data = {
"pageNum":1, "pageNum": 1,
"pageSize":1000, "pageSize": 1000,
"ownerCodes":['001'], "ownerCodes": ['001'],
"showChildNodeData":1, "showChildNodeData": 1,
# "isOnline":1 # "isOnline":1
} }
_, res = dhClient.request(**dhapis['dev_page'], json=json_data) _, res = dhClient.request(**dhapis['dev_page'], json=json_data)
@ -136,41 +129,40 @@ class EmployeeViewSet(CustomModelViewSet):
devs.append(i['deviceCode']) devs.append(i['deviceCode'])
# 编辑人像库 # 编辑人像库
json_data = { json_data = {
"groupid":settings.DAHUA_FACEGROUPID_1, "groupid": settings.DAHUA_FACEGROUPID_1,
"groupname":"全体人员", "groupname": "全体人员",
"groupdetail":"全体人员", "groupdetail": "全体人员",
"grouptype":3, "grouptype": 3,
"deviceCodeList":devs, "deviceCodeList": devs,
"syncState":0 "syncState": 0
} }
dhClient.request(**dhapis['face_group_update'], json=json_data) dhClient.request(**dhapis['face_group_update'], json=json_data)
# 人像绑定 # 人像绑定
json_data = { json_data = {
"deptId":1, "deptId": 1,
"groupIdList":[settings.DAHUA_FACEGROUPID_1], "groupIdList": [settings.DAHUA_FACEGROUPID_1],
"cascade":True "cascade": True
} }
dhClient.request(**dhapis['face_bind'], json=json_data) dhClient.request(**dhapis['face_bind'], json=json_data)
return Response() return Response()
@action(methods=['get'], detail=False, perms_map={'get': 'employee:face_bind_1'} @action(methods=['get'], detail=False, perms_map={'get': 'employee:face_bind_1'},
, serializer_class=serializers.Serializer) serializer_class=serializers.Serializer)
def face_status_1(self, request, pk=None): def face_status_1(self, request, pk=None):
"""人像下发状态 """人像下发状态
人像下发状态 人像下发状态
""" """
params = {'id':settings.DAHUA_FACEGROUPID_1} params = {'id': settings.DAHUA_FACEGROUPID_1}
_, res = dhClient.request(**dhapis['face_group_info'], params=params) _, res = dhClient.request(**dhapis['face_group_info'], params=params)
return Response(res) return Response(res)
class ClockRecordViewSet(ListModelMixin, CustomGenericViewSet): class ClockRecordViewSet(ListModelMixin, CustomGenericViewSet):
""" """
打卡记录 打卡记录
""" """
perms_map = {'get':'*', 'post':'*'} perms_map = {'get': '*', 'post': '*'}
authentication_classes = [] authentication_classes = []
permission_classes = [AllowAny] permission_classes = [AllowAny]
queryset = ClockRecord.objects.all() queryset = ClockRecord.objects.all()
@ -184,7 +176,7 @@ class NotWorkRemarkViewSet(ListModelMixin, CustomGenericViewSet):
""" """
离岗说明 离岗说明
""" """
perms_map = {'get':'*'} perms_map = {'get': '*'}
queryset = NotWorkRemark.objects.all() queryset = NotWorkRemark.objects.all()
select_related_fields = ['user'] select_related_fields = ['user']
serializer_class = NotWorkRemarkListSerializer serializer_class = NotWorkRemarkListSerializer

View File

@ -1,7 +1,7 @@
import imp
import json import json
from channels.generic.websocket import AsyncWebsocketConsumer from channels.generic.websocket import AsyncWebsocketConsumer
class MonitorConsumer(AsyncWebsocketConsumer): class MonitorConsumer(AsyncWebsocketConsumer):
async def connect(self): async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name'] self.room_name = self.scope['url_route']['kwargs']['room_name']

View File

@ -1 +1 @@
LOG_NOT_FONED = {"code":"log_not_found", "detail":"日志不存在"} LOG_NOT_FONED = {"code": "log_not_found", "detail": "日志不存在"}

View File

@ -3,6 +3,7 @@ from django.db import models
from apps.utils.models import BaseModel from apps.utils.models import BaseModel
class DrfRequestLog(BaseModel): class DrfRequestLog(BaseModel):
"""Logs Django rest framework API requests""" """Logs Django rest framework API requests"""

View File

@ -1,5 +1,4 @@
from django.urls import path, include from django.urls import path
from rest_framework import routers
from .views import DrfRequestLogViewSet, ServerInfoView, LogView, LogDetailView, index, room, video from .views import DrfRequestLogViewSet, ServerInfoView, LogView, LogDetailView, index, room, video
API_BASE_URL = 'api/monitor/' API_BASE_URL = 'api/monitor/'
@ -13,5 +12,5 @@ urlpatterns = [
path(API_BASE_URL + 'log/', LogView.as_view()), path(API_BASE_URL + 'log/', LogView.as_view()),
path(API_BASE_URL + 'log/<str:name>/', LogDetailView.as_view()), path(API_BASE_URL + 'log/<str:name>/', LogDetailView.as_view()),
path(API_BASE_URL + 'server/', ServerInfoView.as_view()), path(API_BASE_URL + 'server/', ServerInfoView.as_view()),
path(API_BASE_URL + 'request_log/', DrfRequestLogViewSet.as_view({'get':'list'}), name='requestlog_view') path(API_BASE_URL + 'request_log/', DrfRequestLogViewSet.as_view({'get': 'list'}), name='requestlog_view')
] ]

View File

@ -4,10 +4,9 @@ import psutil
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework.viewsets import ViewSet
from django.conf import settings from django.conf import settings
import os import os
from rest_framework import serializers, status from rest_framework import serializers
from drf_yasg import openapi from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema from drf_yasg.utils import swagger_auto_schema
from rest_framework.exceptions import NotFound from rest_framework.exceptions import NotFound
@ -28,12 +27,14 @@ def room(request, room_name):
'room_name': room_name 'room_name': room_name
}) })
def video(request): def video(request):
return render(request, 'monitor/video.html') return render(request, 'monitor/video.html')
class ServerInfoView(APIView): class ServerInfoView(APIView):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
""" """
获取服务器当前状态 获取服务器当前状态
@ -124,19 +125,19 @@ class LogDetailView(APIView):
except: except:
raise NotFound(**LOG_NOT_FONED) raise NotFound(**LOG_NOT_FONED)
class DrfRequestLogSerializer(serializers.ModelSerializer): class DrfRequestLogSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = DrfRequestLog model = DrfRequestLog
fields = '__all__' fields = '__all__'
class DrfRequestLogViewSet(ListModelMixin, CustomGenericViewSet): class DrfRequestLogViewSet(ListModelMixin, CustomGenericViewSet):
"""请求日志 """请求日志
请求日志 请求日志
""" """
perms_map = {'get':'requestlog.view'} perms_map = {'get': 'requestlog.view'}
queryset = DrfRequestLog.objects.all() queryset = DrfRequestLog.objects.all()
list_serializer_class = DrfRequestLogSerializer list_serializer_class = DrfRequestLogSerializer
ordering = ['-requested_at'] ordering = ['-requested_at']

View File

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

View File

@ -1,18 +1,17 @@
import json
import re
from django_celery_beat.models import PeriodicTask, CrontabSchedule, IntervalSchedule from django_celery_beat.models import PeriodicTask, CrontabSchedule, IntervalSchedule
from rest_framework import serializers from rest_framework import serializers
from django_celery_results.models import TaskResult 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.serializers import CustomModelSerializer
from apps.utils.constants import EXCLUDE_FIELDS, EXCLUDE_FIELDS_BASE from apps.utils.constants import EXCLUDE_FIELDS, EXCLUDE_FIELDS_BASE
from .models import (Dictionary, DictType, File, Dept, Permission, Post, from .models import (Dictionary, DictType, File, Dept, Permission, Post,
Role, User, UserPost) Role, User, UserPost)
from rest_framework.exceptions import ParseError, APIException from rest_framework.exceptions import ParseError
from django.db import transaction from django.db import transaction
from apps.third.tapis import dhapis from apps.third.tapis import dhapis
from rest_framework.validators import UniqueValidator from rest_framework.validators import UniqueValidator
from django.contrib.auth.hashers import make_password
class IntervalSerializer(CustomModelSerializer): class IntervalSerializer(CustomModelSerializer):
class Meta: class Meta:
@ -72,6 +71,7 @@ class DictTypeSerializer(CustomModelSerializer):
model = DictType model = DictType
fields = '__all__' fields = '__all__'
class DictTypeCreateUpdateSerializer(CustomModelSerializer): class DictTypeCreateUpdateSerializer(CustomModelSerializer):
class Meta: class Meta:
model = DictType model = DictType
@ -87,6 +87,7 @@ class DictSerializer(CustomModelSerializer):
model = Dictionary model = Dictionary
fields = '__all__' fields = '__all__'
class DictCreateUpdateSerializer(CustomModelSerializer): class DictCreateUpdateSerializer(CustomModelSerializer):
""" """
数据字典序列化 数据字典序列化
@ -96,6 +97,7 @@ class DictCreateUpdateSerializer(CustomModelSerializer):
model = Dictionary model = Dictionary
exclude = EXCLUDE_FIELDS exclude = EXCLUDE_FIELDS
class PostSerializer(CustomModelSerializer): class PostSerializer(CustomModelSerializer):
""" """
岗位序列化 岗位序列化
@ -105,6 +107,7 @@ class PostSerializer(CustomModelSerializer):
model = Post model = Post
fields = '__all__' fields = '__all__'
class PostCreateUpdateSerializer(CustomModelSerializer): class PostCreateUpdateSerializer(CustomModelSerializer):
""" """
岗位序列化 岗位序列化
@ -145,10 +148,12 @@ class RoleCreateUpdateSerializer(CustomModelSerializer):
UniqueValidator(queryset=Role.objects.all(), message='已存在相同名称的角色')]) UniqueValidator(queryset=Role.objects.all(), message='已存在相同名称的角色')])
code = serializers.CharField(label="标识", validators=[ code = serializers.CharField(label="标识", validators=[
UniqueValidator(queryset=Role.objects.all(), message='已存在相同标识的角色')]) UniqueValidator(queryset=Role.objects.all(), message='已存在相同标识的角色')])
class Meta: class Meta:
model = Role model = Role
exclude = EXCLUDE_FIELDS exclude = EXCLUDE_FIELDS
class PermissionSerializer(serializers.ModelSerializer): class PermissionSerializer(serializers.ModelSerializer):
""" """
权限序列化 权限序列化
@ -158,6 +163,7 @@ class PermissionSerializer(serializers.ModelSerializer):
model = Permission model = Permission
fields = '__all__' fields = '__all__'
class PermissionCreateUpdateSerializer(serializers.ModelSerializer): class PermissionCreateUpdateSerializer(serializers.ModelSerializer):
""" """
权限序列化 权限序列化
@ -167,6 +173,7 @@ class PermissionCreateUpdateSerializer(serializers.ModelSerializer):
model = Permission model = Permission
exclude = EXCLUDE_FIELDS_BASE exclude = EXCLUDE_FIELDS_BASE
class DeptSimpleSerializer(CustomModelSerializer): class DeptSimpleSerializer(CustomModelSerializer):
class Meta: class Meta:
model = Dept model = Dept
@ -184,11 +191,13 @@ class DeptSerializer(CustomModelSerializer):
model = Dept model = Dept
fields = '__all__' fields = '__all__'
class DeptCreateUpdateSerializer(CustomModelSerializer): class DeptCreateUpdateSerializer(CustomModelSerializer):
""" """
部门序列化 部门序列化
""" """
parent = serializers.PrimaryKeyRelatedField(queryset = Dept.objects.all(), required=True) parent = serializers.PrimaryKeyRelatedField(queryset=Dept.objects.all(), required=True)
class Meta: class Meta:
model = Dept model = Dept
exclude = EXCLUDE_FIELDS + ['third_info'] exclude = EXCLUDE_FIELDS + ['third_info']
@ -198,16 +207,15 @@ class DeptCreateUpdateSerializer(CustomModelSerializer):
from apps.third.clients import dhClient from apps.third.clients import dhClient
if dhClient: if dhClient:
data = { data = {
"parentId":1, "parentId": 1,
"name":validated_data['name'], "name": validated_data['name'],
"service":"ehs" "service": "ehs"
} }
_, res = dhClient.request(**dhapis['dept_create'],json=data) _, res = dhClient.request(**dhapis['dept_create'], json=data)
third_info = {'dh_id':str(res['id'])} third_info = {'dh_id': str(res['id'])}
validated_data['third_info'] = third_info validated_data['third_info'] = third_info
return super().create(validated_data) return super().create(validated_data)
@transaction.atomic @transaction.atomic
def update(self, instance, validated_data): def update(self, instance, validated_data):
from apps.third.clients import dhClient from apps.third.clients import dhClient
@ -215,24 +223,25 @@ class DeptCreateUpdateSerializer(CustomModelSerializer):
if dhClient and not third_info.get('dh_id', False): if dhClient and not third_info.get('dh_id', False):
# 如果dh_id 不存在 # 如果dh_id 不存在
data = { data = {
"parentId":1, "parentId": 1,
"name":validated_data['name'], "name": validated_data['name'],
"service":"ehs" "service": "ehs"
} }
_, res = dhClient.request(**dhapis['dept_create'],json=data) _, res = dhClient.request(**dhapis['dept_create'], json=data)
third_info['dh_id'] = res['id'] third_info['dh_id'] = res['id']
instance.third_info = third_info instance.third_info = third_info
instance.save() instance.save()
elif instance.name != validated_data.get('name', ''): elif instance.name != validated_data.get('name', ''):
if dhClient and instance.third_info.get('dh_id', False): if dhClient and instance.third_info.get('dh_id', False):
data = { data = {
"id":instance.third_info['dh_id'], "id": instance.third_info['dh_id'],
"parentId":1, "parentId": 1,
"name":validated_data['name'] "name": validated_data['name']
} }
dhClient.request(**dhapis['dept_update'],json=data) dhClient.request(**dhapis['dept_update'], json=data)
return super().update(instance, validated_data) return super().update(instance, validated_data)
class UserSimpleSerializer(CustomModelSerializer): class UserSimpleSerializer(CustomModelSerializer):
class Meta: class Meta:
model = User model = User
@ -260,11 +269,13 @@ class UserListSerializer(CustomModelSerializer):
# if User.objects.filter(phone=phone).exists(): # if User.objects.filter(phone=phone).exists():
# raise serializers.ValidationError(**PHONE_EXIST) # raise serializers.ValidationError(**PHONE_EXIST)
def user_exist(username): def user_exist(username):
if User.objects.filter(username=username).exists(): if User.objects.filter(username=username).exists():
raise serializers.ValidationError(**USERNAME_EXIST) raise serializers.ValidationError(**USERNAME_EXIST)
return username return username
class UserUpdateSerializer(CustomModelSerializer): class UserUpdateSerializer(CustomModelSerializer):
""" """
用户编辑序列化 用户编辑序列化
@ -280,8 +291,6 @@ class UserUpdateSerializer(CustomModelSerializer):
return super().update(instance, validated_data) return super().update(instance, validated_data)
class UserCreateSerializer(CustomModelSerializer): class UserCreateSerializer(CustomModelSerializer):
""" """
创建用户序列化 创建用户序列化
@ -293,12 +302,12 @@ class UserCreateSerializer(CustomModelSerializer):
fields = ['username', 'name', 'avatar', 'is_active'] fields = ['username', 'name', 'avatar', 'is_active']
class PasswordChangeSerializer(serializers.Serializer): class PasswordChangeSerializer(serializers.Serializer):
old_password = serializers.CharField(label="原密码") old_password = serializers.CharField(label="原密码")
new_password1 = serializers.CharField(label="新密码1") new_password1 = serializers.CharField(label="新密码1")
new_password2 = serializers.CharField(label="新密码2") new_password2 = serializers.CharField(label="新密码2")
class PTaskResultSerializer(CustomModelSerializer): class PTaskResultSerializer(CustomModelSerializer):
class Meta: class Meta:
model = TaskResult model = TaskResult
@ -317,6 +326,7 @@ class UserPostSerializer(CustomModelSerializer):
model = UserPost model = UserPost
fields = '__all__' fields = '__all__'
class UserPostCreateSerializer(CustomModelSerializer): class UserPostCreateSerializer(CustomModelSerializer):
class Meta: class Meta:
model = UserPost model = UserPost

View File

@ -1,5 +1,8 @@
from django.urls import path, include 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 from rest_framework import routers
API_BASE_URL = 'api/system/' API_BASE_URL = 'api/system/'

View File

@ -17,14 +17,15 @@ from apps.system.errors import OLD_PASSWORD_WRONG, PASSWORD_NOT_SAME, SCHEDULE_W
from apps.utils.mixins import (CustomCreateModelMixin) from apps.utils.mixins import (CustomCreateModelMixin)
from django.conf import settings from django.conf import settings
from apps.utils.permission import ALL_PERMS, get_user_perms_map 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 apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
from server.celery import app as celery_app from server.celery import app as celery_app
from .filters import UserFilter from .filters import UserFilter
from .models import (Dept, Dictionary, DictType, File, Permission, Post, Role, User, from .models import (Dept, Dictionary, DictType, File, Permission, Post, Role, User,
UserPost) UserPost)
from .serializers import (DeptCreateUpdateSerializer, DeptSerializer, DictCreateUpdateSerializer, DictSerializer, DictTypeCreateUpdateSerializer, DictTypeSerializer, from .serializers import (DeptCreateUpdateSerializer, DeptSerializer, DictCreateUpdateSerializer,
FileSerializer, PasswordChangeSerializer, PermissionCreateUpdateSerializer, PermissionSerializer, PostCreateUpdateSerializer, PostSerializer, DictSerializer, DictTypeCreateUpdateSerializer, DictTypeSerializer,
FileSerializer, PasswordChangeSerializer, PermissionCreateUpdateSerializer,
PermissionSerializer, PostCreateUpdateSerializer, PostSerializer,
PTaskCreateUpdateSerializer, PTaskResultSerializer, PTaskCreateUpdateSerializer, PTaskResultSerializer,
PTaskSerializer, RoleCreateUpdateSerializer, RoleSerializer, PTaskSerializer, RoleCreateUpdateSerializer, RoleSerializer,
UserCreateSerializer, UserListSerializer, UserPostCreateSerializer, UserCreateSerializer, UserListSerializer, UserPostCreateSerializer,
@ -280,7 +281,7 @@ class UserPostViewSet(CreateModelMixin, DestroyModelMixin, ListModelMixin, Custo
def perform_destroy(self, instance): def perform_destroy(self, instance):
user = instance.user user = instance.user
instance.delete(update_by = self.request.user) instance.delete(update_by=self.request.user)
fdept = UserPost.objects.filter(user=user).order_by('sort', 'create_time').first() fdept = UserPost.objects.filter(user=user).order_by('sort', 'create_time').first()
if fdept: if fdept:
user.belong_dept = fdept user.belong_dept = fdept
@ -313,7 +314,7 @@ class UserViewSet(CustomModelViewSet):
@action(methods=['put'], detail=False, @action(methods=['put'], detail=False,
permission_classes=[IsAuthenticated], permission_classes=[IsAuthenticated],
serializer_class = PasswordChangeSerializer) serializer_class=PasswordChangeSerializer)
def password(self, request, pk=None): def password(self, request, pk=None):
"""修改密码 """修改密码

View File

@ -1 +1 @@
TAPI_CODE_WRONG = {"code":"tapi_code_wrong", "detail":"三方接口请求标识有误"} TAPI_CODE_WRONG = {"code": "tapi_code_wrong", "detail": "三方接口请求标识有误"}

View File

@ -3,10 +3,10 @@ from rest_framework import serializers
class RequestCommonSerializer(serializers.Serializer): class RequestCommonSerializer(serializers.Serializer):
method_choice = ( method_choice = (
('post','post'), ('post', 'post'),
('get','get'), ('get', 'get'),
('put','put'), ('put', 'put'),
('delete','delete') ('delete', 'delete')
) )
url = serializers.CharField(label='请求地址', required=False) url = serializers.CharField(label='请求地址', required=False)
method = serializers.ChoiceField(label='请求方法', choices=method_choice, required=False) method = serializers.ChoiceField(label='请求方法', choices=method_choice, required=False)

View File

@ -1,88 +1,88 @@
# 大华API接口 # 大华API接口
dhapis = { dhapis = {
"dept_tree":{ "dept_tree": {
"url":"/evo-apigw/evo-brm/1.2.0/department/tree", "url": "/evo-apigw/evo-brm/1.2.0/department/tree",
"method":"post" "method": "post"
}, },
"dept_create":{ "dept_create": {
"url":"/evo-apigw/evo-brm/1.0.0/department/add", "url": "/evo-apigw/evo-brm/1.0.0/department/add",
"method":"post" "method": "post"
}, },
"dept_update":{ "dept_update": {
"url":"/evo-apigw/evo-brm/1.0.0/department/update", "url": "/evo-apigw/evo-brm/1.0.0/department/update",
"method":"put" "method": "put"
}, },
"person_gen_id":{ "person_gen_id": {
"url":"/evo-apigw/evo-brm/1.0.0/person/generate-id", "url": "/evo-apigw/evo-brm/1.0.0/person/generate-id",
"method":"get" "method": "get"
}, },
"person_add": { "person_add": {
"url":"/evo-apigw/evo-brm/1.2.0/person/subsystem/add", "url": "/evo-apigw/evo-brm/1.2.0/person/subsystem/add",
"method":"post" "method": "post"
}, },
"person_update": { "person_update": {
"url":"/evo-apigw/evo-brm/1.2.0/person/subsystem/update", "url": "/evo-apigw/evo-brm/1.2.0/person/subsystem/update",
"method":"put" "method": "put"
}, },
"person_img_upload": { "person_img_upload": {
"url":"/evo-apigw/evo-brm/1.2.0/person/upload/img", "url": "/evo-apigw/evo-brm/1.2.0/person/upload/img",
"method":"post" "method": "post"
}, },
"person_detail": { "person_detail": {
"url":"/evo-apigw/evo-brm/1.0.0/person/subsystem/{id}", "url": "/evo-apigw/evo-brm/1.0.0/person/subsystem/{id}",
"method":"get" "method": "get"
}, },
"device_list": { "device_list": {
"url":"/evo-apigw/evo-brm/1.2.0/device/subsystem/page", "url": "/evo-apigw/evo-brm/1.2.0/device/subsystem/page",
"method":"get" "method": "get"
}, },
"device_detail": { "device_detail": {
"url":"/evo-apigw/evo-brm/1.0.0/device/{deviceCode}", "url": "/evo-apigw/evo-brm/1.0.0/device/{deviceCode}",
"method":"get" "method": "get"
}, },
"channel_list": { "channel_list": {
"url":"/evo-apigw/evo-brm/1.2.0/device/channel/subsystem/page", "url": "/evo-apigw/evo-brm/1.2.0/device/channel/subsystem/page",
"method":"post" "method": "post"
}, },
"card_add": { "card_add": {
"url":"/evo-apigw/evo-brm/1.2.0/card/add", "url": "/evo-apigw/evo-brm/1.2.0/card/add",
"method":"post" "method": "post"
}, },
"card_gen_id": { "card_gen_id": {
"url":"/evo-apigw/evo-brm/1.0.0/card/generate-id", "url": "/evo-apigw/evo-brm/1.0.0/card/generate-id",
"method":"get" "method": "get"
}, },
"card_door_authority": { "card_door_authority": {
"url":"/evo-apigw/evo-accesscontrol/1.0.0/card/accessControl/doorAuthority", "url": "/evo-apigw/evo-accesscontrol/1.0.0/card/accessControl/doorAuthority",
"method":"post" "method": "post"
}, },
"mq_subscribe": { "mq_subscribe": {
"url":"/evo-apigw/evo-event/1.0.0/subscribe/mqinfo", "url": "/evo-apigw/evo-event/1.0.0/subscribe/mqinfo",
"method":"post" "method": "post"
}, },
"mq_unsubscribe": { "mq_unsubscribe": {
"url":"/evo-apigw/evo-event/1.0.0/subscribe/mqinfo", "url": "/evo-apigw/evo-event/1.0.0/subscribe/mqinfo",
"method":"delete" "method": "delete"
}, },
"face_bind":{ "face_bind": {
"url":"/evo-apigw/evo-face/deptGroup/sync/bindPerson", "url": "/evo-apigw/evo-face/deptGroup/sync/bindPerson",
"method":"post" "method": "post"
}, },
"dev_tree":{ "dev_tree": {
"url":"/evo-apigw/evo-face/tree/getDevChnIdsAndName", "url": "/evo-apigw/evo-face/tree/getDevChnIdsAndName",
"method":"get" "method": "get"
}, },
"dev_page":{ "dev_page": {
"url":"/evo-apigw/evo-brm/1.2.0/device/subsystem/page", "url": "/evo-apigw/evo-brm/1.2.0/device/subsystem/page",
"method":"post" "method": "post"
}, },
"face_group_update":{ "face_group_update": {
"url":"/evo-apigw/evo-face/groupInfo/update", "url": "/evo-apigw/evo-face/groupInfo/update",
"method":"post" "method": "post"
}, },
"face_group_info":{ "face_group_info": {
"url":"/evo-apigw/evo-face/groupInfo/view/{id}", "url": "/evo-apigw/evo-face/groupInfo/view/{id}",
"method":"get" "method": "get"
} }
} }

View File

@ -2,7 +2,6 @@ from rest_framework.exceptions import ParseError, APIException
from apps.third.tapis import dhapis, xxapis from apps.third.tapis import dhapis, xxapis
from apps.third.erros import TAPI_CODE_WRONG from apps.third.erros import TAPI_CODE_WRONG
from apps.third.clients import dhClient from apps.third.clients import dhClient
from apps.utils.errors import XX_REQUEST_ERROR
from apps.utils.mixins import MyLoggingMixin from apps.utils.mixins import MyLoggingMixin
from apps.third.clients import xxClient from apps.third.clients import xxClient
from rest_framework.response import Response from rest_framework.response import Response
@ -51,6 +50,8 @@ class XxTestView(APIView):
raise APIException(**res) raise APIException(**res)
# 寻息事件订阅 # 寻息事件订阅
class XxListener(stomp.ConnectionListener): class XxListener(stomp.ConnectionListener):
def on_error(self, frame): def on_error(self, frame):
print('received an error "%s"' % frame.body) print('received an error "%s"' % frame.body)
@ -65,6 +66,7 @@ if settings.XX_ENABLED:
c.connect(settings.XX_USERNAME, settings.XX_LICENCE) c.connect(settings.XX_USERNAME, settings.XX_LICENCE)
c.subscribe(settings.XX_QUEUE, id='') c.subscribe(settings.XX_QUEUE, id='')
class XxCommonViewSet(CreateModelMixin, CustomGenericViewSet): class XxCommonViewSet(CreateModelMixin, CustomGenericViewSet):
""" """
寻息通用调用接口 寻息通用调用接口
@ -175,7 +177,7 @@ class DhCommonViewSet(CreateModelMixin, CustomGenericViewSet):
取消事件订阅 取消事件订阅
""" """
dhClient.request(**dhapis['mq_unsubscribe'], params={'name':'127.0.0.1_8000'}) dhClient.request(**dhapis['mq_unsubscribe'], params={'name': '127.0.0.1_8000'})
return Response() return Response()
@action(methods=['post'], detail=False, @action(methods=['post'], detail=False,
@ -189,7 +191,7 @@ class DhCommonViewSet(CreateModelMixin, CustomGenericViewSet):
data = request.data data = request.data
method = data['method'] method = data['method']
subsystem = data.get('subsystem', None) subsystem = data.get('subsystem', None)
info = data.get('info', {}) # info = data.get('info', {})
if method == 'department.update': if method == 'department.update':
pass pass
elif method == 'person.update': elif method == 'person.update':

View File

@ -1,22 +1,22 @@
import time
from threading import Thread from threading import Thread
import traceback
import requests 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.errors import DH_REQUEST_ERROR
from apps.utils.tools import print_roundtrip 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() requests.packages.urllib3.disable_warnings()
class DhClient: class DhClient:
""" """
大华 大华
""" """
def __init__(self, client_id= settings.DAHUA_CLIENTID def __init__(self, client_id=settings.DAHUA_CLIENTID,
, client_secret = settings.DAHUA_SECRET) -> None: client_secret=settings.DAHUA_SECRET) -> None:
if not settings.DAHUA_ENABLED: if not settings.DAHUA_ENABLED:
return None return None
self.client_id = client_id self.client_id = client_id
@ -34,7 +34,8 @@ class DhClient:
'client_id': self.client_id, 'client_id': self.client_id,
'client_secret': self.client_secret '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() ret = r.json()
if ret['success']: if ret['success']:
self.headers['Authorization'] = 'bearer ' + ret['data']['access_token'] self.headers['Authorization'] = 'bearer ' + ret['data']['access_token']
@ -66,7 +67,8 @@ class DhClient:
self.isRuning = False self.isRuning = False
# self.t.join() # 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: if self.isGetingToken:
req_num = 0 req_num = 0
while True: while True:
@ -83,8 +85,9 @@ class DhClient:
try: try:
if params: if params:
url = url.format(**params) url = url.format(**params)
r = getattr(requests, method)('{}{}'.format(settings.DAHUA_BASE_URL, url) r = getattr(requests, method)('{}{}'.format(settings.DAHUA_BASE_URL, url),
, headers = self.headers, params=params, json=json, timeout=timeout, files=files, verify=False) headers=self.headers, params=params, json=json,
timeout=timeout, files=files, verify=False)
except: except:
if raise_exception: if raise_exception:
raise APIException(**DH_REQUEST_ERROR) raise APIException(**DH_REQUEST_ERROR)
@ -92,16 +95,15 @@ class DhClient:
# if settings.DEBUG: # if settings.DEBUG:
# print_roundtrip(r) # print_roundtrip(r)
if r.status_code == 200: if r.status_code == 200:
"""
请求成功
"""
ret = r.json() ret = r.json()
if ret.get('code') == '27001007': if ret.get('code') == '27001007':
self.get_token() # 重新获取token self.get_token() # 重新获取token
self.request(url, method, params, json, timeout, file_path_rela, raise_exception) self.request(url, method, params, json, timeout, file_path_rela, raise_exception)
else: else:
if ret['code'] not in ['0', '100', '00000', '1000', 0, 100, 1000]: 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'])) err_detail = dict(detail=detail, code='dh_'+str(ret['code']))
if raise_exception: if raise_exception:
raise ParseError(**err_detail) raise ParseError(**err_detail)
@ -111,4 +113,5 @@ class DhClient:
raise APIException(**DH_REQUEST_ERROR) raise APIException(**DH_REQUEST_ERROR)
return 'error', DH_REQUEST_ERROR return 'error', DH_REQUEST_ERROR
dhClient = DhClient() dhClient = DhClient()

View File

@ -1,4 +1,4 @@
XX_REQUEST_ERROR = {"code":"xx_request_error", "detail":"寻息接口访问异常"} XX_REQUEST_ERROR = {"code": "xx_request_error", "detail": "寻息接口访问异常"}
DH_REQUEST_ERROR = {"code":"dh_request_error", "detail":"大华接口访问异常"} DH_REQUEST_ERROR = {"code": "dh_request_error", "detail": "大华接口访问异常"}
SIGN_MAKE_FAIL = {"code":"sign_make_fail", "detail":"签名照生成失败,请重新上传"} SIGN_MAKE_FAIL = {"code": "sign_make_fail", "detail": "签名照生成失败,请重新上传"}
PKS_ERROR = {"code":"pks_error", "detail":"未获取到主键列表"} PKS_ERROR = {"code": "pks_error", "detail": "未获取到主键列表"}

View File

@ -1,12 +1,13 @@
import traceback
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.http import Http404 from django.http import Http404
from rest_framework.response import Response
from rest_framework import exceptions from rest_framework import exceptions
from rest_framework.response import Response
from rest_framework.views import set_rollback from rest_framework.views import set_rollback
from django.utils.translation import gettext_lazy as _
import traceback
from server.settings import myLogger from server.settings import myLogger
def custom_exception_hander(exc, context): def custom_exception_hander(exc, context):
""" """
自定义异常处理 自定义异常处理
@ -25,15 +26,15 @@ def custom_exception_hander(exc, context):
headers['Retry-After'] = '%d' % exc.wait headers['Retry-After'] = '%d' % exc.wait
if isinstance(exc.detail, (list, dict)): if isinstance(exc.detail, (list, dict)):
data = {'err_detail':exc.detail} data = {'err_detail': exc.detail}
data['err_code'] = exc.default_code data['err_code'] = exc.default_code
data['err_msg'] = exc.default_detail data['err_msg'] = exc.default_detail
else: else:
data = {'err_msg': exc.detail, 'err_code':exc.get_codes()} data = {'err_msg': exc.detail, 'err_code': exc.get_codes()}
set_rollback() set_rollback()
data['request_id'] = request_id data['request_id'] = request_id
return Response(data, status=exc.status_code, headers=headers) return Response(data, status=exc.status_code, headers=headers)
err_str = traceback.format_exc() args = (request_id, traceback.format_exc())
myLogger.error('{}-{}'.format(request_id, err_str)) myLogger.error(f"{args[0]}-{args[1]}")
return Response(data={'err_code':'server_error', 'err_msg':'服务器错误', 'request_id': request_id}, status=500) return Response(data={'err_code': 'server_error', 'err_msg': '服务器错误', 'request_id': request_id}, status=500)

View File

@ -1,6 +1,5 @@
import uuid import uuid
from django.db.models.query import QuerySet from rest_framework.mixins import CreateModelMixin, UpdateModelMixin, DestroyModelMixin
from rest_framework.mixins import CreateModelMixin, UpdateModelMixin, ListModelMixin, RetrieveModelMixin, DestroyModelMixin
import ast import ast
import ipaddress import ipaddress
import traceback import traceback
@ -8,58 +7,67 @@ from apps.monitor.models import DrfRequestLog
from server.settings import myLogger from server.settings import myLogger
from django.db import connection from django.db import connection
from django.utils.timezone import now from django.utils.timezone import now
from apps.utils.snowflake import idWorker
from user_agents import parse from user_agents import parse
class CreateUpdateModelAMixin: class CreateUpdateModelAMixin:
""" """
业务用基本表A用 业务用基本表A用
""" """
def perform_create(self, serializer): def perform_create(self, serializer):
serializer.save(create_by = self.request.user) serializer.save(create_by=self.request.user)
def perform_update(self, serializer): def perform_update(self, serializer):
serializer.save(update_by = self.request.user) serializer.save(update_by=self.request.user)
class CreateUpdateModelBMixin: class CreateUpdateModelBMixin:
""" """
业务用基本表B用 业务用基本表B用
""" """
def perform_create(self, serializer): def perform_create(self, serializer):
serializer.save(create_by = self.request.user, belong_dept=self.request.user.dept) serializer.save(create_by=self.request.user, belong_dept=self.request.user.dept)
def perform_update(self, serializer): def perform_update(self, serializer):
serializer.save(update_by = self.request.user) serializer.save(update_by=self.request.user)
class CreateUpdateCustomMixin: class CreateUpdateCustomMixin:
""" """
整合 整合
""" """
def perform_create(self, serializer): def perform_create(self, serializer):
if hasattr(self.queryset.model, 'belong_dept'): if hasattr(self.queryset.model, 'belong_dept'):
serializer.save(create_by = self.request.user, belong_dept=self.request.user.dept) serializer.save(create_by=self.request.user, belong_dept=self.request.user.dept)
else: else:
serializer.save(create_by = self.request.user) serializer.save(create_by=self.request.user)
def perform_update(self, serializer): def perform_update(self, serializer):
serializer.save(update_by = self.request.user) serializer.save(update_by=self.request.user)
class CustomCreateModelMixin(CreateModelMixin): class CustomCreateModelMixin(CreateModelMixin):
def perform_create(self, serializer): def perform_create(self, serializer):
if hasattr(self.queryset.model, 'belong_dept'): if hasattr(self.queryset.model, 'belong_dept'):
serializer.save(create_by = self.request.user, belong_dept=self.request.user.dept) serializer.save(create_by=self.request.user, belong_dept=self.request.user.dept)
else: else:
serializer.save(create_by = self.request.user) serializer.save(create_by=self.request.user)
class CustomUpdateModelMixin(UpdateModelMixin): class CustomUpdateModelMixin(UpdateModelMixin):
def perform_update(self, serializer): def perform_update(self, serializer):
serializer.save(update_by = self.request.user) serializer.save(update_by=self.request.user)
class CustomDestoryModelMixin(DestroyModelMixin): class CustomDestoryModelMixin(DestroyModelMixin):
def perform_destroy(self, instance): def perform_destroy(self, instance):
instance.delete(update_by = self.request.user) instance.delete(update_by=self.request.user)
class MyLoggingMixin(object): class MyLoggingMixin(object):
@ -79,7 +87,7 @@ class MyLoggingMixin(object):
def initial(self, request, *args, **kwargs): def initial(self, request, *args, **kwargs):
request_id = uuid.uuid4() request_id = uuid.uuid4()
self.log = {"requested_at": now(), "id":request_id} self.log = {"requested_at": now(), "id": request_id}
setattr(request, 'request_id', request_id) setattr(request, 'request_id', request_id)
if not getattr(self, "decode_request_body", False): if not getattr(self, "decode_request_body", False):
self.log["data"] = "" self.log["data"] = ""
@ -114,7 +122,8 @@ class MyLoggingMixin(object):
) )
if should_log(request, response): 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) # response with exception (HTTP status like: 401, 404, etc)
# pointwise disable atomic block for handle log (TransactionManagementError) # pointwise disable atomic block for handle log (TransactionManagementError)
connection.set_rollback(True) connection.set_rollback(True)
@ -277,4 +286,3 @@ class MyLoggingMixin(object):
if key.lower() in SENSITIVE_FIELDS: if key.lower() in SENSITIVE_FIELDS:
data[key] = self.CLEANED_SUBSTITUTE data[key] = self.CLEANED_SUBSTITUTE
return data return data

View File

@ -1,10 +1,7 @@
from copy import copy
import django.utils.timezone as timezone import django.utils.timezone as timezone
from django.db import models from django.db import models
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from django.core.exceptions import ObjectDoesNotExist from apps.utils.snowflake import idWorker
from apps.utils.snowflake import IdWorker, idWorker
from typing import Iterable, Optional
# 自定义软删除查询基类 # 自定义软删除查询基类
@ -101,9 +98,11 @@ class CommonAModel(SoftModel):
业务用基本表A,包含create_by, update_by字段 业务用基本表A,包含create_by, update_by字段
""" """
create_by = models.ForeignKey( 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( 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: class Meta:
abstract = True abstract = True
@ -114,11 +113,14 @@ class CommonBModel(SoftModel):
业务用基本表B,包含create_by, update_by, belong_dept字段 业务用基本表B,包含create_by, update_by, belong_dept字段
""" """
create_by = models.ForeignKey( 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( 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( 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: class Meta:
abstract = True abstract = True
@ -129,9 +131,11 @@ class CommonADModel(BaseModel):
业务用基本表A, 物理删除, 包含create_by, update_by字段 业务用基本表A, 物理删除, 包含create_by, update_by字段
""" """
create_by = models.ForeignKey( 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( 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: class Meta:
abstract = True abstract = True
@ -142,11 +146,14 @@ class CommonBDModel(BaseModel):
业务用基本表B, 物理删除, 包含create_by, update_by, belong_dept字段 业务用基本表B, 物理删除, 包含create_by, update_by, belong_dept字段
""" """
create_by = models.ForeignKey( 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( 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( 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: class Meta:
abstract = True abstract = True

View File

@ -1,14 +1,17 @@
from rest_framework.pagination import PageNumberPagination from rest_framework.pagination import PageNumberPagination
from rest_framework.exceptions import ParseError from rest_framework.exceptions import ParseError
class MyPagination(PageNumberPagination): class MyPagination(PageNumberPagination):
"""
自定义分页/传入page为0则不分页
"""
page_size = 10 page_size = 10
page_size_query_param = 'page_size' 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 request.query_params.get('pageoff', None) or request.query_params.get('page', None) == '0':
if queryset.count()<500: if queryset.count() < 500:
return None return None
elif queryset.count()>=500:
raise ParseError('单次请求数据量大,请分页获取') raise ParseError('单次请求数据量大,请分页获取')
return super().paginate_queryset(queryset, request, view=view) return super().paginate_queryset(queryset, request, view=view)

View File

@ -1,21 +1,21 @@
from django.core.cache import cache 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.utils.queryset import get_child_queryset2
from apps.system.models import Dept, Permission, Post, Role, UserPost from apps.system.models import Dept, Permission, Post, UserPost
from django.db.models import Q
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
ALL_PERMS = [ ALL_PERMS = [
] ]
def get_user_perms_map(user): def get_user_perms_map(user):
""" """
获取权限字典,可用redis存取 获取权限字典,可用redis存取
""" """
user_perms_map = {} user_perms_map = {}
if user.is_superuser: if user.is_superuser:
user_perms_map = {'superuser':None} user_perms_map = {'superuser': None}
else: else:
objs = UserPost.objects.filter(user=user) objs = UserPost.objects.filter(user=user)
for i in objs: for i in objs:
@ -29,7 +29,7 @@ def get_user_perms_map(user):
if i.post.data_range < data_range: if i.post.data_range < data_range:
user_perms_map[code][dept_id] = data_range user_perms_map[code][dept_id] = data_range
else: else:
user_perms_map[code] = {dept_id:i.post.data_range} user_perms_map[code] = {dept_id: i.post.data_range}
cache.set('perms_' + user.id, user_perms_map, 60*60) cache.set('perms_' + user.id, user_perms_map, 60*60)
return user_perms_map return user_perms_map
@ -65,6 +65,7 @@ class RbacPermission(BasePermission):
return False return False
return False return False
class RbacDataMixin: class RbacDataMixin:
""" """
数据权限控权返回的queryset 数据权限控权返回的queryset
@ -115,17 +116,16 @@ class RbacDataMixin:
belong_depts = get_child_queryset2(dept.parent) belong_depts = get_child_queryset2(dept.parent)
else: else:
belong_depts = get_child_queryset2(dept) belong_depts = get_child_queryset2(dept)
queryset = queryset.filter(belong_dept__in = belong_depts) queryset = queryset.filter(belong_dept__in=belong_depts)
elif data_range == Post.POST_DATA_THISLEVEL_AND_BELOW: elif data_range == Post.POST_DATA_THISLEVEL_AND_BELOW:
belong_depts = get_child_queryset2(dept) belong_depts = get_child_queryset2(dept)
queryset = queryset.filter(belong_dept__in = belong_depts) queryset = queryset.filter(belong_dept__in=belong_depts)
elif data_range == Post.POST_DATA_THISLEVEL: elif data_range == Post.POST_DATA_THISLEVEL:
queryset = queryset.filter(belong_dept = dept) queryset = queryset.filter(belong_dept=dept)
elif data_range == Post.POST_DATA_THISLEVEL: elif data_range == Post.POST_DATA_THISLEVEL:
queryset = queryset.filter(create_by = user) queryset = queryset.filter(create_by=user)
new_queryset = new_queryset | queryset new_queryset = new_queryset | queryset
return new_queryset return new_queryset
else: else:
return queryset.none() return queryset.none()
return queryset return queryset

View File

@ -1,4 +1,3 @@
from django.db import models
from django.apps import apps 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) child_queryset = cls.objects.filter(parent__in=child_queryset)
return queryset return queryset
def get_child_queryset2(obj, hasParent=True): 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) child_queryset = cls.objects.filter(parent__in=child_queryset)
return queryset return queryset
def get_parent_queryset(obj, hasSelf=True): def get_parent_queryset(obj, hasSelf=True):
cls = type(obj) cls = type(obj)
ids = [] ids = []

View File

@ -1,5 +1,7 @@
import json import json
from user_agents import parse from user_agents import parse
def get_request_ip(request): def get_request_ip(request):
""" """
获取请求IP 获取请求IP
@ -11,6 +13,7 @@ def get_request_ip(request):
ip = request.META.get('REMOTE_ADDR', '') or getattr(request, 'request_ip', None) ip = request.META.get('REMOTE_ADDR', '') or getattr(request, 'request_ip', None)
return ip or 'unknown' return ip or 'unknown'
def get_request_data(request): def get_request_data(request):
""" """
获取请求参数 获取请求参数
@ -55,6 +58,7 @@ def get_request_path(request, *args, **kwargs):
path = path.replace('/' + value, '/' + '{id}') path = path.replace('/' + value, '/' + '{id}')
return path return path
def get_browser(request, ): def get_browser(request, ):
""" """
获取浏览器名 获取浏览器名

View File

@ -3,14 +3,20 @@ from rest_framework import serializers
from django_restql.mixins import DynamicFieldsMixin from django_restql.mixins import DynamicFieldsMixin
from rest_framework.fields import empty from rest_framework.fields import empty
from rest_framework.request import Request from rest_framework.request import Request
class PkSerializer(serializers.Serializer): class PkSerializer(serializers.Serializer):
pks = serializers.ListField(child=serializers.CharField(max_length=20), label="主键ID列表") pks = serializers.ListField(child=serializers.CharField(max_length=20), label="主键ID列表")
class GenSignatureSerializer(serializers.Serializer): class GenSignatureSerializer(serializers.Serializer):
path = serializers.CharField(label="图片地址") 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): def __init__(self, instance=None, data=empty, request=None, **kwargs):
super().__init__(instance, data, **kwargs) super().__init__(instance, data, **kwargs)
self.request: Request = request or self.context.get('request', None) self.request: Request = request or self.context.get('request', None)
@ -19,7 +25,7 @@ class CustomModelSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
if self.request: if self.request:
if getattr(self.request, 'user', None): if getattr(self.request, 'user', None):
validated_data['create_by'] = self.request.user validated_data['create_by'] = self.request.user
if 'belong_dept' in validated_data: # 如果指定了数据归属部门 if 'belong_dept' in validated_data:
pass pass
elif getattr(self.request.user, 'belong_dept', None): elif getattr(self.request.user, 'belong_dept', None):
if hasattr(self.Meta.model, 'belong_dept'): if hasattr(self.Meta.model, 'belong_dept'):

View File

@ -4,12 +4,14 @@
import time import time
from server.settings import SNOW_DATACENTER_ID, SNOW_WORKER_ID from server.settings import SNOW_DATACENTER_ID, SNOW_WORKER_ID
class InvalidSystemClock(Exception): class InvalidSystemClock(Exception):
""" """
时钟回拨异常 时钟回拨异常
""" """
pass pass
class Constant(object): class Constant(object):
# 64位ID的划分 # 64位ID的划分
WORKER_ID_BITS = 5 WORKER_ID_BITS = 5
@ -84,7 +86,8 @@ class IdWorker(object):
self.last_timestamp = timestamp 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 (self.worker_id << Constant.WOKER_ID_SHIFT) | self.sequence
return new_id return new_id
@ -97,6 +100,7 @@ class IdWorker(object):
timestamp = self._gen_timestamp() timestamp = self._gen_timestamp()
return timestamp return timestamp
idWorker = IdWorker(SNOW_DATACENTER_ID, SNOW_WORKER_ID) idWorker = IdWorker(SNOW_DATACENTER_ID, SNOW_WORKER_ID)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -2,8 +2,9 @@ import textwrap
import random import random
import string import string
def print_roundtrip(response, *args, **kwargs): 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(''' print(textwrap.dedent('''
---------------- request ---------------- ---------------- request ----------------
{req.method} {req.url} {req.method} {req.url}
@ -22,10 +23,12 @@ def print_roundtrip(response, *args, **kwargs):
reshdrs=format_headers(response.headers), reshdrs=format_headers(response.headers),
)) ))
def ranstr(num): def ranstr(num):
salt = ''.join(random.sample(string.ascii_lowercase + string.digits, num)) salt = ''.join(random.sample(string.ascii_lowercase + string.digits, num))
return salt return salt
def rannum(num): def rannum(num):
salt = ''.join(random.sample(string.digits, num)) salt = ''.join(random.sample(string.digits, num))
return salt return salt

View File

@ -1,4 +1,4 @@
from rest_framework.views import APIView
import os import os
import cv2 import cv2
from apps.utils.errors import SIGN_MAKE_FAIL 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.mixins import CustomCreateModelMixin
from apps.utils.serializers import GenSignatureSerializer from apps.utils.serializers import GenSignatureSerializer
class SignatureViewSet(CustomCreateModelMixin, CustomGenericViewSet): class SignatureViewSet(CustomCreateModelMixin, CustomGenericViewSet):
authentication_classes = () authentication_classes = ()
permission_classes = () permission_classes = ()

View File

@ -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.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.system.models import Dept, Post
from apps.utils.errors import PKS_ERROR from apps.utils.errors import PKS_ERROR
from apps.utils.mixins import CustomDestoryModelMixin, MyLoggingMixin from apps.utils.mixins import CustomDestoryModelMixin, MyLoggingMixin
from apps.utils.permission import ALL_PERMS, RbacPermission, get_user_perms_map from apps.utils.permission import ALL_PERMS, RbacPermission, get_user_perms_map
from apps.utils.queryset import get_child_queryset2 from apps.utils.queryset import get_child_queryset2
from apps.utils.serializers import PkSerializer 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): class CustomGenericViewSet(MyLoggingMixin, GenericViewSet):
@ -45,7 +47,7 @@ class CustomGenericViewSet(MyLoggingMixin, GenericViewSet):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if self.perms_map: if self.perms_map:
for k, v in self.perms_map.items(): for k, v in self.perms_map.items():
if v not in ALL_PERMS and v!='*': if v not in ALL_PERMS and v != '*':
ALL_PERMS.append(v) ALL_PERMS.append(v)
def get_queryset(self): def get_queryset(self):
@ -81,43 +83,45 @@ class CustomGenericViewSet(MyLoggingMixin, GenericViewSet):
belong_depts = get_child_queryset2(dept.parent) belong_depts = get_child_queryset2(dept.parent)
else: else:
belong_depts = get_child_queryset2(dept) belong_depts = get_child_queryset2(dept)
queryset = queryset.filter(belong_dept__in = belong_depts) queryset = queryset.filter(belong_dept__in=belong_depts)
elif data_range == Post.POST_DATA_THISLEVEL_AND_BELOW: elif data_range == Post.POST_DATA_THISLEVEL_AND_BELOW:
belong_depts = get_child_queryset2(dept) belong_depts = get_child_queryset2(dept)
queryset = queryset.filter(belong_dept__in = belong_depts) queryset = queryset.filter(belong_dept__in=belong_depts)
elif data_range == Post.POST_DATA_THISLEVEL: elif data_range == Post.POST_DATA_THISLEVEL:
queryset = queryset.filter(belong_dept = dept) queryset = queryset.filter(belong_dept=dept)
elif data_range == Post.POST_DATA_THISLEVEL: elif data_range == Post.POST_DATA_THISLEVEL:
queryset = queryset.filter(create_by = user) queryset = queryset.filter(create_by=user)
new_queryset = new_queryset | queryset new_queryset = new_queryset | queryset
return new_queryset return new_queryset
else: else:
return queryset.none() return queryset.none()
return queryset return queryset
class CustomModelViewSet(CreateModelMixin
, UpdateModelMixin, ListModelMixin, RetrieveModelMixin class CustomModelViewSet(CreateModelMixin, UpdateModelMixin, ListModelMixin,
, CustomDestoryModelMixin, CustomGenericViewSet): RetrieveModelMixin, CustomDestoryModelMixin, CustomGenericViewSet):
""" """
增强的ModelViewSet 增强的ModelViewSet
""" """
def __init__(self, **kwargs) -> None: def __init__(self, **kwargs) -> None:
super().__init__(**kwargs) super().__init__(**kwargs)
# 增加默认权限标识 # 增加默认权限标识
if not self.perms_map: if not self.perms_map:
basename = self.basename basename = self.basename
self.perms_map = {'get':'*', 'post':'{}:create'.format(basename) self.perms_map = {'get': '*', 'post': '{}:create'.format(basename), 'put': '{}:update'.format(
,'put':'{}:update'.format(basename) basename), 'patch': '{}:update'.format(basename), 'delete': '{}:delete'.format(basename)}
,'patch':'{}:update'.format(basename)
,'delete':'{}:delete'.format(basename)}
for k, v in self.perms_map.items(): for k, v in self.perms_map.items():
if v not in ALL_PERMS and v!='*': if v not in ALL_PERMS and v != '*':
ALL_PERMS.append(v) ALL_PERMS.append(v)
@action(methods=['post'], detail=False, serializer_class=PkSerializer) @action(methods=['post'], detail=False, serializer_class=PkSerializer)
def deletes(self,request,*args,**kwargs): def deletes(self, request, *args, **kwargs):
"""
批量删除
"""
request_data = request.data request_data = request.data
pks = request_data.get('pks',None) pks = request_data.get('pks', None)
if pks: if pks:
self.get_queryset().filter(id__in=pks).delete(update_by=request.user) self.get_queryset().filter(id__in=pks).delete(update_by=request.user)
return Response() return Response()

View File

@ -1,6 +1,5 @@
from threading import Thread from threading import Thread
import requests import requests
import json
from apps.utils.errors import XX_REQUEST_ERROR from apps.utils.errors import XX_REQUEST_ERROR
from apps.utils.tools import print_roundtrip from apps.utils.tools import print_roundtrip
from django.conf import settings from django.conf import settings
@ -9,11 +8,11 @@ from rest_framework.exceptions import APIException, ParseError
requests.packages.urllib3.disable_warnings() requests.packages.urllib3.disable_warnings()
class XxClient: class XxClient:
""" """
寻息 寻息
""" """
def __init__(self, licence=settings.XX_LICENCE, username=settings.XX_USERNAME) -> None: def __init__(self, licence=settings.XX_LICENCE, username=settings.XX_USERNAME) -> None:
if not settings.XX_ENABLED: if not settings.XX_ENABLED:
return None return None
@ -28,7 +27,7 @@ class XxClient:
def _get_token_loop(self): def _get_token_loop(self):
while self.isRuning: while self.isRuning:
json = { json = {
'licence':self.licence 'licence': self.licence
} }
r = requests.post(json=json, url=settings.XX_BASE_URL + '/getAccessTokenV2', verify=False, timeout=20) r = requests.post(json=json, url=settings.XX_BASE_URL + '/getAccessTokenV2', verify=False, timeout=20)
ret = r.json() ret = r.json()
@ -39,7 +38,7 @@ class XxClient:
def get_token(self): def get_token(self):
self.isGetingToken = True self.isGetingToken = True
json = { json = {
'licence':self.licence 'licence': self.licence
} }
r = requests.post(json=json, url=settings.XX_BASE_URL + '/getAccessTokenV2', verify=False, timeout=20) r = requests.post(json=json, url=settings.XX_BASE_URL + '/getAccessTokenV2', verify=False, timeout=20)
ret = r.json() ret = r.json()
@ -48,7 +47,7 @@ class XxClient:
self.token = ret['data']['token'] self.token = ret['data']['token']
def setup(self): def setup(self):
self.t = Thread(target= self._get_token_loop, args=(), daemon=True) self.t = Thread(target=self._get_token_loop, args=(), daemon=True)
self.t.start() self.t.start()
def __del__(self): def __del__(self):
@ -58,7 +57,7 @@ class XxClient:
self.isRuning = False self.isRuning = False
self.t.join() self.t.join()
def request(self, url:str, method:str='post', params=dict(), json=dict(), timeout=4, raise_exception=True): def request(self, url: str, method: str = 'post', params=dict(), json=dict(), timeout=4, raise_exception=True):
params['accessToken'] = self.token params['accessToken'] = self.token
json['username'] = self.username json['username'] = self.username
json['buildId'] = settings.XX_BUILDID json['buildId'] = settings.XX_BUILDID
@ -72,8 +71,8 @@ class XxClient:
if req_num > 4: if req_num > 4:
break break
else: else:
r = getattr(requests, method)('{}{}'.format(settings.XX_BASE_URL, url) r = getattr(requests, method)('{}{}'.format(settings.XX_BASE_URL, url),
, params=params, json=json, timeout=timeout, verify=False) params=params, json=json, timeout=timeout, verify=False)
# if settings.DEBUG: # if settings.DEBUG:
# print_roundtrip(r) # print_roundtrip(r)
ret = r.json() ret = r.json()

View File

@ -1,7 +1,6 @@
from django.apps import AppConfig from django.apps import AppConfig
class WfConfig(AppConfig): class WfConfig(AppConfig):
name = 'apps.wf' name = 'apps.wf'
verbose_name = '工作流管理' verbose_name = '工作流管理'

View File

@ -1,24 +1,29 @@
from django_filters import rest_framework as filters from django_filters import rest_framework as filters
from .models import Ticket from .models import Ticket
class TicketFilterSet(filters.FilterSet): class TicketFilterSet(filters.FilterSet):
start_create = filters.DateFilter(field_name="create_time", lookup_expr='gte') start_create = filters.DateFilter(field_name="create_time", lookup_expr='gte')
end_create = filters.DateFilter(field_name="create_time", lookup_expr='lte') end_create = filters.DateFilter(field_name="create_time", lookup_expr='lte')
category = filters.ChoiceFilter(choices = Ticket.category_choices, method='filter_category') category = filters.ChoiceFilter(choices=Ticket.category_choices, method='filter_category')
class Meta: class Meta:
model = Ticket model = Ticket
fields = ['workflow', 'state', 'act_state', 'start_create', 'end_create', 'category'] fields = ['workflow', 'state', 'act_state', 'start_create', 'end_create', 'category']
def filter_category(self, queryset, name, value): def filter_category(self, queryset, name, value):
user=self.request.user user = self.request.user
if value == 'owner': # 我的 if value == 'owner': # 我的
queryset = queryset.filter(create_by=user) queryset = queryset.filter(create_by=user)
elif value == 'duty': # 待办 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': # 处理过的 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': # 抄送我的 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': elif value == 'all':
pass pass
else: else:

View File

@ -1,12 +1,7 @@
from random import choice
from django.db import models 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.utils.models import CommonAModel
from apps.system.models import Dept, User, File from apps.system.models import User
from apps.utils.models import SoftModel, BaseModel from apps.utils.models import BaseModel
from simple_history.models import HistoricalRecords
class Workflow(CommonAModel): class Workflow(CommonAModel):
@ -18,10 +13,15 @@ class Workflow(CommonAModel):
sn_prefix = models.CharField('流水号前缀', max_length=50, default='hb') sn_prefix = models.CharField('流水号前缀', max_length=50, default='hb')
description = models.CharField('描述', max_length=200, null=True, blank=True) description = models.CharField('描述', max_length=200, null=True, blank=True)
view_permission_check = models.BooleanField('查看权限校验', default=True, help_text='开启后,只允许工单的关联人(创建人、曾经的处理人)有权限查看工单') 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的用户提交工单)') limit_expression = models.JSONField(
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:工作流名称') '限制表达式', 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的用户提交工单)')
title_template = models.CharField('标题模板', max_length=50, default='{title}', null=True, blank=True, help_text='工单字段的值可以作为参数写到模板中,格式如:你有一个待办工单:{title}') display_form_str = models.JSONField('展现表单字段', default=list, blank=True,
content_template = models.CharField('内容模板', max_length=1000, default='标题:{title}, 创建时间:{create_time}', null=True, blank=True, help_text='工单字段的值可以作为参数写到模板中,格式如:标题:{title}, 创建时间:{create_time}') 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): class State(CommonAModel):
""" """
@ -59,18 +59,18 @@ class State(CommonAModel):
STATE_DISTRIBUTE_TYPE_DIRECT = 2 # 直接处理(当前为多人的情况,都可以处理,而不需要先接单) STATE_DISTRIBUTE_TYPE_DIRECT = 2 # 直接处理(当前为多人的情况,都可以处理,而不需要先接单)
STATE_DISTRIBUTE_TYPE_RANDOM = 3 # 随机分配 STATE_DISTRIBUTE_TYPE_RANDOM = 3 # 随机分配
STATE_DISTRIBUTE_TYPE_ALL = 4 # 全部处理 STATE_DISTRIBUTE_TYPE_ALL = 4 # 全部处理
state_distribute_choices=( state_distribute_choices = (
(STATE_DISTRIBUTE_TYPE_ACTIVE, '主动接单'), (STATE_DISTRIBUTE_TYPE_ACTIVE, '主动接单'),
(STATE_DISTRIBUTE_TYPE_DIRECT, '直接处理'), (STATE_DISTRIBUTE_TYPE_DIRECT, '直接处理'),
(STATE_DISTRIBUTE_TYPE_RANDOM, '随机分配'), (STATE_DISTRIBUTE_TYPE_RANDOM, '随机分配'),
(STATE_DISTRIBUTE_TYPE_ALL, '全部处理'), (STATE_DISTRIBUTE_TYPE_ALL, '全部处理'),
) )
STATE_FIELD_READONLY= 1 # 字段只读 STATE_FIELD_READONLY = 1 # 字段只读
STATE_FIELD_REQUIRED = 2 # 字段必填 STATE_FIELD_REQUIRED = 2 # 字段必填
STATE_FIELD_OPTIONAL = 3 # 字段可选 STATE_FIELD_OPTIONAL = 3 # 字段可选
STATE_FIELD_HIDDEN = 4 # 字段隐藏 STATE_FIELD_HIDDEN = 4 # 字段隐藏
state_filter_choices=( state_filter_choices = (
(0, ''), (0, ''),
(1, '和工单同属一及上级部门'), (1, '和工单同属一及上级部门'),
(2, '和创建人同属一及上级部门'), (2, '和创建人同属一及上级部门'),
@ -80,15 +80,22 @@ class State(CommonAModel):
workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, verbose_name='所属工作流') workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, verbose_name='所属工作流')
is_hidden = models.BooleanField('是否隐藏', default=False, help_text='设置为True时,获取工单步骤api中不显示此状态(当前处于此状态时除外)') is_hidden = models.BooleanField('是否隐藏', default=False, help_text='设置为True时,获取工单步骤api中不显示此状态(当前处于此状态时除外)')
sort = models.IntegerField('状态顺序', default=0, help_text='用于工单步骤接口时step上状态的顺序(因为存在网状情况,所以需要人为设定顺序),值越小越靠前') 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='开启后允许工单创建人在此状态直接撤回工单到初始状态') 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_type = models.IntegerField('参与者类型', choices=state_participanttype_choices, default=1, blank=True,
participant = models.JSONField('参与者', default=list, blank=True, help_text='可以为空(无处理人的情况,如结束状态)、userid、userid列表\部门id\角色id\变量(create_by,create_by_tl)\脚本记录的id等包含子工作流的需要设置处理人为loonrobot') help_text='0.无处理人,1.个人,2.多人,3.部门,4.角色,5.变量(支持工单创建人,创建人的leader),6.脚本,7.工单的字段内容(如表单中的"测试负责人",需要为用户名或者逗号隔开的多个用户名),8.父工单的字段内容。 初始状态请选择类型5参与人填create_by')
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不显示, 字典的字典 participant = models.JSONField('参与者', default=list, blank=True,
distribute_type = models.IntegerField('分配方式', default=1, choices=state_distribute_choices, help_text='1.主动接单(如果当前处理人实际为多人的时候,需要先接单才能处理) 2.直接处理(即使当前处理人实际为多人,也可以直接处理) 3.随机分配(如果实际为多人,则系统会随机分配给其中一个人) 4.全部处理(要求所有参与人都要处理一遍,才能进入下一步)') 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) filter_policy = models.IntegerField('参与人过滤策略', default=0, choices=state_filter_choices)
participant_cc = models.JSONField('抄送给', default=list, blank=True, help_text='抄送给(userid列表)') participant_cc = models.JSONField('抄送给', default=list, blank=True, help_text='抄送给(userid列表)')
class Transition(CommonAModel): class Transition(CommonAModel):
""" """
工作流流转定时器条件(允许跳过) 条件流转与定时器不可同时存在 工作流流转定时器条件(允许跳过) 条件流转与定时器不可同时存在
@ -131,11 +138,16 @@ class Transition(CommonAModel):
name = models.CharField('操作', max_length=50) name = models.CharField('操作', max_length=50)
workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, verbose_name='所属工作流') workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, verbose_name='所属工作流')
timer = models.IntegerField('定时器(单位秒)', default=0, help_text='单位秒。处于源状态X秒后如果状态都没有过变化则自动流转到目标状态。设置时间有效') timer = models.IntegerField('定时器(单位秒)', default=0, help_text='单位秒。处于源状态X秒后如果状态都没有过变化则自动流转到目标状态。设置时间有效')
source_state = models.ForeignKey(State, on_delete=models.CASCADE, verbose_name='源状态', related_name='sstate_transition') source_state = models.ForeignKey(State, on_delete=models.CASCADE,
destination_state = models.ForeignKey(State, on_delete=models.CASCADE, verbose_name='目的状态', related_name='dstate_transition') verbose_name='源状态', related_name='sstate_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会以首次匹配成功的条件为准所以多个条件不要有冲突' ) destination_state = models.ForeignKey(State, on_delete=models.CASCADE,
attribute_type = models.IntegerField('属性类型', default=1, choices=attribute_type_choices, help_text='属性类型1.同意2.拒绝3.其他') verbose_name='目的状态', related_name='dstate_transition')
field_require_check = models.BooleanField('是否校验必填项', default=True, help_text='默认在用户点击操作的时候需要校验工单表单的必填项,如果设置为否则不检查。用于如"退回"属性的操作,不需要填写表单内容') 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): 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') 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_key = models.CharField('字段标识', max_length=50, help_text='字段类型请尽量特殊,避免与系统中关键字冲突')
field_name = models.CharField('字段名称', max_length=50) field_name = models.CharField('字段名称', max_length=50)
sort = models.IntegerField('排序', default=0, help_text='工单基础字段在表单中排序为:流水号0,标题20,状态id40,状态名41,创建人80,创建时间100,更新时间120.前端展示工单信息的表单可以根据这个id顺序排列') sort = models.IntegerField(
default_value = models.CharField('默认值', null=True, blank=True, max_length=100, help_text='前端展示时,可以将此内容作为表单中的该字段的默认值') '排序', default=0, help_text='工单基础字段在表单中排序为:流水号0,标题20,状态id40,状态名41,创建人80,创建时间100,更新时间120.前端展示工单信息的表单可以根据这个id顺序排列')
description = models.CharField('描述', max_length=100, blank=True, null=True, help_text='字段的描述信息,可用于显示在字段的下方对该字段的详细描述') 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='用户工单详情表单中作为字段的占位符显示') placeholder = models.CharField('占位符', max_length=100, blank=True, null=True, help_text='用户工单详情表单中作为字段的占位符显示')
field_template = models.TextField('文本域模板', null=True, blank=True, help_text='文本域类型字段前端显示时可以将此内容作为字段的placeholder') field_template = models.TextField('文本域模板', null=True, blank=True, help_text='文本域类型字段前端显示时可以将此内容作为字段的placeholder')
boolean_field_display = models.JSONField('布尔类型显示名', default=dict, blank=True, 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='获取下拉选项用于动态选项值') # hook = models.CharField('hook', max_length=1000, default='', help_text='获取下拉选项用于动态选项值')
is_hidden = models.BooleanField('是否隐藏', default=False, help_text='可用于携带不需要用户查看的字段信息') is_hidden = models.BooleanField('是否隐藏', default=False, help_text='可用于携带不需要用户查看的字段信息')
class Ticket(CommonAModel): class Ticket(CommonAModel):
""" """
工单 工单
@ -189,7 +205,7 @@ class Ticket(CommonAModel):
TICKET_ACT_STATE_FINISH = 4 # 已完成 TICKET_ACT_STATE_FINISH = 4 # 已完成
TICKET_ACT_STATE_CLOSED = 5 # 已关闭 TICKET_ACT_STATE_CLOSED = 5 # 已关闭
act_state_choices =( act_state_choices = (
(TICKET_ACT_STATE_DRAFT, '草稿中'), (TICKET_ACT_STATE_DRAFT, '草稿中'),
(TICKET_ACT_STATE_ONGOING, '进行中'), (TICKET_ACT_STATE_ONGOING, '进行中'),
(TICKET_ACT_STATE_BACK, '被退回'), (TICKET_ACT_STATE_BACK, '被退回'),
@ -197,7 +213,7 @@ class Ticket(CommonAModel):
(TICKET_ACT_STATE_FINISH, '已完成'), (TICKET_ACT_STATE_FINISH, '已完成'),
(TICKET_ACT_STATE_CLOSED, '已关闭') (TICKET_ACT_STATE_CLOSED, '已关闭')
) )
category_choices =( category_choices = (
('all', '全部'), ('all', '全部'),
('owner', '我创建的'), ('owner', '我创建的'),
('duty', '待办'), ('duty', '待办'),
@ -209,12 +225,15 @@ class Ticket(CommonAModel):
sn = models.CharField('流水号', max_length=25, help_text="工单的流水号") sn = models.CharField('流水号', max_length=25, help_text="工单的流水号")
state = models.ForeignKey(State, on_delete=models.CASCADE, verbose_name='当前状态', related_name='ticket_state') 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 = 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='工单自定义字段内容') ticket_data = models.JSONField('工单数据', default=dict, help_text='工单自定义字段内容')
in_add_node = models.BooleanField('加签状态中', default=False, 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) 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列表') participant = models.JSONField('当前处理人', default=list, blank=True, help_text='可以为空(无处理人的情况,如结束状态)、userid、userid列表')
act_state = models.IntegerField('进行状态', default=1, help_text='当前工单的进行状态', choices=act_state_choices) act_state = models.IntegerField('进行状态', default=1, help_text='当前工单的进行状态', choices=act_state_choices)
multi_all_person = models.JSONField('全部处理的结果', default=dict, blank=True, help_text='需要当前状态处理人全部处理时实际的处理结果json格式') 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') 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) 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_type = models.IntegerField(
participant = models.ForeignKey(User, verbose_name='处理人', on_delete=models.SET_NULL, null=True, blank=True, related_name='ticketflow_participant') '处理人类型', 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='非人工处理的处理人相关信息') 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) 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格式') ticket_data = models.JSONField('工单数据', default=dict, blank=True, help_text='可以用于记录当前表单数据json格式')
intervene_type = models.IntegerField('干预类型', default=0, help_text='流转类型', choices=Transition.intervene_type_choices) intervene_type = models.IntegerField('干预类型', default=0, help_text='流转类型', choices=Transition.intervene_type_choices)
participant_cc = models.JSONField('抄送给', default=list, blank=True, help_text='抄送给(userid列表)') participant_cc = models.JSONField('抄送给', default=list, blank=True, help_text='抄送给(userid列表)')

View File

@ -7,30 +7,27 @@ class GetParticipants:
获取处理人脚本 获取处理人脚本
""" """
all_funcs = [ all_funcs = [
{'func':'get_create_by', 'name':'获取工单创建人'} {'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 @classmethod
def get_create_by(cls, state:dict={}, ticket:dict={}, new_ticket_data:dict={}, handler:User={}): def get_create_by(cls, state: dict = {}, ticket: dict = {}, new_ticket_data: dict = {}, handler: User = {}):
"""工单创建人""" """工单创建人"""
participant = ticket.create_by.id participant = ticket.create_by.id
return participant return participant
class HandleScripts: class HandleScripts:
""" """
任务处理脚本 任务处理脚本
""" """
all_funcs = [ all_funcs = [
{'func': 'handle_something', 'name':'处理一些工作'} {'func': 'handle_something', 'name': '处理一些工作'}
] ]
@classmethod @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() transition_obj = Transition.objects.filter(source_state=ticket.state, is_deleted=False).first()
@ -41,16 +38,15 @@ class HandleScripts:
from .services import WfService 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 return ticket
@classmethod @classmethod
def handle_something(cls, ticket:Ticket): def handle_something(cls, ticket: Ticket):
"""处理一些工作""" """处理一些工作"""
# 任务处理代码区 # 任务处理代码区
# 调用自动流转 # 调用自动流转
ticket = cls.to_next(ticket=ticket, by_task=True, script_str= 'handle_something') ticket = cls.to_next(ticket=ticket, by_task=True, script_str='handle_something')

View File

@ -1,6 +1,5 @@
from apps.system.models import Dept, User from apps.system.models import Dept, User
from apps.system.serializers import UserSimpleSerializer from apps.system.serializers import UserSimpleSerializer
import rest_framework
from rest_framework import serializers from rest_framework import serializers
from .models import State, Ticket, TicketFlow, Workflow, Transition, CustomField from .models import State, Ticket, TicketFlow, Workflow, Transition, CustomField
@ -11,33 +10,40 @@ class WorkflowSerializer(serializers.ModelSerializer):
model = Workflow model = Workflow
fields = '__all__' fields = '__all__'
class StateSerializer(serializers.ModelSerializer): class StateSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = State model = State
fields = '__all__' fields = '__all__'
class WorkflowSimpleSerializer(serializers.ModelSerializer): class WorkflowSimpleSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Workflow model = Workflow
fields = ['id', 'name'] fields = ['id', 'name']
class StateSimpleSerializer(serializers.ModelSerializer): class StateSimpleSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = State model = State
fields = ['id', 'name', 'type', 'distribute_type', 'enable_retreat'] fields = ['id', 'name', 'type', 'distribute_type', 'enable_retreat']
class TransitionSerializer(serializers.ModelSerializer): class TransitionSerializer(serializers.ModelSerializer):
source_state_ = StateSimpleSerializer(source='source_state', read_only=True) source_state_ = StateSimpleSerializer(source='source_state', read_only=True)
destination_state_ = StateSimpleSerializer(source='destination_state', read_only=True) destination_state_ = StateSimpleSerializer(source='destination_state', read_only=True)
class Meta: class Meta:
model = Transition model = Transition
fields = '__all__' fields = '__all__'
@staticmethod @staticmethod
def setup_eager_loading(queryset): def setup_eager_loading(queryset):
""" Perform necessary eager loading of data. """ """ Perform necessary eager loading of data. """
queryset = queryset.select_related('source_state','destination_state') queryset = queryset.select_related('source_state', 'destination_state')
return queryset return queryset
class AllField(serializers.Field): class AllField(serializers.Field):
def to_representation(self, value): def to_representation(self, value):
return value return value
@ -45,6 +51,7 @@ class AllField(serializers.Field):
def to_internal_value(self, data): def to_internal_value(self, data):
return data return data
class FieldChoiceSerializer(serializers.Serializer): class FieldChoiceSerializer(serializers.Serializer):
id = AllField(label='ID') id = AllField(label='ID')
name = serializers.CharField(label='名称') name = serializers.CharField(label='名称')
@ -55,9 +62,11 @@ class CustomFieldSerializer(serializers.ModelSerializer):
model = CustomField model = CustomField
fields = '__all__' fields = '__all__'
class CustomFieldCreateUpdateSerializer(serializers.ModelSerializer): class CustomFieldCreateUpdateSerializer(serializers.ModelSerializer):
field_choice = FieldChoiceSerializer(label='选项列表', many=True, required=False) field_choice = FieldChoiceSerializer(label='选项列表', many=True, required=False)
class Meta: class Meta:
model = CustomField model = CustomField
fields = ['workflow', 'field_type', 'field_key', 'field_name', fields = ['workflow', 'field_type', 'field_key', 'field_name',
@ -70,16 +79,19 @@ class TicketSimpleSerializer(serializers.ModelSerializer):
model = Ticket model = Ticket
fields = '__all__' fields = '__all__'
class TicketCreateSerializer(serializers.ModelSerializer): class TicketCreateSerializer(serializers.ModelSerializer):
transition = serializers.PrimaryKeyRelatedField(queryset=Transition.objects.all(), write_only=True) transition = serializers.PrimaryKeyRelatedField(queryset=Transition.objects.all(), write_only=True)
title = serializers.CharField(allow_blank=True, required=False) title = serializers.CharField(allow_blank=True, required=False)
class Meta: class Meta:
model=Ticket model = Ticket
fields=['title','workflow', 'ticket_data', 'transition'] fields = ['title', 'workflow', 'ticket_data', 'transition']
def create(self, validated_data): def create(self, validated_data):
return super().create(validated_data) return super().create(validated_data)
class TicketSerializer(serializers.ModelSerializer): class TicketSerializer(serializers.ModelSerializer):
workflow_ = WorkflowSimpleSerializer(source='workflow', read_only=True) workflow_ = WorkflowSimpleSerializer(source='workflow', read_only=True)
state_ = StateSimpleSerializer(source='state', read_only=True) state_ = StateSimpleSerializer(source='state', read_only=True)
@ -90,33 +102,37 @@ class TicketSerializer(serializers.ModelSerializer):
@staticmethod @staticmethod
def setup_eager_loading(queryset): def setup_eager_loading(queryset):
queryset = queryset.select_related('workflow','state') queryset = queryset.select_related('workflow', 'state')
return queryset return queryset
class TicketListSerializer(serializers.ModelSerializer): class TicketListSerializer(serializers.ModelSerializer):
workflow_ = WorkflowSimpleSerializer(source='workflow', read_only=True) workflow_ = WorkflowSimpleSerializer(source='workflow', read_only=True)
state_ = StateSimpleSerializer(source='state', read_only=True) state_ = StateSimpleSerializer(source='state', read_only=True)
class Meta: class Meta:
model = Ticket 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 @staticmethod
def setup_eager_loading(queryset): def setup_eager_loading(queryset):
queryset = queryset.select_related('workflow','state') queryset = queryset.select_related('workflow', 'state')
return queryset return queryset
class TicketDetailSerializer(serializers.ModelSerializer): class TicketDetailSerializer(serializers.ModelSerializer):
workflow_ = WorkflowSimpleSerializer(source='workflow', read_only=True) workflow_ = WorkflowSimpleSerializer(source='workflow', read_only=True)
state_ = StateSimpleSerializer(source='state', read_only=True) state_ = StateSimpleSerializer(source='state', read_only=True)
ticket_data_ = serializers.SerializerMethodField() ticket_data_ = serializers.SerializerMethodField()
class Meta: class Meta:
model = Ticket model = Ticket
fields = '__all__' fields = '__all__'
@staticmethod @staticmethod
def setup_eager_loading(queryset): def setup_eager_loading(queryset):
queryset = queryset.select_related('workflow','state') queryset = queryset.select_related('workflow', 'state')
return queryset return queryset
def get_ticket_data_(self, obj): def get_ticket_data_(self, obj):
@ -132,12 +148,14 @@ class TicketDetailSerializer(serializers.ModelSerializer):
if i['field_value']: if i['field_value']:
if 'sys_user' in i['label']: if 'sys_user' in i['label']:
if isinstance(i['field_value'], list): 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: else:
i['field_display'] = User.objects.get(id=i['field_value']).name i['field_display'] = User.objects.get(id=i['field_value']).name
elif 'deptSelect' in i['label']: elif 'deptSelect' in i['label']:
if isinstance(i['field_value'], list): 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: else:
i['field_display'] = Dept.objects.get(id=i['field_value']).name i['field_display'] = Dept.objects.get(id=i['field_value']).name
elif i['field_type'] in ['radio', 'select']: elif i['field_type'] in ['radio', 'select']:
@ -156,16 +174,20 @@ class TicketDetailSerializer(serializers.ModelSerializer):
if item['id'] == field_value: if item['id'] == field_value:
return return
class TicketFlowSerializer(serializers.ModelSerializer): class TicketFlowSerializer(serializers.ModelSerializer):
participant_ = UserSimpleSerializer(source='participant', read_only=True) participant_ = UserSimpleSerializer(source='participant', read_only=True)
state_ = StateSimpleSerializer(source='state', read_only=True) state_ = StateSimpleSerializer(source='state', read_only=True)
class Meta: class Meta:
model = TicketFlow model = TicketFlow
fields = '__all__' fields = '__all__'
class TicketFlowSimpleSerializer(serializers.ModelSerializer): class TicketFlowSimpleSerializer(serializers.ModelSerializer):
participant_ = UserSimpleSerializer(source='participant', read_only=True) participant_ = UserSimpleSerializer(source='participant', read_only=True)
state_ = StateSimpleSerializer(source='state', read_only=True) state_ = StateSimpleSerializer(source='state', read_only=True)
class Meta: class Meta:
model = TicketFlow model = TicketFlow
exclude = ['ticket_data'] exclude = ['ticket_data']
@ -174,20 +196,25 @@ class TicketFlowSimpleSerializer(serializers.ModelSerializer):
class TicketHandleSerializer(serializers.Serializer): class TicketHandleSerializer(serializers.Serializer):
transition = serializers.PrimaryKeyRelatedField(queryset=Transition.objects.all(), label="流转id") transition = serializers.PrimaryKeyRelatedField(queryset=Transition.objects.all(), label="流转id")
ticket_data = serializers.JSONField(label="表单数据json") ticket_data = serializers.JSONField(label="表单数据json")
suggestion = serializers.CharField(label="处理意见", required = False, allow_blank=True) suggestion = serializers.CharField(label="处理意见", required=False, allow_blank=True)
class TicketRetreatSerializer(serializers.Serializer): class TicketRetreatSerializer(serializers.Serializer):
suggestion = serializers.CharField(label="撤回原因", required = False) suggestion = serializers.CharField(label="撤回原因", required=False)
class TicketCloseSerializer(serializers.Serializer): class TicketCloseSerializer(serializers.Serializer):
suggestion = serializers.CharField(label="关闭原因", required = False) suggestion = serializers.CharField(label="关闭原因", required=False)
class TicketAddNodeSerializer(serializers.Serializer): class TicketAddNodeSerializer(serializers.Serializer):
suggestion = serializers.CharField(label="加签说明", required = False) suggestion = serializers.CharField(label="加签说明", required=False)
toadd_user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), label='发送给谁去加签') toadd_user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), label='发送给谁去加签')
class TicketAddNodeEndSerializer(serializers.Serializer): class TicketAddNodeEndSerializer(serializers.Serializer):
suggestion = serializers.CharField(label="加签意见", required = False) suggestion = serializers.CharField(label="加签意见", required=False)
class TicketDestorySerializer(serializers.Serializer): class TicketDestorySerializer(serializers.Serializer):
ids = serializers.ListField(child=serializers.PrimaryKeyRelatedField(queryset=Ticket.objects.all()), label='工单ID列表') ids = serializers.ListField(child=serializers.PrimaryKeyRelatedField(queryset=Ticket.objects.all()), label='工单ID列表')

View File

@ -1,6 +1,4 @@
from apps.wf.serializers import CustomFieldSerializer from apps.wf.serializers import TicketSimpleSerializer
from apps.wf.serializers import TicketSerializer, TicketSimpleSerializer
from typing import Tuple
from apps.system.models import User from apps.system.models import User
from apps.wf.models import CustomField, State, Ticket, TicketFlow, Transition, Workflow from apps.wf.models import CustomField, State, Ticket, TicketFlow, Transition, Workflow
from rest_framework.exceptions import APIException, PermissionDenied, ValidationError from rest_framework.exceptions import APIException, PermissionDenied, ValidationError
@ -10,23 +8,24 @@ import random
from .scripts import GetParticipants, HandleScripts from .scripts import GetParticipants, HandleScripts
from apps.utils.queryset import get_parent_queryset from apps.utils.queryset import get_parent_queryset
class WfService(object): class WfService(object):
@staticmethod @staticmethod
def get_worlflow_states(workflow:Workflow): def get_worlflow_states(workflow: Workflow):
""" """
获取工作流状态列表 获取工作流状态列表
""" """
return State.objects.filter(workflow=workflow, is_deleted=False).order_by('sort') return State.objects.filter(workflow=workflow, is_deleted=False).order_by('sort')
@staticmethod @staticmethod
def get_workflow_transitions(workflow:Workflow): def get_workflow_transitions(workflow: Workflow):
""" """
获取工作流流转列表 获取工作流流转列表
""" """
return Transition.objects.filter(workflow=workflow, is_deleted=False) return Transition.objects.filter(workflow=workflow, is_deleted=False)
@staticmethod @staticmethod
def get_workflow_start_state(workflow:Workflow): def get_workflow_start_state(workflow: Workflow):
""" """
获取工作流初始状态 获取工作流初始状态
""" """
@ -37,7 +36,7 @@ class WfService(object):
raise APIException('工作流状态配置错误') raise APIException('工作流状态配置错误')
@staticmethod @staticmethod
def get_workflow_end_state(workflow:Workflow): def get_workflow_end_state(workflow: Workflow):
""" """
获取工作流结束状态 获取工作流结束状态
""" """
@ -48,35 +47,36 @@ class WfService(object):
raise APIException('工作流状态配置错误') raise APIException('工作流状态配置错误')
@staticmethod @staticmethod
def get_workflow_custom_fields(workflow:Workflow): def get_workflow_custom_fields(workflow: Workflow):
""" """
获取工单字段 获取工单字段
""" """
return CustomField.objects.filter(is_deleted=False, workflow=workflow).order_by('sort') return CustomField.objects.filter(is_deleted=False, workflow=workflow).order_by('sort')
@staticmethod @staticmethod
def get_workflow_custom_fields_list(workflow:Workflow): def get_workflow_custom_fields_list(workflow: Workflow):
""" """
获取工单字段key List 获取工单字段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 @classmethod
def get_ticket_transitions(cls, ticket:Ticket): def get_ticket_transitions(cls, ticket: Ticket):
""" """
获取工单当前状态下可用的流转条件 获取工单当前状态下可用的流转条件
""" """
return cls.get_state_transitions(ticket.state) return cls.get_state_transitions(ticket.state)
@classmethod @classmethod
def get_state_transitions(cls, state:State): def get_state_transitions(cls, state: State):
""" """
获取状态可执行的操作 获取状态可执行的操作
""" """
return Transition.objects.filter(is_deleted=False, source_state=state).all() return Transition.objects.filter(is_deleted=False, source_state=state).all()
@classmethod @classmethod
def get_ticket_steps(cls, ticket:Ticket): def get_ticket_steps(cls, ticket: Ticket):
steps = cls.get_worlflow_states(ticket.workflow) steps = cls.get_worlflow_states(ticket.workflow)
nsteps_list = [] nsteps_list = []
for i in steps: for i in steps:
@ -85,14 +85,14 @@ class WfService(object):
return nsteps_list return nsteps_list
@classmethod @classmethod
def get_ticket_transitions(cls, ticket:Ticket): def get_ticket_transitions(cls, ticket: Ticket):
""" """
获取工单可执行的操作 获取工单可执行的操作
""" """
return cls.get_state_transitions(ticket.state) return cls.get_state_transitions(ticket.state)
@classmethod @classmethod
def get_transition_by_args(cls, kwargs:dict): def get_transition_by_args(cls, kwargs: dict):
""" """
查询并获取流转 查询并获取流转
""" """
@ -100,24 +100,23 @@ class WfService(object):
return Transition.objects.filter(**kwargs).all() return Transition.objects.filter(**kwargs).all()
@classmethod @classmethod
def get_ticket_sn(cls, workflow:Workflow): def get_ticket_sn(cls, workflow: Workflow):
""" """
生成工单流水号 生成工单流水号
""" """
now = timezone.now() now = timezone.now()
today = str(now)[:10]+' 00:00:00' today = str(now)[:10]+' 00:00:00'
next_day = str(now+timedelta(days=1))[: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) return '%s_%04d%02d%02d%04d' % (workflow.sn_prefix, now.year, now.month, now.day, ticket_day_count_new)
@classmethod @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 destination_state = transition.destination_state
ticket_all_value = cls.get_ticket_all_field_value(ticket) ticket_all_value = cls.get_ticket_all_field_value(ticket)
ticket_all_value.update(**new_ticket_data) ticket_all_value.update(**new_ticket_data)
@ -127,14 +126,16 @@ class WfService(object):
if transition.condition_expression: if transition.condition_expression:
for i in transition.condition_expression: for i in transition.condition_expression:
expression = i['expression'].format(**ticket_all_value) expression = i['expression'].format(**ticket_all_value)
import datetime, time # 用于支持条件表达式中对时间的操作 import datetime
if eval(expression, {'__builtins__':None}, {'datetime':datetime, 'time':time}): import time # 用于支持条件表达式中对时间的操作
if eval(expression, {'__builtins__': None}, {'datetime': datetime, 'time': time}):
destination_state = State.objects.get(pk=i['target_state']) destination_state = State.objects.get(pk=i['target_state'])
return destination_state return destination_state
return destination_state return destination_state
@classmethod @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,17 +156,18 @@ class WfService(object):
multi_all_person_dict = {} multi_all_person_dict = {}
destination_participant_type, destination_participant = state.participant_type, state.participant destination_participant_type, destination_participant = state.participant_type, state.participant
if destination_participant_type == State.PARTICIPANT_TYPE_FIELD: if destination_participant_type == State.PARTICIPANT_TYPE_FIELD:
destination_participant = new_ticket_data.get(destination_participant, 0) if destination_participant in new_ticket_data \ destination_participant = new_ticket_data.get(destination_participant, 0) if destination_participant \
else Ticket.ticket_data.get(destination_participant, 0) in new_ticket_data else Ticket.ticket_data.get(destination_participant, 0)
elif destination_participant_type == State.PARTICIPANT_TYPE_FORMCODE:#代码获取 elif destination_participant_type == State.PARTICIPANT_TYPE_FORMCODE: # 代码获取
destination_participant = getattr(GetParticipants, destination_participant)( destination_participant = getattr(GetParticipants, destination_participant)(
state=state, ticket=ticket, new_ticket_data=new_ticket_data, hander=handler) state=state, ticket=ticket, new_ticket_data=new_ticket_data, hander=handler)
elif destination_participant_type == State.PARTICIPANT_TYPE_DEPT:#部门 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:#角色 elif destination_participant_type == State.PARTICIPANT_TYPE_ROLE: # 角色
user_queryset = User.objects.filter(roles__in=destination_participant) user_queryset = User.objects.filter(roles__in=destination_participant)
# 如果选择了角色, 需要走过滤策略 # 如果选择了角色, 需要走过滤策略
if state.filter_policy == 1: if state.filter_policy == 1:
@ -191,14 +193,14 @@ class WfService(object):
destination_participant = random.choice(destination_participant) destination_participant = random.choice(destination_participant)
elif state.distribute_type == State.STATE_DISTRIBUTE_TYPE_ALL: elif state.distribute_type == State.STATE_DISTRIBUTE_TYPE_ALL:
for i in destination_participant: for i in destination_participant:
multi_all_person_dict[i]={} multi_all_person_dict[i] = {}
return dict(destination_participant_type=destination_participant_type, return dict(destination_participant_type=destination_participant_type,
destination_participant=destination_participant, destination_participant=destination_participant,
multi_all_person=multi_all_person_dict) multi_all_person=multi_all_person_dict)
@classmethod @classmethod
def ticket_handle_permission_check(cls, ticket:Ticket, user:User)-> dict: def ticket_handle_permission_check(cls, ticket: Ticket, user: User) -> dict:
transitions = cls.get_state_transitions(ticket.state) transitions = cls.get_state_transitions(ticket.state)
if not transitions: if not transitions:
return dict(permission=True, msg="工单当前状态无需操作") return dict(permission=True, msg="工单当前状态无需操作")
@ -209,7 +211,8 @@ class WfService(object):
if participant_type == State.PARTICIPANT_TYPE_PERSONAL: if participant_type == State.PARTICIPANT_TYPE_PERSONAL:
if user.id != participant: if user.id != participant:
return dict(permission=False, msg="非当前处理人", need_accept=False) 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: if user.id not in participant:
return dict(permission=False, msg="非当前处理人", need_accept=False) return dict(permission=False, msg="非当前处理人", need_accept=False)
current_participant_count = len(participant) current_participant_count = len(participant)
@ -218,7 +221,7 @@ class WfService(object):
pass pass
else: else:
return dict(permission=False, msg="非当前处理人", need_accept=False) return dict(permission=False, msg="非当前处理人", need_accept=False)
elif current_participant_count >1 and state.distribute_type == State.STATE_DISTRIBUTE_TYPE_ACTIVE: elif current_participant_count > 1 and state.distribute_type == State.STATE_DISTRIBUTE_TYPE_ACTIVE:
if user.id not in participant: if user.id not in participant:
return dict(permission=False, msg="非当前处理人", need_accept=False) return dict(permission=False, msg="非当前处理人", need_accept=False)
return dict(permission=False, msg="需要先接单再处理", need_accept=True) return dict(permission=False, msg="需要先接单再处理", need_accept=True)
@ -227,7 +230,7 @@ class WfService(object):
return dict(permission=True, msg="", need_accept=False) return dict(permission=True, msg="", need_accept=False)
@classmethod @classmethod
def check_dict_has_all_same_value(cls, dict_obj: object)->tuple: def check_dict_has_all_same_value(cls, dict_obj: object) -> tuple:
""" """
check whether all key are equal in a dict check whether all key are equal in a dict
:param dict_obj: :param dict_obj:
@ -243,7 +246,7 @@ class WfService(object):
return True return True
@classmethod @classmethod
def get_ticket_all_field_value(cls, ticket: Ticket)->dict: def get_ticket_all_field_value(cls, ticket: Ticket) -> dict:
""" """
工单所有字段的值 工单所有字段的值
get ticket's all field value get ticket's all field value
@ -259,8 +262,9 @@ class WfService(object):
return field_info_dict return field_info_dict
@classmethod @classmethod
def handle_ticket(cls, ticket:Ticket, transition: Transition, new_ticket_data:dict={}, handler:User=None, 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_state = ticket.state
source_ticket_data = ticket.ticket_data source_ticket_data = ticket.ticket_data
@ -281,10 +285,11 @@ class WfService(object):
destination_state = cls.get_next_state_by_transition_and_ticket_info(ticket, transition, new_ticket_data) destination_state = cls.get_next_state_by_transition_and_ticket_info(ticket, transition, new_ticket_data)
multi_all_person = ticket.multi_all_person multi_all_person = ticket.multi_all_person
if multi_all_person: if multi_all_person:
multi_all_person[handler.id] =dict(transition=transition.id) multi_all_person[handler.id] = dict(transition=transition.id)
# 判断所有人处理结果是否一致 # 判断所有人处理结果是否一致
if WfService.check_dict_has_all_same_value(multi_all_person): 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_type = participant_info.get('destination_participant_type', 0)
destination_participant = participant_info.get('destination_participant', 0) destination_participant = participant_info.get('destination_participant', 0)
multi_all_person = {} multi_all_person = {}
@ -329,7 +334,8 @@ class WfService(object):
# 更新工单流转记录 # 更新工单流转记录
if not by_task: 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, suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL,
participant=handler, transition=transition) participant=handler, transition=transition)
@ -350,4 +356,3 @@ class WfService(object):
getattr(HandleScripts, destination_state.participant)(ticket) getattr(HandleScripts, destination_state.participant)(ticket)
return ticket return ticket

View File

@ -1,6 +1,5 @@
from django.db.models import base from apps.wf.views import CustomFieldViewSet, FromCodeListView, StateViewSet, TicketFlowViewSet, \
from rest_framework import urlpatterns TicketViewSet, TransitionViewSet, WorkflowViewSet
from apps.wf.views import CustomFieldViewSet, FromCodeListView, StateViewSet, TicketFlowViewSet, TicketViewSet, TransitionViewSet, WorkflowViewSet
from django.urls import path, include from django.urls import path, include
from rest_framework.routers import DefaultRouter 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 + 'participant_from_code', FromCodeListView.as_view()),
path(API_BASE_URL, include(router.urls)), path(API_BASE_URL, include(router.urls)),
] ]

View File

@ -1,26 +1,26 @@
from django.utils import timezone from django.utils import timezone
from django.db import transaction 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 rest_framework.views import APIView
from apps.system.models import User from apps.system.models import User
from apps.utils.viewsets import CustomModelViewSet
from apps.wf.filters import TicketFilterSet from apps.wf.filters import TicketFilterSet
from django.core.exceptions import AppRegistryNotReady
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import serializers from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModelMixin, \
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin 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 apps.wf.serializers import CustomFieldCreateUpdateSerializer, CustomFieldSerializer, StateSerializer, \
from django.shortcuts import get_object_or_404, render TicketAddNodeEndSerializer, TicketAddNodeSerializer, TicketCloseSerializer, \
TicketCreateSerializer, TicketDestorySerializer, TicketFlowSerializer, \
TicketHandleSerializer, TicketRetreatSerializer, \
TicketSerializer, TransitionSerializer, WorkflowSerializer, \
TicketListSerializer, TicketDetailSerializer
from rest_framework.viewsets import GenericViewSet, ModelViewSet 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.wf.models import CustomField, Ticket, Workflow, State, Transition, TicketFlow
from apps.utils.mixins import CreateUpdateCustomMixin, CreateUpdateModelAMixin from apps.utils.mixins import CreateUpdateCustomMixin, CreateUpdateModelAMixin
from apps.wf.services import WfService 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 rest_framework import status
from django.db.models import Count from django.db.models import Count
from .scripts import GetParticipants, HandleScripts from .scripts import GetParticipants
# Create your views here. # Create your views here.
@ -31,6 +31,7 @@ class FromCodeListView(APIView):
""" """
return Response(GetParticipants.all_funcs) return Response(GetParticipants.all_funcs)
class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet): class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet):
perms_map = {'get': '*', 'post': 'workflow:create', perms_map = {'get': '*', 'post': 'workflow:create',
'put': 'workflow:update', 'delete': 'workflow:delete'} 'put': 'workflow:update', 'delete': 'workflow:delete'}
@ -41,7 +42,8 @@ class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet):
ordering_fields = ['create_time'] ordering_fields = ['create_time']
ordering = ['-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): 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) serializer = self.serializer_class(instance=WfService.get_worlflow_states(wf), many=True)
return Response(serializer.data) 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): def transitions(self, request, pk=None):
""" """
工作流下的流转规则 工作流下的流转规则
@ -59,21 +62,23 @@ class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet):
serializer = self.serializer_class(instance=WfService.get_workflow_transitions(wf), many=True) serializer = self.serializer_class(instance=WfService.get_workflow_transitions(wf), many=True)
return Response(serializer.data) 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): def customfields(self, request, pk=None):
""" """
工作流下的自定义字段 工作流下的自定义字段
""" """
wf = self.get_object() 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) return Response(serializer.data)
@action(methods=['get'], detail=True, perms_map={'get':'workflow:init'}) @action(methods=['get'], detail=True, perms_map={'get': 'workflow:init'})
def init(self, request, pk=None): def init(self, request, pk=None):
""" """
新建工单初始化 新建工单初始化
""" """
ret={} ret = {}
wf = self.get_object() wf = self.get_object()
start_state = WfService.get_workflow_start_state(wf) start_state = WfService.get_workflow_start_state(wf)
transitions = WfService.get_state_transitions(start_state) transitions = WfService.get_state_transitions(start_state)
@ -88,27 +93,30 @@ class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet):
ret['field_list'] = field_list ret['field_list'] = field_list
return Response(ret) return Response(ret)
class StateViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet): class StateViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet):
perms_map = {'get':'*', 'post':'workflow:update', perms_map = {'get': '*', 'post': 'workflow:update',
'put':'workflow:update', 'delete':'workflow:update'} 'put': 'workflow:update', 'delete': 'workflow:update'}
queryset = State.objects.all() queryset = State.objects.all()
serializer_class = StateSerializer serializer_class = StateSerializer
search_fields = ['name'] search_fields = ['name']
filterset_fields = ['workflow'] filterset_fields = ['workflow']
ordering = ['sort'] ordering = ['sort']
class TransitionViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet): class TransitionViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet):
perms_map = {'get':'*', 'post':'workflow:update', perms_map = {'get': '*', 'post': 'workflow:update',
'put':'workflow:update', 'delete':'workflow:update'} 'put': 'workflow:update', 'delete': 'workflow:update'}
queryset = Transition.objects.all() queryset = Transition.objects.all()
serializer_class = TransitionSerializer serializer_class = TransitionSerializer
search_fields = ['name'] search_fields = ['name']
filterset_fields = ['workflow'] filterset_fields = ['workflow']
ordering = ['id'] ordering = ['id']
class CustomFieldViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet): class CustomFieldViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet):
perms_map = {'get':'*', 'post':'workflow:update', perms_map = {'get': '*', 'post': 'workflow:update',
'put':'workflow:update', 'delete':'workflow:update'} 'put': 'workflow:update', 'delete': 'workflow:update'}
queryset = CustomField.objects.all() queryset = CustomField.objects.all()
serializer_class = CustomFieldSerializer serializer_class = CustomFieldSerializer
search_fields = ['field_name'] search_fields = ['field_name']
@ -120,8 +128,9 @@ class CustomFieldViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin,
return CustomFieldCreateUpdateSerializer return CustomFieldCreateUpdateSerializer
return super().get_serializer_class() return super().get_serializer_class()
class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, RetrieveModelMixin, GenericViewSet): class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, RetrieveModelMixin, GenericViewSet):
perms_map = {'get':'*', 'post':'ticket:create'} perms_map = {'get': '*', 'post': 'ticket:create'}
queryset = Ticket.objects.all() queryset = Ticket.objects.all()
serializer_class = TicketSerializer serializer_class = TicketSerializer
search_fields = ['title'] search_fields = ['title']
@ -154,7 +163,7 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
rdata = request.data rdata = request.data
serializer = self.get_serializer(data=rdata) serializer = self.get_serializer(data=rdata)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data #校验之后的数据 vdata = serializer.validated_data # 校验之后的数据
start_state = WfService.get_workflow_start_state(vdata['workflow']) start_state = WfService.get_workflow_start_state(vdata['workflow'])
transition = vdata.pop('transition') transition = vdata.pop('transition')
ticket_data = vdata['ticket_data'] ticket_data = vdata['ticket_data']
@ -190,7 +199,7 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
handler=request.user, created=True) handler=request.user, created=True)
return Response(TicketSerializer(instance=ticket).data) return Response(TicketSerializer(instance=ticket).data)
@action(methods=['get'], detail=False, perms_map={'get':'*'}) @action(methods=['get'], detail=False, perms_map={'get': '*'})
def duty_agg(self, request, pk=None): def duty_agg(self, request, pk=None):
""" """
工单待办聚合 工单待办聚合
@ -199,10 +208,10 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
queryset = Ticket.objects.filter(participant__contains=request.user.id, is_deleted=False)\ queryset = Ticket.objects.filter(participant__contains=request.user.id, is_deleted=False)\
.exclude(act_state__in=[Ticket.TICKET_ACT_STATE_FINISH, Ticket.TICKET_ACT_STATE_CLOSED]) .exclude(act_state__in=[Ticket.TICKET_ACT_STATE_FINISH, Ticket.TICKET_ACT_STATE_CLOSED])
ret['total_count'] = queryset.count() ret['total_count'] = queryset.count()
ret['details'] = list(queryset.values('workflow', 'workflow__name').annotate(count = Count('workflow'))) ret['details'] = list(queryset.values('workflow', 'workflow__name').annotate(count=Count('workflow')))
return Response(ret) return Response(ret)
@action(methods=['post'], detail=True, perms_map={'post':'*'}) @action(methods=['post'], detail=True, perms_map={'post': '*'})
@transaction.atomic @transaction.atomic
def handle(self, request, pk=None): def handle(self, request, pk=None):
""" """
@ -216,11 +225,11 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
new_ticket_data.update(**vdata['ticket_data']) new_ticket_data.update(**vdata['ticket_data'])
ticket = WfService.handle_ticket(ticket=ticket, transition=vdata['transition'], 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) return Response(TicketSerializer(instance=ticket).data)
@action(methods=['get'], detail=True, perms_map={'get': '*'})
@action(methods=['get'], detail=True, perms_map={'get':'*'})
def flowsteps(self, request, pk=None): def flowsteps(self, request, pk=None):
""" """
工单流转step, 用于显示当前状态的step图(线性结构) 工单流转step, 用于显示当前状态的step图(线性结构)
@ -229,7 +238,7 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
steps = WfService.get_ticket_steps(ticket) steps = WfService.get_ticket_steps(ticket)
return Response(StateSerializer(instance=steps, many=True).data) return Response(StateSerializer(instance=steps, many=True).data)
@action(methods=['get'], detail=True, perms_map={'get':'*'}) @action(methods=['get'], detail=True, perms_map={'get': '*'})
def flowlogs(self, request, pk=None): def flowlogs(self, request, pk=None):
""" """
工单流转记录 工单流转记录
@ -239,7 +248,7 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
serializer = TicketFlowSerializer(instance=flowlogs, many=True) serializer = TicketFlowSerializer(instance=flowlogs, many=True)
return Response(serializer.data) return Response(serializer.data)
@action(methods=['get'], detail=True, perms_map={'get':'*'}) @action(methods=['get'], detail=True, perms_map={'get': '*'})
def transitions(self, request, pk=None): def transitions(self, request, pk=None):
""" """
获取工单可执行的操作 获取工单可执行的操作
@ -248,7 +257,7 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
transitions = WfService.get_ticket_transitions(ticket) transitions = WfService.get_ticket_transitions(ticket)
return Response(TransitionSerializer(instance=transitions, many=True).data) return Response(TransitionSerializer(instance=transitions, many=True).data)
@action(methods=['post'], detail=True, perms_map={'post':'*'}) @action(methods=['post'], detail=True, perms_map={'post': '*'})
def accpet(self, request, pk=None): def accpet(self, request, pk=None):
""" """
接单,当工单当前处理人实际为多个人时(角色部门多人都有可能 注意角色和部门有可能实际只有一人) 接单,当工单当前处理人实际为多个人时(角色部门多人都有可能 注意角色和部门有可能实际只有一人)
@ -261,14 +270,16 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
ticket.save() ticket.save()
# 接单日志 # 接单日志
# 更新工单流转记录 # 更新工单流转记录
TicketFlow.objects.create(ticket=ticket, state=ticket.state, ticket_data=WfService.get_ticket_all_field_value(ticket), TicketFlow.objects.create(ticket=ticket, state=ticket.state,
suggestion='', participant_type=State.PARTICIPANT_TYPE_PERSONAL, intervene_type=Transition.TRANSITION_ATTRIBUTE_TYPE_ACCEPT, 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) participant=request.user, transition=None)
return Response() return Response()
else: else:
raise APIException('无需接单') raise APIException('无需接单')
@action(methods=['post'], detail=True, perms_map={'post':'*'}) @action(methods=['post'], detail=True, perms_map={'post': '*'})
def retreat(self, request, pk=None): def retreat(self, request, pk=None):
""" """
撤回工单允许创建人在指定状态撤回工单至初始状态状态设置中开启允许撤回 撤回工单允许创建人在指定状态撤回工单至初始状态状态设置中开启允许撤回
@ -286,12 +297,14 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
ticket.save() ticket.save()
# 更新流转记录 # 更新流转记录
suggestion = request.data.get('suggestion', '') # 撤回原因 suggestion = request.data.get('suggestion', '') # 撤回原因
TicketFlow.objects.create(ticket=ticket, state=ticket.state, ticket_data=WfService.get_ticket_all_field_value(ticket), TicketFlow.objects.create(ticket=ticket, state=ticket.state,
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_RETREAT, 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) participant=request.user, transition=None)
return Response() return Response()
@action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=TicketAddNodeSerializer) @action(methods=['post'], detail=True, perms_map={'post': '*'}, serializer_class=TicketAddNodeSerializer)
def add_node(self, request, pk=None): def add_node(self, request, pk=None):
""" """
加签 加签
@ -306,12 +319,14 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
ticket.save() ticket.save()
# 更新流转记录 # 更新流转记录
suggestion = request.data.get('suggestion', '') # 加签说明 suggestion = request.data.get('suggestion', '') # 加签说明
TicketFlow.objects.create(ticket=ticket, state=ticket.state, ticket_data=WfService.get_ticket_all_field_value(ticket), TicketFlow.objects.create(ticket=ticket, state=ticket.state,
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_ADD_NODE, 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) participant=request.user, transition=None)
return Response() return Response()
@action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=TicketAddNodeEndSerializer) @action(methods=['post'], detail=True, perms_map={'post': '*'}, serializer_class=TicketAddNodeEndSerializer)
def add_node_end(self, request, pk=None): def add_node_end(self, request, pk=None):
""" """
加签完成 加签完成
@ -324,19 +339,21 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
ticket.save() ticket.save()
# 更新流转记录 # 更新流转记录
suggestion = request.data.get('suggestion', '') # 加签意见 suggestion = request.data.get('suggestion', '') # 加签意见
TicketFlow.objects.create(ticket=ticket, state=ticket.state, ticket_data=WfService.get_ticket_all_field_value(ticket), TicketFlow.objects.create(ticket=ticket, state=ticket.state,
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_ADD_NODE_END, 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) participant=request.user, transition=None)
return Response() return Response()
@action(methods=['post'], detail=True, perms_map={'post': '*'},
@action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=TicketCloseSerializer) serializer_class=TicketCloseSerializer)
def close(self, request, pk=None): def close(self, request, pk=None):
""" """
关闭工单(创建人在初始状态) 关闭工单(创建人在初始状态)
""" """
ticket = self.get_object() ticket = self.get_object()
if ticket.state.type == State.STATE_TYPE_START and ticket.create_by==request.user: if ticket.state.type == State.STATE_TYPE_START and ticket.create_by == request.user:
end_state = WfService.get_workflow_end_state(ticket.workflow) end_state = WfService.get_workflow_end_state(ticket.workflow)
ticket.state = end_state ticket.state = end_state
ticket.participant_type = 0 ticket.participant_type = 0
@ -345,14 +362,17 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
ticket.save() ticket.save()
# 更新流转记录 # 更新流转记录
suggestion = request.data.get('suggestion', '') # 关闭原因 suggestion = request.data.get('suggestion', '') # 关闭原因
TicketFlow.objects.create(ticket=ticket, state=ticket.state, ticket_data=WfService.get_ticket_all_field_value(ticket), TicketFlow.objects.create(ticket=ticket, state=ticket.state,
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_CLOSE, 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) participant=request.user, transition=None)
return Response() return Response()
else: else:
return Response('工单不可关闭', status=status.HTTP_400_BAD_REQUEST) 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): def destorys(self, request, pk=None):
""" """
批量物理删除 批量物理删除
@ -361,12 +381,11 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
return Response() return Response()
class TicketFlowViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): class TicketFlowViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet):
""" """
工单日志 工单日志
""" """
perms_map = {'get':'*'} perms_map = {'get': '*'}
queryset = TicketFlow.objects.all() queryset = TicketFlow.objects.all()
serializer_class = TicketFlowSerializer serializer_class = TicketFlowSerializer
search_fields = ['suggestion'] search_fields = ['suggestion']

View File

@ -217,8 +217,8 @@ CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler'
# swagger配置 # swagger配置
SWAGGER_SETTINGS = { SWAGGER_SETTINGS = {
'LOGIN_URL':'/django/login/', 'LOGIN_URL': '/django/login/',
'LOGOUT_URL':'/django/logout/', 'LOGOUT_URL': '/django/logout/',
} }
# 日志配置 # 日志配置

View File

@ -57,8 +57,7 @@ urlpatterns = [
path('api/redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'), path('api/redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
# 前端页面入口 # 前端页面入口
path('',TemplateView.as_view(template_name="index.html")), path('', TemplateView.as_view(template_name="index.html")),
] + \ ] + \
static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + \ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + \
static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)