Compare commits

...

16 Commits

15 changed files with 182 additions and 59 deletions

View File

@ -45,5 +45,6 @@ class DeptFilterSet(filters.FilterSet):
model = Dept
fields = {
'type': ['exact', 'in'],
'name': ['exact', 'in', 'contains']
'name': ['exact', 'in', 'contains'],
"parent": ['exact', 'isnull'],
}

View File

@ -0,0 +1,100 @@
# Generated by Django 4.2.19 on 2025-02-23 02:59
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('system', '0006_auto_20241213_1249'),
]
operations = [
migrations.AlterField(
model_name='dept',
name='create_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
),
migrations.AlterField(
model_name='dept',
name='update_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
),
migrations.AlterField(
model_name='dictionary',
name='create_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
),
migrations.AlterField(
model_name='dictionary',
name='update_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
),
migrations.AlterField(
model_name='dicttype',
name='create_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
),
migrations.AlterField(
model_name='dicttype',
name='update_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
),
migrations.AlterField(
model_name='file',
name='create_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
),
migrations.AlterField(
model_name='file',
name='update_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
),
migrations.AlterField(
model_name='myschedule',
name='create_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
),
migrations.AlterField(
model_name='myschedule',
name='update_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
),
migrations.AlterField(
model_name='post',
name='create_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
),
migrations.AlterField(
model_name='post',
name='update_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
),
migrations.AlterField(
model_name='role',
name='create_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
),
migrations.AlterField(
model_name='role',
name='update_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
),
migrations.AlterField(
model_name='user',
name='belong_dept',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_belong_dept', to='system.dept', verbose_name='所属部门'),
),
migrations.AlterField(
model_name='user',
name='create_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人'),
),
migrations.AlterField(
model_name='user',
name='update_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人'),
),
]

View File

@ -300,7 +300,10 @@ class ComplexQueryMixin:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(new_qs, many=True)
return Response(serializer.data)
rdata = serializer.data
if hasattr(self, 'add_info_for_list'):
rdata = self.add_info_for_list(rdata)
return Response(rdata)
class MyLoggingMixin(object):
"""Mixin to log requests"""

View File

@ -150,7 +150,33 @@ class BaseModel(models.Model):
raise
time.sleep(0.1 * (attempt + 1))
@classmethod
def locked_get_or_create(cls, defaults: dict, **kwargs):
"""
仅用于事务内
并发安全的 get_or_create
"""
if not connection.in_atomic_block:
raise RuntimeError("locked_get_or_create 必须在事务中调用")
defaults = defaults or {}
qs = cls.objects.select_for_update().filter(**kwargs)
cnt = qs.count()
if cnt > 1:
raise RuntimeError(
f"{cls.__name__} 数据异常:定位条件 {kwargs} 命中 {cnt}"
)
if cnt == 1:
return qs.get(), False
params = {**kwargs, **defaults}
obj = cls.objects.create(**params)
return obj, True
def handle_parent(self):
pass

View File

@ -1,4 +1,3 @@
import json
import logging
from server.settings import get_sysconfig

View File

@ -1,4 +1,3 @@
from aip import AipSpeech
from django.conf import settings
import uuid
import os
@ -17,6 +16,7 @@ def generate_voice(msg: str, per: int = 0):
str: 地址
dict: result
"""
from aip import AipSpeech
client = AipSpeech(settings.BD_SP_ID, settings.BD_SP_KEY, settings.BD_SP_SECRET)
result = client.synthesis(msg, 'zh', 1, {'vol': 5, 'spd': 5, 'per': per})
# 识别正确返回语音二进制 错误则返回dict 参照下面错误码

View File

@ -1,21 +1,12 @@
import os
import cv2
from django.http import HttpResponse
from apps.utils.errors import SIGN_MAKE_FAIL
from server.settings import BASE_DIR
import numpy as np
from rest_framework.response import Response
from rest_framework.exceptions import ParseError
from apps.utils.viewsets import CustomGenericViewSet
from apps.utils.mixins import CustomCreateModelMixin
from apps.utils.serializers import GenSignatureSerializer
from rest_framework.views import APIView
from rest_framework.decorators import action
from rest_framework.serializers import Serializer
from django.core.cache import cache
import json
import requests
class SignatureViewSet(CustomCreateModelMixin, CustomGenericViewSet):
@ -29,6 +20,8 @@ class SignatureViewSet(CustomCreateModelMixin, CustomGenericViewSet):
照片生成透明签名图片
"""
import cv2
import numpy as np
path = (BASE_DIR + request.data['path']).replace('\\', '/')
try:
image = cv2.imread(path, cv2.IMREAD_UNCHANGED)

View File

