增加请求日志

This commit is contained in:
caoqianming 2022-04-08 09:31:17 +08:00
parent a6f0e386c0
commit 680a0dceeb
50 changed files with 1421 additions and 181 deletions

View File

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

View File

@ -7,6 +7,7 @@ from rest_framework import status
from django.contrib.auth import authenticate, login, logout from django.contrib.auth import authenticate, login, logout
from rest_framework.generics import CreateAPIView from rest_framework.generics import CreateAPIView
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from apps.auth1.errors import USERNAME_OR_PASSWORD_WRONG
from apps.auth1.serializers import LoginSerializer from apps.auth1.serializers import LoginSerializer
@ -45,7 +46,7 @@ class LoginView(CreateAPIView):
if user is not None: if user is not None:
login(request, user) login(request, user)
return Response() return Response()
raise ParseError('账户名或密码错误', 'username_or_password_wrong') raise ParseError(**USERNAME_OR_PASSWORD_WRONG)
class LogoutView(APIView): class LogoutView(APIView):
authentication_classes = [] authentication_classes = []

0
apps/hrm/__init__.py Normal file
View File

3
apps/hrm/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

9
apps/hrm/apps.py Normal file
View File

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

37
apps/hrm/filters.py Normal file
View File

@ -0,0 +1,37 @@
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']
def filter_year(self, queryset, name, value):
return queryset.filter(create_time_date__year=value)
def filter_month(self, queryset, name, value):
return queryset.filter(create_time_date__month=value)
class EmployeeFilterSet(filters.FilterSet):
class Meta:
model = Employee
fields = ['job_state', 'show_atwork']
class NotWorkRemarkFilterSet(filters.FilterSet):
year = filters.NumberFilter(method='filter_year')
month = filters.NumberFilter(method='filter_month')
class Meta:
model = NotWorkRemark
fields = ['year', 'month', 'user']
def filter_year(self, queryset, name, value):
return queryset.filter(not_work_date__year=value)
def filter_month(self, queryset, name, value):
return queryset.filter(not_work_date__month=value)

View File

@ -0,0 +1,39 @@
# Generated by Django 3.2.6 on 2021-08-13 09:16
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Employee',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('number', models.CharField(blank=True, max_length=50, null=True, unique=True, verbose_name='人员编号')),
('photo', models.CharField(blank=True, max_length=1000, null=True, verbose_name='证件照')),
('ID_number', models.CharField(blank=True, max_length=100, null=True, verbose_name='身份证号')),
('gender', models.CharField(default='', max_length=10, verbose_name='性别')),
('signature', models.CharField(blank=True, max_length=200, null=True, verbose_name='签名图片')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='employee_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='employee_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='employee_user', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': '员工补充信息',
'verbose_name_plural': '员工补充信息',
},
),
]

View File

@ -0,0 +1,35 @@
# Generated by Django 3.2.6 on 2021-09-24 03:27
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('system', '0003_auto_20210812_0909'),
('hrm', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='employee',
name='academic',
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='学历'),
),
migrations.AddField(
model_name='employee',
name='birthdate',
field=models.DateField(blank=True, null=True, verbose_name='出生年月'),
),
migrations.AddField(
model_name='employee',
name='job',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='system.position', verbose_name='岗位'),
),
migrations.AddField(
model_name='employee',
name='jobstate',
field=models.IntegerField(choices=[(1, '在职'), (2, '离职')], default=1, verbose_name='在职状态'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.6 on 2021-10-18 05:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hrm', '0002_auto_20210924_1127'),
]
operations = [
migrations.AddField(
model_name='employee',
name='face_data',
field=models.JSONField(blank=True, null=True, verbose_name='人脸识别数据'),
),
]

View File

@ -0,0 +1,32 @@
# Generated by Django 3.2.9 on 2022-01-21 06:45
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('hrm', '0003_employee_face_data'),
]
operations = [
migrations.CreateModel(
name='ClockRecord',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('type', models.PositiveSmallIntegerField(choices=[(10, '上班打卡')], default=10, verbose_name='打卡类型')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='clockrecord_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='clockrecord_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -0,0 +1,37 @@
# Generated by Django 3.2.9 on 2022-01-26 05:51
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('hrm', '0004_clockrecord'),
]
operations = [
migrations.RenameField(
model_name='employee',
old_name='birthdate',
new_name='birthday',
),
migrations.RenameField(
model_name='employee',
old_name='ID_number',
new_name='id_number',
),
migrations.RenameField(
model_name='employee',
old_name='jobstate',
new_name='job_state',
),
migrations.RenameField(
model_name='employee',
old_name='academic',
new_name='qualification',
),
migrations.RemoveField(
model_name='employee',
name='job',
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 3.2.9 on 2022-02-17 13:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hrm', '0005_auto_20220126_1351'),
]
operations = [
migrations.AddField(
model_name='employee',
name='is_atwork',
field=models.BooleanField(default=False, verbose_name='当前在岗'),
),
migrations.AddField(
model_name='employee',
name='last_check_time',
field=models.DateTimeField(blank=True, null=True, verbose_name='打卡时间'),
),
]

View File

@ -0,0 +1,41 @@
# Generated by Django 3.2.9 on 2022-02-18 00:43
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('hrm', '0006_auto_20220217_2155'),
]
operations = [
migrations.AddField(
model_name='employee',
name='not_work_remark',
field=models.CharField(blank=True, max_length=200, null=True, verbose_name='当前未打卡说明'),
),
migrations.CreateModel(
name='NotWorkRemark',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('year', models.PositiveSmallIntegerField(default=2022, verbose_name='')),
('month', models.PositiveSmallIntegerField(default=2, verbose_name='')),
('day', models.PositiveSmallIntegerField(default=1, verbose_name='')),
('remark', models.CharField(blank=True, max_length=200, null=True, verbose_name='未打卡说明')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='notworkremark_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='notworkremark_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='用户')),
],
options={
'abstract': False,
},
),
]

View File

@ -0,0 +1,32 @@
# Generated by Django 3.2.9 on 2022-02-22 03:12
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('hrm', '0007_auto_20220218_0843'),
]
operations = [
migrations.RemoveField(
model_name='notworkremark',
name='day',
),
migrations.RemoveField(
model_name='notworkremark',
name='month',
),
migrations.RemoveField(
model_name='notworkremark',
name='year',
),
migrations.AddField(
model_name='notworkremark',
name='not_work_date',
field=models.DateField(default=django.utils.timezone.now, verbose_name='未打卡日期'),
preserve_default=False,
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.9 on 2022-03-17 03:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hrm', '0008_auto_20220222_1112'),
]
operations = [
migrations.AddField(
model_name='employee',
name='show_atwork',
field=models.BooleanField(default=True, verbose_name='是否展示在岗状态'),
),
]

View File

63
apps/hrm/models.py Normal file
View File

@ -0,0 +1,63 @@
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.db.models.base import Model
import django.utils.timezone as timezone
from django.db.models.query import QuerySet
from apps.system.models import CommonADModel, CommonAModel, CommonBModel, Organization, User, Dict, File,Position
from utils.model import SoftModel, BaseModel
from simple_history.models import HistoricalRecords
class Employee(CommonAModel):
"""
员工信息
"""
JOB_ON = 1
JOB_OFF = 2
jobstate_choices = (
(JOB_ON, '在职'),
(JOB_OFF, '离职'),
)
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='employee_user')
number = models.CharField('人员编号', max_length=50,null=True, blank=True, unique=True)
photo = models.CharField('证件照', max_length=1000, null=True, blank=True)
id_number = models.CharField('身份证号', max_length=100, null=True, blank=True)
gender = models.CharField('性别', max_length=10, default='')
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)
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)
class Meta:
verbose_name = '员工补充信息'
verbose_name_plural = verbose_name
def __str__(self):
return self.name
class NotWorkRemark(CommonAModel):
"""
离岗说明
"""
not_work_date = models.DateField('未打卡日期')
user = models.ForeignKey(User, verbose_name='用户', on_delete=models.CASCADE)
remark = models.CharField('未打卡说明', null=True, blank=True, max_length=200)
class ClockRecord(CommonADModel):
"""
打卡记录
"""
ClOCK_WORK1 = 10
type_choice = (
(ClOCK_WORK1, '上班打卡'),
)
type = models.PositiveSmallIntegerField('打卡类型', choices=type_choice, default=ClOCK_WORK1)

37
apps/hrm/serializers.py Normal file
View File

@ -0,0 +1,37 @@
from apps.system.models import User
from rest_framework.serializers import ModelSerializer
from rest_framework import serializers
from utils.mixins import DynamicFieldsSerializerMixin
from .models import ClockRecord, Employee, NotWorkRemark
from apps.system.serializers import OrganizationSimpleSerializer, UserSimpleSerializer
class EmployeeSerializer(DynamicFieldsSerializerMixin, ModelSerializer):
name = serializers.CharField(source='user.name', read_only=True)
dept_ = OrganizationSimpleSerializer(source='user.dept', read_only=True)
class Meta:
model = Employee
exclude = ['face_data']
class EmployeeNotWorkRemarkSerializer(ModelSerializer):
class Meta:
model = Employee
fields = ['not_work_remark']
class FaceLoginSerializer(serializers.Serializer):
base64 = serializers.CharField()
class FaceClockCreateSerializer(serializers.Serializer):
base64 = serializers.CharField()
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
fields = '__all__'

50
apps/hrm/services.py Normal file
View File

