语法pep8修正
This commit is contained in:
parent
d1c79e516a
commit
2a9c70eee5
|
@ -4,4 +4,3 @@ from django.apps import AppConfig
|
|||
class AuthConfig(AppConfig):
|
||||
name = 'apps.auth1'
|
||||
verbose_name = "认证"
|
||||
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
|
||||
USERNAME_OR_PASSWORD_WRONG = {"code":"username_or_password_wrong", "detail":"账户名或密码错误"}
|
||||
USERNAME_OR_PASSWORD_WRONG = {"code": "username_or_password_wrong", "detail": "账户名或密码错误"}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
|
||||
class LoginSerializer(serializers.Serializer):
|
||||
username = serializers.CharField(label="用户名")
|
||||
password = serializers.CharField(label="密码")
|
|
@ -1,6 +1,5 @@
|
|||
|
||||
from rest_framework.exceptions import ParseError, ValidationError
|
||||
from django.shortcuts import render
|
||||
from rest_framework.exceptions import ParseError
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
|
@ -14,6 +13,7 @@ from apps.auth1.serializers import LoginSerializer
|
|||
|
||||
# Create your views here.
|
||||
|
||||
|
||||
class TokenBlackView(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
|
@ -26,6 +26,7 @@ class TokenBlackView(APIView):
|
|||
"""
|
||||
return Response(status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class LoginView(CreateAPIView):
|
||||
"""
|
||||
Session登录
|
||||
|
@ -41,13 +42,14 @@ class LoginView(CreateAPIView):
|
|||
serializer = self.get_serializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
vdata = serializer.validated_data
|
||||
user = authenticate(username = vdata.get('username'),
|
||||
password = vdata.get('password'))
|
||||
user = authenticate(username=vdata.get('username'),
|
||||
password=vdata.get('password'))
|
||||
if user is not None:
|
||||
login(request, user)
|
||||
return Response()
|
||||
raise ParseError(**USERNAME_OR_PASSWORD_WRONG)
|
||||
|
||||
|
||||
class LogoutView(APIView):
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
from __future__ import absolute_import, unicode_literals
|
||||
from distutils import command
|
||||
from rest_framework.response import Response
|
||||
from celery import shared_task
|
||||
import os
|
||||
import subprocess
|
||||
from server.settings import DATABASES, BACKUP_PATH, SH_PATH, SD_PWD
|
||||
|
||||
|
||||
@shared_task
|
||||
def backup_database():
|
||||
"""
|
||||
|
@ -23,22 +21,23 @@ def backup_database():
|
|||
completed = subprocess.run(command, shell=True, capture_output=True, text=True)
|
||||
return completed
|
||||
|
||||
|
||||
@shared_task
|
||||
def reload_server_git():
|
||||
command = 'bash {}/git_server.sh'.format(SH_PATH)
|
||||
completed = subprocess.run(command, shell=True, capture_output=True, text=True)
|
||||
return completed
|
||||
|
||||
|
||||
@shared_task
|
||||
def reload_server_only():
|
||||
command = 'echo "{}" | sudo -S service supervisor reload'.format(SD_PWD)
|
||||
completed = subprocess.run(command, shell=True, capture_output=True, text=True)
|
||||
return completed
|
||||
|
||||
|
||||
@shared_task
|
||||
def backup_media():
|
||||
command = 'bash {}/backup_media.sh'.format(SH_PATH)
|
||||
completed = subprocess.run(command, shell=True, capture_output=True, text=True)
|
||||
return completed
|
||||
|
||||
|
|
@ -1,7 +1,4 @@
|
|||
from django.db.models import base
|
||||
from rest_framework import urlpatterns
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from django.urls import path
|
||||
from apps.develop.views import BackupDatabase, BackupMedia, ReloadServerGit, ReloadServerOnly
|
||||
|
||||
API_BASE_URL = 'api/develop/'
|
||||
|
@ -13,4 +10,3 @@ urlpatterns = [
|
|||
path(API_BASE_URL + 'backup_database/', BackupDatabase.as_view()),
|
||||
path(API_BASE_URL + 'backup_media/', BackupMedia.as_view())
|
||||
]
|
||||
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
from django.db import transaction
|
||||
from django.shortcuts import render
|
||||
from rest_framework.decorators import permission_classes
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.permissions import IsAdminUser, AllowAny
|
||||
from rest_framework.permissions import IsAdminUser
|
||||
from rest_framework.response import Response
|
||||
from apps.develop.tasks import backup_database, backup_media, reload_server_git, reload_server_only
|
||||
from rest_framework.exceptions import APIException
|
||||
|
@ -11,6 +8,7 @@ from rest_framework.exceptions import APIException
|
|||
|
||||
class ReloadServerGit(APIView):
|
||||
permission_classes = [IsAdminUser]
|
||||
|
||||
def post(self, request):
|
||||
"""
|
||||
拉取后端代码并重启服务
|
||||
|
@ -23,8 +21,10 @@ class ReloadServerGit(APIView):
|
|||
else:
|
||||
raise APIException(completed.stdout)
|
||||
|
||||
|
||||
class ReloadServerOnly(APIView):
|
||||
permission_classes = [IsAdminUser]
|
||||
|
||||
def post(self, request):
|
||||
"""
|
||||
仅重启服务
|
||||
|
@ -40,6 +40,7 @@ class ReloadServerOnly(APIView):
|
|||
|
||||
class BackupDatabase(APIView):
|
||||
permission_classes = [IsAdminUser]
|
||||
|
||||
def post(self, request):
|
||||
"""
|
||||
备份数据库到指定位置
|
||||
|
@ -52,8 +53,10 @@ class BackupDatabase(APIView):
|
|||
else:
|
||||
raise APIException(completed.stdout)
|
||||
|
||||
|
||||
class BackupMedia(APIView):
|
||||
permission_classes = [IsAdminUser]
|
||||
|
||||
def post(self, request):
|
||||
"""
|
||||
备份资源到指定位置
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class HrmConfig(AppConfig):
|
||||
name = 'apps.hrm'
|
||||
verbose_name = '人力资源管理'
|
||||
|
||||
def ready(self):
|
||||
import apps.hrm.signals
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
from enum import Enum
|
||||
|
||||
|
||||
NO_NEED_LEVEL_REMARK = {"code":"no_need_level_remark", "detail":"无需填写离岗说明"}
|
||||
PHONE_F_WRONG = {"code":"phone_f_wrong", "detail":"手机号格式错误"}
|
||||
PHONE_EXIST = {"code":"phone_exist", "detail":"手机号已存在"}
|
||||
DH_PHOTO_FALI = {"code":"dh_photo_fail", "detail":"大华照片人脸提取失败"}
|
||||
NO_NEED_LEVEL_REMARK = {"code": "no_need_level_remark", "detail": "无需填写离岗说明"}
|
||||
PHONE_F_WRONG = {"code": "phone_f_wrong", "detail": "手机号格式错误"}
|
||||
PHONE_EXIST = {"code": "phone_exist", "detail": "手机号已存在"}
|
||||
DH_PHOTO_FALI = {"code": "dh_photo_fail", "detail": "大华照片人脸提取失败"}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
from django_filters import rest_framework as filters
|
||||
from apps.hrm.models import ClockRecord, Employee, NotWorkRemark
|
||||
|
||||
|
||||
class ClockRecordFilterSet(filters.FilterSet):
|
||||
create_time_start = filters.DateFilter(field_name="create_time", lookup_expr='gte')
|
||||
create_time_end = filters.DateFilter(field_name="create_time", lookup_expr='lte')
|
||||
year = filters.NumberFilter(method='filter_year')
|
||||
month = filters.NumberFilter(method='filter_month')
|
||||
|
||||
class Meta:
|
||||
model = ClockRecord
|
||||
fields = ['create_by', 'create_time_start', 'create_time_end', 'year', 'month']
|
||||
|
@ -16,6 +18,7 @@ class ClockRecordFilterSet(filters.FilterSet):
|
|||
def filter_month(self, queryset, name, value):
|
||||
return queryset.filter(create_time_date__month=value)
|
||||
|
||||
|
||||
class EmployeeFilterSet(filters.FilterSet):
|
||||
|
||||
class Meta:
|
||||
|
@ -26,6 +29,7 @@ class EmployeeFilterSet(filters.FilterSet):
|
|||
class NotWorkRemarkFilterSet(filters.FilterSet):
|
||||
year = filters.NumberFilter(method='filter_year')
|
||||
month = filters.NumberFilter(method='filter_month')
|
||||
|
||||
class Meta:
|
||||
model = NotWorkRemark
|
||||
fields = ['year', 'month', 'user']
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
from django.db import models
|
||||
from django.db.models.query import QuerySet
|
||||
from apps.system.models import User
|
||||
|
||||
from apps.utils.models import CommonADModel, CommonAModel, CommonBModel
|
||||
|
||||
|
||||
|
||||
|
||||
class Employee(CommonBModel):
|
||||
"""
|
||||
员工信息
|
||||
|
@ -14,12 +11,12 @@ class Employee(CommonBModel):
|
|||
JOB_ON = 10
|
||||
JOB_OFF = 20
|
||||
jobstate_choices = (
|
||||
(JOB_ON, '在职'),
|
||||
(JOB_OFF, '离职'),
|
||||
(JOB_ON, '在职'),
|
||||
(JOB_OFF, '离职'),
|
||||
)
|
||||
user = models.OneToOneField(User,
|
||||
verbose_name='系统账号',
|
||||
on_delete=models.PROTECT, related_name='employee_user', null=True, blank=True)
|
||||
verbose_name='系统账号',
|
||||
on_delete=models.PROTECT, related_name='employee_user', null=True, blank=True)
|
||||
name = models.CharField('姓名', max_length=20)
|
||||
phone = models.CharField('手机号', max_length=11, null=True, blank=True)
|
||||
email = models.EmailField('邮箱号', null=True, blank=True)
|
||||
|
@ -30,13 +27,14 @@ class Employee(CommonBModel):
|
|||
signature = models.CharField('签名图片', max_length=200, null=True, blank=True)
|
||||
birthday = models.DateField('出生年月日', null=True, blank=True)
|
||||
qualification = models.CharField('学历', max_length=50, null=True, blank=True)
|
||||
job_state = models.IntegerField('在职状态', choices=jobstate_choices, default=1)
|
||||
job_state = models.IntegerField('在职状态', choices=jobstate_choices, default=1)
|
||||
face_data = models.JSONField('人脸识别数据', null=True, blank=True)
|
||||
is_atwork = models.BooleanField('当前在岗', default=False)
|
||||
show_atwork = models.BooleanField('是否展示在岗状态', default=True)
|
||||
last_check_time = models.DateTimeField('打卡时间', null=True, blank=True)
|
||||
not_work_remark = models.CharField('当前未打卡说明', null=True, blank=True, max_length=200)
|
||||
third_info = models.JSONField('三方信息', default=dict, null=False, blank=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = '员工补充信息'
|
||||
verbose_name_plural = verbose_name
|
||||
|
@ -53,7 +51,6 @@ class Employee(CommonBModel):
|
|||
# CARD_LOCATION = 20
|
||||
|
||||
|
||||
|
||||
class NotWorkRemark(CommonAModel):
|
||||
"""
|
||||
离岗说明
|
||||
|
@ -62,6 +59,7 @@ class NotWorkRemark(CommonAModel):
|
|||
user = models.ForeignKey(User, verbose_name='用户', on_delete=models.CASCADE)
|
||||
remark = models.CharField('未打卡说明', null=True, blank=True, max_length=200)
|
||||
|
||||
|
||||
class ClockRecord(CommonADModel):
|
||||
"""
|
||||
打卡记录
|
||||
|
@ -71,5 +69,3 @@ class ClockRecord(CommonADModel):
|
|||
(ClOCK_WORK1, '上班打卡'),
|
||||
)
|
||||
type = models.PositiveSmallIntegerField('打卡类型', choices=type_choice, default=ClOCK_WORK1)
|
||||
|
||||
|
||||
|
|
|
@ -1,20 +1,18 @@
|
|||
from apps.hrm.errors import DH_PHOTO_FALI, PHONE_EXIST, PHONE_F_WRONG
|
||||
from apps.system.models import Dept, User
|
||||
from apps.hrm.errors import PHONE_F_WRONG
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework import serializers
|
||||
|
||||
from apps.utils.serializers import CustomModelSerializer
|
||||
from apps.utils.constants import EXCLUDE_FIELDS
|
||||
from apps.utils.tools import rannum, ranstr
|
||||
from .models import ClockRecord, Employee, NotWorkRemark
|
||||
from apps.system.serializers import DeptSimpleSerializer,UserSimpleSerializer
|
||||
from apps.utils.tools import rannum
|
||||
from apps.hrm.models import ClockRecord, Employee, NotWorkRemark
|
||||
from apps.system.serializers import DeptSimpleSerializer, UserSimpleSerializer
|
||||
from django.db import transaction
|
||||
from apps.third.clients import dhClient
|
||||
from apps.third.tapis import dhapis
|
||||
import re
|
||||
from server.settings import DEBUG
|
||||
from rest_framework.exceptions import ParseError
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class EmployeeBaseSerializer(CustomModelSerializer):
|
||||
def save(self, **kwargs):
|
||||
|
@ -26,24 +24,26 @@ class EmployeeBaseSerializer(CustomModelSerializer):
|
|||
|
||||
|
||||
def phone_check(phone):
|
||||
re_phone = '^1[358]\d{9}$|^147\d{8}$|^176\d{8}$'
|
||||
re_phone = r'^1[358]\d{9}$|^147\d{8}$|^176\d{8}$'
|
||||
if not re.match(re_phone, phone):
|
||||
raise serializers.ValidationError(**PHONE_F_WRONG)
|
||||
return phone
|
||||
|
||||
|
||||
class EmployeeCreateUpdateSerializer(EmployeeBaseSerializer):
|
||||
phone = serializers.CharField(label="手机号", validators=[phone_check])
|
||||
|
||||
class Meta:
|
||||
model = Employee
|
||||
exclude = EXCLUDE_FIELDS + ['face_data',
|
||||
'is_atwork', 'last_check_time',
|
||||
'not_work_remark', 'third_info']
|
||||
exclude = EXCLUDE_FIELDS + ['face_data',
|
||||
'is_atwork', 'last_check_time',
|
||||
'not_work_remark', 'third_info']
|
||||
extra_kwargs = {
|
||||
'phone': {'required': True},
|
||||
'number': {'required': True},
|
||||
'photo': {'required': True},
|
||||
'id_number': {'required': True},
|
||||
}
|
||||
}
|
||||
|
||||
@transaction.atomic
|
||||
def create(self, validated_data):
|
||||
|
@ -59,7 +59,7 @@ class EmployeeCreateUpdateSerializer(EmployeeBaseSerializer):
|
|||
except:
|
||||
pass
|
||||
json_data = {
|
||||
"service":"ehs",
|
||||
"service": "ehs",
|
||||
"id": personId,
|
||||
"name": instance.name,
|
||||
"code": instance.number,
|
||||
|
@ -75,11 +75,11 @@ class EmployeeCreateUpdateSerializer(EmployeeBaseSerializer):
|
|||
dh_photo = res["fileUrl"]
|
||||
json_data.update(
|
||||
{
|
||||
"biosignatureTypeList":[3],
|
||||
"personBiosignatures":[{
|
||||
"type":3,
|
||||
"index":1,
|
||||
"path": dh_photo
|
||||
"biosignatureTypeList": [3],
|
||||
"personBiosignatures": [{
|
||||
"type": 3,
|
||||
"index": 1,
|
||||
"path": dh_photo
|
||||
}]
|
||||
}
|
||||
)
|
||||
|
@ -91,20 +91,20 @@ class EmployeeCreateUpdateSerializer(EmployeeBaseSerializer):
|
|||
now = datetime.now()
|
||||
startDate = now.strftime("%Y-%m-%d %H:%M:%S")
|
||||
endDate = (datetime(year=now.year+50,
|
||||
month=now.month, day=1)).strftime("%Y-%m-%d %H:%M:%S")
|
||||
month=now.month, day=1)).strftime("%Y-%m-%d %H:%M:%S")
|
||||
json_data = {
|
||||
"id": cardId,
|
||||
"cardNumber": cardNumber,
|
||||
"category": 0,
|
||||
"cardType": 0,
|
||||
"personId": personId,
|
||||
"departmentId":departmentId,
|
||||
"departmentId": departmentId,
|
||||
"startDate": startDate,
|
||||
"endDate": endDate
|
||||
}
|
||||
_, res = dhClient.request(**dhapis['card_add'], json=json_data)
|
||||
instance.third_info = {'dh_id':personId,
|
||||
'dh_photo':dh_photo, 'dh_face_card':res['id']}
|
||||
instance.third_info = {'dh_id': personId,
|
||||
'dh_photo': dh_photo, 'dh_face_card': res['id']}
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
|
@ -123,7 +123,7 @@ class EmployeeCreateUpdateSerializer(EmployeeBaseSerializer):
|
|||
dh_id = instance.third_info['dh_id']
|
||||
dh_photo = third_info['dh_photo']
|
||||
json_data = {
|
||||
"service":"ehs",
|
||||
"service": "ehs",
|
||||
"id": dh_id,
|
||||
"name": instance.name,
|
||||
"code": instance.number,
|
||||
|
@ -134,23 +134,23 @@ class EmployeeCreateUpdateSerializer(EmployeeBaseSerializer):
|
|||
"phone": instance.phone,
|
||||
"email": instance.email,
|
||||
"sex": 1 if instance.gender == '男' else 2,
|
||||
"biosignatureTypeList":[3],
|
||||
"personBiosignatures":[{
|
||||
"type":3,
|
||||
"index":1,
|
||||
"biosignatureTypeList": [3],
|
||||
"personBiosignatures": [{
|
||||
"type": 3,
|
||||
"index": 1,
|
||||
"path": third_info['dh_photo']
|
||||
}]
|
||||
}]
|
||||
}
|
||||
if instance.photo != old_photo:
|
||||
_, res = dhClient.request(**dhapis['person_img_upload'], file_path_rela=instance.photo)
|
||||
dh_photo = res["fileUrl"]
|
||||
json_data.update(
|
||||
{
|
||||
"biosignatureTypeList":[3],
|
||||
"personBiosignatures":[{
|
||||
"type":3,
|
||||
"index":1,
|
||||
"path": dh_photo
|
||||
"biosignatureTypeList": [3],
|
||||
"personBiosignatures": [{
|
||||
"type": 3,
|
||||
"index": 1,
|
||||
"path": dh_photo
|
||||
}]
|
||||
}
|
||||
)
|
||||
|
@ -165,14 +165,14 @@ class EmployeeCreateUpdateSerializer(EmployeeBaseSerializer):
|
|||
now = datetime.now()
|
||||
startDate = now.strftime("%Y-%m-%d %H:%M:%S")
|
||||
endDate = (datetime(year=now.year+50,
|
||||
month=now.month, day=1)).strftime("%Y-%m-%d %H:%M:%S")
|
||||
month=now.month, day=1)).strftime("%Y-%m-%d %H:%M:%S")
|
||||
json_data = {
|
||||
"id": cardId,
|
||||
"cardNumber": cardNumber,
|
||||
"category": 0,
|
||||
"cardType": 0,
|
||||
"personId": third_info['dh_id'],
|
||||
"departmentId":departmentId,
|
||||
"departmentId": departmentId,
|
||||
"startDate": startDate,
|
||||
"endDate": endDate
|
||||
}
|
||||
|
@ -181,31 +181,35 @@ class EmployeeCreateUpdateSerializer(EmployeeBaseSerializer):
|
|||
instance.save()
|
||||
return instance
|
||||
|
||||
|
||||
class ChannelAuthoritySerializer(serializers.Serializer):
|
||||
pks = serializers.ListField(child=serializers.CharField(max_length=20), label="员工ID列表")
|
||||
channels = serializers.ListField(child=serializers.CharField(max_length=20), label="门通道ID列表")
|
||||
|
||||
|
||||
class EmployeeSerializer(EmployeeBaseSerializer):
|
||||
belong_dept_ = DeptSimpleSerializer(source='belong_dept', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Employee
|
||||
exclude = ['face_data']
|
||||
exclude = ['face_data']
|
||||
read_only_fields = ['is_atwork', 'last_check_time', 'not_work_remark']
|
||||
|
||||
|
||||
|
||||
|
||||
class EmployeeNotWorkRemarkSerializer(ModelSerializer):
|
||||
class Meta:
|
||||
model = Employee
|
||||
fields = ['not_work_remark']
|
||||
|
||||
|
||||
class ClockRecordListSerializer(serializers.ModelSerializer):
|
||||
create_by_ = UserSimpleSerializer(source='create_by', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ClockRecord
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class NotWorkRemarkListSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = NotWorkRemark
|
||||
|
|
|
@ -2,7 +2,7 @@ from django.db.models.signals import post_save
|
|||
from apps.system.models import User
|
||||
from django.dispatch import receiver
|
||||
from apps.hrm.models import Employee
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
def updateEmployee(sender, instance, created, **kwargs):
|
||||
|
@ -13,4 +13,3 @@ def updateEmployee(sender, instance, created, **kwargs):
|
|||
if ep.belong_dept and ep.belong_dept != instance.belong_dept:
|
||||
ep.belong_dept = instance.belong_dept
|
||||
ep.save()
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
from rest_framework import urlpatterns
|
||||
from apps.hrm.views import ClockRecordViewSet, EmployeeViewSet, NotWorkRemarkViewSet
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
@ -14,4 +12,3 @@ router.register('not_work_remark', NotWorkRemarkViewSet, basename='not_work_ream
|
|||
urlpatterns = [
|
||||
path(API_BASE_URL, include(router.urls)),
|
||||
]
|
||||
|
||||
|
|
|
@ -1,18 +1,13 @@
|
|||
from datetime import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
from django.shortcuts import render
|
||||
from django.utils import timezone
|
||||
from rest_framework import exceptions, serializers, status
|
||||
from rest_framework import serializers
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import ParseError
|
||||
from rest_framework.generics import CreateAPIView
|
||||
from rest_framework.mixins import (CreateModelMixin, ListModelMixin,
|
||||
RetrieveModelMixin, UpdateModelMixin)
|
||||
from rest_framework.mixins import ListModelMixin
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.response import Response
|
||||
from rest_framework_simplejwt.tokens import RefreshToken
|
||||
|
||||
from apps.hrm.errors import NO_NEED_LEVEL_REMARK
|
||||
from apps.hrm.filters import (ClockRecordFilterSet, EmployeeFilterSet,
|
||||
|
@ -24,11 +19,9 @@ from apps.hrm.serializers import (ChannelAuthoritySerializer,
|
|||
EmployeeNotWorkRemarkSerializer,
|
||||
EmployeeSerializer,
|
||||
NotWorkRemarkListSerializer)
|
||||
from apps.system.models import User
|
||||
from apps.system.serializers import UserSimpleSerializer
|
||||
|
||||
from apps.third.clients import dhClient
|
||||
from apps.third.tapis import dhapis
|
||||
from apps.utils.serializers import PkSerializer
|
||||
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
|
||||
|
||||
|
||||
|
@ -47,8 +40,8 @@ class EmployeeViewSet(CustomModelViewSet):
|
|||
search_fields = ['name', 'number', 'user__username']
|
||||
ordering = ['-pk']
|
||||
|
||||
@action(methods=['post'], detail=True, perms_map={'post': 'employee:notworkremark'}
|
||||
, serializer_class=EmployeeNotWorkRemarkSerializer)
|
||||
@action(methods=['post'], detail=True, perms_map={'post': 'employee:notworkremark'},
|
||||
serializer_class=EmployeeNotWorkRemarkSerializer)
|
||||
def not_work_remark(self, request, pk=None):
|
||||
"""
|
||||
填写离岗说明
|
||||
|
@ -62,13 +55,13 @@ class EmployeeViewSet(CustomModelViewSet):
|
|||
obj.save()
|
||||
now = timezone.now()
|
||||
instance, created = NotWorkRemark.objects.get_or_create(
|
||||
not_work_date = now.date(),
|
||||
user = obj.user,
|
||||
not_work_date=now.date(),
|
||||
user=obj.user,
|
||||
defaults={
|
||||
"not_work_date":now.date(),
|
||||
"user":obj.user,
|
||||
"remark":remark,
|
||||
"create_by":request.user,
|
||||
"not_work_date": now.date(),
|
||||
"user": obj.user,
|
||||
"remark": remark,
|
||||
"create_by": request.user,
|
||||
}
|
||||
)
|
||||
if not created:
|
||||
|
@ -79,8 +72,8 @@ class EmployeeViewSet(CustomModelViewSet):
|
|||
raise ParseError(**NO_NEED_LEVEL_REMARK)
|
||||
|
||||
@transaction.atomic
|
||||
@action(methods=['post'], detail=False, perms_map={'post': 'employee:channel_authority'}
|
||||
, serializer_class=ChannelAuthoritySerializer)
|
||||
@action(methods=['post'], detail=False, perms_map={'post': 'employee:channel_authority'},
|
||||
serializer_class=ChannelAuthoritySerializer)
|
||||
def channel_authority(self, request, pk=None):
|
||||
"""门通道授权
|
||||
|
||||
|
@ -110,12 +103,12 @@ class EmployeeViewSet(CustomModelViewSet):
|
|||
dhClient.request(**dhapis['card_door_authority'], json=json_data)
|
||||
for i in objs:
|
||||
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()
|
||||
|
||||
@transaction.atomic
|
||||
@action(methods=['post'], detail=False, perms_map={'post': 'employee:face_bind_1'}
|
||||
, serializer_class=serializers.Serializer)
|
||||
@action(methods=['post'], detail=False, perms_map={'post': 'employee:face_bind_1'},
|
||||
serializer_class=serializers.Serializer)
|
||||
def face_bind_1(self, request, pk=None):
|
||||
"""同步人脸库1
|
||||
|
||||
|
@ -123,10 +116,10 @@ class EmployeeViewSet(CustomModelViewSet):
|
|||
"""
|
||||
# 获取设备
|
||||
json_data = {
|
||||
"pageNum":1,
|
||||
"pageSize":1000,
|
||||
"ownerCodes":['001'],
|
||||
"showChildNodeData":1,
|
||||
"pageNum": 1,
|
||||
"pageSize": 1000,
|
||||
"ownerCodes": ['001'],
|
||||
"showChildNodeData": 1,
|
||||
# "isOnline":1
|
||||
}
|
||||
_, res = dhClient.request(**dhapis['dev_page'], json=json_data)
|
||||
|
@ -136,41 +129,40 @@ class EmployeeViewSet(CustomModelViewSet):
|
|||
devs.append(i['deviceCode'])
|
||||
# 编辑人像库
|
||||
json_data = {
|
||||
"groupid":settings.DAHUA_FACEGROUPID_1,
|
||||
"groupname":"全体人员",
|
||||
"groupdetail":"全体人员",
|
||||
"grouptype":3,
|
||||
"deviceCodeList":devs,
|
||||
"syncState":0
|
||||
"groupid": settings.DAHUA_FACEGROUPID_1,
|
||||
"groupname": "全体人员",
|
||||
"groupdetail": "全体人员",
|
||||
"grouptype": 3,
|
||||
"deviceCodeList": devs,
|
||||
"syncState": 0
|
||||
}
|
||||
dhClient.request(**dhapis['face_group_update'], json=json_data)
|
||||
# 人像绑定
|
||||
json_data = {
|
||||
"deptId":1,
|
||||
"groupIdList":[settings.DAHUA_FACEGROUPID_1],
|
||||
"cascade":True
|
||||
"deptId": 1,
|
||||
"groupIdList": [settings.DAHUA_FACEGROUPID_1],
|
||||
"cascade": True
|
||||
}
|
||||
dhClient.request(**dhapis['face_bind'], json=json_data)
|
||||
return Response()
|
||||
|
||||
@action(methods=['get'], detail=False, perms_map={'get': 'employee:face_bind_1'}
|
||||
, serializer_class=serializers.Serializer)
|
||||
@action(methods=['get'], detail=False, perms_map={'get': 'employee:face_bind_1'},
|
||||
serializer_class=serializers.Serializer)
|
||||
def face_status_1(self, request, pk=None):
|
||||
"""人像下发状态
|
||||
|
||||
人像下发状态
|
||||
"""
|
||||
params = {'id':settings.DAHUA_FACEGROUPID_1}
|
||||
params = {'id': settings.DAHUA_FACEGROUPID_1}
|
||||
_, res = dhClient.request(**dhapis['face_group_info'], params=params)
|
||||
return Response(res)
|
||||
|
||||
|
||||
|
||||
class ClockRecordViewSet(ListModelMixin, CustomGenericViewSet):
|
||||
"""
|
||||
打卡记录
|
||||
"""
|
||||
perms_map = {'get':'*', 'post':'*'}
|
||||
perms_map = {'get': '*', 'post': '*'}
|
||||
authentication_classes = []
|
||||
permission_classes = [AllowAny]
|
||||
queryset = ClockRecord.objects.all()
|
||||
|
@ -184,7 +176,7 @@ class NotWorkRemarkViewSet(ListModelMixin, CustomGenericViewSet):
|
|||
"""
|
||||
离岗说明
|
||||
"""
|
||||
perms_map = {'get':'*'}
|
||||
perms_map = {'get': '*'}
|
||||
queryset = NotWorkRemark.objects.all()
|
||||
select_related_fields = ['user']
|
||||
serializer_class = NotWorkRemarkListSerializer
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import imp
|
||||
import json
|
||||
from channels.generic.websocket import AsyncWebsocketConsumer
|
||||
|
||||
|
||||
class MonitorConsumer(AsyncWebsocketConsumer):
|
||||
async def connect(self):
|
||||
self.room_name = self.scope['url_route']['kwargs']['room_name']
|
||||
|
|
|
@ -1 +1 @@
|
|||
LOG_NOT_FONED = {"code":"log_not_found", "detail":"日志不存在"}
|
||||
LOG_NOT_FONED = {"code": "log_not_found", "detail": "日志不存在"}
|
||||
|
|
|
@ -3,6 +3,7 @@ from django.db import models
|
|||
|
||||
from apps.utils.models import BaseModel
|
||||
|
||||
|
||||
class DrfRequestLog(BaseModel):
|
||||
"""Logs Django rest framework API requests"""
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from django.urls import path, include
|
||||
from rest_framework import routers
|
||||
from django.urls import path
|
||||
from .views import DrfRequestLogViewSet, ServerInfoView, LogView, LogDetailView, index, room, video
|
||||
|
||||
API_BASE_URL = 'api/monitor/'
|
||||
|
@ -13,5 +12,5 @@ urlpatterns = [
|
|||
path(API_BASE_URL + 'log/', LogView.as_view()),
|
||||
path(API_BASE_URL + 'log/<str:name>/', LogDetailView.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')
|
||||
]
|
||||
|
|
|
@ -4,10 +4,9 @@ import psutil
|
|||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.viewsets import ViewSet
|
||||
from django.conf import settings
|
||||
import os
|
||||
from rest_framework import serializers, status
|
||||
from rest_framework import serializers
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
from rest_framework.exceptions import NotFound
|
||||
|
@ -28,12 +27,14 @@ def room(request, room_name):
|
|||
'room_name': room_name
|
||||
})
|
||||
|
||||
|
||||
def video(request):
|
||||
return render(request, 'monitor/video.html')
|
||||
|
||||
|
||||
class ServerInfoView(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""
|
||||
获取服务器当前状态
|
||||
|
@ -124,19 +125,19 @@ class LogDetailView(APIView):
|
|||
except:
|
||||
raise NotFound(**LOG_NOT_FONED)
|
||||
|
||||
|
||||
class DrfRequestLogSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = DrfRequestLog
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class DrfRequestLogViewSet(ListModelMixin, CustomGenericViewSet):
|
||||
"""请求日志
|
||||
|
||||
请求日志
|
||||
"""
|
||||
perms_map = {'get':'requestlog.view'}
|
||||
perms_map = {'get': 'requestlog.view'}
|
||||
queryset = DrfRequestLog.objects.all()
|
||||
list_serializer_class = DrfRequestLogSerializer
|
||||
ordering = ['-requested_at']
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
SCHEDULE_WRONG = {"code":"schedule_wrong", "detail":"时间策略有误"}
|
||||
PASSWORD_NOT_SAME = {"code":"password_not_same", "detail":"新旧密码不一致"}
|
||||
OLD_PASSWORD_WRONG = {"code":"old_password_wrong", "detail":"旧密码错误"}
|
||||
SCHEDULE_WRONG = {"code": "schedule_wrong", "detail": "时间策略有误"}
|
||||
PASSWORD_NOT_SAME = {"code": "password_not_same", "detail": "新旧密码不一致"}
|
||||
OLD_PASSWORD_WRONG = {"code": "old_password_wrong", "detail": "旧密码错误"}
|
||||
|
||||
USERNAME_EXIST = {"code":"username_exist", "detail":"账户已存在"}
|
||||
ROLE_NAME_EXIST = {"code":"role_name_exist", "detail":"角色名已存在"}
|
||||
ROLE_CODE_EXIST = {"code":"role_code_exist", "detail":"角色标识已存在"}
|
||||
USERNAME_EXIST = {"code": "username_exist", "detail": "账户已存在"}
|
||||
ROLE_NAME_EXIST = {"code": "role_name_exist", "detail": "角色名已存在"}
|
||||
ROLE_CODE_EXIST = {"code": "role_code_exist", "detail": "角色标识已存在"}
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
import json
|
||||
import re
|
||||
|
||||
from django_celery_beat.models import PeriodicTask, CrontabSchedule, IntervalSchedule
|
||||
from rest_framework import serializers
|
||||
from django_celery_results.models import TaskResult
|
||||
from apps.system.errors import ROLE_CODE_EXIST, ROLE_NAME_EXIST, USERNAME_EXIST
|
||||
from apps.system.errors import USERNAME_EXIST
|
||||
from apps.utils.serializers import CustomModelSerializer
|
||||
from apps.utils.constants import EXCLUDE_FIELDS, EXCLUDE_FIELDS_BASE
|
||||
from .models import (Dictionary, DictType, File, Dept, Permission, Post,
|
||||
Role, User, UserPost)
|
||||
from rest_framework.exceptions import ParseError, APIException
|
||||
from rest_framework.exceptions import ParseError
|
||||
from django.db import transaction
|
||||
from apps.third.tapis import dhapis
|
||||
from rest_framework.validators import UniqueValidator
|
||||
from django.contrib.auth.hashers import make_password
|
||||
|
||||
|
||||
class IntervalSerializer(CustomModelSerializer):
|
||||
class Meta:
|
||||
|
@ -72,6 +71,7 @@ class DictTypeSerializer(CustomModelSerializer):
|
|||
model = DictType
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class DictTypeCreateUpdateSerializer(CustomModelSerializer):
|
||||
class Meta:
|
||||
model = DictType
|
||||
|
@ -87,6 +87,7 @@ class DictSerializer(CustomModelSerializer):
|
|||
model = Dictionary
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class DictCreateUpdateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
数据字典序列化
|
||||
|
@ -96,6 +97,7 @@ class DictCreateUpdateSerializer(CustomModelSerializer):
|
|||
model = Dictionary
|
||||
exclude = EXCLUDE_FIELDS
|
||||
|
||||
|
||||
class PostSerializer(CustomModelSerializer):
|
||||
"""
|
||||
岗位序列化
|
||||
|
@ -105,6 +107,7 @@ class PostSerializer(CustomModelSerializer):
|
|||
model = Post
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class PostCreateUpdateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
岗位序列化
|
||||
|
@ -145,10 +148,12 @@ class RoleCreateUpdateSerializer(CustomModelSerializer):
|
|||
UniqueValidator(queryset=Role.objects.all(), message='已存在相同名称的角色')])
|
||||
code = serializers.CharField(label="标识", validators=[
|
||||
UniqueValidator(queryset=Role.objects.all(), message='已存在相同标识的角色')])
|
||||
|
||||
class Meta:
|
||||
model = Role
|
||||
exclude = EXCLUDE_FIELDS
|
||||
|
||||
|
||||
class PermissionSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
权限序列化
|
||||
|
@ -158,6 +163,7 @@ class PermissionSerializer(serializers.ModelSerializer):
|
|||
model = Permission
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class PermissionCreateUpdateSerializer(serializers.ModelSerializer):
|
||||
"""
|
||||
权限序列化
|
||||
|
@ -167,6 +173,7 @@ class PermissionCreateUpdateSerializer(serializers.ModelSerializer):
|
|||
model = Permission
|
||||
exclude = EXCLUDE_FIELDS_BASE
|
||||
|
||||
|
||||
class DeptSimpleSerializer(CustomModelSerializer):
|
||||
class Meta:
|
||||
model = Dept
|
||||
|
@ -184,11 +191,13 @@ class DeptSerializer(CustomModelSerializer):
|
|||
model = Dept
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class DeptCreateUpdateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
部门序列化
|
||||
"""
|
||||
parent = serializers.PrimaryKeyRelatedField(queryset = Dept.objects.all(), required=True)
|
||||
parent = serializers.PrimaryKeyRelatedField(queryset=Dept.objects.all(), required=True)
|
||||
|
||||
class Meta:
|
||||
model = Dept
|
||||
exclude = EXCLUDE_FIELDS + ['third_info']
|
||||
|
@ -198,16 +207,15 @@ class DeptCreateUpdateSerializer(CustomModelSerializer):
|
|||
from apps.third.clients import dhClient
|
||||
if dhClient:
|
||||
data = {
|
||||
"parentId":1,
|
||||
"name":validated_data['name'],
|
||||
"service":"ehs"
|
||||
"parentId": 1,
|
||||
"name": validated_data['name'],
|
||||
"service": "ehs"
|
||||
}
|
||||
_, res = dhClient.request(**dhapis['dept_create'],json=data)
|
||||
third_info = {'dh_id':str(res['id'])}
|
||||
_, res = dhClient.request(**dhapis['dept_create'], json=data)
|
||||
third_info = {'dh_id': str(res['id'])}
|
||||
validated_data['third_info'] = third_info
|
||||
return super().create(validated_data)
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def update(self, instance, validated_data):
|
||||
from apps.third.clients import dhClient
|
||||
|
@ -215,24 +223,25 @@ class DeptCreateUpdateSerializer(CustomModelSerializer):
|
|||
if dhClient and not third_info.get('dh_id', False):
|
||||
# 如果dh_id 不存在
|
||||
data = {
|
||||
"parentId":1,
|
||||
"name":validated_data['name'],
|
||||
"service":"ehs"
|
||||
"parentId": 1,
|
||||
"name": validated_data['name'],
|
||||
"service": "ehs"
|
||||
}
|
||||
_, res = dhClient.request(**dhapis['dept_create'],json=data)
|
||||
_, res = dhClient.request(**dhapis['dept_create'], json=data)
|
||||
third_info['dh_id'] = res['id']
|
||||
instance.third_info = third_info
|
||||
instance.save()
|
||||
elif instance.name != validated_data.get('name', ''):
|
||||
if dhClient and instance.third_info.get('dh_id', False):
|
||||
data = {
|
||||
"id":instance.third_info['dh_id'],
|
||||
"parentId":1,
|
||||
"name":validated_data['name']
|
||||
"id": instance.third_info['dh_id'],
|
||||
"parentId": 1,
|
||||
"name": validated_data['name']
|
||||
}
|
||||
dhClient.request(**dhapis['dept_update'],json=data)
|
||||
dhClient.request(**dhapis['dept_update'], json=data)
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
|
||||
class UserSimpleSerializer(CustomModelSerializer):
|
||||
class Meta:
|
||||
model = User
|
||||
|
@ -260,11 +269,13 @@ class UserListSerializer(CustomModelSerializer):
|
|||
# if User.objects.filter(phone=phone).exists():
|
||||
# raise serializers.ValidationError(**PHONE_EXIST)
|
||||
|
||||
|
||||
def user_exist(username):
|
||||
if User.objects.filter(username=username).exists():
|
||||
raise serializers.ValidationError(**USERNAME_EXIST)
|
||||
return username
|
||||
|
||||
|
||||
class UserUpdateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
用户编辑序列化
|
||||
|
@ -275,13 +286,11 @@ class UserUpdateSerializer(CustomModelSerializer):
|
|||
|
||||
def update(self, instance, validated_data):
|
||||
if User.objects.filter(username=validated_data['username']
|
||||
).exclude(id=instance.id).exists():
|
||||
).exclude(id=instance.id).exists():
|
||||
raise ParseError(**USERNAME_EXIST)
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
|
||||
|
||||
|
||||
class UserCreateSerializer(CustomModelSerializer):
|
||||
"""
|
||||
创建用户序列化
|
||||
|
@ -293,12 +302,12 @@ class UserCreateSerializer(CustomModelSerializer):
|
|||
fields = ['username', 'name', 'avatar', 'is_active']
|
||||
|
||||
|
||||
|
||||
class PasswordChangeSerializer(serializers.Serializer):
|
||||
old_password = serializers.CharField(label="原密码")
|
||||
new_password1 = serializers.CharField(label="新密码1")
|
||||
new_password2 = serializers.CharField(label="新密码2")
|
||||
|
||||
|
||||
class PTaskResultSerializer(CustomModelSerializer):
|
||||
class Meta:
|
||||
model = TaskResult
|
||||
|
@ -317,6 +326,7 @@ class UserPostSerializer(CustomModelSerializer):
|
|||
model = UserPost
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class UserPostCreateSerializer(CustomModelSerializer):
|
||||
class Meta:
|
||||
model = UserPost
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
from django.urls import path, include
|
||||
from .views import FileViewSet, PTaskResultViewSet, TaskList, UserPostViewSet, UserViewSet, DeptViewSet, PermissionViewSet, RoleViewSet, PostViewSet, DictTypeViewSet, DictViewSet, PTaskViewSet
|
||||
from .views import FileViewSet, PTaskResultViewSet, TaskList, \
|
||||
UserPostViewSet, UserViewSet, DeptViewSet, \
|
||||
PermissionViewSet, RoleViewSet, PostViewSet, \
|
||||
DictTypeViewSet, DictViewSet, PTaskViewSet
|
||||
from rest_framework import routers
|
||||
|
||||
API_BASE_URL = 'api/system/'
|
||||
|
|
|
@ -17,14 +17,15 @@ from apps.system.errors import OLD_PASSWORD_WRONG, PASSWORD_NOT_SAME, SCHEDULE_W
|
|||
from apps.utils.mixins import (CustomCreateModelMixin)
|
||||
from django.conf import settings
|
||||
from apps.utils.permission import ALL_PERMS, get_user_perms_map
|
||||
from apps.utils.queryset import get_child_queryset2
|
||||
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
|
||||
from server.celery import app as celery_app
|
||||
from .filters import UserFilter
|
||||
from .models import (Dept, Dictionary, DictType, File, Permission, Post, Role, User,
|
||||
UserPost)
|
||||
from .serializers import (DeptCreateUpdateSerializer, DeptSerializer, DictCreateUpdateSerializer, DictSerializer, DictTypeCreateUpdateSerializer, DictTypeSerializer,
|
||||
FileSerializer, PasswordChangeSerializer, PermissionCreateUpdateSerializer, PermissionSerializer, PostCreateUpdateSerializer, PostSerializer,
|
||||
from .serializers import (DeptCreateUpdateSerializer, DeptSerializer, DictCreateUpdateSerializer,
|
||||
DictSerializer, DictTypeCreateUpdateSerializer, DictTypeSerializer,
|
||||
FileSerializer, PasswordChangeSerializer, PermissionCreateUpdateSerializer,
|
||||
PermissionSerializer, PostCreateUpdateSerializer, PostSerializer,
|
||||
PTaskCreateUpdateSerializer, PTaskResultSerializer,
|
||||
PTaskSerializer, RoleCreateUpdateSerializer, RoleSerializer,
|
||||
UserCreateSerializer, UserListSerializer, UserPostCreateSerializer,
|
||||
|
@ -280,7 +281,7 @@ class UserPostViewSet(CreateModelMixin, DestroyModelMixin, ListModelMixin, Custo
|
|||
|
||||
def perform_destroy(self, instance):
|
||||
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()
|
||||
if fdept:
|
||||
user.belong_dept = fdept
|
||||
|
@ -312,8 +313,8 @@ class UserViewSet(CustomModelViewSet):
|
|||
return Response(data=serializer.data)
|
||||
|
||||
@action(methods=['put'], detail=False,
|
||||
permission_classes=[IsAuthenticated],
|
||||
serializer_class = PasswordChangeSerializer)
|
||||
permission_classes=[IsAuthenticated],
|
||||
serializer_class=PasswordChangeSerializer)
|
||||
def password(self, request, pk=None):
|
||||
"""修改密码
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
TAPI_CODE_WRONG = {"code":"tapi_code_wrong", "detail":"三方接口请求标识有误"}
|
||||
TAPI_CODE_WRONG = {"code": "tapi_code_wrong", "detail": "三方接口请求标识有误"}
|
||||
|
|
|
@ -3,10 +3,10 @@ from rest_framework import serializers
|
|||
|
||||
class RequestCommonSerializer(serializers.Serializer):
|
||||
method_choice = (
|
||||
('post','post'),
|
||||
('get','get'),
|
||||
('put','put'),
|
||||
('delete','delete')
|
||||
('post', 'post'),
|
||||
('get', 'get'),
|
||||
('put', 'put'),
|
||||
('delete', 'delete')
|
||||
)
|
||||
url = serializers.CharField(label='请求地址', required=False)
|
||||
method = serializers.ChoiceField(label='请求方法', choices=method_choice, required=False)
|
||||
|
|
|
@ -1,88 +1,88 @@
|
|||
# 大华API接口
|
||||
dhapis = {
|
||||
"dept_tree":{
|
||||
"url":"/evo-apigw/evo-brm/1.2.0/department/tree",
|
||||
"method":"post"
|
||||
"dept_tree": {
|
||||
"url": "/evo-apigw/evo-brm/1.2.0/department/tree",
|
||||
"method": "post"
|
||||
},
|
||||
"dept_create":{
|
||||
"url":"/evo-apigw/evo-brm/1.0.0/department/add",
|
||||
"method":"post"
|
||||
"dept_create": {
|
||||
"url": "/evo-apigw/evo-brm/1.0.0/department/add",
|
||||
"method": "post"
|
||||
},
|
||||
"dept_update":{
|
||||
"url":"/evo-apigw/evo-brm/1.0.0/department/update",
|
||||
"method":"put"
|
||||
"dept_update": {
|
||||
"url": "/evo-apigw/evo-brm/1.0.0/department/update",
|
||||
"method": "put"
|
||||
},
|
||||
"person_gen_id":{
|
||||
"url":"/evo-apigw/evo-brm/1.0.0/person/generate-id",
|
||||
"method":"get"
|
||||
"person_gen_id": {
|
||||
"url": "/evo-apigw/evo-brm/1.0.0/person/generate-id",
|
||||
"method": "get"
|
||||
},
|
||||
"person_add": {
|
||||
"url":"/evo-apigw/evo-brm/1.2.0/person/subsystem/add",
|
||||
"method":"post"
|
||||
"url": "/evo-apigw/evo-brm/1.2.0/person/subsystem/add",
|
||||
"method": "post"
|
||||
},
|
||||
"person_update": {
|
||||
"url":"/evo-apigw/evo-brm/1.2.0/person/subsystem/update",
|
||||
"method":"put"
|
||||
"url": "/evo-apigw/evo-brm/1.2.0/person/subsystem/update",
|
||||
"method": "put"
|
||||
},
|
||||
"person_img_upload": {
|
||||
"url":"/evo-apigw/evo-brm/1.2.0/person/upload/img",
|
||||
"method":"post"
|
||||
"url": "/evo-apigw/evo-brm/1.2.0/person/upload/img",
|
||||
"method": "post"
|
||||
},
|
||||
"person_detail": {
|
||||
"url":"/evo-apigw/evo-brm/1.0.0/person/subsystem/{id}",
|
||||
"method":"get"
|
||||
"url": "/evo-apigw/evo-brm/1.0.0/person/subsystem/{id}",
|
||||
"method": "get"
|
||||
},
|
||||
"device_list": {
|
||||
"url":"/evo-apigw/evo-brm/1.2.0/device/subsystem/page",
|
||||
"method":"get"
|
||||
"url": "/evo-apigw/evo-brm/1.2.0/device/subsystem/page",
|
||||
"method": "get"
|
||||
},
|
||||
"device_detail": {
|
||||
"url":"/evo-apigw/evo-brm/1.0.0/device/{deviceCode}",
|
||||
"method":"get"
|
||||
"url": "/evo-apigw/evo-brm/1.0.0/device/{deviceCode}",
|
||||
"method": "get"
|
||||
},
|
||||
"channel_list": {
|
||||
"url":"/evo-apigw/evo-brm/1.2.0/device/channel/subsystem/page",
|
||||
"method":"post"
|
||||
"url": "/evo-apigw/evo-brm/1.2.0/device/channel/subsystem/page",
|
||||
"method": "post"
|
||||
},
|
||||
"card_add": {
|
||||
"url":"/evo-apigw/evo-brm/1.2.0/card/add",
|
||||
"method":"post"
|
||||
"url": "/evo-apigw/evo-brm/1.2.0/card/add",
|
||||
"method": "post"
|
||||
},
|
||||
"card_gen_id": {
|
||||
"url":"/evo-apigw/evo-brm/1.0.0/card/generate-id",
|
||||
"method":"get"
|
||||
"url": "/evo-apigw/evo-brm/1.0.0/card/generate-id",
|
||||
"method": "get"
|
||||
},
|
||||
"card_door_authority": {
|
||||
"url":"/evo-apigw/evo-accesscontrol/1.0.0/card/accessControl/doorAuthority",
|
||||
"method":"post"
|
||||
"url": "/evo-apigw/evo-accesscontrol/1.0.0/card/accessControl/doorAuthority",
|
||||
"method": "post"
|
||||
},
|
||||
"mq_subscribe": {
|
||||
"url":"/evo-apigw/evo-event/1.0.0/subscribe/mqinfo",
|
||||
"method":"post"
|
||||
"url": "/evo-apigw/evo-event/1.0.0/subscribe/mqinfo",
|
||||
"method": "post"
|
||||
},
|
||||
"mq_unsubscribe": {
|
||||
"url":"/evo-apigw/evo-event/1.0.0/subscribe/mqinfo",
|
||||
"method":"delete"
|
||||
"url": "/evo-apigw/evo-event/1.0.0/subscribe/mqinfo",
|
||||
"method": "delete"
|
||||
},
|
||||
"face_bind":{
|
||||
"url":"/evo-apigw/evo-face/deptGroup/sync/bindPerson",
|
||||
"method":"post"
|
||||
"face_bind": {
|
||||
"url": "/evo-apigw/evo-face/deptGroup/sync/bindPerson",
|
||||
"method": "post"
|
||||
},
|
||||
"dev_tree":{
|
||||
"url":"/evo-apigw/evo-face/tree/getDevChnIdsAndName",
|
||||
"method":"get"
|
||||
"dev_tree": {
|
||||
"url": "/evo-apigw/evo-face/tree/getDevChnIdsAndName",
|
||||
"method": "get"
|
||||
},
|
||||
"dev_page":{
|
||||
"url":"/evo-apigw/evo-brm/1.2.0/device/subsystem/page",
|
||||
"method":"post"
|
||||
"dev_page": {
|
||||
"url": "/evo-apigw/evo-brm/1.2.0/device/subsystem/page",
|
||||
"method": "post"
|
||||
},
|
||||
"face_group_update":{
|
||||
"url":"/evo-apigw/evo-face/groupInfo/update",
|
||||
"method":"post"
|
||||
"face_group_update": {
|
||||
"url": "/evo-apigw/evo-face/groupInfo/update",
|
||||
"method": "post"
|
||||
},
|
||||
"face_group_info":{
|
||||
"url":"/evo-apigw/evo-face/groupInfo/view/{id}",
|
||||
"method":"get"
|
||||
"face_group_info": {
|
||||
"url": "/evo-apigw/evo-face/groupInfo/view/{id}",
|
||||
"method": "get"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ from rest_framework.exceptions import ParseError, APIException
|
|||
from apps.third.tapis import dhapis, xxapis
|
||||
from apps.third.erros import TAPI_CODE_WRONG
|
||||
from apps.third.clients import dhClient
|
||||
from apps.utils.errors import XX_REQUEST_ERROR
|
||||
from apps.utils.mixins import MyLoggingMixin
|
||||
from apps.third.clients import xxClient
|
||||
from rest_framework.response import Response
|
||||
|
@ -51,6 +50,8 @@ class XxTestView(APIView):
|
|||
raise APIException(**res)
|
||||
|
||||
# 寻息事件订阅
|
||||
|
||||
|
||||
class XxListener(stomp.ConnectionListener):
|
||||
def on_error(self, frame):
|
||||
print('received an error "%s"' % frame.body)
|
||||
|
@ -65,6 +66,7 @@ if settings.XX_ENABLED:
|
|||
c.connect(settings.XX_USERNAME, settings.XX_LICENCE)
|
||||
c.subscribe(settings.XX_QUEUE, id='')
|
||||
|
||||
|
||||
class XxCommonViewSet(CreateModelMixin, CustomGenericViewSet):
|
||||
"""
|
||||
寻息通用调用接口
|
||||
|
@ -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()
|
||||
|
||||
@action(methods=['post'], detail=False,
|
||||
|
@ -189,7 +191,7 @@ class DhCommonViewSet(CreateModelMixin, CustomGenericViewSet):
|
|||
data = request.data
|
||||
method = data['method']
|
||||
subsystem = data.get('subsystem', None)
|
||||
info = data.get('info', {})
|
||||
# info = data.get('info', {})
|
||||
if method == 'department.update':
|
||||
pass
|
||||
elif method == 'person.update':
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
import time
|
||||
from threading import Thread
|
||||
import traceback
|
||||
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from rest_framework.exceptions import APIException, ParseError
|
||||
|
||||
from apps.utils.errors import DH_REQUEST_ERROR
|
||||
from apps.utils.tools import print_roundtrip
|
||||
from django.conf import settings
|
||||
import json
|
||||
import time
|
||||
from rest_framework.exceptions import APIException, ParseError
|
||||
import os
|
||||
requests.packages.urllib3.disable_warnings()
|
||||
|
||||
|
||||
class DhClient:
|
||||
"""
|
||||
大华
|
||||
"""
|
||||
|
||||
def __init__(self, client_id= settings.DAHUA_CLIENTID
|
||||
, client_secret = settings.DAHUA_SECRET) -> None:
|
||||
def __init__(self, client_id=settings.DAHUA_CLIENTID,
|
||||
client_secret=settings.DAHUA_SECRET) -> None:
|
||||
if not settings.DAHUA_ENABLED:
|
||||
return None
|
||||
self.client_id = client_id
|
||||
|
@ -34,7 +34,8 @@ class DhClient:
|
|||
'client_id': self.client_id,
|
||||
'client_secret': self.client_secret
|
||||
}
|
||||
r = requests.post(params=params, url=settings.DAHUA_BASE_URL + '/evo-apigw/evo-oauth/oauth/token', verify=False)
|
||||
r = requests.post(params=params,
|
||||
url=settings.DAHUA_BASE_URL + '/evo-apigw/evo-oauth/oauth/token', verify=False)
|
||||
ret = r.json()
|
||||
if ret['success']:
|
||||
self.headers['Authorization'] = 'bearer ' + ret['data']['access_token']
|
||||
|
@ -66,7 +67,8 @@ class DhClient:
|
|||
self.isRuning = False
|
||||
# self.t.join()
|
||||
|
||||
def request(self, url:str, method:str, params=dict(), json=dict(), timeout=10, file_path_rela=None, raise_exception=True):
|
||||
def request(self, url: str, method: str, params=dict(), json=dict(), timeout=10,
|
||||
file_path_rela=None, raise_exception=True):
|
||||
if self.isGetingToken:
|
||||
req_num = 0
|
||||
while True:
|
||||
|
@ -78,13 +80,14 @@ class DhClient:
|
|||
break
|
||||
else:
|
||||
files = None
|
||||
if file_path_rela: # 相对路径
|
||||
if file_path_rela: # 相对路径
|
||||
files = {'file': open(settings.BASE_DIR + file_path_rela, 'rb')}
|
||||
try:
|
||||
if params:
|
||||
url = url.format(**params)
|
||||
r = getattr(requests, method)('{}{}'.format(settings.DAHUA_BASE_URL, url)
|
||||
, headers = self.headers, params=params, json=json, timeout=timeout, files=files, verify=False)
|
||||
r = getattr(requests, method)('{}{}'.format(settings.DAHUA_BASE_URL, url),
|
||||
headers=self.headers, params=params, json=json,
|
||||
timeout=timeout, files=files, verify=False)
|
||||
except:
|
||||
if raise_exception:
|
||||
raise APIException(**DH_REQUEST_ERROR)
|
||||
|
@ -92,16 +95,15 @@ class DhClient:
|
|||
# if settings.DEBUG:
|
||||
# print_roundtrip(r)
|
||||
if r.status_code == 200:
|
||||
"""
|
||||
请求成功
|
||||
"""
|
||||
ret = r.json()
|
||||
if ret.get('code') == '27001007':
|
||||
self.get_token() # 重新获取token
|
||||
self.get_token() # 重新获取token
|
||||
self.request(url, method, params, json, timeout, file_path_rela, raise_exception)
|
||||
else:
|
||||
if ret['code'] not in ['0', '100', '00000', '1000', 0, 100, 1000]:
|
||||
detail = '大华错误:' + '{}|{}{}{}'.format(str(ret['code']), ret.get('errMsg',''), ret.get('desc', ''), str(ret.get('data', '')))
|
||||
detail = '大华错误:' + \
|
||||
'{}|{}{}{}'.format(str(ret['code']), ret.get('errMsg', ''),
|
||||
ret.get('desc', ''), str(ret.get('data', '')))
|
||||
err_detail = dict(detail=detail, code='dh_'+str(ret['code']))
|
||||
if raise_exception:
|
||||
raise ParseError(**err_detail)
|
||||
|
@ -111,4 +113,5 @@ class DhClient:
|
|||
raise APIException(**DH_REQUEST_ERROR)
|
||||
return 'error', DH_REQUEST_ERROR
|
||||
|
||||
|
||||
dhClient = DhClient()
|
|
@ -1,4 +1,4 @@
|
|||
XX_REQUEST_ERROR = {"code":"xx_request_error", "detail":"寻息接口访问异常"}
|
||||
DH_REQUEST_ERROR = {"code":"dh_request_error", "detail":"大华接口访问异常"}
|
||||
SIGN_MAKE_FAIL = {"code":"sign_make_fail", "detail":"签名照生成失败,请重新上传"}
|
||||
PKS_ERROR = {"code":"pks_error", "detail":"未获取到主键列表"}
|
||||
XX_REQUEST_ERROR = {"code": "xx_request_error", "detail": "寻息接口访问异常"}
|
||||
DH_REQUEST_ERROR = {"code": "dh_request_error", "detail": "大华接口访问异常"}
|
||||
SIGN_MAKE_FAIL = {"code": "sign_make_fail", "detail": "签名照生成失败,请重新上传"}
|
||||
PKS_ERROR = {"code": "pks_error", "detail": "未获取到主键列表"}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import traceback
|
||||
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import Http404
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import exceptions
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import set_rollback
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
import traceback
|
||||
from server.settings import myLogger
|
||||
|
||||
|
||||
def custom_exception_hander(exc, context):
|
||||
"""
|
||||
自定义异常处理
|
||||
|
@ -25,15 +26,15 @@ def custom_exception_hander(exc, context):
|
|||
headers['Retry-After'] = '%d' % exc.wait
|
||||
|
||||
if isinstance(exc.detail, (list, dict)):
|
||||
data = {'err_detail':exc.detail}
|
||||
data = {'err_detail': exc.detail}
|
||||
data['err_code'] = exc.default_code
|
||||
data['err_msg'] = exc.default_detail
|
||||
else:
|
||||
data = {'err_msg': exc.detail, 'err_code':exc.get_codes()}
|
||||
data = {'err_msg': exc.detail, 'err_code': exc.get_codes()}
|
||||
|
||||
set_rollback()
|
||||
data['request_id'] = request_id
|
||||
return Response(data, status=exc.status_code, headers=headers)
|
||||
err_str = traceback.format_exc()
|
||||
myLogger.error('{}-{}'.format(request_id, err_str))
|
||||
return Response(data={'err_code':'server_error', 'err_msg':'服务器错误', 'request_id': request_id}, status=500)
|
||||
args = (request_id, traceback.format_exc())
|
||||
myLogger.error(f"{args[0]}-{args[1]}")
|
||||
return Response(data={'err_code': 'server_error', 'err_msg': '服务器错误', 'request_id': request_id}, status=500)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import uuid
|
||||
from django.db.models.query import QuerySet
|
||||
from rest_framework.mixins import CreateModelMixin, UpdateModelMixin, ListModelMixin, RetrieveModelMixin, DestroyModelMixin
|
||||
from rest_framework.mixins import CreateModelMixin, UpdateModelMixin, DestroyModelMixin
|
||||
import ast
|
||||
import ipaddress
|
||||
import traceback
|
||||
|
@ -8,58 +7,67 @@ from apps.monitor.models import DrfRequestLog
|
|||
from server.settings import myLogger
|
||||
from django.db import connection
|
||||
from django.utils.timezone import now
|
||||
from apps.utils.snowflake import idWorker
|
||||
from user_agents import parse
|
||||
|
||||
|
||||
class CreateUpdateModelAMixin:
|
||||
"""
|
||||
业务用基本表A用
|
||||
"""
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(create_by = self.request.user)
|
||||
serializer.save(create_by=self.request.user)
|
||||
|
||||
def perform_update(self, serializer):
|
||||
serializer.save(update_by = self.request.user)
|
||||
serializer.save(update_by=self.request.user)
|
||||
|
||||
|
||||
class CreateUpdateModelBMixin:
|
||||
"""
|
||||
业务用基本表B用
|
||||
"""
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(create_by = self.request.user, belong_dept=self.request.user.dept)
|
||||
serializer.save(create_by=self.request.user, belong_dept=self.request.user.dept)
|
||||
|
||||
def perform_update(self, serializer):
|
||||
serializer.save(update_by = self.request.user)
|
||||
serializer.save(update_by=self.request.user)
|
||||
|
||||
|
||||
class CreateUpdateCustomMixin:
|
||||
"""
|
||||
整合
|
||||
"""
|
||||
|
||||
def perform_create(self, serializer):
|
||||
if hasattr(self.queryset.model, 'belong_dept'):
|
||||
serializer.save(create_by = self.request.user, belong_dept=self.request.user.dept)
|
||||
serializer.save(create_by=self.request.user, belong_dept=self.request.user.dept)
|
||||
else:
|
||||
serializer.save(create_by = self.request.user)
|
||||
serializer.save(create_by=self.request.user)
|
||||
|
||||
def perform_update(self, serializer):
|
||||
serializer.save(update_by = self.request.user)
|
||||
serializer.save(update_by=self.request.user)
|
||||
|
||||
|
||||
class CustomCreateModelMixin(CreateModelMixin):
|
||||
|
||||
def perform_create(self, serializer):
|
||||
if hasattr(self.queryset.model, 'belong_dept'):
|
||||
serializer.save(create_by = self.request.user, belong_dept=self.request.user.dept)
|
||||
serializer.save(create_by=self.request.user, belong_dept=self.request.user.dept)
|
||||
else:
|
||||
serializer.save(create_by = self.request.user)
|
||||
serializer.save(create_by=self.request.user)
|
||||
|
||||
|
||||
class CustomUpdateModelMixin(UpdateModelMixin):
|
||||
|
||||
def perform_update(self, serializer):
|
||||
serializer.save(update_by = self.request.user)
|
||||
serializer.save(update_by=self.request.user)
|
||||
|
||||
|
||||
class CustomDestoryModelMixin(DestroyModelMixin):
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
instance.delete(update_by = self.request.user)
|
||||
instance.delete(update_by=self.request.user)
|
||||
|
||||
|
||||
class MyLoggingMixin(object):
|
||||
|
@ -79,7 +87,7 @@ class MyLoggingMixin(object):
|
|||
|
||||
def initial(self, request, *args, **kwargs):
|
||||
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)
|
||||
if not getattr(self, "decode_request_body", False):
|
||||
self.log["data"] = ""
|
||||
|
@ -114,7 +122,8 @@ class MyLoggingMixin(object):
|
|||
)
|
||||
|
||||
if should_log(request, response):
|
||||
if (connection.settings_dict.get("ATOMIC_REQUESTS") and getattr(response, "exception", None) and connection.in_atomic_block):
|
||||
if (connection.settings_dict.get("ATOMIC_REQUESTS") and
|
||||
getattr(response, "exception", None) and connection.in_atomic_block):
|
||||
# response with exception (HTTP status like: 401, 404, etc)
|
||||
# pointwise disable atomic block for handle log (TransactionManagementError)
|
||||
connection.set_rollback(True)
|
||||
|
@ -277,4 +286,3 @@ class MyLoggingMixin(object):
|
|||
if key.lower() in SENSITIVE_FIELDS:
|
||||
data[key] = self.CLEANED_SUBSTITUTE
|
||||
return data
|
||||
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
from copy import copy
|
||||
import django.utils.timezone as timezone
|
||||
from django.db import models
|
||||
from django.db.models.query import QuerySet
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from apps.utils.snowflake import IdWorker, idWorker
|
||||
from typing import Iterable, Optional
|
||||
from apps.utils.snowflake import idWorker
|
||||
|
||||
# 自定义软删除查询基类
|
||||
|
||||
|
@ -101,9 +98,11 @@ class CommonAModel(SoftModel):
|
|||
业务用基本表A,包含create_by, update_by字段
|
||||
"""
|
||||
create_by = models.ForeignKey(
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='创建人', related_name='%(class)s_create_by')
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL,
|
||||
verbose_name='创建人', related_name='%(class)s_create_by')
|
||||
update_by = models.ForeignKey(
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='最后编辑人', related_name='%(class)s_update_by')
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL,
|
||||
verbose_name='最后编辑人', related_name='%(class)s_update_by')
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
@ -114,11 +113,14 @@ class CommonBModel(SoftModel):
|
|||
业务用基本表B,包含create_by, update_by, belong_dept字段
|
||||
"""
|
||||
create_by = models.ForeignKey(
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='创建人', related_name='%(class)s_create_by')
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL,
|
||||
verbose_name='创建人', related_name='%(class)s_create_by')
|
||||
update_by = models.ForeignKey(
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='最后编辑人', related_name='%(class)s_update_by')
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL,
|
||||
verbose_name='最后编辑人', related_name='%(class)s_update_by')
|
||||
belong_dept = models.ForeignKey(
|
||||
'system.dept', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='所属部门', related_name='%(class)s_belong_dept')
|
||||
'system.dept', null=True, blank=True, on_delete=models.SET_NULL,
|
||||
verbose_name='所属部门', related_name='%(class)s_belong_dept')
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
@ -129,9 +131,11 @@ class CommonADModel(BaseModel):
|
|||
业务用基本表A, 物理删除, 包含create_by, update_by字段
|
||||
"""
|
||||
create_by = models.ForeignKey(
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='创建人', related_name='%(class)s_create_by')
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL,
|
||||
verbose_name='创建人', related_name='%(class)s_create_by')
|
||||
update_by = models.ForeignKey(
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='最后编辑人', related_name='%(class)s_update_by')
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL,
|
||||
verbose_name='最后编辑人', related_name='%(class)s_update_by')
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
@ -142,11 +146,14 @@ class CommonBDModel(BaseModel):
|
|||
业务用基本表B, 物理删除, 包含create_by, update_by, belong_dept字段
|
||||
"""
|
||||
create_by = models.ForeignKey(
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='创建人', related_name='%(class)s_create_by')
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL,
|
||||
verbose_name='创建人', related_name='%(class)s_create_by')
|
||||
update_by = models.ForeignKey(
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='最后编辑人', related_name='%(class)s_update_by')
|
||||
'system.user', null=True, blank=True, on_delete=models.SET_NULL,
|
||||
verbose_name='最后编辑人', related_name='%(class)s_update_by')
|
||||
belong_dept = models.ForeignKey(
|
||||
'system.organzation', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='所属部门', related_name='%(class)s_belong_dept')
|
||||
'system.organzation', null=True, blank=True, on_delete=models.SET_NULL,
|
||||
verbose_name='所属部门', related_name='%(class)s_belong_dept')
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
from rest_framework.pagination import PageNumberPagination
|
||||
from rest_framework.exceptions import ParseError
|
||||
|
||||
|
||||
class MyPagination(PageNumberPagination):
|
||||
"""
|
||||
自定义分页/传入page为0则不分页
|
||||
"""
|
||||
page_size = 10
|
||||
page_size_query_param = 'page_size'
|
||||
|
||||
def paginate_queryset(self, queryset, request, view):
|
||||
if request.query_params.get('pageoff', None) or request.query_params.get('page', None)=='0':
|
||||
if queryset.count()<500:
|
||||
def paginate_queryset(self, queryset, request, view=None):
|
||||
if request.query_params.get('pageoff', None) or request.query_params.get('page', None) == '0':
|
||||
if queryset.count() < 500:
|
||||
return None
|
||||
elif queryset.count()>=500:
|
||||
raise ParseError('单次请求数据量大,请分页获取')
|
||||
raise ParseError('单次请求数据量大,请分页获取')
|
||||
return super().paginate_queryset(queryset, request, view=view)
|
|
@ -1,26 +1,26 @@
|
|||
from django.core.cache import cache
|
||||
from rest_framework.permissions import BasePermission, DjangoModelPermissions
|
||||
from rest_framework.permissions import BasePermission
|
||||
from apps.utils.queryset import get_child_queryset2
|
||||
from apps.system.models import Dept, Permission, Post, Role, UserPost
|
||||
from django.db.models import Q
|
||||
from apps.system.models import Dept, Permission, Post, UserPost
|
||||
from django.db.models.query import QuerySet
|
||||
|
||||
ALL_PERMS = [
|
||||
|
||||
]
|
||||
|
||||
|
||||
def get_user_perms_map(user):
|
||||
"""
|
||||
获取权限字典,可用redis存取
|
||||
"""
|
||||
user_perms_map = {}
|
||||
if user.is_superuser:
|
||||
user_perms_map = {'superuser':None}
|
||||
user_perms_map = {'superuser': None}
|
||||
else:
|
||||
objs = UserPost.objects.filter(user=user)
|
||||
for i in objs:
|
||||
dept_id = str(i.dept.id)
|
||||
if i.post.roles: # 岗位下有角色
|
||||
if i.post.roles: # 岗位下有角色
|
||||
for perm in Permission.objects.filter(role__perms__in=i.post.roles):
|
||||
if perm.codes:
|
||||
for code in perm.codes:
|
||||
|
@ -29,7 +29,7 @@ def get_user_perms_map(user):
|
|||
if i.post.data_range < data_range:
|
||||
user_perms_map[code][dept_id] = data_range
|
||||
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)
|
||||
return user_perms_map
|
||||
|
||||
|
@ -65,6 +65,7 @@ class RbacPermission(BasePermission):
|
|||
return False
|
||||
return False
|
||||
|
||||
|
||||
class RbacDataMixin:
|
||||
"""
|
||||
数据权限控权返回的queryset
|
||||
|
@ -115,17 +116,16 @@ class RbacDataMixin:
|
|||
belong_depts = get_child_queryset2(dept.parent)
|
||||
else:
|
||||
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:
|
||||
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:
|
||||
queryset = queryset.filter(belong_dept = dept)
|
||||
queryset = queryset.filter(belong_dept=dept)
|
||||
elif data_range == Post.POST_DATA_THISLEVEL:
|
||||
queryset = queryset.filter(create_by = user)
|
||||
queryset = queryset.filter(create_by=user)
|
||||
new_queryset = new_queryset | queryset
|
||||
return new_queryset
|
||||
else:
|
||||
return queryset.none()
|
||||
return queryset
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
from django.db import models
|
||||
from django.apps import apps
|
||||
|
||||
|
||||
|
@ -41,6 +40,7 @@ def get_child_queryset(name, pk, hasParent=True):
|
|||
child_queryset = cls.objects.filter(parent__in=child_queryset)
|
||||
return queryset
|
||||
|
||||
|
||||
def get_child_queryset2(obj, hasParent=True):
|
||||
'''
|
||||
获取所有子集
|
||||
|
@ -59,6 +59,7 @@ def get_child_queryset2(obj, hasParent=True):
|
|||
child_queryset = cls.objects.filter(parent__in=child_queryset)
|
||||
return queryset
|
||||
|
||||
|
||||
def get_parent_queryset(obj, hasSelf=True):
|
||||
cls = type(obj)
|
||||
ids = []
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import json
|
||||
from user_agents import parse
|
||||
|
||||
|
||||
def get_request_ip(request):
|
||||
"""
|
||||
获取请求IP
|
||||
|
@ -11,6 +13,7 @@ def get_request_ip(request):
|
|||
ip = request.META.get('REMOTE_ADDR', '') or getattr(request, 'request_ip', None)
|
||||
return ip or 'unknown'
|
||||
|
||||
|
||||
def get_request_data(request):
|
||||
"""
|
||||
获取请求参数
|
||||
|
@ -55,6 +58,7 @@ def get_request_path(request, *args, **kwargs):
|
|||
path = path.replace('/' + value, '/' + '{id}')
|
||||
return path
|
||||
|
||||
|
||||
def get_browser(request, ):
|
||||
"""
|
||||
获取浏览器名
|
||||
|
|
|
@ -3,27 +3,33 @@ from rest_framework import serializers
|
|||
from django_restql.mixins import DynamicFieldsMixin
|
||||
from rest_framework.fields import empty
|
||||
from rest_framework.request import Request
|
||||
|
||||
|
||||
class PkSerializer(serializers.Serializer):
|
||||
pks = serializers.ListField(child=serializers.CharField(max_length=20), label="主键ID列表")
|
||||
|
||||
|
||||
class GenSignatureSerializer(serializers.Serializer):
|
||||
path = serializers.CharField(label="图片地址")
|
||||
|
||||
class CustomModelSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
|
||||
|
||||
class CustomModelSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
|
||||
"""
|
||||
自定义serializer/包含创建和新增字段处理
|
||||
"""
|
||||
def __init__(self, instance=None, data=empty, request=None, **kwargs):
|
||||
super().__init__(instance, data, **kwargs)
|
||||
self.request: Request = request or self.context.get('request', None)
|
||||
|
||||
def create(self, validated_data):
|
||||
if self.request:
|
||||
if getattr(self.request, 'user', None):
|
||||
validated_data['create_by'] = self.request.user
|
||||
if 'belong_dept' in validated_data: # 如果指定了数据归属部门
|
||||
pass
|
||||
elif getattr(self.request.user, 'belong_dept', None):
|
||||
if hasattr(self.Meta.model, 'belong_dept'):
|
||||
validated_data['belong_dept'] = self.request.user.belong_dept
|
||||
if getattr(self.request, 'user', None):
|
||||
validated_data['create_by'] = self.request.user
|
||||
if 'belong_dept' in validated_data:
|
||||
pass
|
||||
elif getattr(self.request.user, 'belong_dept', None):
|
||||
if hasattr(self.Meta.model, 'belong_dept'):
|
||||
validated_data['belong_dept'] = self.request.user.belong_dept
|
||||
return super().create(validated_data)
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
|
|
|
@ -4,12 +4,14 @@
|
|||
import time
|
||||
from server.settings import SNOW_DATACENTER_ID, SNOW_WORKER_ID
|
||||
|
||||
|
||||
class InvalidSystemClock(Exception):
|
||||
"""
|
||||
时钟回拨异常
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class Constant(object):
|
||||
# 64位ID的划分
|
||||
WORKER_ID_BITS = 5
|
||||
|
@ -17,7 +19,7 @@ class Constant(object):
|
|||
SEQUENCE_BITS = 12
|
||||
|
||||
# 最大取值计算
|
||||
MAX_WORKER_ID = -1 ^ (-1 << WORKER_ID_BITS) # 2**5-1 0b11111
|
||||
MAX_WORKER_ID = -1 ^ (-1 << WORKER_ID_BITS) # 2**5-1 0b11111
|
||||
MAX_DATACENTER_ID = -1 ^ (-1 << DATACENTER_ID_BITS)
|
||||
|
||||
# 移位偏移计算
|
||||
|
@ -55,7 +57,7 @@ class IdWorker(object):
|
|||
self.datacenter_id = datacenter_id
|
||||
self.sequence = sequence
|
||||
|
||||
self.last_timestamp = -1 # 上次计算的时间戳
|
||||
self.last_timestamp = -1 # 上次计算的时间戳
|
||||
|
||||
def _gen_timestamp(self):
|
||||
"""
|
||||
|
@ -84,8 +86,9 @@ class IdWorker(object):
|
|||
|
||||
self.last_timestamp = timestamp
|
||||
|
||||
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
|
||||
new_id = ((timestamp - Constant.TWEPOCH) << Constant.TIMESTAMP_LEFT_SHIFT
|
||||
) | (self.datacenter_id << Constant.DATACENTER_ID_SHIFT) | \
|
||||
(self.worker_id << Constant.WOKER_ID_SHIFT) | self.sequence
|
||||
return new_id
|
||||
|
||||
def _til_next_millis(self, last_timestamp):
|
||||
|
@ -97,6 +100,7 @@ class IdWorker(object):
|
|||
timestamp = self._gen_timestamp()
|
||||
return timestamp
|
||||
|
||||
|
||||
idWorker = IdWorker(SNOW_DATACENTER_ID, SNOW_WORKER_ID)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -2,8 +2,9 @@ import textwrap
|
|||
import random
|
||||
import string
|
||||
|
||||
|
||||
def print_roundtrip(response, *args, **kwargs):
|
||||
format_headers = lambda d: '\n'.join(f'{k}: {v}' for k, v in d.items())
|
||||
def format_headers(d): return '\n'.join(f'{k}: {v}' for k, v in d.items())
|
||||
print(textwrap.dedent('''
|
||||
---------------- request ----------------
|
||||
{req.method} {req.url}
|
||||
|
@ -22,10 +23,12 @@ def print_roundtrip(response, *args, **kwargs):
|
|||
reshdrs=format_headers(response.headers),
|
||||
))
|
||||
|
||||
|
||||
def ranstr(num):
|
||||
salt = ''.join(random.sample(string.ascii_lowercase + string.digits, num))
|
||||
return salt
|
||||
|
||||
|
||||
def rannum(num):
|
||||
salt = ''.join(random.sample(string.digits, num))
|
||||
return salt
|
|
@ -1,4 +1,4 @@
|
|||
from rest_framework.views import APIView
|
||||
|
||||
import os
|
||||
import cv2
|
||||
from apps.utils.errors import SIGN_MAKE_FAIL
|
||||
|
@ -10,6 +10,7 @@ from apps.utils.viewsets import CustomGenericViewSet
|
|||
from apps.utils.mixins import CustomCreateModelMixin
|
||||
from apps.utils.serializers import GenSignatureSerializer
|
||||
|
||||
|
||||
class SignatureViewSet(CustomCreateModelMixin, CustomGenericViewSet):
|
||||
authentication_classes = ()
|
||||
permission_classes = ()
|
||||
|
|
|
@ -1,24 +1,26 @@
|
|||
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
from django.core.cache import cache
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.mixins import (CreateModelMixin, ListModelMixin,
|
||||
RetrieveModelMixin, UpdateModelMixin)
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
|
||||
from apps.system.models import Dept, Post
|
||||
from apps.utils.errors import PKS_ERROR
|
||||
from apps.utils.mixins import CustomDestoryModelMixin, MyLoggingMixin
|
||||
from apps.utils.permission import ALL_PERMS, RbacPermission, get_user_perms_map
|
||||
from apps.utils.queryset import get_child_queryset2
|
||||
from apps.utils.serializers import PkSerializer
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.mixins import RetrieveModelMixin, ListModelMixin, CreateModelMixin, UpdateModelMixin
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from django.core.cache import cache
|
||||
|
||||
|
||||
class CustomGenericViewSet(MyLoggingMixin, GenericViewSet):
|
||||
"""
|
||||
增强的GenericViewSet
|
||||
"""
|
||||
perms_map = {} # 权限标识
|
||||
perms_map = {} # 权限标识
|
||||
logging_methods = ['POST', 'PUT', 'PATCH', 'DELETE']
|
||||
ordering_fields = '__all__'
|
||||
filter_fields = '__all__'
|
||||
|
@ -32,7 +34,7 @@ class CustomGenericViewSet(MyLoggingMixin, GenericViewSet):
|
|||
select_related_fields = []
|
||||
prefetch_related_fields = []
|
||||
permission_classes = [IsAuthenticated & RbacPermission]
|
||||
data_filter = False # 数据权限过滤是否开启(需要RbacPermission)
|
||||
data_filter = False # 数据权限过滤是否开启(需要RbacPermission)
|
||||
|
||||
def get_serializer_class(self):
|
||||
action_serializer_name = f"{self.action}_serializer_class"
|
||||
|
@ -45,7 +47,7 @@ class CustomGenericViewSet(MyLoggingMixin, GenericViewSet):
|
|||
super().__init__(*args, **kwargs)
|
||||
if self.perms_map:
|
||||
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)
|
||||
|
||||
def get_queryset(self):
|
||||
|
@ -81,43 +83,45 @@ class CustomGenericViewSet(MyLoggingMixin, GenericViewSet):
|
|||
belong_depts = get_child_queryset2(dept.parent)
|
||||
else:
|
||||
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:
|
||||
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:
|
||||
queryset = queryset.filter(belong_dept = dept)
|
||||
queryset = queryset.filter(belong_dept=dept)
|
||||
elif data_range == Post.POST_DATA_THISLEVEL:
|
||||
queryset = queryset.filter(create_by = user)
|
||||
queryset = queryset.filter(create_by=user)
|
||||
new_queryset = new_queryset | queryset
|
||||
return new_queryset
|
||||
else:
|
||||
return queryset.none()
|
||||
return queryset
|
||||
|
||||
class CustomModelViewSet(CreateModelMixin
|
||||
, UpdateModelMixin, ListModelMixin, RetrieveModelMixin
|
||||
, CustomDestoryModelMixin, CustomGenericViewSet):
|
||||
|
||||
class CustomModelViewSet(CreateModelMixin, UpdateModelMixin, ListModelMixin,
|
||||
RetrieveModelMixin, CustomDestoryModelMixin, CustomGenericViewSet):
|
||||
"""
|
||||
增强的ModelViewSet
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
super().__init__(**kwargs)
|
||||
# 增加默认权限标识
|
||||
if not self.perms_map:
|
||||
basename = self.basename
|
||||
self.perms_map = {'get':'*', 'post':'{}:create'.format(basename)
|
||||
,'put':'{}:update'.format(basename)
|
||||
,'patch':'{}:update'.format(basename)
|
||||
,'delete':'{}:delete'.format(basename)}
|
||||
self.perms_map = {'get': '*', 'post': '{}:create'.format(basename), 'put': '{}:update'.format(
|
||||
basename), 'patch': '{}:update'.format(basename), 'delete': '{}:delete'.format(basename)}
|
||||
for k, v in self.perms_map.items():
|
||||
if v not in ALL_PERMS and v!='*':
|
||||
if v not in ALL_PERMS and v != '*':
|
||||
ALL_PERMS.append(v)
|
||||
|
||||
@action(methods=['post'], detail=False, serializer_class=PkSerializer)
|
||||
def deletes(self,request,*args,**kwargs):
|
||||
def deletes(self, request, *args, **kwargs):
|
||||
"""
|
||||
批量删除
|
||||
"""
|
||||
request_data = request.data
|
||||
pks = request_data.get('pks',None)
|
||||
pks = request_data.get('pks', None)
|
||||
if pks:
|
||||
self.get_queryset().filter(id__in=pks).delete(update_by=request.user)
|
||||
return Response()
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
from threading import Thread
|
||||
import requests
|
||||
import json
|
||||
from apps.utils.errors import XX_REQUEST_ERROR
|
||||
from apps.utils.tools import print_roundtrip
|
||||
from django.conf import settings
|
||||
|
@ -9,11 +8,11 @@ from rest_framework.exceptions import APIException, ParseError
|
|||
requests.packages.urllib3.disable_warnings()
|
||||
|
||||
|
||||
|
||||
class XxClient:
|
||||
"""
|
||||
寻息
|
||||
"""
|
||||
|
||||
def __init__(self, licence=settings.XX_LICENCE, username=settings.XX_USERNAME) -> None:
|
||||
if not settings.XX_ENABLED:
|
||||
return None
|
||||
|
@ -28,7 +27,7 @@ class XxClient:
|
|||
def _get_token_loop(self):
|
||||
while self.isRuning:
|
||||
json = {
|
||||
'licence':self.licence
|
||||
'licence': self.licence
|
||||
}
|
||||
r = requests.post(json=json, url=settings.XX_BASE_URL + '/getAccessTokenV2', verify=False, timeout=20)
|
||||
ret = r.json()
|
||||
|
@ -39,7 +38,7 @@ class XxClient:
|
|||
def get_token(self):
|
||||
self.isGetingToken = True
|
||||
json = {
|
||||
'licence':self.licence
|
||||
'licence': self.licence
|
||||
}
|
||||
r = requests.post(json=json, url=settings.XX_BASE_URL + '/getAccessTokenV2', verify=False, timeout=20)
|
||||
ret = r.json()
|
||||
|
@ -48,7 +47,7 @@ class XxClient:
|
|||
self.token = ret['data']['token']
|
||||
|
||||
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()
|
||||
|
||||
def __del__(self):
|
||||
|
@ -58,7 +57,7 @@ class XxClient:
|
|||
self.isRuning = False
|
||||
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
|
||||
json['username'] = self.username
|
||||
json['buildId'] = settings.XX_BUILDID
|
||||
|
@ -72,20 +71,20 @@ class XxClient:
|
|||
if req_num > 4:
|
||||
break
|
||||
else:
|
||||
r = getattr(requests, method)('{}{}'.format(settings.XX_BASE_URL, url)
|
||||
, params=params, json=json, timeout=timeout, verify=False)
|
||||
r = getattr(requests, method)('{}{}'.format(settings.XX_BASE_URL, url),
|
||||
params=params, json=json, timeout=timeout, verify=False)
|
||||
# if settings.DEBUG:
|
||||
# print_roundtrip(r)
|
||||
ret = r.json()
|
||||
if ret.get('errorCode') == '1060000':
|
||||
self.get_token() # 重新获取token
|
||||
self.request(url, method, params, json, timeout, raise_exception) # 重新请求
|
||||
self.get_token() # 重新获取token
|
||||
self.request(url, method, params, json, timeout, raise_exception) # 重新请求
|
||||
else:
|
||||
if ret['errorCode'] != 0:
|
||||
err_detail = dict(detail='寻息错误:' + '|'.join(ret['errorMsg']),
|
||||
code='xx_' + str(ret['errorCode']))
|
||||
code='xx_' + str(ret['errorCode']))
|
||||
if raise_exception:
|
||||
raise ParseError(**err_detail)
|
||||
raise ParseError(**err_detail)
|
||||
return 'fail', err_detail
|
||||
return 'success', ret['data']
|
||||
if raise_exception:
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class WfConfig(AppConfig):
|
||||
name = 'apps.wf'
|
||||
verbose_name = '工作流管理'
|
||||
|
||||
|
||||
|
|
|
@ -1,24 +1,29 @@
|
|||
from django_filters import rest_framework as filters
|
||||
from .models import Ticket
|
||||
|
||||
|
||||
class TicketFilterSet(filters.FilterSet):
|
||||
start_create = filters.DateFilter(field_name="create_time", lookup_expr='gte')
|
||||
end_create = filters.DateFilter(field_name="create_time", lookup_expr='lte')
|
||||
category = filters.ChoiceFilter(choices = Ticket.category_choices, method='filter_category')
|
||||
category = filters.ChoiceFilter(choices=Ticket.category_choices, method='filter_category')
|
||||
|
||||
class Meta:
|
||||
model = Ticket
|
||||
fields = ['workflow', 'state', 'act_state', 'start_create', 'end_create', 'category']
|
||||
|
||||
def filter_category(self, queryset, name, value):
|
||||
user=self.request.user
|
||||
if value == 'owner': # 我的
|
||||
user = self.request.user
|
||||
if value == 'owner': # 我的
|
||||
queryset = queryset.filter(create_by=user)
|
||||
elif value == 'duty': # 待办
|
||||
queryset = queryset.filter(participant__contains=user.id).exclude(act_state__in=[Ticket.TICKET_ACT_STATE_FINISH, Ticket.TICKET_ACT_STATE_CLOSED])
|
||||
elif value == 'worked': # 处理过的
|
||||
queryset = queryset.filter(ticketflow_ticket__participant=user).exclude(create_by=user).order_by('-update_time').distinct()
|
||||
elif value == 'cc': # 抄送我的
|
||||
queryset = queryset.filter(ticketflow_ticket__participant_cc__contains=user.id).exclude(create_by=user).order_by('-update_time').distinct()
|
||||
elif value == 'duty': # 待办
|
||||
queryset = queryset.filter(participant__contains=user.id).exclude(
|
||||
act_state__in=[Ticket.TICKET_ACT_STATE_FINISH, Ticket.TICKET_ACT_STATE_CLOSED])
|
||||
elif value == 'worked': # 处理过的
|
||||
queryset = queryset.filter(ticketflow_ticket__participant=user).exclude(
|
||||
create_by=user).order_by('-update_time').distinct()
|
||||
elif value == 'cc': # 抄送我的
|
||||
queryset = queryset.filter(ticketflow_ticket__participant_cc__contains=user.id).exclude(
|
||||
create_by=user).order_by('-update_time').distinct()
|
||||
elif value == 'all':
|
||||
pass
|
||||
else:
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
from random import choice
|
||||
from django.db import models
|
||||
from django.db.models.base import Model
|
||||
import django.utils.timezone as timezone
|
||||
from django.db.models.query import QuerySet
|
||||
from apps.utils.models import CommonAModel
|
||||
from apps.system.models import Dept, User, File
|
||||
from apps.utils.models import SoftModel, BaseModel
|
||||
from simple_history.models import HistoricalRecords
|
||||
from apps.system.models import User
|
||||
from apps.utils.models import BaseModel
|
||||
|
||||
|
||||
class Workflow(CommonAModel):
|
||||
|
@ -18,10 +13,15 @@ class Workflow(CommonAModel):
|
|||
sn_prefix = models.CharField('流水号前缀', max_length=50, default='hb')
|
||||
description = models.CharField('描述', max_length=200, null=True, blank=True)
|
||||
view_permission_check = models.BooleanField('查看权限校验', default=True, help_text='开启后,只允许工单的关联人(创建人、曾经的处理人)有权限查看工单')
|
||||
limit_expression = models.JSONField('限制表达式', default=dict, blank=True, help_text='限制周期({"period":24} 24小时), 限制次数({"count":1}在限制周期内只允许提交1次), 限制级别({"level":1} 针对(1单个用户 2全局)限制周期限制次数,默认特定用户);允许特定人员提交({"allow_persons":"zhangsan,lisi"}只允许张三提交工单,{"allow_depts":"1,2"}只允许部门id为1和2的用户提交工单,{"allow_roles":"1,2"}只允许角色id为1和2的用户提交工单)')
|
||||
display_form_str = models.JSONField('展现表单字段', default=list, blank=True, help_text='默认"[]",用于用户只有对应工单查看权限时显示哪些字段,field_key的list的json,如["days","sn"],内置特殊字段participant_info.participant_name:当前处理人信息(部门名称、角色名称),state.state_name:当前状态的状态名,workflow.workflow_name:工作流名称')
|
||||
title_template = models.CharField('标题模板', max_length=50, default='{title}', null=True, blank=True, help_text='工单字段的值可以作为参数写到模板中,格式如:你有一个待办工单:{title}')
|
||||
content_template = models.CharField('内容模板', max_length=1000, default='标题:{title}, 创建时间:{create_time}', null=True, blank=True, help_text='工单字段的值可以作为参数写到模板中,格式如:标题:{title}, 创建时间:{create_time}')
|
||||
limit_expression = models.JSONField(
|
||||
'限制表达式', default=dict, blank=True, help_text='限制周期({"period":24} 24小时), 限制次数({"count":1}在限制周期内只允许提交1次), 限制级别({"level":1} 针对(1单个用户 2全局)限制周期限制次数,默认特定用户);允许特定人员提交({"allow_persons":"zhangsan,lisi"}只允许张三提交工单,{"allow_depts":"1,2"}只允许部门id为1和2的用户提交工单,{"allow_roles":"1,2"}只允许角色id为1和2的用户提交工单)')
|
||||
display_form_str = models.JSONField('展现表单字段', default=list, blank=True,
|
||||
help_text='默认"[]",用于用户只有对应工单查看权限时显示哪些字段,field_key的list的json,如["days","sn"],内置特殊字段participant_info.participant_name:当前处理人信息(部门名称、角色名称),state.state_name:当前状态的状态名,workflow.workflow_name:工作流名称')
|
||||
title_template = models.CharField(
|
||||
'标题模板', max_length=50, default='{title}', null=True, blank=True, help_text='工单字段的值可以作为参数写到模板中,格式如:你有一个待办工单:{title}')
|
||||
content_template = models.CharField(
|
||||
'内容模板', max_length=1000, default='标题:{title}, 创建时间:{create_time}', null=True, blank=True, help_text='工单字段的值可以作为参数写到模板中,格式如:标题:{title}, 创建时间:{create_time}')
|
||||
|
||||
|
||||
class State(CommonAModel):
|
||||
"""
|
||||
|
@ -55,22 +55,22 @@ class State(CommonAModel):
|
|||
# (PARTICIPANT_TYPE_PARENT_FIELD, '父工单的字段'),
|
||||
(PARTICIPANT_TYPE_FORMCODE, '代码获取')
|
||||
)
|
||||
STATE_DISTRIBUTE_TYPE_ACTIVE = 1 # 主动接单
|
||||
STATE_DISTRIBUTE_TYPE_DIRECT = 2 # 直接处理(当前为多人的情况,都可以处理,而不需要先接单)
|
||||
STATE_DISTRIBUTE_TYPE_RANDOM = 3 # 随机分配
|
||||
STATE_DISTRIBUTE_TYPE_ALL = 4 # 全部处理
|
||||
state_distribute_choices=(
|
||||
STATE_DISTRIBUTE_TYPE_ACTIVE = 1 # 主动接单
|
||||
STATE_DISTRIBUTE_TYPE_DIRECT = 2 # 直接处理(当前为多人的情况,都可以处理,而不需要先接单)
|
||||
STATE_DISTRIBUTE_TYPE_RANDOM = 3 # 随机分配
|
||||
STATE_DISTRIBUTE_TYPE_ALL = 4 # 全部处理
|
||||
state_distribute_choices = (
|
||||
(STATE_DISTRIBUTE_TYPE_ACTIVE, '主动接单'),
|
||||
(STATE_DISTRIBUTE_TYPE_DIRECT, '直接处理'),
|
||||
(STATE_DISTRIBUTE_TYPE_RANDOM, '随机分配'),
|
||||
(STATE_DISTRIBUTE_TYPE_ALL, '全部处理'),
|
||||
)
|
||||
|
||||
STATE_FIELD_READONLY= 1 # 字段只读
|
||||
STATE_FIELD_REQUIRED = 2 # 字段必填
|
||||
STATE_FIELD_OPTIONAL = 3 # 字段可选
|
||||
STATE_FIELD_HIDDEN = 4 # 字段隐藏
|
||||
state_filter_choices=(
|
||||
STATE_FIELD_READONLY = 1 # 字段只读
|
||||
STATE_FIELD_REQUIRED = 2 # 字段必填
|
||||
STATE_FIELD_OPTIONAL = 3 # 字段可选
|
||||
STATE_FIELD_HIDDEN = 4 # 字段隐藏
|
||||
state_filter_choices = (
|
||||
(0, '无'),
|
||||
(1, '和工单同属一及上级部门'),
|
||||
(2, '和创建人同属一及上级部门'),
|
||||
|
@ -80,15 +80,22 @@ class State(CommonAModel):
|
|||
workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, verbose_name='所属工作流')
|
||||
is_hidden = models.BooleanField('是否隐藏', default=False, help_text='设置为True时,获取工单步骤api中不显示此状态(当前处于此状态时除外)')
|
||||
sort = models.IntegerField('状态顺序', default=0, help_text='用于工单步骤接口时,step上状态的顺序(因为存在网状情况,所以需要人为设定顺序),值越小越靠前')
|
||||
type = models.IntegerField('状态类型', default=0, choices=type_choices, help_text='0.普通类型 1.初始状态(用于新建工单时,获取对应的字段必填及transition信息) 2.结束状态(此状态下的工单不得再处理,即没有对应的transition)')
|
||||
type = models.IntegerField('状态类型', default=0, choices=type_choices,
|
||||
help_text='0.普通类型 1.初始状态(用于新建工单时,获取对应的字段必填及transition信息) 2.结束状态(此状态下的工单不得再处理,即没有对应的transition)')
|
||||
enable_retreat = models.BooleanField('允许撤回', default=False, help_text='开启后允许工单创建人在此状态直接撤回工单到初始状态')
|
||||
participant_type = models.IntegerField('参与者类型', choices=state_participanttype_choices, default=1, blank=True, help_text='0.无处理人,1.个人,2.多人,3.部门,4.角色,5.变量(支持工单创建人,创建人的leader),6.脚本,7.工单的字段内容(如表单中的"测试负责人",需要为用户名或者逗号隔开的多个用户名),8.父工单的字段内容。 初始状态请选择类型5,参与人填create_by')
|
||||
participant = models.JSONField('参与者', default=list, blank=True, help_text='可以为空(无处理人的情况,如结束状态)、userid、userid列表\部门id\角色id\变量(create_by,create_by_tl)\脚本记录的id等,包含子工作流的需要设置处理人为loonrobot')
|
||||
state_fields = models.JSONField('表单字段', default=dict, help_text='json格式字典存储,包括读写属性1:只读,2:必填,3:可选, 4:隐藏 示例:{"create_time":1,"title":2, "sn":1}, 内置特殊字段participant_info.participant_name:当前处理人信息(部门名称、角色名称),state.state_name:当前状态的状态名,workflow.workflow_name:工作流名称') # json格式存储,包括读写属性1:只读,2:必填,3:可选,4:不显示, 字典的字典
|
||||
distribute_type = models.IntegerField('分配方式', default=1, choices=state_distribute_choices, help_text='1.主动接单(如果当前处理人实际为多人的时候,需要先接单才能处理) 2.直接处理(即使当前处理人实际为多人,也可以直接处理) 3.随机分配(如果实际为多人,则系统会随机分配给其中一个人) 4.全部处理(要求所有参与人都要处理一遍,才能进入下一步)')
|
||||
participant_type = models.IntegerField('参与者类型', choices=state_participanttype_choices, default=1, blank=True,
|
||||
help_text='0.无处理人,1.个人,2.多人,3.部门,4.角色,5.变量(支持工单创建人,创建人的leader),6.脚本,7.工单的字段内容(如表单中的"测试负责人",需要为用户名或者逗号隔开的多个用户名),8.父工单的字段内容。 初始状态请选择类型5,参与人填create_by')
|
||||
participant = models.JSONField('参与者', default=list, blank=True,
|
||||
help_text='可以为空(无处理人的情况,如结束状态)、userid、userid列表\部门id\角色id\变量(create_by,create_by_tl)\脚本记录的id等,包含子工作流的需要设置处理人为loonrobot')
|
||||
# json格式存储,包括读写属性1:只读,2:必填,3:可选,4:不显示, 字典的字典
|
||||
state_fields = models.JSONField(
|
||||
'表单字段', default=dict, help_text='json格式字典存储,包括读写属性1:只读,2:必填,3:可选, 4:隐藏 示例:{"create_time":1,"title":2, "sn":1}, 内置特殊字段participant_info.participant_name:当前处理人信息(部门名称、角色名称),state.state_name:当前状态的状态名,workflow.workflow_name:工作流名称')
|
||||
distribute_type = models.IntegerField('分配方式', default=1, choices=state_distribute_choices,
|
||||
help_text='1.主动接单(如果当前处理人实际为多人的时候,需要先接单才能处理) 2.直接处理(即使当前处理人实际为多人,也可以直接处理) 3.随机分配(如果实际为多人,则系统会随机分配给其中一个人) 4.全部处理(要求所有参与人都要处理一遍,才能进入下一步)')
|
||||
filter_policy = models.IntegerField('参与人过滤策略', default=0, choices=state_filter_choices)
|
||||
participant_cc = models.JSONField('抄送给', default=list, blank=True, help_text='抄送给(userid列表)')
|
||||
|
||||
|
||||
class Transition(CommonAModel):
|
||||
"""
|
||||
工作流流转,定时器,条件(允许跳过), 条件流转与定时器不可同时存在
|
||||
|
@ -111,7 +118,7 @@ class Transition(CommonAModel):
|
|||
TRANSITION_INTERVENE_TYPE_ALTER_STATE = 8 # 强制修改状态操作
|
||||
TRANSITION_INTERVENE_TYPE_HOOK = 9 # hook操作
|
||||
TRANSITION_INTERVENE_TYPE_RETREAT = 10 # 撤回
|
||||
TRANSITION_INTERVENE_TYPE_CC = 11 # 抄送
|
||||
TRANSITION_INTERVENE_TYPE_CC = 11 # 抄送
|
||||
|
||||
intervene_type_choices = (
|
||||
(0, '正常处理'),
|
||||
|
@ -131,11 +138,16 @@ class Transition(CommonAModel):
|
|||
name = models.CharField('操作', max_length=50)
|
||||
workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, verbose_name='所属工作流')
|
||||
timer = models.IntegerField('定时器(单位秒)', default=0, help_text='单位秒。处于源状态X秒后如果状态都没有过变化则自动流转到目标状态。设置时间有效')
|
||||
source_state = models.ForeignKey(State, on_delete=models.CASCADE, verbose_name='源状态', related_name='sstate_transition')
|
||||
destination_state = models.ForeignKey(State, on_delete=models.CASCADE, verbose_name='目的状态', related_name='dstate_transition')
|
||||
condition_expression = models.JSONField('条件表达式', max_length=1000, default=list, help_text='流转条件表达式,根据表达式中的条件来确定流转的下个状态,格式为[{"expression":"{days} > 3 and {days}<10", "target_state":11}] 其中{}用于填充工单的字段key,运算时会换算成实际的值,当符合条件下个状态将变为target_state_id中的值,表达式只支持简单的运算或datetime/time运算.loonflow会以首次匹配成功的条件为准,所以多个条件不要有冲突' )
|
||||
attribute_type = models.IntegerField('属性类型', default=1, choices=attribute_type_choices, help_text='属性类型,1.同意,2.拒绝,3.其他')
|
||||
field_require_check = models.BooleanField('是否校验必填项', default=True, help_text='默认在用户点击操作的时候需要校验工单表单的必填项,如果设置为否则不检查。用于如"退回"属性的操作,不需要填写表单内容')
|
||||
source_state = models.ForeignKey(State, on_delete=models.CASCADE,
|
||||
verbose_name='源状态', related_name='sstate_transition')
|
||||
destination_state = models.ForeignKey(State, on_delete=models.CASCADE,
|
||||
verbose_name='目的状态', related_name='dstate_transition')
|
||||
condition_expression = models.JSONField('条件表达式', max_length=1000, default=list,
|
||||
help_text='流转条件表达式,根据表达式中的条件来确定流转的下个状态,格式为[{"expression":"{days} > 3 and {days}<10", "target_state":11}] 其中{}用于填充工单的字段key,运算时会换算成实际的值,当符合条件下个状态将变为target_state_id中的值,表达式只支持简单的运算或datetime/time运算.loonflow会以首次匹配成功的条件为准,所以多个条件不要有冲突')
|
||||
attribute_type = models.IntegerField(
|
||||
'属性类型', default=1, choices=attribute_type_choices, help_text='属性类型,1.同意,2.拒绝,3.其他')
|
||||
field_require_check = models.BooleanField(
|
||||
'是否校验必填项', default=True, help_text='默认在用户点击操作的时候需要校验工单表单的必填项,如果设置为否则不检查。用于如"退回"属性的操作,不需要填写表单内容')
|
||||
|
||||
|
||||
class CustomField(CommonAModel):
|
||||
|
@ -160,12 +172,15 @@ class CustomField(CommonAModel):
|
|||
)
|
||||
workflow = models.ForeignKey(Workflow, on_delete=models.CASCADE, verbose_name='所属工作流')
|
||||
field_type = models.CharField('类型', max_length=50, choices=field_type_choices,
|
||||
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_name = models.CharField('字段名称', max_length=50)
|
||||
sort = models.IntegerField('排序', default=0, help_text='工单基础字段在表单中排序为:流水号0,标题20,状态id40,状态名41,创建人80,创建时间100,更新时间120.前端展示工单信息的表单可以根据这个id顺序排列')
|
||||
default_value = models.CharField('默认值', null=True, blank=True, max_length=100, help_text='前端展示时,可以将此内容作为表单中的该字段的默认值')
|
||||
description = models.CharField('描述', max_length=100, blank=True, null=True, help_text='字段的描述信息,可用于显示在字段的下方对该字段的详细描述')
|
||||
sort = models.IntegerField(
|
||||
'排序', default=0, help_text='工单基础字段在表单中排序为:流水号0,标题20,状态id40,状态名41,创建人80,创建时间100,更新时间120.前端展示工单信息的表单可以根据这个id顺序排列')
|
||||
default_value = models.CharField('默认值', null=True, blank=True, max_length=100,
|
||||
help_text='前端展示时,可以将此内容作为表单中的该字段的默认值')
|
||||
description = models.CharField('描述', max_length=100, blank=True, null=True,
|
||||
help_text='字段的描述信息,可用于显示在字段的下方对该字段的详细描述')
|
||||
placeholder = models.CharField('占位符', max_length=100, blank=True, null=True, help_text='用户工单详情表单中作为字段的占位符显示')
|
||||
field_template = models.TextField('文本域模板', null=True, blank=True, help_text='文本域类型字段前端显示时可以将此内容作为字段的placeholder')
|
||||
boolean_field_display = models.JSONField('布尔类型显示名', default=dict, blank=True,
|
||||
|
@ -178,6 +193,7 @@ class CustomField(CommonAModel):
|
|||
# hook = models.CharField('hook', max_length=1000, default='', help_text='获取下拉选项用于动态选项值')
|
||||
is_hidden = models.BooleanField('是否隐藏', default=False, help_text='可用于携带不需要用户查看的字段信息')
|
||||
|
||||
|
||||
class Ticket(CommonAModel):
|
||||
"""
|
||||
工单
|
||||
|
@ -189,7 +205,7 @@ class Ticket(CommonAModel):
|
|||
TICKET_ACT_STATE_FINISH = 4 # 已完成
|
||||
TICKET_ACT_STATE_CLOSED = 5 # 已关闭
|
||||
|
||||
act_state_choices =(
|
||||
act_state_choices = (
|
||||
(TICKET_ACT_STATE_DRAFT, '草稿中'),
|
||||
(TICKET_ACT_STATE_ONGOING, '进行中'),
|
||||
(TICKET_ACT_STATE_BACK, '被退回'),
|
||||
|
@ -197,7 +213,7 @@ class Ticket(CommonAModel):
|
|||
(TICKET_ACT_STATE_FINISH, '已完成'),
|
||||
(TICKET_ACT_STATE_CLOSED, '已关闭')
|
||||
)
|
||||
category_choices =(
|
||||
category_choices = (
|
||||
('all', '全部'),
|
||||
('owner', '我创建的'),
|
||||
('duty', '待办'),
|
||||
|
@ -209,12 +225,15 @@ class Ticket(CommonAModel):
|
|||
sn = models.CharField('流水号', max_length=25, help_text="工单的流水号")
|
||||
state = models.ForeignKey(State, on_delete=models.CASCADE, verbose_name='当前状态', related_name='ticket_state')
|
||||
parent = models.ForeignKey('self', null=True, blank=True, on_delete=models.CASCADE, verbose_name='父工单')
|
||||
parent_state = models.ForeignKey(State, null=True, blank=True, on_delete=models.CASCADE, verbose_name='父工单状态', related_name='ticket_parent_state')
|
||||
parent_state = models.ForeignKey(State, null=True, blank=True, on_delete=models.CASCADE,
|
||||
verbose_name='父工单状态', related_name='ticket_parent_state')
|
||||
ticket_data = models.JSONField('工单数据', default=dict, help_text='工单自定义字段内容')
|
||||
in_add_node = models.BooleanField('加签状态中', default=False, help_text='是否处于加签状态下')
|
||||
add_node_man = models.ForeignKey(User, verbose_name='加签人', on_delete=models.SET_NULL, null=True, blank=True, help_text='加签操作的人,工单当前处理人处理完成后会回到该处理人,当处于加签状态下才有效')
|
||||
add_node_man = models.ForeignKey(User, verbose_name='加签人', on_delete=models.SET_NULL,
|
||||
null=True, blank=True, help_text='加签操作的人,工单当前处理人处理完成后会回到该处理人,当处于加签状态下才有效')
|
||||
script_run_last_result = models.BooleanField('脚本最后一次执行结果', default=True)
|
||||
participant_type = models.IntegerField('当前处理人类型', default=0, help_text='0.无处理人,1.个人,2.多人', choices=State.state_participanttype_choices)
|
||||
participant_type = models.IntegerField(
|
||||
'当前处理人类型', default=0, help_text='0.无处理人,1.个人,2.多人', choices=State.state_participanttype_choices)
|
||||
participant = models.JSONField('当前处理人', default=list, blank=True, help_text='可以为空(无处理人的情况,如结束状态)、userid、userid列表')
|
||||
act_state = models.IntegerField('进行状态', default=1, help_text='当前工单的进行状态', choices=act_state_choices)
|
||||
multi_all_person = models.JSONField('全部处理的结果', default=dict, blank=True, help_text='需要当前状态处理人全部处理时实际的处理结果,json格式')
|
||||
|
@ -225,13 +244,15 @@ class TicketFlow(BaseModel):
|
|||
工单流转日志
|
||||
"""
|
||||
ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE, verbose_name='关联工单', related_name='ticketflow_ticket')
|
||||
transition = models.ForeignKey(Transition, verbose_name='流转id', help_text='与worklow.Transition关联, 为空时表示认为干预的操作', on_delete=models.CASCADE, null=True, blank=True)
|
||||
transition = models.ForeignKey(Transition, verbose_name='流转id',
|
||||
help_text='与worklow.Transition关联, 为空时表示认为干预的操作', on_delete=models.CASCADE, null=True, blank=True)
|
||||
suggestion = models.CharField('处理意见', max_length=10000, default='', blank=True)
|
||||
participant_type = models.IntegerField('处理人类型', default=0, help_text='0.无处理人,1.个人,2.多人等', choices=State.state_participanttype_choices)
|
||||
participant = models.ForeignKey(User, verbose_name='处理人', on_delete=models.SET_NULL, null=True, blank=True, related_name='ticketflow_participant')
|
||||
participant_type = models.IntegerField(
|
||||
'处理人类型', default=0, help_text='0.无处理人,1.个人,2.多人等', choices=State.state_participanttype_choices)
|
||||
participant = models.ForeignKey(User, verbose_name='处理人', on_delete=models.SET_NULL,
|
||||
null=True, blank=True, related_name='ticketflow_participant')
|
||||
participant_str = models.CharField('处理人', max_length=200, null=True, blank=True, help_text='非人工处理的处理人相关信息')
|
||||
state = models.ForeignKey(State, verbose_name='当前状态', default=0, blank=True, on_delete=models.CASCADE)
|
||||
ticket_data = models.JSONField('工单数据', default=dict, blank=True, help_text='可以用于记录当前表单数据,json格式')
|
||||
intervene_type = models.IntegerField('干预类型', default=0, help_text='流转类型', choices=Transition.intervene_type_choices)
|
||||
participant_cc = models.JSONField('抄送给', default=list, blank=True, help_text='抄送给(userid列表)')
|
||||
|
||||
|
|
|
@ -7,50 +7,46 @@ class GetParticipants:
|
|||
获取处理人脚本
|
||||
"""
|
||||
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
|
||||
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
|
||||
return participant
|
||||
|
||||
|
||||
class HandleScripts:
|
||||
"""
|
||||
任务处理脚本
|
||||
"""
|
||||
all_funcs = [
|
||||
{'func': 'handle_something', 'name':'处理一些工作'}
|
||||
{'func': 'handle_something', 'name': '处理一些工作'}
|
||||
]
|
||||
|
||||
|
||||
@classmethod
|
||||
def to_next(cls, ticket:Ticket, by_timer:bool=False, by_task:bool=False, by_hook:bool=False, script_str:str=''):
|
||||
def to_next(cls, ticket: Ticket, by_timer: bool = False,
|
||||
by_task: bool = False, by_hook: bool = False, script_str: str = ''):
|
||||
# 获取信息
|
||||
transition_obj = Transition.objects.filter(source_state=ticket.state, is_deleted=False).first()
|
||||
|
||||
TicketFlow.objects.create(ticket=ticket, state=ticket.state,
|
||||
participant_type=State.PARTICIPANT_TYPE_ROBOT,
|
||||
participant_str='func:{}'.format(script_str),
|
||||
transition=transition_obj)
|
||||
participant_type=State.PARTICIPANT_TYPE_ROBOT,
|
||||
participant_str='func:{}'.format(script_str),
|
||||
transition=transition_obj)
|
||||
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
|
||||
|
||||
@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')
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
from apps.system.models import Dept, User
|
||||
from apps.system.serializers import UserSimpleSerializer
|
||||
import rest_framework
|
||||
from rest_framework import serializers
|
||||
|
||||
from .models import State, Ticket, TicketFlow, Workflow, Transition, CustomField
|
||||
|
@ -11,33 +10,40 @@ class WorkflowSerializer(serializers.ModelSerializer):
|
|||
model = Workflow
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class StateSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = State
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class WorkflowSimpleSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Workflow
|
||||
fields = ['id', 'name']
|
||||
|
||||
|
||||
class StateSimpleSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = State
|
||||
fields = ['id', 'name', 'type', 'distribute_type', 'enable_retreat']
|
||||
|
||||
|
||||
class TransitionSerializer(serializers.ModelSerializer):
|
||||
source_state_ = StateSimpleSerializer(source='source_state', read_only=True)
|
||||
destination_state_ = StateSimpleSerializer(source='destination_state', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Transition
|
||||
fields = '__all__'
|
||||
|
||||
@staticmethod
|
||||
def setup_eager_loading(queryset):
|
||||
""" Perform necessary eager loading of data. """
|
||||
queryset = queryset.select_related('source_state','destination_state')
|
||||
queryset = queryset.select_related('source_state', 'destination_state')
|
||||
return queryset
|
||||
|
||||
|
||||
class AllField(serializers.Field):
|
||||
def to_representation(self, value):
|
||||
return value
|
||||
|
@ -45,6 +51,7 @@ class AllField(serializers.Field):
|
|||
def to_internal_value(self, data):
|
||||
return data
|
||||
|
||||
|
||||
class FieldChoiceSerializer(serializers.Serializer):
|
||||
id = AllField(label='ID')
|
||||
name = serializers.CharField(label='名称')
|
||||
|
@ -55,14 +62,16 @@ class CustomFieldSerializer(serializers.ModelSerializer):
|
|||
model = CustomField
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class CustomFieldCreateUpdateSerializer(serializers.ModelSerializer):
|
||||
|
||||
field_choice = FieldChoiceSerializer(label='选项列表', many=True, required=False)
|
||||
|
||||
class Meta:
|
||||
model = CustomField
|
||||
fields = ['workflow', 'field_type', 'field_key', 'field_name',
|
||||
'sort', 'default_value', 'description', 'placeholder', 'field_template',
|
||||
'boolean_field_display', 'field_choice', 'label', 'is_hidden']
|
||||
'sort', 'default_value', 'description', 'placeholder', 'field_template',
|
||||
'boolean_field_display', 'field_choice', 'label', 'is_hidden']
|
||||
|
||||
|
||||
class TicketSimpleSerializer(serializers.ModelSerializer):
|
||||
|
@ -70,16 +79,19 @@ class TicketSimpleSerializer(serializers.ModelSerializer):
|
|||
model = Ticket
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class TicketCreateSerializer(serializers.ModelSerializer):
|
||||
transition = serializers.PrimaryKeyRelatedField(queryset=Transition.objects.all(), write_only=True)
|
||||
title = serializers.CharField(allow_blank=True, required=False)
|
||||
|
||||
class Meta:
|
||||
model=Ticket
|
||||
fields=['title','workflow', 'ticket_data', 'transition']
|
||||
model = Ticket
|
||||
fields = ['title', 'workflow', 'ticket_data', 'transition']
|
||||
|
||||
def create(self, validated_data):
|
||||
return super().create(validated_data)
|
||||
|
||||
|
||||
class TicketSerializer(serializers.ModelSerializer):
|
||||
workflow_ = WorkflowSimpleSerializer(source='workflow', read_only=True)
|
||||
state_ = StateSimpleSerializer(source='state', read_only=True)
|
||||
|
@ -90,33 +102,37 @@ class TicketSerializer(serializers.ModelSerializer):
|
|||
|
||||
@staticmethod
|
||||
def setup_eager_loading(queryset):
|
||||
queryset = queryset.select_related('workflow','state')
|
||||
queryset = queryset.select_related('workflow', 'state')
|
||||
return queryset
|
||||
|
||||
|
||||
class TicketListSerializer(serializers.ModelSerializer):
|
||||
workflow_ = WorkflowSimpleSerializer(source='workflow', read_only=True)
|
||||
state_ = StateSimpleSerializer(source='state', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Ticket
|
||||
fields = ['id', 'title', 'sn', 'workflow', 'workflow_', 'state', 'state_', 'act_state', 'create_time', 'update_time', 'participant_type', 'create_by']
|
||||
fields = ['id', 'title', 'sn', 'workflow', 'workflow_', 'state', 'state_',
|
||||
'act_state', 'create_time', 'update_time', 'participant_type', 'create_by']
|
||||
|
||||
@staticmethod
|
||||
def setup_eager_loading(queryset):
|
||||
queryset = queryset.select_related('workflow','state')
|
||||
queryset = queryset.select_related('workflow', 'state')
|
||||
return queryset
|
||||
|
||||
|
||||
class TicketDetailSerializer(serializers.ModelSerializer):
|
||||
workflow_ = WorkflowSimpleSerializer(source='workflow', read_only=True)
|
||||
state_ = StateSimpleSerializer(source='state', read_only=True)
|
||||
ticket_data_ = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Ticket
|
||||
fields = '__all__'
|
||||
|
||||
@staticmethod
|
||||
def setup_eager_loading(queryset):
|
||||
queryset = queryset.select_related('workflow','state')
|
||||
queryset = queryset.select_related('workflow', 'state')
|
||||
return queryset
|
||||
|
||||
def get_ticket_data_(self, obj):
|
||||
|
@ -128,16 +144,18 @@ class TicketDetailSerializer(serializers.ModelSerializer):
|
|||
key = i['field_key']
|
||||
i['field_state'] = state_fields.get(key, 1)
|
||||
i['field_value'] = ticket_data.get(key, None)
|
||||
i['field_display'] = i['field_value'] # 该字段是用于查看详情直接展示
|
||||
i['field_display'] = i['field_value'] # 该字段是用于查看详情直接展示
|
||||
if i['field_value']:
|
||||
if 'sys_user' in i['label']:
|
||||
if isinstance(i['field_value'], list):
|
||||
i['field_display'] = ','.join(list(User.objects.filter(id__in=i['field_value']).values_list('name', flat=True)))
|
||||
i['field_display'] = ','.join(list(User.objects.filter(
|
||||
id__in=i['field_value']).values_list('name', flat=True)))
|
||||
else:
|
||||
i['field_display'] = User.objects.get(id=i['field_value']).name
|
||||
elif 'deptSelect' in i['label']:
|
||||
if isinstance(i['field_value'], list):
|
||||
i['field_display'] = ','.join(list(Dept.objects.filter(id__in=i['field_value']).values_list('name', flat=True)))
|
||||
i['field_display'] = ','.join(list(Dept.objects.filter(
|
||||
id__in=i['field_value']).values_list('name', flat=True)))
|
||||
else:
|
||||
i['field_display'] = Dept.objects.get(id=i['field_value']).name
|
||||
elif i['field_type'] in ['radio', 'select']:
|
||||
|
@ -156,16 +174,20 @@ class TicketDetailSerializer(serializers.ModelSerializer):
|
|||
if item['id'] == field_value:
|
||||
return
|
||||
|
||||
|
||||
class TicketFlowSerializer(serializers.ModelSerializer):
|
||||
participant_ = UserSimpleSerializer(source='participant', read_only=True)
|
||||
state_ = StateSimpleSerializer(source='state', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = TicketFlow
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class TicketFlowSimpleSerializer(serializers.ModelSerializer):
|
||||
participant_ = UserSimpleSerializer(source='participant', read_only=True)
|
||||
state_ = StateSimpleSerializer(source='state', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = TicketFlow
|
||||
exclude = ['ticket_data']
|
||||
|
@ -174,20 +196,25 @@ class TicketFlowSimpleSerializer(serializers.ModelSerializer):
|
|||
class TicketHandleSerializer(serializers.Serializer):
|
||||
transition = serializers.PrimaryKeyRelatedField(queryset=Transition.objects.all(), label="流转id")
|
||||
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):
|
||||
suggestion = serializers.CharField(label="撤回原因", required = False)
|
||||
suggestion = serializers.CharField(label="撤回原因", required=False)
|
||||
|
||||
|
||||
class TicketCloseSerializer(serializers.Serializer):
|
||||
suggestion = serializers.CharField(label="关闭原因", required = False)
|
||||
suggestion = serializers.CharField(label="关闭原因", required=False)
|
||||
|
||||
|
||||
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='发送给谁去加签')
|
||||
|
||||
|
||||
class TicketAddNodeEndSerializer(serializers.Serializer):
|
||||
suggestion = serializers.CharField(label="加签意见", required = False)
|
||||
suggestion = serializers.CharField(label="加签意见", required=False)
|
||||
|
||||
|
||||
class TicketDestorySerializer(serializers.Serializer):
|
||||
ids = serializers.ListField(child=serializers.PrimaryKeyRelatedField(queryset=Ticket.objects.all()), label='工单ID列表')
|
|
@ -1,6 +1,4 @@
|
|||
from apps.wf.serializers import CustomFieldSerializer
|
||||
from apps.wf.serializers import TicketSerializer, TicketSimpleSerializer
|
||||
from typing import Tuple
|
||||
from apps.wf.serializers import TicketSimpleSerializer
|
||||
from apps.system.models import User
|
||||
from apps.wf.models import CustomField, State, Ticket, TicketFlow, Transition, Workflow
|
||||
from rest_framework.exceptions import APIException, PermissionDenied, ValidationError
|
||||
|
@ -10,23 +8,24 @@ import random
|
|||
from .scripts import GetParticipants, HandleScripts
|
||||
from apps.utils.queryset import get_parent_queryset
|
||||
|
||||
|
||||
class WfService(object):
|
||||
@staticmethod
|
||||
def get_worlflow_states(workflow:Workflow):
|
||||
def get_worlflow_states(workflow: Workflow):
|
||||
"""
|
||||
获取工作流状态列表
|
||||
"""
|
||||
return State.objects.filter(workflow=workflow, is_deleted=False).order_by('sort')
|
||||
|
||||
@staticmethod
|
||||
def get_workflow_transitions(workflow:Workflow):
|
||||
def get_workflow_transitions(workflow: Workflow):
|
||||
"""
|
||||
获取工作流流转列表
|
||||
"""
|
||||
return Transition.objects.filter(workflow=workflow, is_deleted=False)
|
||||
|
||||
@staticmethod
|
||||
def get_workflow_start_state(workflow:Workflow):
|
||||
def get_workflow_start_state(workflow: Workflow):
|
||||
"""
|
||||
获取工作流初始状态
|
||||
"""
|
||||
|
@ -37,7 +36,7 @@ class WfService(object):
|
|||
raise APIException('工作流状态配置错误')
|
||||
|
||||
@staticmethod
|
||||
def get_workflow_end_state(workflow:Workflow):
|
||||
def get_workflow_end_state(workflow: Workflow):
|
||||
"""
|
||||
获取工作流结束状态
|
||||
"""
|
||||
|
@ -48,35 +47,36 @@ class WfService(object):
|
|||
raise APIException('工作流状态配置错误')
|
||||
|
||||
@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')
|
||||
|
||||
@staticmethod
|
||||
def get_workflow_custom_fields_list(workflow:Workflow):
|
||||
def get_workflow_custom_fields_list(workflow: Workflow):
|
||||
"""
|
||||
获取工单字段key List
|
||||
"""
|
||||
return list(CustomField.objects.filter(is_deleted=False, workflow=workflow).order_by('sort').values_list('field_key', flat=True))
|
||||
return list(CustomField.objects.filter(is_deleted=False,
|
||||
workflow=workflow).order_by('sort').values_list('field_key', flat=True))
|
||||
|
||||
@classmethod
|
||||
def get_ticket_transitions(cls, ticket:Ticket):
|
||||
def get_ticket_transitions(cls, ticket: Ticket):
|
||||
"""
|
||||
获取工单当前状态下可用的流转条件
|
||||
"""
|
||||
return cls.get_state_transitions(ticket.state)
|
||||
|
||||
@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()
|
||||
|
||||
@classmethod
|
||||
def get_ticket_steps(cls, ticket:Ticket):
|
||||
def get_ticket_steps(cls, ticket: Ticket):
|
||||
steps = cls.get_worlflow_states(ticket.workflow)
|
||||
nsteps_list = []
|
||||
for i in steps:
|
||||
|
@ -85,14 +85,14 @@ class WfService(object):
|
|||
return nsteps_list
|
||||
|
||||
@classmethod
|
||||
def get_ticket_transitions(cls, ticket:Ticket):
|
||||
def get_ticket_transitions(cls, ticket: Ticket):
|
||||
"""
|
||||
获取工单可执行的操作
|
||||
"""
|
||||
return cls.get_state_transitions(ticket.state)
|
||||
|
||||
@classmethod
|
||||
def get_transition_by_args(cls, kwargs:dict):
|
||||
def get_transition_by_args(cls, kwargs: dict):
|
||||
"""
|
||||
查询并获取流转
|
||||
"""
|
||||
|
@ -100,41 +100,42 @@ class WfService(object):
|
|||
return Transition.objects.filter(**kwargs).all()
|
||||
|
||||
@classmethod
|
||||
def get_ticket_sn(cls, workflow:Workflow):
|
||||
def get_ticket_sn(cls, workflow: Workflow):
|
||||
"""
|
||||
生成工单流水号
|
||||
"""
|
||||
now = timezone.now()
|
||||
today = str(now)[:10]+' 00:00:00'
|
||||
next_day = str(now+timedelta(days=1))[:10]+' 00:00:00'
|
||||
ticket_day_count_new = Ticket.objects.filter(create_time__gte=today, create_time__lte=next_day, workflow=workflow).count()+1
|
||||
ticket_day_count_new = Ticket.objects.filter(
|
||||
create_time__gte=today, create_time__lte=next_day, workflow=workflow).count()+1
|
||||
return '%s_%04d%02d%02d%04d' % (workflow.sn_prefix, now.year, now.month, now.day, ticket_day_count_new)
|
||||
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_next_state_by_transition_and_ticket_info(cls, ticket:Ticket, transition: Transition, new_ticket_data:dict={})->object:
|
||||
def get_next_state_by_transition_and_ticket_info(cls, ticket: Ticket,
|
||||
transition: Transition, new_ticket_data: dict = {}) -> object:
|
||||
"""
|
||||
获取下个节点状态
|
||||
"""
|
||||
source_state = ticket.state
|
||||
destination_state = transition.destination_state
|
||||
ticket_all_value = cls.get_ticket_all_field_value(ticket)
|
||||
ticket_all_value.update(**new_ticket_data)
|
||||
for key, value in ticket_all_value.items():
|
||||
if isinstance(ticket_all_value[key], str):
|
||||
ticket_all_value[key] = "'" + ticket_all_value[key] + "'"
|
||||
if isinstance(ticket_all_value[key], str):
|
||||
ticket_all_value[key] = "'" + ticket_all_value[key] + "'"
|
||||
if transition.condition_expression:
|
||||
for i in transition.condition_expression:
|
||||
expression = i['expression'].format(**ticket_all_value)
|
||||
import datetime, time # 用于支持条件表达式中对时间的操作
|
||||
if eval(expression, {'__builtins__':None}, {'datetime':datetime, 'time':time}):
|
||||
import datetime
|
||||
import time # 用于支持条件表达式中对时间的操作
|
||||
if eval(expression, {'__builtins__': None}, {'datetime': datetime, 'time': time}):
|
||||
destination_state = State.objects.get(pk=i['target_state'])
|
||||
return destination_state
|
||||
return destination_state
|
||||
|
||||
@classmethod
|
||||
def get_ticket_state_participant_info(cls, state:State, ticket:Ticket, new_ticket_data:dict={}, handler:User=None):
|
||||
def get_ticket_state_participant_info(cls, state: State,
|
||||
ticket: Ticket, new_ticket_data: dict = {}, handler: User = None):
|
||||
"""
|
||||
获取工单目标状态实际的处理人, 处理人类型
|
||||
"""
|
||||
|
@ -143,29 +144,30 @@ class WfService(object):
|
|||
回到初始状态
|
||||
"""
|
||||
return dict(destination_participant_type=State.PARTICIPANT_TYPE_PERSONAL,
|
||||
destination_participant=ticket.create_by.id,
|
||||
multi_all_person={})
|
||||
destination_participant=ticket.create_by.id,
|
||||
multi_all_person={})
|
||||
elif state.type == State.STATE_TYPE_END:
|
||||
"""
|
||||
到达结束状态
|
||||
"""
|
||||
return dict(destination_participant_type=0,
|
||||
destination_participant=0,
|
||||
multi_all_person={})
|
||||
destination_participant=0,
|
||||
multi_all_person={})
|
||||
multi_all_person_dict = {}
|
||||
destination_participant_type, destination_participant = state.participant_type, state.participant
|
||||
if destination_participant_type == State.PARTICIPANT_TYPE_FIELD:
|
||||
destination_participant = new_ticket_data.get(destination_participant, 0) if destination_participant in new_ticket_data \
|
||||
else Ticket.ticket_data.get(destination_participant, 0)
|
||||
destination_participant = new_ticket_data.get(destination_participant, 0) if destination_participant \
|
||||
in new_ticket_data else Ticket.ticket_data.get(destination_participant, 0)
|
||||
|
||||
elif destination_participant_type == State.PARTICIPANT_TYPE_FORMCODE:#代码获取
|
||||
elif destination_participant_type == State.PARTICIPANT_TYPE_FORMCODE: # 代码获取
|
||||
destination_participant = getattr(GetParticipants, destination_participant)(
|
||||
state=state, ticket=ticket, new_ticket_data=new_ticket_data, hander=handler)
|
||||
|
||||
elif destination_participant_type == State.PARTICIPANT_TYPE_DEPT:#部门
|
||||
destination_participant = list(User.objects.filter(dept__in=destination_participant).values_list('id', flat=True))
|
||||
elif destination_participant_type == State.PARTICIPANT_TYPE_DEPT: # 部门
|
||||
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)
|
||||
# 如果选择了角色, 需要走过滤策略
|
||||
if state.filter_policy == 1:
|
||||
|
@ -181,7 +183,7 @@ class WfService(object):
|
|||
if type(destination_participant) == list:
|
||||
destination_participant_type = State.PARTICIPANT_TYPE_MULTI
|
||||
destination_participant = list(set(destination_participant))
|
||||
if len(destination_participant) == 1: # 如果只有一个人
|
||||
if len(destination_participant) == 1: # 如果只有一个人
|
||||
destination_participant_type = State.PARTICIPANT_TYPE_PERSONAL
|
||||
destination_participant = destination_participant[0]
|
||||
else:
|
||||
|
@ -191,14 +193,14 @@ class WfService(object):
|
|||
destination_participant = random.choice(destination_participant)
|
||||
elif state.distribute_type == State.STATE_DISTRIBUTE_TYPE_ALL:
|
||||
for i in destination_participant:
|
||||
multi_all_person_dict[i]={}
|
||||
multi_all_person_dict[i] = {}
|
||||
|
||||
return dict(destination_participant_type=destination_participant_type,
|
||||
destination_participant=destination_participant,
|
||||
multi_all_person=multi_all_person_dict)
|
||||
destination_participant=destination_participant,
|
||||
multi_all_person=multi_all_person_dict)
|
||||
|
||||
@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)
|
||||
if not transitions:
|
||||
return dict(permission=True, msg="工单当前状态无需操作")
|
||||
|
@ -209,7 +211,8 @@ class WfService(object):
|
|||
if participant_type == State.PARTICIPANT_TYPE_PERSONAL:
|
||||
if user.id != participant:
|
||||
return dict(permission=False, msg="非当前处理人", need_accept=False)
|
||||
elif participant_type in [State.PARTICIPANT_TYPE_MULTI, State.PARTICIPANT_TYPE_DEPT, State.PARTICIPANT_TYPE_ROLE]:
|
||||
elif participant_type in [State.PARTICIPANT_TYPE_MULTI,
|
||||
State.PARTICIPANT_TYPE_DEPT, State.PARTICIPANT_TYPE_ROLE]:
|
||||
if user.id not in participant:
|
||||
return dict(permission=False, msg="非当前处理人", need_accept=False)
|
||||
current_participant_count = len(participant)
|
||||
|
@ -218,7 +221,7 @@ class WfService(object):
|
|||
pass
|
||||
else:
|
||||
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:
|
||||
return dict(permission=False, msg="非当前处理人", need_accept=False)
|
||||
return dict(permission=False, msg="需要先接单再处理", need_accept=True)
|
||||
|
@ -227,7 +230,7 @@ class WfService(object):
|
|||
return dict(permission=True, msg="", need_accept=False)
|
||||
|
||||
@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
|
||||
:param dict_obj:
|
||||
|
@ -243,7 +246,7 @@ class WfService(object):
|
|||
return True
|
||||
|
||||
@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
|
||||
|
@ -259,14 +262,15 @@ class WfService(object):
|
|||
return field_info_dict
|
||||
|
||||
@classmethod
|
||||
def handle_ticket(cls, ticket:Ticket, transition: Transition, new_ticket_data:dict={}, handler:User=None,
|
||||
suggestion:str='', created:bool=False, by_timer:bool=False, by_task:bool=False, by_hook:bool=False):
|
||||
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):
|
||||
|
||||
source_state = ticket.state
|
||||
source_ticket_data = ticket.ticket_data
|
||||
|
||||
# 校验处理权限
|
||||
if not handler or not created: # 没有处理人意味着系统触发不校验处理权限
|
||||
if not handler or not created: # 没有处理人意味着系统触发不校验处理权限
|
||||
result = WfService.ticket_handle_permission_check(ticket, handler)
|
||||
if result.get('permission') is False:
|
||||
raise PermissionDenied(result.get('msg'))
|
||||
|
@ -281,10 +285,11 @@ class WfService(object):
|
|||
destination_state = cls.get_next_state_by_transition_and_ticket_info(ticket, transition, new_ticket_data)
|
||||
multi_all_person = ticket.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):
|
||||
participant_info = WfService.get_ticket_state_participant_info(destination_state, ticket, new_ticket_data)
|
||||
participant_info = WfService.get_ticket_state_participant_info(
|
||||
destination_state, ticket, new_ticket_data)
|
||||
destination_participant_type = participant_info.get('destination_participant_type', 0)
|
||||
destination_participant = participant_info.get('destination_participant', 0)
|
||||
multi_all_person = {}
|
||||
|
@ -321,33 +326,33 @@ class WfService(object):
|
|||
# 只更新必填和可选的字段
|
||||
if not created:
|
||||
for key, value in source_state.state_fields.items():
|
||||
if value in (State.STATE_FIELD_REQUIRED, State.STATE_FIELD_OPTIONAL):
|
||||
if key in new_ticket_data:
|
||||
source_ticket_data[key] = new_ticket_data[key]
|
||||
if value in (State.STATE_FIELD_REQUIRED, State.STATE_FIELD_OPTIONAL):
|
||||
if key in new_ticket_data:
|
||||
source_ticket_data[key] = new_ticket_data[key]
|
||||
ticket.ticket_data = source_ticket_data
|
||||
ticket.save()
|
||||
|
||||
# 更新工单流转记录
|
||||
if not by_task:
|
||||
TicketFlow.objects.create(ticket=ticket, state=source_state, ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL,
|
||||
participant=handler, transition=transition)
|
||||
TicketFlow.objects.create(ticket=ticket, state=source_state,
|
||||
ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL,
|
||||
participant=handler, transition=transition)
|
||||
|
||||
if created:
|
||||
if source_state.participant_cc:
|
||||
TicketFlow.objects.create(ticket=ticket, state=source_state,
|
||||
participant_type=0, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_CC,
|
||||
participant=None, participant_cc=source_state.participant_cc)
|
||||
participant_type=0, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_CC,
|
||||
participant=None, participant_cc=source_state.participant_cc)
|
||||
|
||||
# 目标状态需要抄送
|
||||
if destination_state.participant_cc:
|
||||
TicketFlow.objects.create(ticket=ticket, state=destination_state,
|
||||
participant_type=0, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_CC,
|
||||
participant=None, participant_cc=destination_state.participant_cc)
|
||||
participant_type=0, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_CC,
|
||||
participant=None, participant_cc=destination_state.participant_cc)
|
||||
|
||||
# 如果目标状态是脚本则执行
|
||||
if destination_state.participant_type == State.PARTICIPANT_TYPE_ROBOT:
|
||||
getattr(HandleScripts, destination_state.participant)(ticket)
|
||||
|
||||
return ticket
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
from django.db.models import base
|
||||
from rest_framework import urlpatterns
|
||||
from apps.wf.views import CustomFieldViewSet, FromCodeListView, StateViewSet, TicketFlowViewSet, TicketViewSet, TransitionViewSet, WorkflowViewSet
|
||||
from apps.wf.views import CustomFieldViewSet, FromCodeListView, StateViewSet, TicketFlowViewSet, \
|
||||
TicketViewSet, TransitionViewSet, WorkflowViewSet
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
|
@ -18,4 +17,3 @@ urlpatterns = [
|
|||
path(API_BASE_URL + 'participant_from_code', FromCodeListView.as_view()),
|
||||
path(API_BASE_URL, include(router.urls)),
|
||||
]
|
||||
|
||||
|
|
157
apps/wf/views.py
157
apps/wf/views.py
|
@ -1,26 +1,26 @@
|
|||
from django.utils import timezone
|
||||
from django.db import transaction
|
||||
from django.db.models import query
|
||||
from rest_framework.utils import serializer_helpers
|
||||
from rest_framework.views import APIView
|
||||
from apps.system.models import User
|
||||
from apps.utils.viewsets import CustomModelViewSet
|
||||
from apps.wf.filters import TicketFilterSet
|
||||
from django.core.exceptions import AppRegistryNotReady
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import serializers
|
||||
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin
|
||||
from apps.wf.serializers import CustomFieldCreateUpdateSerializer, CustomFieldSerializer, StateSerializer, TicketAddNodeEndSerializer, TicketAddNodeSerializer, TicketCloseSerializer, TicketCreateSerializer, TicketDestorySerializer, TicketFlowSerializer, TicketFlowSimpleSerializer, TicketHandleSerializer, TicketRetreatSerializer, TicketSerializer, TransitionSerializer, WorkflowSerializer, TicketListSerializer, TicketDetailSerializer
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin, ListModelMixin, \
|
||||
RetrieveModelMixin, UpdateModelMixin
|
||||
from apps.wf.serializers import CustomFieldCreateUpdateSerializer, CustomFieldSerializer, StateSerializer, \
|
||||
TicketAddNodeEndSerializer, TicketAddNodeSerializer, TicketCloseSerializer, \
|
||||
TicketCreateSerializer, TicketDestorySerializer, TicketFlowSerializer, \
|
||||
TicketHandleSerializer, TicketRetreatSerializer, \
|
||||
TicketSerializer, TransitionSerializer, WorkflowSerializer, \
|
||||
TicketListSerializer, TicketDetailSerializer
|
||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||
from rest_framework.decorators import action, api_view
|
||||
from rest_framework.decorators import action
|
||||
from apps.wf.models import CustomField, Ticket, Workflow, State, Transition, TicketFlow
|
||||
from apps.utils.mixins import CreateUpdateCustomMixin, CreateUpdateModelAMixin
|
||||
from apps.wf.services import WfService
|
||||
from rest_framework.exceptions import APIException, PermissionDenied
|
||||
from rest_framework.exceptions import APIException
|
||||
from rest_framework import status
|
||||
from django.db.models import Count
|
||||
from .scripts import GetParticipants, HandleScripts
|
||||
from .scripts import GetParticipants
|
||||
|
||||
|
||||
# Create your views here.
|
||||
|
@ -31,6 +31,7 @@ class FromCodeListView(APIView):
|
|||
"""
|
||||
return Response(GetParticipants.all_funcs)
|
||||
|
||||
|
||||
class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet):
|
||||
perms_map = {'get': '*', 'post': 'workflow:create',
|
||||
'put': 'workflow:update', 'delete': 'workflow:delete'}
|
||||
|
@ -41,7 +42,8 @@ class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet):
|
|||
ordering_fields = ['create_time']
|
||||
ordering = ['-create_time']
|
||||
|
||||
@action(methods=['get'], detail=True, perms_map={'get':'workflow:update'}, pagination_class=None, serializer_class=StateSerializer)
|
||||
@action(methods=['get'], detail=True, perms_map={'get': 'workflow:update'},
|
||||
pagination_class=None, serializer_class=StateSerializer)
|
||||
def states(self, request, pk=None):
|
||||
"""
|
||||
工作流下的状态节点
|
||||
|
@ -50,7 +52,8 @@ class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet):
|
|||
serializer = self.serializer_class(instance=WfService.get_worlflow_states(wf), many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(methods=['get'], detail=True, perms_map={'get':'workflow:update'}, pagination_class=None, serializer_class=TransitionSerializer)
|
||||
@action(methods=['get'], detail=True, perms_map={'get': 'workflow:update'},
|
||||
pagination_class=None, serializer_class=TransitionSerializer)
|
||||
def transitions(self, request, pk=None):
|
||||
"""
|
||||
工作流下的流转规则
|
||||
|
@ -59,21 +62,23 @@ class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet):
|
|||
serializer = self.serializer_class(instance=WfService.get_workflow_transitions(wf), many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(methods=['get'], detail=True, perms_map={'get':'workflow:update'}, pagination_class=None, serializer_class=CustomFieldSerializer)
|
||||
@action(methods=['get'], detail=True, perms_map={'get': 'workflow:update'},
|
||||
pagination_class=None, serializer_class=CustomFieldSerializer)
|
||||
def customfields(self, request, pk=None):
|
||||
"""
|
||||
工作流下的自定义字段
|
||||
"""
|
||||
wf = self.get_object()
|
||||
serializer = self.serializer_class(instance=CustomField.objects.filter(workflow=wf, is_deleted=False).order_by('sort'), many=True)
|
||||
serializer = self.serializer_class(instance=CustomField.objects.filter(
|
||||
workflow=wf, is_deleted=False).order_by('sort'), many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(methods=['get'], detail=True, perms_map={'get':'workflow:init'})
|
||||
@action(methods=['get'], detail=True, perms_map={'get': 'workflow:init'})
|
||||
def init(self, request, pk=None):
|
||||
"""
|
||||
新建工单初始化
|
||||
"""
|
||||
ret={}
|
||||
ret = {}
|
||||
wf = self.get_object()
|
||||
start_state = WfService.get_workflow_start_state(wf)
|
||||
transitions = WfService.get_state_transitions(start_state)
|
||||
|
@ -88,27 +93,30 @@ class WorkflowViewSet(CreateUpdateModelAMixin, ModelViewSet):
|
|||
ret['field_list'] = field_list
|
||||
return Response(ret)
|
||||
|
||||
|
||||
class StateViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet):
|
||||
perms_map = {'get':'*', 'post':'workflow:update',
|
||||
'put':'workflow:update', 'delete':'workflow:update'}
|
||||
perms_map = {'get': '*', 'post': 'workflow:update',
|
||||
'put': 'workflow:update', 'delete': 'workflow:update'}
|
||||
queryset = State.objects.all()
|
||||
serializer_class = StateSerializer
|
||||
search_fields = ['name']
|
||||
filterset_fields = ['workflow']
|
||||
ordering = ['sort']
|
||||
|
||||
|
||||
class TransitionViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet):
|
||||
perms_map = {'get':'*', 'post':'workflow:update',
|
||||
'put':'workflow:update', 'delete':'workflow:update'}
|
||||
perms_map = {'get': '*', 'post': 'workflow:update',
|
||||
'put': 'workflow:update', 'delete': 'workflow:update'}
|
||||
queryset = Transition.objects.all()
|
||||
serializer_class = TransitionSerializer
|
||||
search_fields = ['name']
|
||||
filterset_fields = ['workflow']
|
||||
ordering = ['id']
|
||||
|
||||
|
||||
class CustomFieldViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin, DestroyModelMixin, GenericViewSet):
|
||||
perms_map = {'get':'*', 'post':'workflow:update',
|
||||
'put':'workflow:update', 'delete':'workflow:update'}
|
||||
perms_map = {'get': '*', 'post': 'workflow:update',
|
||||
'put': 'workflow:update', 'delete': 'workflow:update'}
|
||||
queryset = CustomField.objects.all()
|
||||
serializer_class = CustomFieldSerializer
|
||||
search_fields = ['field_name']
|
||||
|
@ -120,8 +128,9 @@ class CustomFieldViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin,
|
|||
return CustomFieldCreateUpdateSerializer
|
||||
return super().get_serializer_class()
|
||||
|
||||
|
||||
class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, RetrieveModelMixin, GenericViewSet):
|
||||
perms_map = {'get':'*', 'post':'ticket:create'}
|
||||
perms_map = {'get': '*', 'post': 'ticket:create'}
|
||||
queryset = Ticket.objects.all()
|
||||
serializer_class = TicketSerializer
|
||||
search_fields = ['title']
|
||||
|
@ -154,7 +163,7 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
|||
rdata = request.data
|
||||
serializer = self.get_serializer(data=rdata)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
vdata = serializer.validated_data #校验之后的数据
|
||||
vdata = serializer.validated_data # 校验之后的数据
|
||||
start_state = WfService.get_workflow_start_state(vdata['workflow'])
|
||||
transition = vdata.pop('transition')
|
||||
ticket_data = vdata['ticket_data']
|
||||
|
@ -171,26 +180,26 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
|||
save_ticket_data[key] = ticket_data[key]
|
||||
|
||||
ticket = serializer.save(state=start_state,
|
||||
create_by=request.user,
|
||||
create_time=timezone.now(),
|
||||
act_state=Ticket.TICKET_ACT_STATE_DRAFT,
|
||||
belong_dept=request.user.dept,
|
||||
ticket_data=save_ticket_data) # 先创建出来
|
||||
create_by=request.user,
|
||||
create_time=timezone.now(),
|
||||
act_state=Ticket.TICKET_ACT_STATE_DRAFT,
|
||||
belong_dept=request.user.dept,
|
||||
ticket_data=save_ticket_data) # 先创建出来
|
||||
# 更新title和sn
|
||||
title = vdata.get('title', '')
|
||||
title_template = ticket.workflow.title_template
|
||||
if title_template:
|
||||
all_ticket_data = {**rdata, **ticket_data}
|
||||
title = title_template.format(**all_ticket_data)
|
||||
sn = WfService.get_ticket_sn(ticket.workflow) # 流水号
|
||||
sn = WfService.get_ticket_sn(ticket.workflow) # 流水号
|
||||
ticket.sn = sn
|
||||
ticket.title = title
|
||||
ticket.save()
|
||||
ticket = WfService.handle_ticket(ticket=ticket, transition=transition, new_ticket_data=ticket_data,
|
||||
handler=request.user, created=True)
|
||||
handler=request.user, created=True)
|
||||
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):
|
||||
"""
|
||||
工单待办聚合
|
||||
|
@ -199,10 +208,10 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
|||
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])
|
||||
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)
|
||||
|
||||
@action(methods=['post'], detail=True, perms_map={'post':'*'})
|
||||
@action(methods=['post'], detail=True, perms_map={'post': '*'})
|
||||
@transaction.atomic
|
||||
def handle(self, request, pk=None):
|
||||
"""
|
||||
|
@ -216,11 +225,11 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
|||
new_ticket_data.update(**vdata['ticket_data'])
|
||||
|
||||
ticket = WfService.handle_ticket(ticket=ticket, transition=vdata['transition'],
|
||||
new_ticket_data=new_ticket_data, handler=request.user, suggestion=vdata['suggestion'])
|
||||
new_ticket_data=new_ticket_data, handler=request.user,
|
||||
suggestion=vdata['suggestion'])
|
||||
return Response(TicketSerializer(instance=ticket).data)
|
||||
|
||||
|
||||
@action(methods=['get'], detail=True, perms_map={'get':'*'})
|
||||
@action(methods=['get'], detail=True, perms_map={'get': '*'})
|
||||
def flowsteps(self, request, pk=None):
|
||||
"""
|
||||
工单流转step, 用于显示当前状态的step图(线性结构)
|
||||
|
@ -229,7 +238,7 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
|||
steps = WfService.get_ticket_steps(ticket)
|
||||
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):
|
||||
"""
|
||||
工单流转记录
|
||||
|
@ -239,7 +248,7 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
|||
serializer = TicketFlowSerializer(instance=flowlogs, many=True)
|
||||
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):
|
||||
"""
|
||||
获取工单可执行的操作
|
||||
|
@ -248,7 +257,7 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
|||
transitions = WfService.get_ticket_transitions(ticket)
|
||||
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):
|
||||
"""
|
||||
接单,当工单当前处理人实际为多个人时(角色、部门、多人都有可能, 注意角色和部门有可能实际只有一人)
|
||||
|
@ -261,14 +270,16 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
|||
ticket.save()
|
||||
# 接单日志
|
||||
# 更新工单流转记录
|
||||
TicketFlow.objects.create(ticket=ticket, state=ticket.state, ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||
suggestion='', participant_type=State.PARTICIPANT_TYPE_PERSONAL, intervene_type=Transition.TRANSITION_ATTRIBUTE_TYPE_ACCEPT,
|
||||
participant=request.user, transition=None)
|
||||
TicketFlow.objects.create(ticket=ticket, state=ticket.state,
|
||||
ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||
suggestion='', participant_type=State.PARTICIPANT_TYPE_PERSONAL,
|
||||
intervene_type=Transition.TRANSITION_ATTRIBUTE_TYPE_ACCEPT,
|
||||
participant=request.user, transition=None)
|
||||
return Response()
|
||||
else:
|
||||
raise APIException('无需接单')
|
||||
|
||||
@action(methods=['post'], detail=True, perms_map={'post':'*'})
|
||||
@action(methods=['post'], detail=True, perms_map={'post': '*'})
|
||||
def retreat(self, request, pk=None):
|
||||
"""
|
||||
撤回工单,允许创建人在指定状态撤回工单至初始状态,状态设置中开启允许撤回
|
||||
|
@ -285,13 +296,15 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
|||
ticket.act_state = Ticket.TICKET_ACT_STATE_RETREAT
|
||||
ticket.save()
|
||||
# 更新流转记录
|
||||
suggestion = request.data.get('suggestion', '') # 撤回原因
|
||||
TicketFlow.objects.create(ticket=ticket, state=ticket.state, ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_RETREAT,
|
||||
participant=request.user, transition=None)
|
||||
suggestion = request.data.get('suggestion', '') # 撤回原因
|
||||
TicketFlow.objects.create(ticket=ticket, state=ticket.state,
|
||||
ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL,
|
||||
intervene_type=Transition.TRANSITION_INTERVENE_TYPE_RETREAT,
|
||||
participant=request.user, transition=None)
|
||||
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):
|
||||
"""
|
||||
加签
|
||||
|
@ -305,13 +318,15 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
|||
ticket.add_node_man = request.user
|
||||
ticket.save()
|
||||
# 更新流转记录
|
||||
suggestion = request.data.get('suggestion', '') # 加签说明
|
||||
TicketFlow.objects.create(ticket=ticket, state=ticket.state, ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_ADD_NODE,
|
||||
participant=request.user, transition=None)
|
||||
suggestion = request.data.get('suggestion', '') # 加签说明
|
||||
TicketFlow.objects.create(ticket=ticket, state=ticket.state,
|
||||
ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL,
|
||||
intervene_type=Transition.TRANSITION_INTERVENE_TYPE_ADD_NODE,
|
||||
participant=request.user, transition=None)
|
||||
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):
|
||||
"""
|
||||
加签完成
|
||||
|
@ -323,20 +338,22 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
|||
ticket.add_node_man = None
|
||||
ticket.save()
|
||||
# 更新流转记录
|
||||
suggestion = request.data.get('suggestion', '') # 加签意见
|
||||
TicketFlow.objects.create(ticket=ticket, state=ticket.state, ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_ADD_NODE_END,
|
||||
participant=request.user, transition=None)
|
||||
suggestion = request.data.get('suggestion', '') # 加签意见
|
||||
TicketFlow.objects.create(ticket=ticket, state=ticket.state,
|
||||
ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL,
|
||||
intervene_type=Transition.TRANSITION_INTERVENE_TYPE_ADD_NODE_END,
|
||||
participant=request.user, transition=None)
|
||||
return Response()
|
||||
|
||||
|
||||
@action(methods=['post'], detail=True, perms_map={'post':'*'}, serializer_class=TicketCloseSerializer)
|
||||
@action(methods=['post'], detail=True, perms_map={'post': '*'},
|
||||
serializer_class=TicketCloseSerializer)
|
||||
def close(self, request, pk=None):
|
||||
"""
|
||||
关闭工单(创建人在初始状态)
|
||||
"""
|
||||
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)
|
||||
ticket.state = end_state
|
||||
ticket.participant_type = 0
|
||||
|
@ -344,15 +361,18 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
|||
ticket.act_state = Ticket.TICKET_ACT_STATE_CLOSED
|
||||
ticket.save()
|
||||
# 更新流转记录
|
||||
suggestion = request.data.get('suggestion', '') # 关闭原因
|
||||
TicketFlow.objects.create(ticket=ticket, state=ticket.state, ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL, intervene_type=Transition.TRANSITION_INTERVENE_TYPE_CLOSE,
|
||||
participant=request.user, transition=None)
|
||||
suggestion = request.data.get('suggestion', '') # 关闭原因
|
||||
TicketFlow.objects.create(ticket=ticket, state=ticket.state,
|
||||
ticket_data=WfService.get_ticket_all_field_value(ticket),
|
||||
suggestion=suggestion, participant_type=State.PARTICIPANT_TYPE_PERSONAL,
|
||||
intervene_type=Transition.TRANSITION_INTERVENE_TYPE_CLOSE,
|
||||
participant=request.user, transition=None)
|
||||
return Response()
|
||||
else:
|
||||
return Response('工单不可关闭', status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@action(methods=['post'], detail=False, perms_map={'post':'ticket:destorys'}, serializer_class=TicketDestorySerializer)
|
||||
@action(methods=['post'], detail=False, perms_map={'post': 'ticket:destorys'},
|
||||
serializer_class=TicketDestorySerializer)
|
||||
def destorys(self, request, pk=None):
|
||||
"""
|
||||
批量物理删除
|
||||
|
@ -361,12 +381,11 @@ class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, R
|
|||
return Response()
|
||||
|
||||
|
||||
|
||||
class TicketFlowViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet):
|
||||
"""
|
||||
工单日志
|
||||
"""
|
||||
perms_map = {'get':'*'}
|
||||
perms_map = {'get': '*'}
|
||||
queryset = TicketFlow.objects.all()
|
||||
serializer_class = TicketFlowSerializer
|
||||
search_fields = ['suggestion']
|
||||
|
|
|
@ -217,8 +217,8 @@ CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler'
|
|||
|
||||
# swagger配置
|
||||
SWAGGER_SETTINGS = {
|
||||
'LOGIN_URL':'/django/login/',
|
||||
'LOGOUT_URL':'/django/logout/',
|
||||
'LOGIN_URL': '/django/login/',
|
||||
'LOGOUT_URL': '/django/logout/',
|
||||
}
|
||||
|
||||
# 日志配置
|
||||
|
|
|
@ -23,14 +23,14 @@ from rest_framework.documentation import include_docs_urls
|
|||
from django.views.generic import TemplateView
|
||||
|
||||
schema_view = get_schema_view(
|
||||
openapi.Info(
|
||||
title="EHS API",
|
||||
default_version='v1',
|
||||
contact=openapi.Contact(email="caoqianming@foxmail.com"),
|
||||
license=openapi.License(name="MIT License"),
|
||||
),
|
||||
public=True,
|
||||
permission_classes=[],
|
||||
openapi.Info(
|
||||
title="EHS API",
|
||||
default_version='v1',
|
||||
contact=openapi.Contact(email="caoqianming@foxmail.com"),
|
||||
license=openapi.License(name="MIT License"),
|
||||
),
|
||||
public=True,
|
||||
permission_classes=[],
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
|
@ -57,8 +57,7 @@ urlpatterns = [
|
|||
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.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
|
||||
static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + \
|
||||
static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
|
|
Loading…
Reference in New Issue