@ -6,7 +6,7 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('wf', '0004_workflow_view_path2'),
('wf', '0003_workflow_view_path'),
]
operations = [

View File

@ -11,7 +11,7 @@ import random
from apps.utils.queryset import get_parent_queryset
from apps.wf.tasks import run_task
from rest_framework.exceptions import ParseError
import time
class WfService(object):
@staticmethod
@ -335,6 +335,15 @@ class WfService(object):
act_state=Ticket.TICKET_ACT_STATE_DRAFT,
belong_dept=handler.belong_dept,
ticket_data=save_ticket_data, participant_type=1, participant=handler.id) # 先创建出来
sn = WfService.get_ticket_sn(ticket.workflow) # 流水号
ticket.sn = sn
ticket.save()
if not transition:
return ticket
just_created = True # 刚创建的工单不需要校验权限
if transition and transition.source_state.type == State.STATE_TYPE_START:
# 更新title和sn
ticket_title = oinfo.get("title", "")
title_template = ticket.workflow.title_template
@ -344,13 +353,8 @@ class WfService(object):
ticket_title = title_template.format(**all_ticket_data)
except KeyError as e:
raise ParseError(f"工单标题模板中存在未定义的变量:{e}")
sn = WfService.get_ticket_sn(ticket.workflow) # 流水号
ticket.sn = sn
ticket.title = ticket_title
ticket.save()
if not transition:
return ticket
just_created = True # 刚创建的工单不需要校验权限
ticket.save(update_fields=["title"])
source_state = ticket.state
source_ticket_data = ticket.ticket_data
@ -502,7 +506,7 @@ class WfService(object):
@classmethod
def send_ticket_notice(cls, ticketflow:TicketFlow):
# 根据ticketflow发送通知
Thread(target=send_ticket_notice_t, args=(ticketflow,), daemon=True).start()
Thread(target=send_ticket_notice_t, args=(ticketflow.id,), daemon=True).start()
@classmethod
@ -538,11 +542,12 @@ class WfService(object):
participant=handler, transition=None)
cls.task_ticket(ticket=ticket)
def send_ticket_notice_t(ticketflow: TicketFlow):
def send_ticket_notice_t(ticketflowId: str):
"""
发送通知
"""
ticket = ticketflow.ticket
time.sleep(3)
ticket = TicketFlow.objects.get(id=ticketflowId).ticket
params = {'workflow': ticket.workflow.name, 'state': ticket.state.name}
if ticket.participant_type == 1:
# 发送短信通知

View File

@ -1,12 +0,0 @@
SECRET_KEY = 'xx'
DEBUG = False
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'xx',
'USER': 'postgres',
'PASSWORD': 'xx',
'HOST': 'xx',
'PORT': '5432',
}
}

BIN
db.json Normal file

Binary file not shown.

View File

@ -1,9 +1,17 @@
## 如何运行
将 server 下的 conf_e.json 以及 conf_e.py重名名为 conf.json 和 conf.py。
将 server 下的 conf_e.json 以及 conf_e.py移动到config文件夹下并重命名为 conf.json 和 conf.py。
根据自己的情况修改参数
进入虚拟环境后运行 python manage.py migrate
导入初始数据 python manage.py loaddata db.json
默认管理员账户密码为admin xtadmin123!
在项目目录下执行 python manage.py runserver 即可
运行后在 localhost:8000/api/swagger/下查看 api 文档

View File

@ -1,22 +1,21 @@
celery==5.2.3
Django==3.2.12
django-celery-beat==2.3.0
django-celery-results==2.4.0
django-cors-headers==3.11.0
django-filter==21.1
djangorestframework==3.13.1
djangorestframework-simplejwt==5.1.0
drf-yasg==1.21.3
celery==5.6.2
Django==4.2.27
django-celery-beat==2.8.1
django-celery-results==2.6.0
django-cors-headers==4.9.0
django-filter==23.5
djangorestframework==3.16.1
djangorestframework-simplejwt==5.5.1
drf-yasg==1.21.7
psutil==5.9.0
redis==4.4.0
django-redis==5.2.0
redis==7.1.0
django-redis==6.0.0
user-agents==2.2.0
daphne==4.0.0
channels-redis==4.0.0
channels-redis==4.3.0
django-restql==0.15.2
requests==2.28.1
xlwt==1.3.0
openpyxl==3.1.0
openpyxl==3.1.5
cron-descriptor==1.2.35
docxtpl==0.16.7
# deepface==0.0.79

View File

@ -3,7 +3,8 @@
"base_name": "xx平台",
"base_logo": "/media/default/logo.png",
"base_name_short": "xx",
"base_logo_side": ""
"base_logo_side": "",
"base_menucate": "dynamic"
},
"apk": {
"apk_version": "1.0",

View File

@ -14,7 +14,12 @@ EMAIL_USE_TLS = True
# 数据库配置
CACHE_LOCATION = "redis://127.0.0.1:6379/2"
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/2', # Redis URL
}
}
CELERY_BROKER_URL = "redis://127.0.0.1:6379/3"
CELERY_TASK_DEFAULT_QUEUE = BASE_PROJECT_CODE
DEBUG = True
@ -32,11 +37,6 @@ DATABASES = {
# 雪花ID
SNOW_DATACENTER_ID = 1
# 百度语音
BD_SP_ID = 'xx'
BD_SP_KEY = 'xx'
BD_SP_SECRET = 'xx'
# 运维相关
SD_PWD = 'xx'
BACKUP_PATH = '/home/xx/xx/xx'