@ -0,0 +1,50 @@
from django.conf import settings
import uuid
import face_recognition
import os
from apps.hrm.models import Employee
from apps.hrm.tasks import update_all_user_facedata_cache
from apps.system.models import User
from django.core.cache import cache
class HRMService:
@classmethod
def face_compare_from_base64(cls, base64_data):
filename = str(uuid.uuid4())
filepath = settings.BASE_DIR +'/temp/' + filename +'.png'
with open(filepath, 'wb') as f:
f.write(base64_data)
try:
unknown_picture = face_recognition.load_image_file(filepath)
unknown_face_encoding = face_recognition.face_encodings(unknown_picture, num_jitters=2)[0]
os.remove(filepath)
except:
os.remove(filepath)
return None, '识别失败,请调整位置'
# 匹配人脸库
face_datas = cache.get('face_datas')
if face_datas is None:
update_all_user_facedata_cache()
face_datas = cache.get('face_datas')
face_users = cache.get('face_users')
results = face_recognition.compare_faces(face_datas,
unknown_face_encoding, tolerance=0.45)
for index, value in enumerate(results):
if value:
# 识别成功
user = User.objects.get(id=face_users[index])
return user, ''
return None, '人脸未匹配,请调整位置'
@classmethod
def get_facedata_from_img(cls, img_path):
try:
photo_path = settings.BASE_DIR + img_path
picture_of_me = face_recognition.load_image_file(photo_path)
my_face_encoding = face_recognition.face_encodings(picture_of_me, num_jitters=2)[0]
face_data_list = my_face_encoding.tolist()
return face_data_list, ''
except:
return None, '人脸数据获取失败请重新上传图片'

13
apps/hrm/signals.py Normal file
View File

@ -0,0 +1,13 @@
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
import face_recognition
import logging
logger = logging.getLogger('log')
@receiver(post_save, sender=User)
def createEmployee(sender, instance, created, **kwargs):
if created:
Employee.objects.get_or_create(user=instance)

30
apps/hrm/tasks.py Normal file
View File

@ -0,0 +1,30 @@
from __future__ import absolute_import, unicode_literals
from celery import shared_task
from apps.hrm.models import Employee
from django.core.cache import cache
@shared_task
def update_all_employee_not_atwork():
"""
将所有员工设为非在岗状态
"""
Employee.objects.all().update(is_atwork=False, last_check_time = None, not_work_remark=None)
@shared_task
def update_all_user_facedata_cache():
"""
更新人脸数据缓存
"""
facedata_queyset = Employee.objects.filter(face_data__isnull=False,
user__is_active=True).values('user', 'face_data')
face_users = []
face_datas = []
for i in facedata_queyset:
face_users.append(i['user'])
face_datas.append(i['face_data'])
cache.set('face_users', face_users, timeout=None)
cache.set('face_datas', face_datas, timeout=None)

3
apps/hrm/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

15
apps/hrm/urls.py Normal file
View File

@ -0,0 +1,15 @@
from rest_framework import urlpatterns
from apps.hrm.views import ClockRecordViewSet, EmployeeViewSet, FaceLogin, NotWorkRemarkViewSet
from django.urls import path, include
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('employee', EmployeeViewSet, basename='employee')
router.register('clock_record', ClockRecordViewSet, basename='clock_record')
router.register('not_work_remark', NotWorkRemarkViewSet, basename='not_work_reamrk')
urlpatterns = [
path('facelogin/', FaceLogin.as_view()),
path('', include(router.urls)),
]

187
apps/hrm/views.py Normal file
View File

@ -0,0 +1,187 @@
from django.shortcuts import render
from django.utils import timezone
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet, GenericViewSet
from rest_framework.mixins import UpdateModelMixin, RetrieveModelMixin, CreateModelMixin, ListModelMixin
from apps.hrm.filters import ClockRecordFilterSet, EmployeeFilterSet, NotWorkRemarkFilterSet
from apps.hrm.services import HRMService
from apps.hrm.tasks import update_all_user_facedata_cache
from apps.system.mixins import CreateUpdateModelAMixin, OptimizationMixin
from apps.hrm.models import ClockRecord, Employee, NotWorkRemark
from apps.hrm.serializers import ClockRecordListSerializer, EmployeeNotWorkRemarkSerializer, EmployeeSerializer, FaceClockCreateSerializer, FaceLoginSerializer, NotWorkRemarkListSerializer
from rest_framework.generics import CreateAPIView
from rest_framework import status
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework import exceptions
from apps.system.models import User
from apps.system.serializers import UserSimpleSerializer
from rest_framework.permissions import AllowAny
from rest_framework.decorators import action
# Create your views here.
class EmployeeViewSet(CreateUpdateModelAMixin, OptimizationMixin, UpdateModelMixin, ListModelMixin, RetrieveModelMixin, GenericViewSet):
"""
员工详细信息
"""
perms_map = {'get': '*', 'put': 'employee_update'}
queryset = Employee.objects.all()
filterset_class = EmployeeFilterSet
serializer_class = EmployeeSerializer
search_fields = ['user__name', 'number', 'user__username']
ordering = ['-pk']
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
data = request.data
serializer = self.get_serializer(instance, data=data, partial=partial)
serializer.is_valid(raise_exception=True)
photo = data.get('photo', None)
if instance.photo != photo:
f_l, msg = HRMService.get_facedata_from_img(img_path=photo)
if f_l:
serializer.save(update_by=request.user, face_data = f_l)
# 更新人脸缓存
update_all_user_facedata_cache.delay()
return Response()
return Response(msg, status=status.HTTP_400_BAD_REQUEST)
serializer.save(update_by=request.user)
return Response()
@action(methods=['post'], detail=True, perms_map={'post': 'employee_notworkremark'}
, serializer_class=EmployeeNotWorkRemarkSerializer)
def not_work_remark(self, request, pk=None):
"""
填写离岗说明
"""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
obj = self.get_object()
if not obj.is_atwork:
remark = request.data.get('not_work_remark', '')
obj.not_work_remark = remark
obj.save()
now = timezone.now()
instance, created = NotWorkRemark.objects.get_or_create(
not_work_date = now.date(),
user = obj.user,
defaults={
"not_work_date":now.date(),
"user":obj.user,
"remark":remark,
"create_by":request.user,
}
)
if not created:
instance.remark = remark
instance.update_by = request.user
instance.save()
return Response()
return Response('无需填写离岗说明', status=status.HTTP_400_BAD_REQUEST)
class ClockRecordViewSet(CreateModelMixin, ListModelMixin, GenericViewSet):
"""
打卡记录
"""
perms_map = {'get':'*', 'post':'*'}
authentication_classes = []
permission_classes = [AllowAny]
queryset = ClockRecord.objects.select_related('create_by').all()
serializer_class = ClockRecordListSerializer
filterset_class = ClockRecordFilterSet
ordering = ['-pk']
def get_serializer_class(self):
if self.action == 'create':
return FaceClockCreateSerializer
return super().get_serializer_class()
def create(self, request, *args, **kwargs):
now = timezone.now()
now_local = timezone.localtime()
if 8<=now_local.hour<=17:
base64_data = base64.urlsafe_b64decode(tran64(
request.data.get('base64').replace(' ', '+')))
user, msg = HRMService.face_compare_from_base64(base64_data)
if user:
ins, created = ClockRecord.objects.get_or_create(
create_by = user, create_time__hour__range = [8,18],
create_time__year=now_local.year, create_time__month=now_local.month,
create_time__day=now_local.day,
defaults={
'type':ClockRecord.ClOCK_WORK1,
'create_by':user,
'create_time':now
})
if not created:
ins.update_time = now
ins.save()
# 设为在岗
Employee.objects.filter(user=user).update(is_atwork=True, last_check_time=now)
return Response(UserSimpleSerializer(instance=user).data)
return Response(msg, status=status.HTTP_400_BAD_REQUEST)
return Response('非打卡时间范围', status=status.HTTP_400_BAD_REQUEST)
class NotWorkRemarkViewSet(ListModelMixin, GenericViewSet):
"""
离岗说明
"""
perms_map = {'get':'*'}
queryset = NotWorkRemark.objects.select_related('user').all()
serializer_class = NotWorkRemarkListSerializer
filterset_class = NotWorkRemarkFilterSet
ordering = ['-pk']
import base64
def tran64(s):
missing_padding = len(s) % 4
if missing_padding != 0:
s = s+'='* (4 - missing_padding)
return s
class FaceLogin(CreateAPIView):
authentication_classes = []
permission_classes = []
serializer_class = FaceLoginSerializer
def create(self, request, *args, **kwargs):
"""
人脸识别登录
"""
base64_data = base64.urlsafe_b64decode(tran64(request.data.get('base64').replace(' ', '+')))
user, msg = HRMService.face_compare_from_base64(base64_data)
if user:
refresh = RefreshToken.for_user(user)
# 可设为在岗
now = timezone.now()
now_local = timezone.localtime()
if 8<=now_local.hour<=17:
ins, created = ClockRecord.objects.get_or_create(
create_by = user, create_time__hour__range = [8,18],
create_time__year=now_local.year, create_time__month=now_local.month,
create_time__day=now_local.day,
defaults={
'type':ClockRecord.ClOCK_WORK1,
'create_by':user,
'create_time':now
})
# 设为在岗
if created:
Employee.objects.filter(user=user).update(is_atwork=True, last_check_time=now)
return Response({
'refresh': str(refresh),
'access': str(refresh.access_token),
'username':user.username,
'name':user.name
})
return Response(msg, status=status.HTTP_400_BAD_REQUEST)

1
apps/monitor/errors.py Normal file
View File

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

View File

