2025-08-12 13:33:25 +03:00

599 lines
23 KiB
Python

from rest_framework import viewsets, permissions, status
from rest_framework.decorators import action
from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters
from django.db.models import Q, Count, Sum
from django.utils import timezone
from datetime import timedelta
from django.contrib.auth import get_user_model
from ..models import (
Message, MessageRecipient, NotificationTemplate, AlertRule,
AlertInstance, CommunicationChannel, DeliveryLog
)
from .serializers import (
MessageSerializer, MessageRecipientSerializer, NotificationTemplateSerializer,
AlertRuleSerializer, AlertInstanceSerializer, CommunicationChannelSerializer,
DeliveryLogSerializer, CommunicationsStatsSerializer, MessageCreateSerializer,
BulkMessageSerializer, AlertCreateSerializer, AlertAcknowledgeSerializer,
AlertResolveSerializer, NotificationSendSerializer, ChannelTestSerializer
)
from core.utils import AuditLogger
User = get_user_model()
class BaseViewSet(viewsets.ModelViewSet):
"""Base ViewSet with common functionality"""
permission_classes = [permissions.IsAuthenticated]
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
def get_queryset(self):
# Filter by tenant if user has one
if hasattr(self.request.user, 'tenant') and self.request.user.tenant:
return self.queryset.filter(tenant=self.request.user.tenant)
return self.queryset
def perform_create(self, serializer):
if hasattr(self.request.user, 'tenant'):
serializer.save(tenant=self.request.user.tenant)
else:
serializer.save()
class CommunicationChannelViewSet(BaseViewSet):
"""ViewSet for CommunicationChannel model"""
queryset = CommunicationChannel.objects.all()
serializer_class = CommunicationChannelSerializer
filterset_fields = ['channel_type', 'is_active', 'is_default']
search_fields = ['name', 'description']
ordering_fields = ['name', 'priority']
ordering = ['priority', 'name']
@action(detail=False, methods=['get'])
def active(self, request):
"""Get active channels"""
queryset = self.get_queryset().filter(is_active=True)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
@action(detail=True, methods=['post'])
def test(self, request, pk=None):
"""Test communication channel"""
channel = self.get_object()
serializer = ChannelTestSerializer(data=request.data)
if serializer.is_valid():
try:
# Mock channel test - in real implementation, would test actual channel
test_result = {
'success': True,
'message': 'Channel test successful',
'response_time': 0.25,
'test_recipient': serializer.validated_data['test_recipient']
}
# Log the action
AuditLogger.log_action(
user=request.user,
action='CHANNEL_TESTED',
model='CommunicationChannel',
object_id=str(channel.channel_id),
details={
'channel_name': channel.name,
'test_result': 'SUCCESS'
}
)
return Response(test_result)
except Exception as e:
return Response({
'success': False,
'message': f'Channel test failed: {str(e)}',
'error': str(e)
}, status=status.HTTP_400_BAD_REQUEST)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class NotificationTemplateViewSet(BaseViewSet):
"""ViewSet for NotificationTemplate model"""
queryset = NotificationTemplate.objects.all()
serializer_class = NotificationTemplateSerializer
filterset_fields = ['template_type', 'is_active']
search_fields = ['name', 'description', 'subject_template']
ordering_fields = ['name', 'template_type']
ordering = ['name']
@action(detail=False, methods=['get'])
def active(self, request):
"""Get active templates"""
queryset = self.get_queryset().filter(is_active=True)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
@action(detail=False, methods=['get'])
def by_type(self, request):
"""Get templates by type"""
template_type = request.query_params.get('type')
if template_type:
queryset = self.get_queryset().filter(
template_type=template_type,
is_active=True
)
else:
queryset = self.get_queryset().filter(is_active=True)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
class MessageViewSet(BaseViewSet):
"""ViewSet for Message model"""
queryset = Message.objects.all()
serializer_class = MessageSerializer
filterset_fields = ['sender', 'message_type', 'priority', 'is_draft']
search_fields = ['subject', 'body', 'sender__first_name', 'sender__last_name']
ordering_fields = ['created_at', 'scheduled_at', 'sent_at']
ordering = ['-created_at']
@action(detail=False, methods=['post'])
def create_message(self, request):
"""Create and send a message"""
serializer = MessageCreateSerializer(data=request.data)
if serializer.is_valid():
# Create message
message = Message.objects.create(
sender=request.user,
subject=serializer.validated_data['subject'],
body=serializer.validated_data['body'],
message_type=serializer.validated_data['message_type'],
priority=serializer.validated_data['priority'],
scheduled_at=serializer.validated_data.get('scheduled_at'),
expires_at=serializer.validated_data.get('expires_at'),
is_draft=serializer.validated_data.get('is_draft', False),
tenant=getattr(request.user, 'tenant', None)
)
# Create recipients
recipients = User.objects.filter(id__in=serializer.validated_data['recipients'])
for recipient in recipients:
MessageRecipient.objects.create(
message=message,
recipient=recipient,
status='PENDING',
tenant=getattr(request.user, 'tenant', None)
)
# Send immediately if not draft and not scheduled
if not message.is_draft and not message.scheduled_at:
message.sent_at = timezone.now()
message.save()
# Update recipient status to sent
MessageRecipient.objects.filter(message=message).update(
status='SENT',
delivered_at=timezone.now()
)
# Log the action
AuditLogger.log_action(
user=request.user,
action='MESSAGE_CREATED',
model='Message',
object_id=str(message.message_id),
details={
'subject': message.subject,
'recipient_count': len(serializer.validated_data['recipients']),
'is_draft': message.is_draft
}
)
return Response({
'message': 'Message created successfully',
'message_data': MessageSerializer(message).data
})
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@action(detail=False, methods=['post'])
def bulk_message(self, request):
"""Send bulk message to groups"""
serializer = BulkMessageSerializer(data=request.data)
if serializer.is_valid():
# Get recipients based on groups
recipients = set()
for group in serializer.validated_data['recipient_groups']:
if group == 'ALL_STAFF':
recipients.update(User.objects.filter(is_active=True))
elif group == 'DOCTORS':
# Mock - in real implementation, would filter by role
recipients.update(User.objects.filter(is_active=True)[:10])
elif group == 'NURSES':
recipients.update(User.objects.filter(is_active=True)[10:20])
# Add more group logic as needed
# Create message
message = Message.objects.create(
sender=request.user,
subject=serializer.validated_data['subject'],
body=serializer.validated_data['body'],
message_type=serializer.validated_data['message_type'],
priority=serializer.validated_data['priority'],
sent_at=timezone.now(),
tenant=getattr(request.user, 'tenant', None)
)
# Create recipients
for recipient in recipients:
MessageRecipient.objects.create(
message=message,
recipient=recipient,
status='SENT',
delivered_at=timezone.now(),
tenant=getattr(request.user, 'tenant', None)
)
# Log the action
AuditLogger.log_action(
user=request.user,
action='BULK_MESSAGE_SENT',
model='Message',
object_id=str(message.message_id),
details={
'subject': message.subject,
'recipient_count': len(recipients),
'groups': serializer.validated_data['recipient_groups']
}
)
return Response({
'message': 'Bulk message sent successfully',
'recipient_count': len(recipients),
'message_data': MessageSerializer(message).data
})
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@action(detail=False, methods=['get'])
def inbox(self, request):
"""Get user's inbox messages"""
queryset = self.get_queryset().filter(
recipients__recipient=request.user
).distinct()
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
@action(detail=False, methods=['get'])
def sent(self, request):
"""Get user's sent messages"""
queryset = self.get_queryset().filter(sender=request.user)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
@action(detail=True, methods=['post'])
def mark_read(self, request, pk=None):
"""Mark message as read"""
message = self.get_object()
try:
recipient = MessageRecipient.objects.get(
message=message,
recipient=request.user
)
recipient.status = 'READ'
recipient.read_at = timezone.now()
recipient.save()
return Response({'message': 'Message marked as read'})
except MessageRecipient.DoesNotExist:
return Response(
{'error': 'You are not a recipient of this message'},
status=status.HTTP_403_FORBIDDEN
)
class AlertRuleViewSet(BaseViewSet):
"""ViewSet for AlertRule model"""
queryset = AlertRule.objects.all()
serializer_class = AlertRuleSerializer
filterset_fields = ['rule_type', 'severity', 'is_active']
search_fields = ['name', 'description']
ordering_fields = ['name', 'severity']
ordering = ['name']
@action(detail=False, methods=['get'])
def active(self, request):
"""Get active alert rules"""
queryset = self.get_queryset().filter(is_active=True)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
class AlertInstanceViewSet(BaseViewSet):
"""ViewSet for AlertInstance model"""
queryset = AlertInstance.objects.all()
serializer_class = AlertInstanceSerializer
filterset_fields = ['rule', 'severity', 'status']
search_fields = ['title', 'description', 'rule__name']
ordering_fields = ['triggered_at', 'severity']
ordering = ['-triggered_at']
@action(detail=False, methods=['post'])
def create_alert(self, request):
"""Create a new alert instance"""
serializer = AlertCreateSerializer(data=request.data)
if serializer.is_valid():
rule = AlertRule.objects.get(id=serializer.validated_data['rule_id'])
# Create alert instance
alert = AlertInstance.objects.create(
rule=rule,
title=serializer.validated_data['title'],
description=serializer.validated_data['description'],
severity=serializer.validated_data['severity'],
status='ACTIVE',
triggered_at=timezone.now(),
data=serializer.validated_data.get('data', {}),
tenant=getattr(request.user, 'tenant', None)
)
# Log the action
AuditLogger.log_action(
user=request.user,
action='ALERT_CREATED',
model='AlertInstance',
object_id=str(alert.alert_id),
details={
'rule_name': rule.name,
'title': alert.title,
'severity': alert.severity
}
)
return Response({
'message': 'Alert created successfully',
'alert': AlertInstanceSerializer(alert).data
})
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@action(detail=False, methods=['get'])
def active(self, request):
"""Get active alerts"""
queryset = self.get_queryset().filter(status='ACTIVE')
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
@action(detail=False, methods=['get'])
def critical(self, request):
"""Get critical alerts"""
queryset = self.get_queryset().filter(
severity='CRITICAL',
status__in=['ACTIVE', 'ACKNOWLEDGED']
)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
@action(detail=True, methods=['post'])
def acknowledge(self, request, pk=None):
"""Acknowledge an alert"""
alert = self.get_object()
serializer = AlertAcknowledgeSerializer(data=request.data)
if serializer.is_valid():
alert.status = 'ACKNOWLEDGED'
alert.acknowledged_at = timezone.now()
alert.acknowledged_by = request.user
alert.save()
# Log the action
AuditLogger.log_action(
user=request.user,
action='ALERT_ACKNOWLEDGED',
model='AlertInstance',
object_id=str(alert.alert_id),
details={
'title': alert.title,
'notes': serializer.validated_data.get('notes', '')
}
)
return Response({'message': 'Alert acknowledged successfully'})
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@action(detail=True, methods=['post'])
def resolve(self, request, pk=None):
"""Resolve an alert"""
alert = self.get_object()
serializer = AlertResolveSerializer(data=request.data)
if serializer.is_valid():
alert.status = 'RESOLVED'
alert.resolved_at = timezone.now()
alert.resolved_by = request.user
alert.save()
# Log the action
AuditLogger.log_action(
user=request.user,
action='ALERT_RESOLVED',
model='AlertInstance',
object_id=str(alert.alert_id),
details={
'title': alert.title,
'resolution_notes': serializer.validated_data['resolution_notes']
}
)
return Response({'message': 'Alert resolved successfully'})
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class MessageRecipientViewSet(viewsets.ReadOnlyModelViewSet):
"""ViewSet for MessageRecipient model (read-only)"""
queryset = MessageRecipient.objects.all()
serializer_class = MessageRecipientSerializer
permission_classes = [permissions.IsAuthenticated]
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_fields = ['message', 'recipient', 'status']
search_fields = ['recipient__first_name', 'recipient__last_name']
ordering_fields = ['delivered_at', 'read_at']
ordering = ['-delivered_at']
def get_queryset(self):
if hasattr(self.request.user, 'tenant') and self.request.user.tenant:
return self.queryset.filter(tenant=self.request.user.tenant)
return self.queryset
class DeliveryLogViewSet(viewsets.ReadOnlyModelViewSet):
"""ViewSet for DeliveryLog model (read-only)"""
queryset = DeliveryLog.objects.all()
serializer_class = DeliveryLogSerializer
permission_classes = [permissions.IsAuthenticated]
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_fields = ['message', 'channel', 'status']
search_fields = ['error_message']
ordering_fields = ['last_attempt_at', 'delivered_at']
ordering = ['-last_attempt_at']
def get_queryset(self):
if hasattr(self.request.user, 'tenant') and self.request.user.tenant:
return self.queryset.filter(tenant=self.request.user.tenant)
return self.queryset
class CommunicationsStatsViewSet(viewsets.ViewSet):
"""ViewSet for communications statistics"""
permission_classes = [permissions.IsAuthenticated]
@action(detail=False, methods=['get'])
def dashboard(self, request):
"""Get communications dashboard statistics"""
tenant_filter = {}
if hasattr(request.user, 'tenant') and request.user.tenant:
tenant_filter['tenant'] = request.user.tenant
today = timezone.now().date()
# Message statistics
messages = Message.objects.filter(**tenant_filter)
total_messages = messages.count()
messages_today = messages.filter(created_at__date=today).count()
pending_messages = messages.filter(
is_draft=False,
sent_at__isnull=True
).count()
# Delivery statistics
recipients = MessageRecipient.objects.filter(**tenant_filter)
delivered_messages = recipients.filter(status='DELIVERED').count()
failed_messages = recipients.filter(status='FAILED').count()
# Alert statistics
alerts = AlertInstance.objects.filter(**tenant_filter)
active_alerts = alerts.filter(status='ACTIVE').count()
# Delivery rate
total_sent = recipients.filter(status__in=['DELIVERED', 'FAILED']).count()
delivery_rate = (delivered_messages / total_sent * 100) if total_sent > 0 else 0
# Message types breakdown
message_types = messages.values('message_type').annotate(
count=Count('id')
).order_by('-count')
# Channel usage (mock data)
channels = CommunicationChannel.objects.filter(**tenant_filter, is_active=True)
channel_usage = {}
for channel in channels:
# Mock usage data
usage_count = DeliveryLog.objects.filter(
channel=channel,
last_attempt_at__date=today
).count()
channel_usage[channel.name] = usage_count
stats = {
'total_messages': total_messages,
'messages_today': messages_today,
'pending_messages': pending_messages,
'delivered_messages': delivered_messages,
'failed_messages': failed_messages,
'active_alerts': active_alerts,
'delivery_rate': round(delivery_rate, 1),
'message_types': {item['message_type']: item['count'] for item in message_types},
'channel_usage': channel_usage
}
serializer = CommunicationsStatsSerializer(stats)
return Response(serializer.data)
@action(detail=False, methods=['post'])
def send_notification(self, request):
"""Send notification using template"""
serializer = NotificationSendSerializer(data=request.data)
if serializer.is_valid():
template = NotificationTemplate.objects.get(
id=serializer.validated_data['template_id']
)
recipients = User.objects.filter(
id__in=serializer.validated_data['recipients']
)
variables = serializer.validated_data.get('variables', {})
# Create message using template
subject = template.subject_template.format(**variables) if variables else template.subject_template
body = template.body_template.format(**variables) if variables else template.body_template
message = Message.objects.create(
sender=request.user,
subject=subject,
body=body,
message_type=template.template_type,
priority='NORMAL',
sent_at=timezone.now(),
tenant=getattr(request.user, 'tenant', None)
)
# Create recipients
for recipient in recipients:
MessageRecipient.objects.create(
message=message,
recipient=recipient,
status='SENT',
delivered_at=timezone.now(),
tenant=getattr(request.user, 'tenant', None)
)
# Log the action
AuditLogger.log_action(
user=request.user,
action='NOTIFICATION_SENT',
model='Message',
object_id=str(message.message_id),
details={
'template_name': template.name,
'recipient_count': len(recipients)
}
)
return Response({
'message': 'Notification sent successfully',
'recipient_count': len(recipients),
'message_data': MessageSerializer(message).data
})
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)