""" Views for SLA & On-Call Management API """ from rest_framework import viewsets, status, filters from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from django_filters.rest_framework import DjangoFilterBackend from django.utils import timezone from datetime import datetime, timedelta from sla_oncall.models import ( BusinessHours, SLADefinition, EscalationPolicy, OnCallRotation, OnCallAssignment, SLAInstance, EscalationInstance, NotificationTemplate, ) from sla_oncall.serializers.sla import ( BusinessHoursSerializer, SLADefinitionSerializer, EscalationPolicySerializer, OnCallRotationSerializer, OnCallAssignmentSerializer, SLAInstanceSerializer, EscalationInstanceSerializer, NotificationTemplateSerializer, SLAInstanceCreateSerializer, OnCallAssignmentCreateSerializer, ) class BusinessHoursViewSet(viewsets.ModelViewSet): """ViewSet for BusinessHours management""" queryset = BusinessHours.objects.all() serializer_class = BusinessHoursSerializer permission_classes = [IsAuthenticated] filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] filterset_fields = ['is_active', 'is_default', 'timezone'] search_fields = ['name', 'description'] ordering_fields = ['name', 'created_at'] ordering = ['name'] @action(detail=True, methods=['post']) def test_business_hours(self, request, pk=None): """Test if a given time is within business hours""" business_hours = self.get_object() test_time = request.data.get('test_time') if test_time: try: test_datetime = datetime.fromisoformat(test_time.replace('Z', '+00:00')) is_business_hours = business_hours.is_business_hours(test_datetime) except ValueError: return Response( {'error': 'Invalid datetime format. Use ISO format.'}, status=status.HTTP_400_BAD_REQUEST ) else: is_business_hours = business_hours.is_business_hours() return Response({ 'is_business_hours': is_business_hours, 'test_time': test_time or timezone.now().isoformat() }) class SLADefinitionViewSet(viewsets.ModelViewSet): """ViewSet for SLA Definition management""" queryset = SLADefinition.objects.all() serializer_class = SLADefinitionSerializer permission_classes = [IsAuthenticated] filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] filterset_fields = ['sla_type', 'is_active', 'is_default', 'business_hours_only'] search_fields = ['name', 'description'] ordering_fields = ['name', 'created_at', 'target_duration_minutes'] ordering = ['name'] @action(detail=True, methods=['post']) def test_applicability(self, request, pk=None): """Test if SLA definition applies to a given incident""" sla_definition = self.get_object() incident_data = request.data # Create a mock incident object for testing class MockIncident: def __init__(self, data): self.category = data.get('category') self.severity = data.get('severity') self.priority = data.get('priority') mock_incident = MockIncident(incident_data) applies = sla_definition.applies_to_incident(mock_incident) return Response({ 'applies': applies, 'incident_data': incident_data }) class EscalationPolicyViewSet(viewsets.ModelViewSet): """ViewSet for Escalation Policy management""" queryset = EscalationPolicy.objects.all() serializer_class = EscalationPolicySerializer permission_classes = [IsAuthenticated] filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] filterset_fields = ['escalation_type', 'trigger_condition', 'is_active'] search_fields = ['name', 'description'] ordering_fields = ['name', 'created_at'] ordering = ['name'] class OnCallRotationViewSet(viewsets.ModelViewSet): """ViewSet for On-Call Rotation management""" queryset = OnCallRotation.objects.all() serializer_class = OnCallRotationSerializer permission_classes = [IsAuthenticated] filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] filterset_fields = ['rotation_type', 'status', 'external_system'] search_fields = ['name', 'team_name', 'description'] ordering_fields = ['name', 'created_at'] ordering = ['name'] @action(detail=True, methods=['get']) def current_oncall(self, request, pk=None): """Get current on-call person for this rotation""" rotation = self.get_object() current_assignment = rotation.get_current_oncall() if current_assignment: serializer = OnCallAssignmentSerializer(current_assignment) return Response(serializer.data) else: return Response({'message': 'No one is currently on-call'}) @action(detail=True, methods=['get']) def upcoming_assignments(self, request, pk=None): """Get upcoming on-call assignments""" rotation = self.get_object() days_ahead = int(request.query_params.get('days', 30)) future_time = timezone.now() + timedelta(days=days_ahead) upcoming = rotation.assignments.filter( start_time__lte=future_time, start_time__gte=timezone.now() ).order_by('start_time') serializer = OnCallAssignmentSerializer(upcoming, many=True) return Response(serializer.data) class OnCallAssignmentViewSet(viewsets.ModelViewSet): """ViewSet for On-Call Assignment management""" queryset = OnCallAssignment.objects.all() permission_classes = [IsAuthenticated] filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] filterset_fields = ['rotation', 'user', 'status'] search_fields = ['user__username', 'user__email', 'rotation__name'] ordering_fields = ['start_time', 'end_time', 'created_at'] ordering = ['-start_time'] def get_serializer_class(self): """Return appropriate serializer based on action""" if self.action == 'create': return OnCallAssignmentCreateSerializer return OnCallAssignmentSerializer @action(detail=True, methods=['post']) def handoff(self, request, pk=None): """Perform on-call handoff""" assignment = self.get_object() handoff_notes = request.data.get('handoff_notes', '') handed_off_from = request.user assignment.handoff_notes = handoff_notes assignment.handed_off_from = handed_off_from assignment.handoff_time = timezone.now() assignment.save() return Response({'message': 'Handoff completed successfully'}) @action(detail=True, methods=['post']) def activate(self, request, pk=None): """Activate on-call assignment""" assignment = self.get_object() if assignment.status != 'SCHEDULED': return Response( {'error': 'Only scheduled assignments can be activated'}, status=status.HTTP_400_BAD_REQUEST ) assignment.status = 'ACTIVE' assignment.save() return Response({'message': 'Assignment activated'}) @action(detail=True, methods=['post']) def complete(self, request, pk=None): """Complete on-call assignment""" assignment = self.get_object() if assignment.status != 'ACTIVE': return Response( {'error': 'Only active assignments can be completed'}, status=status.HTTP_400_BAD_REQUEST ) assignment.status = 'COMPLETED' assignment.save() return Response({'message': 'Assignment completed'}) class SLAInstanceViewSet(viewsets.ModelViewSet): """ViewSet for SLA Instance management""" queryset = SLAInstance.objects.all() permission_classes = [IsAuthenticated] filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] filterset_fields = ['status', 'escalation_triggered', 'sla_definition'] search_fields = ['incident__title', 'sla_definition__name'] ordering_fields = ['created_at', 'target_time', 'started_at'] ordering = ['-created_at'] def get_serializer_class(self): """Return appropriate serializer based on action""" if self.action == 'create': return SLAInstanceCreateSerializer return SLAInstanceSerializer @action(detail=False, methods=['get']) def breached(self, request): """Get all breached SLA instances""" breached_slas = self.queryset.filter( status='BREACHED' ).order_by('-breached_at') serializer = self.get_serializer(breached_slas, many=True) return Response(serializer.data) @action(detail=False, methods=['get']) def at_risk(self, request): """Get SLA instances at risk of breaching (within 15 minutes)""" warning_time = timezone.now() + timedelta(minutes=15) at_risk_slas = self.queryset.filter( status='ACTIVE', target_time__lte=warning_time ).order_by('target_time') serializer = self.get_serializer(at_risk_slas, many=True) return Response(serializer.data) @action(detail=True, methods=['post']) def mark_met(self, request, pk=None): """Mark SLA as met""" sla_instance = self.get_object() if sla_instance.status != 'ACTIVE': return Response( {'error': 'Only active SLA instances can be marked as met'}, status=status.HTTP_400_BAD_REQUEST ) sla_instance.status = 'MET' sla_instance.met_at = timezone.now() sla_instance.save() return Response({'message': 'SLA marked as met'}) @action(detail=True, methods=['post']) def mark_breached(self, request, pk=None): """Mark SLA as breached""" sla_instance = self.get_object() if sla_instance.status != 'ACTIVE': return Response( {'error': 'Only active SLA instances can be marked as breached'}, status=status.HTTP_400_BAD_REQUEST ) sla_instance.status = 'BREACHED' sla_instance.breached_at = timezone.now() sla_instance.save() return Response({'message': 'SLA marked as breached'}) class EscalationInstanceViewSet(viewsets.ModelViewSet): """ViewSet for Escalation Instance management""" queryset = EscalationInstance.objects.all() serializer_class = EscalationInstanceSerializer permission_classes = [IsAuthenticated] filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] filterset_fields = ['status', 'escalation_level', 'escalation_policy'] search_fields = ['incident__title', 'escalation_policy__name'] ordering_fields = ['created_at', 'triggered_at'] ordering = ['-created_at'] @action(detail=True, methods=['post']) def acknowledge(self, request, pk=None): """Acknowledge escalation""" escalation = self.get_object() if escalation.status not in ['TRIGGERED', 'PENDING']: return Response( {'error': 'Only pending or triggered escalations can be acknowledged'}, status=status.HTTP_400_BAD_REQUEST ) escalation.status = 'ACKNOWLEDGED' escalation.acknowledged_at = timezone.now() escalation.save() return Response({'message': 'Escalation acknowledged'}) @action(detail=True, methods=['post']) def resolve(self, request, pk=None): """Resolve escalation""" escalation = self.get_object() if escalation.status not in ['ACKNOWLEDGED', 'TRIGGERED']: return Response( {'error': 'Only acknowledged or triggered escalations can be resolved'}, status=status.HTTP_400_BAD_REQUEST ) escalation.status = 'RESOLVED' escalation.resolved_at = timezone.now() escalation.save() return Response({'message': 'Escalation resolved'}) class NotificationTemplateViewSet(viewsets.ModelViewSet): """ViewSet for Notification Template management""" queryset = NotificationTemplate.objects.all() serializer_class = NotificationTemplateSerializer permission_classes = [IsAuthenticated] filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] filterset_fields = ['template_type', 'channel_type', 'is_active', 'is_default'] search_fields = ['name', 'subject_template'] ordering_fields = ['name', 'created_at'] ordering = ['template_type', 'channel_type', 'name']