@ -1 +0,0 @@
from django.utils.deprecation import MiddlewareMixin

View File

@ -0,0 +1,45 @@
# Generated by Django 3.2.12 on 2022-04-08 00:58
import apps.utils.snowflake
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='DrfRequestLog',
fields=[
('id', models.CharField(default=apps.utils.snowflake.IdWorker.get_id, editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('requested_at', models.DateTimeField(db_index=True)),
('response_ms', models.PositiveIntegerField(default=0)),
('path', models.CharField(db_index=True, help_text='请求地址', max_length=400)),
('view', models.CharField(blank=True, db_index=True, help_text='执行视图', max_length=400, null=True)),
('view_method', models.CharField(blank=True, db_index=True, max_length=6, null=True)),
('remote_addr', models.GenericIPAddressField()),
('host', models.URLField()),
('method', models.CharField(max_length=10)),
('query_params', models.TextField(blank=True, null=True)),
('data', models.TextField(blank=True, null=True)),
('response', models.TextField(blank=True, null=True)),
('errors', models.TextField(blank=True, null=True)),
('status_code', models.PositiveIntegerField(blank=True, db_index=True, null=True)),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'DRF请求日志',
},
),
]

View File

@ -1,3 +1,47 @@
from django.db import models from django.db import models
# Create your models here. from apps.utils.models import BaseModel
class DrfRequestLog(BaseModel):
"""Logs Django rest framework API requests"""
user = models.ForeignKey(
'system.user',
on_delete=models.SET_NULL,
null=True,
blank=True,
)
requested_at = models.DateTimeField(db_index=True)
response_ms = models.PositiveIntegerField(default=0)
path = models.CharField(
max_length=400,
db_index=True,
help_text="请求地址",
)
view = models.CharField(
max_length=400,
null=True,
blank=True,
db_index=True,
help_text="执行视图",
)
view_method = models.CharField(
max_length=6,
null=True,
blank=True,
db_index=True,
)
remote_addr = models.GenericIPAddressField()
host = models.URLField()
method = models.CharField(max_length=10)
query_params = models.TextField(null=True, blank=True)
data = models.TextField(null=True, blank=True)
response = models.TextField(null=True, blank=True)
errors = models.TextField(null=True, blank=True)
status_code = models.PositiveIntegerField(null=True, blank=True, db_index=True)
class Meta:
verbose_name = "DRF请求日志"
def __str__(self):
return "{} {}".format(self.method, self.path)

View File

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

View File

@ -11,6 +11,11 @@ from rest_framework import serializers, status
from drf_yasg import openapi from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema from drf_yasg.utils import swagger_auto_schema
from rest_framework.exceptions import NotFound from rest_framework.exceptions import NotFound
from rest_framework.mixins import ListModelMixin
from apps.monitor.models import DrfRequestLog
from apps.monitor.errors import LOG_NOT_FONED
from apps.utils.viewsets import CustomGenericViewSet
# Create your views here. # Create your views here.
@ -117,5 +122,21 @@ class LogDetailView(APIView):
data = f.read() data = f.read()
return Response(data) return Response(data)
except: except:
raise NotFound('不存在该日志') raise NotFound(**LOG_NOT_FONED)
class DrfRequestLogSerializer(serializers.ModelSerializer):
class Meta:
model = DrfRequestLog
fields = '__all__'
class DrfRequestLogViewSet(ListModelMixin, CustomGenericViewSet):
"""请求日志
请求日志
"""
perms_map = {'get':'requestlog_view'}
queryset = DrfRequestLog.objects.all()
list_serializer_class = DrfRequestLogSerializer
ordering = ['-requested_at']

View File

@ -1,2 +1,6 @@
# 自定义的错误码 SCHEDULE_WRONG = {"code":"schedule_wrong", "detail":"时间策略有误"}
from rest_framework.exceptions import ValidationError PASSWORD_NOT_SAME = {"code":"password_not_same", "detail":"新旧密码不一致"}
OLD_PASSWORD_WRONG = {"code":"old_password_wrong", "detail":"旧密码错误"}
PHONE_F_WRONG = {"code":"phone_f_wrong", "detail":"手机号格式错误"}
PHONE_EXIST = {"code":"phone_exist", "detail":"手机号已存在"}
USERNAME_EXIST = {"code":"username_exist", "detail":"账户已存在"}

View File

