optim: optim import and some email key
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import NotificationEmail, NotificationSettings
|
||||
|
||||
|
||||
@admin.register(NotificationEmail)
|
||||
class NotificationEmailAdmin(admin.ModelAdmin):
|
||||
list_display = ('email', 'is_enabled', 'description', 'created_at')
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
from django.http import JsonResponse
|
||||
from .services import EmailNotificationService
|
||||
import json
|
||||
import logging
|
||||
import threading
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
|
||||
from .services import EmailNotificationService
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -22,6 +22,10 @@ class EmailNotificationMiddleware(MiddlewareMixin):
|
||||
if not request.path.startswith('/api/'):
|
||||
return response
|
||||
|
||||
# 排除凭证相关的API路径,这些已经在视图中单独处理
|
||||
if '/proof-images/' in request.path :
|
||||
return response
|
||||
|
||||
# 只处理数据新增修改操作,但排除DELETE操作,毕竟删除操作已经另外重写
|
||||
if request.method not in ['POST', 'PUT', 'PATCH']:
|
||||
return response
|
||||
@@ -93,10 +97,25 @@ class EmailNotificationMiddleware(MiddlewareMixin):
|
||||
notification_data = {
|
||||
'id': item_data.get('id', ''),
|
||||
'name': item_data.get('name', ''),
|
||||
'item_name': item_data.get('item_name', ''),
|
||||
'item_serial': item_data.get('item_serial', ''),
|
||||
'serial_number': item_data.get('serial_number', ''),
|
||||
'status': item_data.get('status', ''),
|
||||
'category': item_data.get('category', ''),
|
||||
'status': item_data.get('status', ''),
|
||||
'location': item_data.get('location', ''),
|
||||
'description': item_data.get('description', ''),
|
||||
'owner': item_data.get('owner', ''),
|
||||
'purchase_date': item_data.get('purchase_date', ''),
|
||||
'value': item_data.get('value', ''),
|
||||
'user': item_data.get('user', ''),
|
||||
'borrower_contact': item_data.get('borrower_contact', ''),
|
||||
'start_time': item_data.get('start_time', ''),
|
||||
'end_time': item_data.get('end_time', ''),
|
||||
'purpose': item_data.get('purpose', ''),
|
||||
'notes': item_data.get('notes', ''),
|
||||
'is_returned': item_data.get('is_returned', ''),
|
||||
'condition_before': item_data.get('condition_before', ''),
|
||||
'condition_after': item_data.get('condition_after', ''),
|
||||
'timestamp': item_data.get('updated_at', ''),
|
||||
'operation_path': request.path,
|
||||
'operation_method': request.method
|
||||
@@ -181,8 +200,11 @@ class EmailNotificationMiddleware(MiddlewareMixin):
|
||||
personnel_data = {}
|
||||
|
||||
# 构建通知数据,使用正确的字段名
|
||||
message = personnel_data['message', '']
|
||||
personnel_data = personnel_data['data', '']
|
||||
message = ''
|
||||
if 'message' in personnel_data:
|
||||
message = personnel_data['message', '']
|
||||
if 'data' in personnel_data:
|
||||
personnel_data = personnel_data['data', '']
|
||||
notification_data = {
|
||||
'message': message,
|
||||
'id': personnel_data.get('id', ''),
|
||||
@@ -192,6 +214,7 @@ class EmailNotificationMiddleware(MiddlewareMixin):
|
||||
'grader_major': personnel_data.get('grade_major', ''),
|
||||
'department': personnel_data.get('department', ''),
|
||||
'project_group': personnel_data.get('project_group', ''),
|
||||
'project_group_name': personnel_data.get('project_group_name', ''),
|
||||
'position': personnel_data.get('position', ''),
|
||||
'start_date': personnel_data.get('start_date', ''),
|
||||
'end_date': personnel_data.get('end_date', ''),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from django.db import models
|
||||
from django.core.validators import EmailValidator
|
||||
from django.db import models
|
||||
|
||||
|
||||
class NotificationEmail(models.Model):
|
||||
"""通知邮箱模型"""
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
from django.core.mail import send_mail
|
||||
from django.conf import settings
|
||||
import json
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.mail import send_mail
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class EmailNotificationService:
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
from django.test import TestCase
|
||||
from django.core import mail
|
||||
from .models import NotificationEmail, NotificationSettings
|
||||
|
||||
from .models import NotificationEmail
|
||||
from .services import EmailNotificationService
|
||||
|
||||
|
||||
class EmailNotificationTestCase(TestCase):
|
||||
def setUp(self):
|
||||
"""设置测试数据"""
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
app_name = 'email_notice'
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import json
|
||||
import re
|
||||
|
||||
from django.http import JsonResponse
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.decorators.http import require_http_methods
|
||||
|
||||
from .services import EmailNotificationService
|
||||
import json
|
||||
import re
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@require_http_methods(["POST", "GET"])
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import Department, Category, FinancialRecord, ProofImage
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
|
||||
|
||||
def proof_image_upload_path(instance, filename):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from .models import FinancialRecord, Department, Category, ProofImage
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from .views import FinancialRecordViewSet, DepartmentViewSet, CategoryViewSet, ProofImageViewSet
|
||||
|
||||
router = DefaultRouter()
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import os
|
||||
import threading
|
||||
|
||||
from django.utils import timezone
|
||||
from rest_framework import viewsets, status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from django.utils import timezone
|
||||
|
||||
from .models import FinancialRecord, Department, Category, ProofImage
|
||||
from .serializers import (
|
||||
FinancialRecordWriteSerializer,
|
||||
@@ -13,6 +15,7 @@ from .serializers import (
|
||||
ProofImageSerializer
|
||||
)
|
||||
|
||||
|
||||
class FinancialRecordViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
获取财务记录
|
||||
@@ -139,6 +142,39 @@ class FinancialRecordViewSet(viewsets.ModelViewSet):
|
||||
)
|
||||
created_images.append(ProofImageSerializer(proof_image).data)
|
||||
|
||||
# 异步发送凭证上传通知邮件
|
||||
def send_proof_upload_notification():
|
||||
try:
|
||||
from email_notice.services import EmailNotificationService
|
||||
|
||||
user_info = self._get_user_info(request)
|
||||
|
||||
# 构建凭证更新通知数据
|
||||
notification_data = {
|
||||
'record_id': record.id,
|
||||
'record_title': record.title,
|
||||
'record_amount': str(record.amount),
|
||||
'uploaded_images_count': len(created_images),
|
||||
'timestamp': timezone.now().isoformat(),
|
||||
'operation_type': '凭证上传',
|
||||
'operation_path': request.path,
|
||||
'operation_method': request.method
|
||||
}
|
||||
|
||||
# 发送凭证更新通知
|
||||
EmailNotificationService.send_operation_notification(
|
||||
'UPDATE', '财务凭证', notification_data, user_info
|
||||
)
|
||||
print(f"凭证上传通知邮件已发送: 财务记录 ID:{record.id}, 上传{len(created_images)}张图片")
|
||||
|
||||
except Exception as e:
|
||||
print(f"发送凭证上传通知邮件失败: {e}")
|
||||
|
||||
# 启动异步邮件发送线程
|
||||
email_thread = threading.Thread(target=send_proof_upload_notification)
|
||||
email_thread.daemon = True
|
||||
email_thread.start()
|
||||
|
||||
return Response({
|
||||
'message': f'成功上传 {len(created_images)} 张图片',
|
||||
'images': created_images
|
||||
@@ -153,9 +189,19 @@ class ProofImageViewSet(viewsets.ModelViewSet):
|
||||
serializer_class = ProofImageSerializer
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
"""重写删除方法,确保删除图片记录时同时删除物理文件"""
|
||||
"""删除方法,确保删除图片记录时同时删除物理文件,并发送邮件通知"""
|
||||
instance = self.get_object()
|
||||
|
||||
# 获取凭证信息用于邮件通知
|
||||
proof_info = {
|
||||
'id': instance.id,
|
||||
'record_id': instance.financial_record.id,
|
||||
'record_title': instance.financial_record.title,
|
||||
'record_amount': str(instance.financial_record.amount),
|
||||
'image_description': instance.description,
|
||||
'timestamp': timezone.now().isoformat(),
|
||||
}
|
||||
|
||||
# 获取文件路径
|
||||
file_path = None
|
||||
if instance.image:
|
||||
@@ -177,8 +223,61 @@ class ProofImageViewSet(viewsets.ModelViewSet):
|
||||
print(f"删除文件失败: {file_path}, 错误: {e}")
|
||||
# 即使文件删除失败,也不抛出异常,因为数据库记录已经删除
|
||||
|
||||
# 异步发送凭证删除通知邮件
|
||||
def send_proof_delete_notification():
|
||||
try:
|
||||
from email_notice.services import EmailNotificationService
|
||||
|
||||
user_info = self._get_user_info(request)
|
||||
|
||||
# 构建凭证删除通知数据
|
||||
notification_data = {
|
||||
'proof_id': proof_info['id'],
|
||||
'record_id': proof_info['record_id'],
|
||||
'record_title': proof_info['record_title'],
|
||||
'record_amount': proof_info['record_amount'],
|
||||
'image_description': proof_info['image_description'],
|
||||
'timestamp': proof_info['timestamp'],
|
||||
'operation_type': '凭证删除',
|
||||
'operation_path': request.path,
|
||||
'operation_method': request.method
|
||||
}
|
||||
|
||||
# 发送凭证删除通知
|
||||
EmailNotificationService.send_operation_notification(
|
||||
'DELETE', '财务凭证', notification_data, user_info
|
||||
)
|
||||
print(f"凭证删除通知邮件已发送: 财务记录 ID:{proof_info['record_id']}, 凭证 ID:{proof_info['id']}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"发送凭证删除通知邮件失败: {e}")
|
||||
|
||||
# 启动异步邮件发送线程
|
||||
email_thread = threading.Thread(target=send_proof_delete_notification)
|
||||
email_thread.daemon = True
|
||||
email_thread.start()
|
||||
|
||||
return Response({'message': '图片删除成功'}, status=status.HTTP_200_OK)
|
||||
|
||||
def _get_user_info(self, request):
|
||||
"""获取用户信息"""
|
||||
try:
|
||||
if hasattr(request, 'user') and request.user.is_authenticated:
|
||||
return f"{request.user.username} ({request.user.email})"
|
||||
else:
|
||||
return f"匿名用户 (IP: {self._get_client_ip(request)})"
|
||||
except:
|
||||
return "未知用户"
|
||||
|
||||
def _get_client_ip(self, request):
|
||||
"""获取客户端IP"""
|
||||
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
|
||||
if x_forwarded_for:
|
||||
ip = x_forwarded_for.split(',')[0]
|
||||
else:
|
||||
ip = request.META.get('REMOTE_ADDR')
|
||||
return ip
|
||||
|
||||
|
||||
class DepartmentViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
|
||||
@@ -10,9 +10,10 @@ For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/5.2/ref/settings/
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# SECURE 文件用来存储敏感信息,如 SECRET_KEY,SMTP信息 等
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
|
||||
@@ -15,10 +15,10 @@ Including another URLconf
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
|
||||
urlpatterns = [
|
||||
path("admin/", admin.site.urls),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import Item, ItemUsage, Category
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
|
||||
class Item(models.Model):
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from rest_framework import serializers
|
||||
from django.contrib.auth.models import User
|
||||
from rest_framework import serializers
|
||||
|
||||
from .models import Item, ItemUsage, Category
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from . import views
|
||||
|
||||
router = DefaultRouter()
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from django.shortcuts import render
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils import timezone
|
||||
from rest_framework import viewsets, status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils import timezone
|
||||
|
||||
from .models import Item, ItemUsage, Category
|
||||
from .serializers import (
|
||||
ItemSerializer, ItemDetailSerializer, ItemUsageSerializer,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import Personnel, ProjectGroup
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import django_filters
|
||||
from django.db import models
|
||||
from .models import Personnel, ProjectGroup
|
||||
from finance.models import Department
|
||||
|
||||
from .models import Personnel, ProjectGroup
|
||||
|
||||
|
||||
class PersonnelFilter(django_filters.FilterSet):
|
||||
"""人员信息过滤器"""
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from django.db import models
|
||||
from django.core.validators import RegexValidator
|
||||
from django.db import models
|
||||
from finance.models import Department
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from rest_framework import serializers
|
||||
from .models import Personnel, ProjectGroup
|
||||
from finance.models import Department
|
||||
from rest_framework import serializers
|
||||
|
||||
from .models import Personnel, ProjectGroup
|
||||
|
||||
|
||||
class ProjectGroupSerializer(serializers.ModelSerializer):
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from .views import PersonnelViewSet, ProjectGroupViewSet
|
||||
|
||||
router = DefaultRouter()
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
from rest_framework import viewsets, status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework.filters import SearchFilter, OrderingFilter
|
||||
from django.utils import timezone
|
||||
import logging
|
||||
|
||||
from django.utils import timezone
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.filters import SearchFilter, OrderingFilter
|
||||
from rest_framework.response import Response
|
||||
|
||||
from .filters import PersonnelFilter
|
||||
from .models import Personnel, ProjectGroup
|
||||
from .serializers import (
|
||||
PersonnelReadSerializer,
|
||||
PersonnelWriteSerializer,
|
||||
ProjectGroupSerializer
|
||||
)
|
||||
from .filters import PersonnelFilter
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -109,18 +111,23 @@ class PersonnelViewSet(viewsets.ModelViewSet):
|
||||
personnel = self.get_object()
|
||||
personnel_data = {
|
||||
'id': personnel.id,
|
||||
'name': personnel.name,
|
||||
'name': "[已删除]" + personnel.name,
|
||||
'student_id': personnel.student_id,
|
||||
'email': personnel.email,
|
||||
'phone': personnel.phone,
|
||||
'department_name': personnel.department.name if personnel.department else '',
|
||||
'project_group_name': personnel.project_group.name if personnel.project_group else '',
|
||||
'gender': personnel.gender,
|
||||
'grader_major': personnel.grade_major,
|
||||
'department': personnel.department.name,
|
||||
'project_group': personnel.project_group,
|
||||
'position': personnel.position,
|
||||
'start_date': personnel.start_date,
|
||||
'end_date': personnel.end_date,
|
||||
'is_active': personnel.is_active,
|
||||
'start_date': str(personnel.start_date),
|
||||
'end_date': str(personnel.end_date) if personnel.end_date else '',
|
||||
'timestamp': timezone.now().isoformat(),
|
||||
|
||||
'phone': personnel.phone,
|
||||
'qq': personnel.qq,
|
||||
'email': personnel.email,
|
||||
'description': personnel.description,
|
||||
'timestamp': personnel.updated_at,
|
||||
'operation_path': request.path,
|
||||
'operation_method': request.method
|
||||
}
|
||||
|
||||
# 获取用户信息
|
||||
@@ -210,7 +217,7 @@ class ProjectGroupViewSet(viewsets.ModelViewSet):
|
||||
project_group = self.get_object()
|
||||
project_group_data = {
|
||||
'id': project_group.id,
|
||||
'name': project_group.name,
|
||||
'name': "[已删除]" + project_group.name,
|
||||
'department_name': project_group.department.name if project_group.department else '',
|
||||
'description': project_group.description,
|
||||
'created_at': str(project_group.created_at),
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import logging
|
||||
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from django_apscheduler import util
|
||||
from django_apscheduler.jobstores import DjangoJobStore
|
||||
from django_apscheduler.models import DjangoJobExecution
|
||||
from django_apscheduler import util
|
||||
from personnel.models import Personnel
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
Reference in New Issue
Block a user