@ -0,0 +1,64 @@
# Generated by Django 3.2.12 on 2022-04-06 06:58
import apps.utils.snowflake
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('system', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='dept',
name='third_info',
field=models.JSONField(default=dict, verbose_name='三方系统信息'),
),
migrations.AlterField(
model_name='dept',
name='id',
field=models.CharField(default=apps.utils.snowflake.IdWorker.get_id, editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID'),
),
migrations.AlterField(
model_name='dict',
name='id',
field=models.CharField(default=apps.utils.snowflake.IdWorker.get_id, editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID'),
),
migrations.AlterField(
model_name='dicttype',
name='id',
field=models.CharField(default=apps.utils.snowflake.IdWorker.get_id, editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID'),
),
migrations.AlterField(
model_name='file',
name='id',
field=models.CharField(default=apps.utils.snowflake.IdWorker.get_id, editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID'),
),
migrations.AlterField(
model_name='permission',
name='id',
field=models.CharField(default=apps.utils.snowflake.IdWorker.get_id, editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID'),
),
migrations.AlterField(
model_name='post',
name='id',
field=models.CharField(default=apps.utils.snowflake.IdWorker.get_id, editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID'),
),
migrations.AlterField(
model_name='role',
name='id',
field=models.CharField(default=apps.utils.snowflake.IdWorker.get_id, editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID'),
),
migrations.AlterField(
model_name='user',
name='id',
field=models.CharField(default=apps.utils.snowflake.IdWorker.get_id, editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID'),
),
migrations.AlterField(
model_name='userpost',
name='id',
field=models.CharField(default=apps.utils.snowflake.IdWorker.get_id, editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID'),
),
]

View File

@ -46,6 +46,7 @@ class Dept(CommonAModel):
parent = models.ForeignKey('self', null=True, blank=True, parent = models.ForeignKey('self', null=True, blank=True,
on_delete=models.SET_NULL, verbose_name='') on_delete=models.SET_NULL, verbose_name='')
sort = models.PositiveSmallIntegerField('排序标记', default=1) sort = models.PositiveSmallIntegerField('排序标记', default=1)
third_info = models.JSONField('三方系统信息', default=dict)
class Meta: class Meta:
verbose_name = '部门' verbose_name = '部门'

View File

@ -2,11 +2,13 @@ import re
from django_celery_beat.models import PeriodicTask, CrontabSchedule, IntervalSchedule from django_celery_beat.models import PeriodicTask, CrontabSchedule, IntervalSchedule
from rest_framework import serializers from rest_framework import serializers
from django_celery_results.models import TaskResult from django_celery_results.models import TaskResult
from apps.system.errors import PHONE_EXIST, PHONE_F_WRONG, USERNAME_EXIST
from apps.utils.serializers import CustomModelSerializer from apps.utils.serializers import CustomModelSerializer
from apps.utils.vars import EXCLUDE_FIELDS, EXCLUDE_FIELDS_BASE from apps.utils.constants import EXCLUDE_FIELDS, EXCLUDE_FIELDS_BASE
from .models import (Dict, DictType, File, Dept, Permission, Post, from .models import (Dict, DictType, File, Dept, Permission, Post,
Role, User, UserPost) Role, User, UserPost)
from rest_framework.exceptions import ParseError, APIException
from django.db import transaction
class IntervalSerializer(CustomModelSerializer): class IntervalSerializer(CustomModelSerializer):
class Meta: class Meta:
@ -36,12 +38,6 @@ class PTaskSerializer(CustomModelSerializer):
model = PeriodicTask model = PeriodicTask
fields = '__all__' fields = '__all__'
@staticmethod
def setup_eager_loading(queryset):
""" Perform necessary eager loading of data. """
queryset = queryset.select_related('interval', 'crontab')
return queryset
def get_schedule(self, obj): def get_schedule(self, obj):
if obj.interval: if obj.interval:
return obj.interval.__str__() return obj.interval.__str__()
@ -188,7 +184,48 @@ class DeptCreateUpdateSerializer(CustomModelSerializer):
class Meta: class Meta:
model = Dept model = Dept
exclude = EXCLUDE_FIELDS exclude = EXCLUDE_FIELDS + ['third_info']
@transaction.atomic
def create(self, validated_data):
from apps.utils.dahua import dhClient
if dhClient:
data = {
"parentId":1,
"name":validated_data['name'],
"service":"ehs"
}
ok, res = dhClient.request('/evo-apigw/evo-brm/1.0.0/department/add',
'post',json=data)
if ok == 'success':
third_info = {'dh_id':str(res['id'])}
validated_data['third_info'] = third_info
elif ok == 'fail':
raise ParseError(**res)
else:
raise APIException(**res)
return super().create(validated_data)
@transaction.atomic
def update(self, instance, validated_data):
if instance.name != validated_data.get('name', ''):
from apps.utils.dahua import dhClient
if dhClient and instance.third_info.get('dh_id', False):
data = {
"id":instance.third_info['dh_id'],
"parentId":1,
"name":validated_data['name']
}
ok, res = dhClient.request('/evo-apigw/evo-brm/1.0.0/department/update',
'put',json=data)
if ok == 'success':
pass
elif ok == 'fail':
raise ParseError(**res)
else:
raise APIException(**res)
return super().update(instance, validated_data)
class UserSimpleSerializer(CustomModelSerializer): class UserSimpleSerializer(CustomModelSerializer):
class Meta: class Meta:
@ -214,50 +251,54 @@ class UserListSerializer(CustomModelSerializer):
queryset = queryset.prefetch_related('posts') queryset = queryset.prefetch_related('posts')
return queryset return queryset
def phone_check(phone):
re_phone = '^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
def phone_exist(phone):
if User.objects.filter(phone=phone):
raise serializers.ValidationError(**PHONE_EXIST)
def user_exist(username):
if User.objects.filter(username=username):
raise serializers.ValidationError(**USERNAME_EXIST)
return username
class UserUpdateSerializer(CustomModelSerializer): class UserUpdateSerializer(CustomModelSerializer):
""" """
用户编辑序列化 用户编辑序列化
""" """
phone = serializers.CharField(max_length=11, required=False) phone = serializers.CharField(max_length=11,
required=False, validators=[phone_check])
class Meta: class Meta:
model = User model = User
fields = ['id', 'username', 'name', 'phone', 'email', 'belong_dept', fields = ['id', 'username', 'name', 'phone', 'email', 'belong_dept',
'avatar', 'is_active', 'is_superuser'] 'avatar', 'is_active', 'is_superuser']
def validate_phone(self, phone):
re_phone = '^1[358]\d{9}$|^147\d{8}$|^176\d{8}$'
if not re.match(re_phone, phone):
raise serializers.ValidationError('手机号码不合法')
return phone
class UserCreateSerializer(CustomModelSerializer): class UserCreateSerializer(CustomModelSerializer):
""" """
创建用户序列化 创建用户序列化
""" """
username = serializers.CharField(required=True) username = serializers.CharField(required=True, validators=[user_exist])
phone = serializers.CharField(max_length=11, required=False) phone = serializers.CharField(max_length=11,
required=False, validators=[phone_check, phone_exist])
class Meta: class Meta:
model = User model = User
fields = ['id', 'username', 'name', 'phone', 'email', 'belong_dept', fields = ['id', 'username', 'name', 'phone', 'email', 'belong_dept',
'avatar', 'is_active'] 'avatar', 'is_active']
def validate_username(self, username):
if User.objects.filter(username=username):
raise serializers.ValidationError(username + ' 账号已存在')
return username
def validate_phone(self, phone):
re_phone = '^1[358]\d{9}$|^147\d{8}$|^176\d{8}$'
if not re.match(re_phone, phone):
raise serializers.ValidationError('手机号码不合法')
if User.objects.filter(phone=phone):
raise serializers.ValidationError('手机号已经被注册')
return phone
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 PTaskResultSerializer(CustomModelSerializer):
class Meta: class Meta:

View File

@ -12,10 +12,11 @@ from rest_framework.parsers import (JSONParser,
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from apps.system.errors import OLD_PASSWORD_WRONG, PASSWORD_NOT_SAME, SCHEDULE_WRONG
from apps.utils.mixins import (CustomCreateModelMixin) from apps.utils.mixins import (CustomCreateModelMixin)
from django.conf import settings from django.conf import settings
from apps.utils.permission import get_user_perms_map from apps.utils.permission import ALL_PERMS, get_user_perms_map
from apps.utils.queryset import get_child_queryset2 from apps.utils.queryset import get_child_queryset2
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
from server.celery import app as celery_app from server.celery import app as celery_app
@ -23,7 +24,7 @@ from .filters import UserFilter
from .models import (Dept, Dict, DictType, File, Permission, Post, Role, User, from .models import (Dept, Dict, DictType, File, Permission, Post, Role, User,
UserPost) UserPost)
from .serializers import (DeptCreateUpdateSerializer, DeptSerializer, DictCreateUpdateSerializer, DictSerializer, DictTypeCreateUpdateSerializer, DictTypeSerializer, from .serializers import (DeptCreateUpdateSerializer, DeptSerializer, DictCreateUpdateSerializer, DictSerializer, DictTypeCreateUpdateSerializer, DictTypeSerializer,
FileSerializer, PermissionCreateUpdateSerializer, PermissionSerializer, PostCreateUpdateSerializer, PostSerializer, FileSerializer, PasswordChangeSerializer, PermissionCreateUpdateSerializer, PermissionSerializer, PostCreateUpdateSerializer, PostSerializer,
PTaskCreateUpdateSerializer, PTaskResultSerializer, PTaskCreateUpdateSerializer, PTaskResultSerializer,
PTaskSerializer, RoleCreateUpdateSerializer, RoleSerializer, PTaskSerializer, RoleCreateUpdateSerializer, RoleSerializer,
UserCreateSerializer, UserListSerializer, UserPostCreateSerializer, UserCreateSerializer, UserListSerializer, UserPostCreateSerializer,
@ -62,8 +63,10 @@ class PTaskViewSet(CustomModelViewSet):
serializer_class = PTaskSerializer serializer_class = PTaskSerializer
create_serializer_class = PTaskCreateUpdateSerializer create_serializer_class = PTaskCreateUpdateSerializer
update_serializer_class = PTaskCreateUpdateSerializer update_serializer_class = PTaskCreateUpdateSerializer
partial_update_serializer_class = PTaskCreateUpdateSerializer
search_fields = ['name', 'task'] search_fields = ['name', 'task']
filterset_fields = ['enabled'] filterset_fields = ['enabled']
select_related_fields = ['interval', 'crontab']
ordering = ['-create_time'] ordering = ['-create_time']
@action(methods=['put'], detail=True, perms_map={'put': 'ptask_update'}) @action(methods=['put'], detail=True, perms_map={'put': 'ptask_update'})
@ -94,7 +97,7 @@ class PTaskViewSet(CustomModelViewSet):
**interval_, defaults=interval_) **interval_, defaults=interval_)
data['interval'] = interval.id data['interval'] = interval.id
except: except:
raise ParseError('时间策略有误', 'schedule_wrong') raise ParseError(**SCHEDULE_WRONG)
if timetype == 'crontab' and crontab_: if timetype == 'crontab' and crontab_:
data['interval'] = None data['interval'] = None
try: try:
@ -103,7 +106,7 @@ class PTaskViewSet(CustomModelViewSet):
**crontab_, defaults=crontab_) **crontab_, defaults=crontab_)
data['crontab'] = crontab.id data['crontab'] = crontab.id
except: except:
raise ParseError('时间策略有误', 'schedule_wrong') raise ParseError(**SCHEDULE_WRONG)
serializer = self.get_serializer(data=data) serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
serializer.save() serializer.save()
@ -128,7 +131,7 @@ class PTaskViewSet(CustomModelViewSet):
**interval_, defaults=interval_) **interval_, defaults=interval_)
data['interval'] = interval.id data['interval'] = interval.id
except: except:
raise ParseError('时间策略有误', 'schedule_wrong') raise ParseError(**SCHEDULE_WRONG)
if timetype == 'crontab' and crontab_: if timetype == 'crontab' and crontab_:
data['interval'] = None data['interval'] = None
try: try:
@ -139,7 +142,7 @@ class PTaskViewSet(CustomModelViewSet):
**crontab_, defaults=crontab_) **crontab_, defaults=crontab_)
data['crontab'] = crontab.id data['crontab'] = crontab.id
except: except:
raise ParseError('时间策略有误', 'schedule_wrong') raise ParseError(**SCHEDULE_WRONG)
instance = self.get_object() instance = self.get_object()
serializer = self.get_serializer(instance, data=data) serializer = self.get_serializer(instance, data=data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
@ -173,6 +176,7 @@ class DictTypeViewSet(CustomModelViewSet):
serializer_class = DictTypeSerializer serializer_class = DictTypeSerializer
create_serializer_class = DictTypeCreateUpdateSerializer create_serializer_class = DictTypeCreateUpdateSerializer
update_serializer_class = DictTypeCreateUpdateSerializer update_serializer_class = DictTypeCreateUpdateSerializer
partial_update_serializer_class = DictTypeCreateUpdateSerializer
search_fields = ['name'] search_fields = ['name']
@ -187,6 +191,7 @@ class DictViewSet(CustomModelViewSet):
serializer_class = DictSerializer serializer_class = DictSerializer
create_serializer_class = DictCreateUpdateSerializer create_serializer_class = DictCreateUpdateSerializer
update_serializer_class = DictCreateUpdateSerializer update_serializer_class = DictCreateUpdateSerializer
partial_update_serializer_class = DictCreateUpdateSerializer
search_fields = ['name'] search_fields = ['name']
@ -199,6 +204,7 @@ class PostViewSet(CustomModelViewSet):
serializer_class = PostSerializer serializer_class = PostSerializer
create_serializer_class = PostCreateUpdateSerializer create_serializer_class = PostCreateUpdateSerializer
update_serializer_class = PostCreateUpdateSerializer update_serializer_class = PostCreateUpdateSerializer
partial_update_serializer_class = PostCreateUpdateSerializer
search_fields = ['name', 'description'] search_fields = ['name', 'description']
@ -212,8 +218,18 @@ class PermissionViewSet(CustomModelViewSet):
serializer_class = PermissionSerializer serializer_class = PermissionSerializer
create_serializer_class = PermissionCreateUpdateSerializer create_serializer_class = PermissionCreateUpdateSerializer
update_serializer_class = PermissionCreateUpdateSerializer update_serializer_class = PermissionCreateUpdateSerializer
partial_update_serializer_class = PermissionCreateUpdateSerializer
search_fields = ['name', 'code'] search_fields = ['name', 'code']
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
def codes(self, request, pk=None):
"""获取全部权限标识
需要先请求一次swagger
"""
ALL_PERMS.sort()
return Response(ALL_PERMS)
class DeptViewSet(CustomModelViewSet): class DeptViewSet(CustomModelViewSet):
"""部门-增删改查 """部门-增删改查
@ -224,6 +240,7 @@ class DeptViewSet(CustomModelViewSet):
serializer_class = DeptSerializer serializer_class = DeptSerializer
create_serializer_class = DeptCreateUpdateSerializer create_serializer_class = DeptCreateUpdateSerializer
update_serializer_class = DeptCreateUpdateSerializer update_serializer_class = DeptCreateUpdateSerializer
partial_update_serializer_class = DeptCreateUpdateSerializer
filterset_fields = ['type'] filterset_fields = ['type']
search_fields = ['name'] search_fields = ['name']
@ -262,7 +279,7 @@ class UserPostViewSet(CreateModelMixin, DestroyModelMixin, ListModelMixin, Custo
def perform_destroy(self, instance): def perform_destroy(self, instance):
user = instance.user user = instance.user
instance.delete() instance.delete(update_by = self.request.user)
fdept = UserPost.objects.filter(user=user).order_by('sort', 'create_time').first() fdept = UserPost.objects.filter(user=user).order_by('sort', 'create_time').first()
if fdept: if fdept:
user.belong_dept = fdept user.belong_dept = fdept
@ -279,18 +296,8 @@ class UserViewSet(CustomModelViewSet):
update_serializer_class = UserUpdateSerializer update_serializer_class = UserUpdateSerializer
filterset_class = UserFilter filterset_class = UserFilter
search_fields = ['username', 'name', 'phone', 'email'] search_fields = ['username', 'name', 'phone', 'email']
select_related_fields = ['superior', 'belong_dept']
def get_queryset(self): prefetch_related_fields = ['posts']
queryset = self.queryset
if hasattr(self.get_serializer_class(), 'setup_eager_loading'):
queryset = self.get_serializer_class().setup_eager_loading(queryset) # 性能优化
dept = self.request.query_params.get(
'belong_dept', None) # 该部门及其子部门所有员工
if dept:
dept_queryset = get_child_queryset2(
Dept.objects.get(pk=dept))
queryset = queryset.filter(dept__in=dept_queryset)
return queryset
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
"""创建用户 """创建用户
@ -307,7 +314,9 @@ class UserViewSet(CustomModelViewSet):
serializer.save(password=password) serializer.save(password=password)
return Response(data=serializer.data) return Response(data=serializer.data)
@action(methods=['put'], detail=False, permission_classes=[IsAuthenticated]) @action(methods=['put'], detail=False,
permission_classes=[IsAuthenticated],
serializer_class = PasswordChangeSerializer)
def password(self, request, pk=None): def password(self, request, pk=None):
"""修改密码 """修改密码
@ -323,9 +332,9 @@ class UserViewSet(CustomModelViewSet):
user.save() user.save()
return Response() return Response()
else: else:
raise ParseError('新密码两次输入不一致!', 'password_not_same') raise ParseError(**PASSWORD_NOT_SAME)
else: else:
raise ValidationError('旧密码错误!', 'old_password_wrong') raise ValidationError(**OLD_PASSWORD_WRONG)
@action(methods=['get'], detail=False, permission_classes=[IsAuthenticated]) @action(methods=['get'], detail=False, permission_classes=[IsAuthenticated])
def info(self, request, pk=None): def info(self, request, pk=None):

View File

@ -1,4 +1,6 @@
from rest_framework.exceptions import ParseError, APIException
from apps.utils.dahua import dhClient from apps.utils.dahua import dhClient
from apps.utils.errors import XX_REQUEST_ERROR
from apps.utils.xunxi import xxClient from apps.utils.xunxi import xxClient
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
@ -24,8 +26,10 @@ class DahuaTestView(APIView):
"type": "hls" "type": "hls"
} }
} }
res = dhClient.request( # ok, res = dhClient.request(
url='/evo-apigw/admin/API/video/stream/realtime', method='post', json=data) # url='/evo-apigw/admin/API/video/stream/realtime', method='post', json=data)
ok, res = dhClient.request(url='/evo-apigw/evo-brm/1.2.0/department/tree',
method='get')
# data = { # data = {
# "pageNum":1, # "pageNum":1,
# "pageSize":100, # "pageSize":100,
@ -41,7 +45,12 @@ class DahuaTestView(APIView):
# } # }
# res = dhClient.request( # res = dhClient.request(
# url='/evo-apigw/evo-accesscontrol/1.0.0/card/accessControl/channelControl/closeDoor', method='post', json=data) # url='/evo-apigw/evo-accesscontrol/1.0.0/card/accessControl/channelControl/closeDoor', method='post', json=data)
return Response(res) if ok == 'success':
return Response(res)
elif ok == 'fail':
raise ParseError(**res)
else:
raise APIException(**res)
class XxTestView(APIView): class XxTestView(APIView):
@ -51,9 +60,14 @@ class XxTestView(APIView):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
res = xxClient.request( ok, res = xxClient.request(
url='/api/application/build/buildListV2', json={}) url='/api/application/build/buildListV2', json={})
return Response(res) if ok == 'success':
return Response(res)
elif ok == 'fail':
raise ParseError(**res)
else:
raise APIException(**res)
class XxCommonViewSet(CreateModelMixin, CustomGenericViewSet): class XxCommonViewSet(CreateModelMixin, CustomGenericViewSet):
@ -67,12 +81,17 @@ class XxCommonViewSet(CreateModelMixin, CustomGenericViewSet):
serializer = self.get_serializer(data=request.data) serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data vdata = serializer.validated_data
res = xxClient.request( ok, res = xxClient.request(
url=vdata['url'], url=vdata['url'],
method=vdata.get('method', 'post'), method=vdata.get('method', 'post'),
params=vdata.get('params', {}), params=vdata.get('params', {}),
json=vdata.get('data', {})) json=vdata.get('data', {}))
return Response(res) if ok == 'success':
return Response(res)
elif ok == 'fail':
raise ParseError(**res)
else:
raise APIException(**res)
class DhCommonViewSet(CreateModelMixin, CustomGenericViewSet): class DhCommonViewSet(CreateModelMixin, CustomGenericViewSet):
@ -86,9 +105,14 @@ class DhCommonViewSet(CreateModelMixin, CustomGenericViewSet):
serializer = self.get_serializer(data=request.data) serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
vdata = serializer.validated_data vdata = serializer.validated_data
res = dhClient.request( ok, res = dhClient.request(
url=vdata['url'], url=vdata['url'],
method=vdata.get('method', 'post'), method=vdata.get('method', 'post'),
params=vdata.get('params', {}), params=vdata.get('params', {}),
json=vdata.get('data', {})) json=vdata.get('data', {}))
return Response(res) if ok == 'success':
return Response(res)
elif ok == 'fail':
raise ParseError(**res)
else:
raise APIException(**res)

View File

@ -1,10 +1,12 @@
from threading import Thread from threading import Thread
import traceback import traceback
import requests import requests
from apps.utils.errors import DH_REQUEST_ERROR
from apps.utils.tools import print_roundtrip from apps.utils.tools import print_roundtrip
from server import settings from server import settings
import json import json
import time import time
from rest_framework.exceptions import APIException, ParseError
requests.packages.urllib3.disable_warnings() requests.packages.urllib3.disable_warnings()
class DhClient: class DhClient:
@ -14,6 +16,8 @@ class DhClient:
def __init__(self, client_id= settings.DAHUA_CLIENTID def __init__(self, client_id= settings.DAHUA_CLIENTID
, client_secret = settings.DAHUA_SECRET) -> None: , client_secret = settings.DAHUA_SECRET) -> None:
if not settings.DAHUA_ENABLED:
return None
self.client_id = client_id self.client_id = client_id
self.client_secret = client_secret self.client_secret = client_secret
self.headers = {} self.headers = {}
@ -74,8 +78,8 @@ class DhClient:
else: else:
r = getattr(requests, method)('{}{}'.format(settings.DAHUA_BASE_URL, url) r = getattr(requests, method)('{}{}'.format(settings.DAHUA_BASE_URL, url)
, headers = self.headers, params=params, json=json, verify=False) , headers = self.headers, params=params, json=json, verify=False)
if settings.DEBUG: # if settings.DEBUG:
print_roundtrip(r) # print_roundtrip(r)
if r.status_code == 200: if r.status_code == 200:
""" """
请求成功 请求成功
@ -85,15 +89,10 @@ class DhClient:
self.get_token() # 重新获取token self.get_token() # 重新获取token
self.request(url, method, params, json, timeout) # 重新请求 self.request(url, method, params, json, timeout) # 重新请求
else: else:
if ret['code'] not in ['0', '100', '00000', '1000', 0, 100, 1000]:
msg = '{}|{}{}'.format(str(ret['code']), ret.get('errMsg',''), ret.get('desc', '')) detail = '{}|{}{}'.format(str(ret['code']), ret.get('errMsg',''), ret.get('desc', ''))
return 'fail', dict(detail=detail, code='dh_'+str(ret['code']))
return 'success', ret['data']
return 'error', DH_REQUEST_ERROR
res = dict(success=True, code=200000, msg= msg, data=ret.get('data', None)) dhClient = DhClient()
if ret['code'] not in ['0', '100', '00000']:
res['success'] = False
res['code'] = 400000
return res
return dict(success=False, code=400901, msg='大华接口访问异常', data=None)
if settings.DAHUA_ENABLED:
dhClient = DhClient()

4
apps/utils/errors.py Normal file
View File

@ -0,0 +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":"未获取到主键列表"}

View File

@ -1,21 +1,9 @@
from typing import Tuple
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.http import Http404 from django.http import Http404
from server.settings import myLogger
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import exceptions from rest_framework import exceptions
from rest_framework.views import set_rollback from rest_framework.views import set_rollback
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
import traceback
class MyError(exceptions.ParseError):
"""自定义业务异常
"""
def __init__(self, error:Tuple[str, str]=(), detail=None, code=None):
if error:
code, detail = error
super().__init__(detail, code)
def custom_exception_hander(exc, context): def custom_exception_hander(exc, context):
""" """
@ -26,6 +14,7 @@ def custom_exception_hander(exc, context):
elif isinstance(exc, PermissionDenied): elif isinstance(exc, PermissionDenied):
exc = exceptions.PermissionDenied() exc = exceptions.PermissionDenied()
request_id = getattr(context['request'], 'request_id', None)
if isinstance(exc, exceptions.APIException): if isinstance(exc, exceptions.APIException):
headers = {} headers = {}
if getattr(exc, 'auth_header', None): if getattr(exc, 'auth_header', None):
@ -41,7 +30,7 @@ def custom_exception_hander(exc, context):
data = {'err_msg': exc.detail, 'err_code':exc.get_codes()} data = {'err_msg': exc.detail, 'err_code':exc.get_codes()}
set_rollback() set_rollback()
data['request_id'] = request_id
return Response(data, status=exc.status_code, headers=headers) return Response(data, status=exc.status_code, headers=headers)
# 未处理的异常记录日志
myLogger.error(traceback.format_exc()) return Response(data={'err_code':'server_error', 'err_msg':'服务器错误', 'request_id': request_id}, status=500)
return Response(data={'err_code':'server_error', 'err_msg':'服务器错误'}, status=500)

View File

@ -1,5 +1,15 @@
import uuid
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from rest_framework.mixins import CreateModelMixin, UpdateModelMixin, ListModelMixin, RetrieveModelMixin, DestroyModelMixin from rest_framework.mixins import CreateModelMixin, UpdateModelMixin, ListModelMixin, RetrieveModelMixin, DestroyModelMixin
import ast
import ipaddress
import traceback
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
class CreateUpdateModelAMixin: class CreateUpdateModelAMixin:
""" """
业务用基本表A用 业务用基本表A用
@ -32,19 +42,6 @@ class CreateUpdateCustomMixin:
def perform_update(self, serializer): def perform_update(self, serializer):
serializer.save(update_by = self.request.user) serializer.save(update_by = self.request.user)
class OptimizationMixin:
"""
性能优化,需要在序列化器里定义setup_eager_loading,可在必要的View下继承
"""
def get_queryset(self):
queryset = self.queryset
if isinstance(queryset, QuerySet):
# Ensure queryset is re-evaluated on each request.
queryset = queryset.all()
if hasattr(self.get_serializer_class(), 'setup_eager_loading'):
queryset = self.get_serializer_class().setup_eager_loading(queryset) # 性能优化
return queryset
class CustomCreateModelMixin(CreateModelMixin): class CustomCreateModelMixin(CreateModelMixin):
def perform_create(self, serializer): def perform_create(self, serializer):
@ -63,3 +60,217 @@ class CustomDestoryModelMixin(DestroyModelMixin):
def perform_destroy(self, instance): def perform_destroy(self, instance):
instance.delete(update_by = self.request.user) instance.delete(update_by = self.request.user)
class MyLoggingMixin(object):
"""Mixin to log requests"""
CLEANED_SUBSTITUTE = "********************"
# logging_methods = "__all__"
logging_methods = ['POST', 'PUT', 'DELETE', 'PATCH']
sensitive_fields = {}
def __init__(self, *args, **kwargs):
assert isinstance(
self.CLEANED_SUBSTITUTE, str
), "CLEANED_SUBSTITUTE must be a string."
super().__init__(*args, **kwargs)
def initial(self, request, *args, **kwargs):
request_id = idWorker.get_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"] = ""
else:
self.log["data"] = self._clean_data(request.body)
super().initial(request, *args, **kwargs)
try:
# Accessing request.data *for the first time* parses the request body, which may raise
# ParseError and UnsupportedMediaType exceptions. It's important not to swallow these,
# as (depending on implementation details) they may only get raised this once, and
# DRF logic needs them to be raised by the view for error handling to work correctly.
data = self.request.data.dict()
except AttributeError:
data = self.request.data
self.log["data"] = self._clean_data(data)
def handle_exception(self, exc):
response = super().handle_exception(exc)
err_str = traceback.format_exc()
self.log["errors"] = err_str
myLogger.error('{}-{}'.format(self.log['request_id'], err_str))
return response
def finalize_response(self, request, response, *args, **kwargs):
response = super().finalize_response(
request, response, *args, **kwargs
)
# Ensure backward compatibility for those using _should_log hook
should_log = (
self._should_log if hasattr(self, "_should_log") else self.should_log
)
if should_log(request, response):
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)
connection.set_rollback(False)
if response.streaming:
rendered_content = None
elif hasattr(response, "rendered_content"):
rendered_content = response.rendered_content
else:
rendered_content = response.getvalue()
self.log.update(
{
"remote_addr": self._get_ip_address(request),
"view": self._get_view_name(request),
"view_method": self._get_view_method(request),
"path": self._get_path(request),
"host": request.get_host(),
"method": request.method,
"query_params": self._clean_data(request.query_params.dict()),
"user": self._get_user(request),
"response_ms": self._get_response_ms(),
"response": self._clean_data(rendered_content),
"status_code": response.status_code,
}
)
try:
self.handle_log()
except Exception:
# ensure that all exceptions raised by handle_log
# doesn't prevent API call to continue as expected
myLogger.exception("Logging API call raise exception!")
return response
def handle_log(self):
"""
Hook to define what happens with the log.
Defaults on saving the data on the db.
"""
DrfRequestLog(**self.log).save()
def _get_path(self, request):
"""Get the request path and truncate it"""
return request.path
def _get_ip_address(self, request):
"""Get the remote ip address the request was generated from."""
ipaddr = request.META.get("HTTP_X_FORWARDED_FOR", None)
if ipaddr:
ipaddr = ipaddr.split(",")[0]
else:
ipaddr = request.META.get("REMOTE_ADDR", "")
# Account for IPv4 and IPv6 addresses, each possibly with port appended. Possibilities are:
# <ipv4 address>
# <ipv6 address>
# <ipv4 address>:port
# [<ipv6 address>]:port
# Note that ipv6 addresses are colon separated hex numbers
possibles = (ipaddr.lstrip("[").split("]")[0], ipaddr.split(":")[0])
for addr in possibles:
try:
return str(ipaddress.ip_address(addr))
except ValueError:
pass
return ipaddr
def _get_view_name(self, request):
"""Get view name."""
method = request.method.lower()
try:
attributes = getattr(self, method)
return (
type(attributes.__self__).__module__ + "." + type(attributes.__self__).__name__
)
except AttributeError:
return None
def _get_view_method(self, request):
"""Get view method."""
if hasattr(self, "action"):
return self.action or None
return request.method.lower()
def _get_user(self, request):
"""Get user."""
user = request.user
if user.is_anonymous:
return None
return user
def _get_response_ms(self):
"""
Get the duration of the request response cycle is milliseconds.
In case of negative duration 0 is returned.
"""
response_timedelta = now() - self.log["requested_at"]
response_ms = int(response_timedelta.total_seconds() * 1000)
return max(response_ms, 0)
def should_log(self, request, response):
"""
Method that should return a value that evaluated to True if the request should be logged.
By default, check if the request method is in logging_methods.
"""
return (
self.logging_methods == "__all__" or request.method in self.logging_methods
)
def _clean_data(self, data):
"""
Clean a dictionary of data of potentially sensitive info before
sending to the database.
Function based on the "_clean_credentials" function of django
(https://github.com/django/django/blob/stable/1.11.x/django/contrib/auth/__init__.py#L50)
Fields defined by django are by default cleaned with this function
You can define your own sensitive fields in your view by defining a set
eg: sensitive_fields = {'field1', 'field2'}
"""
if isinstance(data, bytes):
data = data.decode(errors="replace")
if isinstance(data, list):
return [self._clean_data(d) for d in data]
if isinstance(data, dict):
SENSITIVE_FIELDS = {
"api",
"token",
"key",
"secret",
"password",
"signature",
}
data = dict(data)
if self.sensitive_fields:
SENSITIVE_FIELDS = SENSITIVE_FIELDS | {
field.lower() for field in self.sensitive_fields
}
for key, value in data.items():
try:
value = ast.literal_eval(value)
except (ValueError, SyntaxError):
pass
if isinstance(value, (list, dict)):
data[key] = self._clean_data(value)
if key.lower() in SENSITIVE_FIELDS:
data[key] = self.CLEANED_SUBSTITUTE
return data

View File

@ -1,7 +1,8 @@
from copy import copy
import django.utils.timezone as timezone import django.utils.timezone as timezone
from django.db import models from django.db import models
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from django.core.exceptions import ObjectDoesNotExist
from apps.utils.snowflake import idWorker from apps.utils.snowflake import idWorker
@ -56,7 +57,8 @@ class BaseModel(models.Model):
""" """
基本表 基本表
""" """
id = models.CharField(max_length=20, primary_key=True, default=idWorker.get_id, editable=False, verbose_name='主键ID', help_text='主键ID') id = models.CharField(max_length=20, primary_key=True, default=idWorker.get_id,
editable=False, verbose_name='主键ID', help_text='主键ID')
create_time = models.DateTimeField( create_time = models.DateTimeField(
default=timezone.now, verbose_name='创建时间', help_text='创建时间') default=timezone.now, verbose_name='创建时间', help_text='创建时间')
update_time = models.DateTimeField( update_time = models.DateTimeField(
@ -67,25 +69,7 @@ class BaseModel(models.Model):
class Meta: class Meta:
abstract = True abstract = True
def save(self, *args, **kwargs):
if self.pk:
# If self.pk is not None then it's an update.
cls = self.__class__
old = cls.objects.filter(pk=self.pk).first()
if old:
# This will get the current model state since super().save() isn't called yet.
new = self # This gets the newly instantiated Mode object with the new values.
changed_fields = []
for field in cls._meta.get_fields():
field_name = field.name
try:
if getattr(old, field_name) != getattr(new, field_name):
changed_fields.append(field_name)
except Exception as ex: # Catch field does not exist exception
pass
kwargs['update_fields'] = changed_fields
super().save(*args, **kwargs)
class SoftModel(BaseModel): class SoftModel(BaseModel):
""" """
软删除基本表 软删除基本表
@ -113,51 +97,52 @@ class CommonAModel(SoftModel):
业务用基本表A,包含create_by, update_by字段 业务用基本表A,包含create_by, update_by字段
""" """
create_by = models.ForeignKey( create_by = models.ForeignKey(
'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='创建人', related_name= '%(class)s_create_by') 'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='创建人', related_name='%(class)s_create_by')
update_by = models.ForeignKey( update_by = models.ForeignKey(
'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='最后编辑人', related_name= '%(class)s_update_by') 'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='最后编辑人', related_name='%(class)s_update_by')
class Meta: class Meta:
abstract = True abstract = True
class CommonBModel(SoftModel): class CommonBModel(SoftModel):
""" """
业务用基本表B,包含create_by, update_by, belong_dept字段 业务用基本表B,包含create_by, update_by, belong_dept字段
""" """
create_by = models.ForeignKey( create_by = models.ForeignKey(
'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='创建人', related_name = '%(class)s_create_by') 'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='创建人', related_name='%(class)s_create_by')
update_by = models.ForeignKey( update_by = models.ForeignKey(
'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='最后编辑人', related_name = '%(class)s_update_by') 'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='最后编辑人', related_name='%(class)s_update_by')
belong_dept = models.ForeignKey( belong_dept = models.ForeignKey(
'system.dept', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='所属部门', related_name= '%(class)s_belong_dept') 'system.dept', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='所属部门', related_name='%(class)s_belong_dept')
class Meta: class Meta:
abstract = True abstract = True
class CommonADModel(BaseModel): class CommonADModel(BaseModel):
""" """
业务用基本表A, 物理删除, 包含create_by, update_by字段 业务用基本表A, 物理删除, 包含create_by, update_by字段
""" """
create_by = models.ForeignKey( create_by = models.ForeignKey(
'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='创建人', related_name= '%(class)s_create_by') 'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='创建人', related_name='%(class)s_create_by')
update_by = models.ForeignKey( update_by = models.ForeignKey(
'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='最后编辑人', related_name= '%(class)s_update_by') 'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='最后编辑人', related_name='%(class)s_update_by')
class Meta: class Meta:
abstract = True abstract = True
class CommonBDModel(BaseModel): class CommonBDModel(BaseModel):
""" """
业务用基本表B, 物理删除, 包含create_by, update_by, belong_dept字段 业务用基本表B, 物理删除, 包含create_by, update_by, belong_dept字段
""" """
create_by = models.ForeignKey( create_by = models.ForeignKey(
'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='创建人', related_name = '%(class)s_create_by') 'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='创建人', related_name='%(class)s_create_by')
update_by = models.ForeignKey( update_by = models.ForeignKey(
'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='最后编辑人', related_name = '%(class)s_update_by') 'system.user', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='最后编辑人', related_name='%(class)s_update_by')
belong_dept = models.ForeignKey( belong_dept = models.ForeignKey(
'system.organzation', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='所属部门', related_name= '%(class)s_belong_dept') 'system.organzation', null=True, blank=True, on_delete=models.SET_NULL, verbose_name='所属部门', related_name='%(class)s_belong_dept')
class Meta: class Meta:
abstract = True abstract = True

View File

@ -5,6 +5,9 @@ from apps.system.models import Dept, Permission, Post, Role, UserPost
from django.db.models import Q from django.db.models import Q
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
ALL_PERMS = [
]
def get_user_perms_map(user): def get_user_perms_map(user):
""" """

View File

@ -1,7 +1,8 @@
from rest_framework import serializers from rest_framework import serializers
from django_restql.mixins import DynamicFieldsMixin from django_restql.mixins import DynamicFieldsMixin
from rest_framework.fields import empty
from rest_framework.request import Request
class PkSerializer(serializers.Serializer): class PkSerializer(serializers.Serializer):
pks = serializers.ListField(child=serializers.CharField(max_length=20), label="主键ID列表") pks = serializers.ListField(child=serializers.CharField(max_length=20), label="主键ID列表")
@ -9,5 +10,21 @@ class GenSignatureSerializer(serializers.Serializer):
path = serializers.CharField(label="图片地址") path = serializers.CharField(label="图片地址")
class CustomModelSerializer(DynamicFieldsMixin, serializers.ModelSerializer): class CustomModelSerializer(DynamicFieldsMixin, serializers.ModelSerializer):
pass
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 getattr(self.request.user, 'belong_dept', None):
validated_data['belong_dept'] = self.request.user.belong_dept
return super().update(validated_data)
def update(self, instance, validated_data):
if self.request:
if hasattr(instance, 'update_by'):
validated_data['update_by'] = getattr(self.request, 'user', None)
return super().update(instance, validated_data)

View File

@ -1,6 +1,7 @@
from rest_framework.views import APIView from rest_framework.views import APIView
import os import os
import cv2 import cv2
from apps.utils.errors import SIGN_MAKE_FAIL
from server.settings import BASE_DIR from server.settings import BASE_DIR
import numpy as np import numpy as np
from rest_framework.response import Response from rest_framework.response import Response
@ -45,4 +46,4 @@ class SignatureViewSet(CustomCreateModelMixin, CustomGenericViewSet):
cv2.imwrite(new_path, image) cv2.imwrite(new_path, image)
return Response({'path': new_path.replace(BASE_DIR, '')}) return Response({'path': new_path.replace(BASE_DIR, '')})
except: except:
raise ParseError('签名照处理失败,请重新上传') raise ParseError(**SIGN_MAKE_FAIL)

View File

@ -1,29 +1,38 @@
from rest_framework.viewsets import ModelViewSet, GenericViewSet from rest_framework.viewsets import GenericViewSet
from rest_framework.decorators import action from rest_framework.decorators import action
from apps.utils.mixins import CustomCreateModelMixin, CustomDestoryModelMixin, CustomUpdateModelMixin, OptimizationMixin from apps.system.models import Dept, Post
from apps.utils.permission import RbacDataMixin, RbacPermission 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 apps.utils.serializers import PkSerializer
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.mixins import DestroyModelMixin, RetrieveModelMixin, ListModelMixin from rest_framework.mixins import RetrieveModelMixin, ListModelMixin, CreateModelMixin, UpdateModelMixin
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
from django.core.cache import cache
class CustomGenericViewSet(GenericViewSet): class CustomGenericViewSet(MyLoggingMixin, GenericViewSet):
""" """
增强的GenericViewSet 增强的GenericViewSet
""" """
perms_map = {} perms_map = {} # 权限标识
logging_methods = ['POST', 'PUT', 'PATCH', 'DELETE']
ordering_fields = '__all__' ordering_fields = '__all__'
filter_fields = '__all__' filter_fields = '__all__'
ordering = '-create_time' ordering = '-create_time'
filterset_fields = '__all__' filterset_fields = '__all__'
create_serializer_class = None create_serializer_class = None
update_serializer_class = None update_serializer_class = None
partial_update_serializer_class = None
list_serializer_class = None list_serializer_class = None
retrieve_serializer_class = None retrieve_serializer_class = None
select_related_fields = []
prefetch_related_fields = []
permission_classes = [IsAuthenticated & RbacPermission] permission_classes = [IsAuthenticated & RbacPermission]
data_filter = False # 数据权限过滤是否开启(需要RbacPermission)
def get_serializer_class(self): def get_serializer_class(self):
action_serializer_name = f"{self.action}_serializer_class" action_serializer_name = f"{self.action}_serializer_class"
@ -32,13 +41,62 @@ class CustomGenericViewSet(GenericViewSet):
return action_serializer_class return action_serializer_class
return super().get_serializer_class() return super().get_serializer_class()
class CustomDataGenericViewSet(RbacDataMixin, CustomGenericViewSet): def __init__(self, *args, **kwargs):
""" super().__init__(*args, **kwargs)
增强的GenericViewSet, 带数据权限过滤 if self.perms_map:
""" for k, v in self.perms_map.items():
if v not in ALL_PERMS and v!='*':
ALL_PERMS.append(v)
def get_queryset(self):
queryset = super().get_queryset()
if self.select_related_fields:
queryset = queryset.select_related(*self.select_related_fields)
if self.prefetch_related_fields:
queryset = queryset.prefetch_related(*self.prefetch_related_fields)
if self.data_filter:
if self.request.user.is_superuser:
return queryset
if hasattr(queryset.model, 'belong_dept'):
user = self.request.user
user_perms_map = cache.get('perms_' + user.id, None)
if user_perms_map is None:
user_perms_map = get_user_perms_map(self.request.user)
if isinstance(user_perms_map, dict):
if hasattr(self.view, 'perms_map'):
perms_map = self.view.perms_map
action_str = perms_map.get(self.request._request.method.lower(), None)
if '*' in perms_map:
return queryset
elif action_str == '*':
return queryset
elif action_str in user_perms_map:
new_queryset = queryset.none()
for dept_id, data_range in user_perms_map[action_str].items:
dept = Dept.objects.get(id=dept_id)
if data_range == Post.POST_DATA_ALL:
return queryset
elif data_range == Post.POST_DATA_SAMELEVE_AND_BELOW:
if dept.parent:
belong_depts = get_child_queryset2(dept.parent)
else:
belong_depts = get_child_queryset2(dept)
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)
elif data_range == Post.POST_DATA_THISLEVEL:
queryset = queryset.filter(belong_dept = dept)
elif data_range == Post.POST_DATA_THISLEVEL:
queryset = queryset.filter(create_by = user)
new_queryset = new_queryset | queryset
return new_queryset
else:
return queryset.none()
return queryset
class CustomModelViewSet(OptimizationMixin, CustomCreateModelMixin class CustomModelViewSet(CreateModelMixin
, CustomUpdateModelMixin, ListModelMixin, RetrieveModelMixin , UpdateModelMixin, ListModelMixin, RetrieveModelMixin
, CustomDestoryModelMixin, CustomGenericViewSet): , CustomDestoryModelMixin, CustomGenericViewSet):
""" """
增强的ModelViewSet 增强的ModelViewSet
@ -53,6 +111,9 @@ class CustomModelViewSet(OptimizationMixin, CustomCreateModelMixin
,'patch':'{}_update'.format(basename) ,'patch':'{}_update'.format(basename)
,'delete':'{}_delete'.format(basename) ,'delete':'{}_delete'.format(basename)
,'deletes':'{}_delete'.format(basename)} ,'deletes':'{}_delete'.format(basename)}
for k, v in self.perms_map.items():
if v not in ALL_PERMS and v!='*':
ALL_PERMS.append(v)
@action(methods=['post'], detail=False, serializer_class=PkSerializer) @action(methods=['post'], detail=False, serializer_class=PkSerializer)
def deletes(self,request,*args,**kwargs): def deletes(self,request,*args,**kwargs):
@ -62,11 +123,4 @@ class CustomModelViewSet(OptimizationMixin, CustomCreateModelMixin
self.get_queryset().filter(id__in=pks).delete(update_by=request.user) self.get_queryset().filter(id__in=pks).delete(update_by=request.user)
return Response() return Response()
else: else:
raise ValidationError("未获取到pks字段") raise ValidationError(**PKS_ERROR)
class CustomDataModelViewSet(RbacDataMixin, CustomModelViewSet):
"""
增强的ModelViewSet,带数据权限过滤
"""

View File

@ -1,9 +1,11 @@
from threading import Thread from threading import Thread
import requests import requests
import json import json
from apps.utils.errors import XX_REQUEST_ERROR
from apps.utils.tools import print_roundtrip from apps.utils.tools import print_roundtrip
from server import settings from server import settings
import time import time
from rest_framework.exceptions import APIException, ParseError
requests.packages.urllib3.disable_warnings() requests.packages.urllib3.disable_warnings()
@ -13,6 +15,8 @@ class XxClient:
寻息 寻息
""" """
def __init__(self, licence=settings.XX_LICENCE, username=settings.XX_USERNAME) -> None: def __init__(self, licence=settings.XX_LICENCE, username=settings.XX_USERNAME) -> None:
if not settings.XX_ENABLED:
return None
self.licence = licence self.licence = licence
self.username = username self.username = username
self.isGetingToken = False self.isGetingToken = False
@ -57,6 +61,7 @@ class XxClient:
def request(self, url:str, method:str='post', params=dict(), json=dict(), timeout=20): def request(self, url:str, method:str='post', params=dict(), json=dict(), timeout=20):
params['accessToken'] = self.token params['accessToken'] = self.token
json['username'] = self.username json['username'] = self.username
json['buildId'] = settings.XX_BUILDID
if self.isGetingToken: if self.isGetingToken:
req_num = 0 req_num = 0
while True: while True:
@ -69,20 +74,17 @@ class XxClient:
else: else:
r = getattr(requests, method)('{}{}'.format(settings.XX_BASE_URL, url) r = getattr(requests, method)('{}{}'.format(settings.XX_BASE_URL, url)
, params=params, json=json, verify=False) , params=params, json=json, verify=False)
if settings.DEBUG: # if settings.DEBUG:
print_roundtrip(r) # print_roundtrip(r)
ret = r.json() ret = r.json()
if ret.get('errorCode') == '1060000': if ret.get('errorCode') == '1060000':
self.get_token() # 重新获取token self.get_token() # 重新获取token
self.request(url, method, params, json, timeout) # 重新请求 self.request(url, method, params, json, timeout) # 重新请求
else: else:
msg = '{}|{}'.format(str(ret['errorCode']), '|'.join(ret['errorMsg']))
res = dict(success=True, code=200000, msg= msg, data=ret['data'])
if ret['errorCode'] != 0: if ret['errorCode'] != 0:
res['success'] = False return 'fail', dict(detail='|'.join(ret['errorMsg']),
res['code'] = 400000 code='xx_' + str(ret['errorCode']))
return res return 'success', ret['data']
return dict(success=False, code=400900, msg='寻息接口访问异常', data=None) return 'error', XX_REQUEST_ERROR
if settings.XX_ENABLED: xxClient = XxClient()
xxClient = XxClient()

View File

@ -14,7 +14,7 @@ from django.shortcuts import get_object_or_404, render
from rest_framework.viewsets import GenericViewSet, ModelViewSet from rest_framework.viewsets import GenericViewSet, ModelViewSet
from rest_framework.decorators import action, api_view from rest_framework.decorators import action, api_view
from apps.wf.models import CustomField, Ticket, Workflow, State, Transition, TicketFlow from apps.wf.models import CustomField, Ticket, Workflow, State, Transition, TicketFlow
from apps.utils.mixins import CreateUpdateCustomMixin, CreateUpdateModelAMixin, OptimizationMixin from apps.utils.mixins import CreateUpdateCustomMixin, CreateUpdateModelAMixin
from apps.wf.services import WfService from apps.wf.services import WfService
from rest_framework.exceptions import APIException, PermissionDenied from rest_framework.exceptions import APIException, PermissionDenied
from rest_framework import status from rest_framework import status
@ -119,7 +119,7 @@ class CustomFieldViewSet(CreateModelMixin, UpdateModelMixin, RetrieveModelMixin,
return CustomFieldCreateUpdateSerializer return CustomFieldCreateUpdateSerializer
return super().get_serializer_class() return super().get_serializer_class()
class TicketViewSet(OptimizationMixin, CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, RetrieveModelMixin, GenericViewSet): class TicketViewSet(CreateUpdateCustomMixin, CreateModelMixin, ListModelMixin, RetrieveModelMixin, GenericViewSet):
perms_map = {'get':'*', 'post':'ticket_create'} perms_map = {'get':'*', 'post':'ticket_create'}
queryset = Ticket.objects.all() queryset = Ticket.objects.all()
serializer_class = TicketSerializer serializer_class = TicketSerializer

View File

@ -4,7 +4,6 @@ django-celery-beat==2.2.1
django-celery-results==2.3.0 django-celery-results==2.3.0
django-cors-headers==3.11.0 django-cors-headers==3.11.0
django-filter==21.1 django-filter==21.1
django-simple-history==3.0.0
djangorestframework==3.13.1 djangorestframework==3.13.1
djangorestframework-simplejwt==5.1.0 djangorestframework-simplejwt==5.1.0
drf-yasg==1.20.0 drf-yasg==1.20.0

View File

@ -46,7 +46,6 @@ INSTALLED_APPS = [
'drf_yasg', 'drf_yasg',
'rest_framework', 'rest_framework',
"django_filters", "django_filters",
'simple_history',
'apps.utils', 'apps.utils',
'apps.third', 'apps.third',
'apps.system', 'apps.system',
@ -64,7 +63,6 @@ MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'simple_history.middleware.HistoryRequestMiddleware',
] ]
ROOT_URLCONF = 'server.urls' ROOT_URLCONF = 'server.urls'
@ -303,7 +301,7 @@ LOGGING = {
} }
} }
# 实例化myLogger # 实例化myLogger
myLogger = logging.getLogger('log') myLogger = logging.getLogger(__name__)
# 大华ICC平台 # 大华ICC平台
DAHUA_ENABLED = conf.DAHUA_ENABLED DAHUA_ENABLED = conf.DAHUA_ENABLED
@ -317,4 +315,5 @@ DAHUA_SECRET = conf.DAHUA_SECRET
XX_ENABLED = conf.XX_ENABLED XX_ENABLED = conf.XX_ENABLED
XX_BASE_URL = conf.XX_BASE_URL XX_BASE_URL = conf.XX_BASE_URL
XX_LICENCE = conf.XX_LICENCE XX_LICENCE = conf.XX_LICENCE
XX_USERNAME = conf.XX_USERNAME XX_USERNAME = conf.XX_USERNAME
XX_BUILDID = conf.XX_BUILDID