from rest_framework import status from rest_framework.viewsets import ViewSet from rest_framework.permissions import IsAuthenticated from drf_spectacular.utils import extend_schema, OpenApiResponse, OpenApiExample, inline_serializer, OpenApiParameter from rest_framework import serializers from rest_framework.decorators import action from rest_framework.response import Response from django.http import HttpResponse from django.db.models import Case, When, IntegerField from api.account.serializers.alert_serializers import AlertSerializer from sitemanagement.models import Alert from api.utils.decorators import handle_exceptions from api.utils.error_serializer import ErrorResponseSerializer from api.utils.report_generators import generate_csv_report, generate_pdf_report from django.utils import timezone @extend_schema(tags=['Алерты']) class AlertView(ViewSet): # permission_classes = [IsAuthenticated] @extend_schema( summary="Получение списка алертов", description="Возвращает список алертов в системе с фильтрацией по уровню тревоги. Алерты сортируются: сначала warning, затем critical", parameters=[ OpenApiParameter( name='severity', description='Фильтр по уровню тревоги', required=False, type=str, enum=['warning', 'critical'] ) ], responses={ 200: OpenApiResponse(response=AlertSerializer(many=True), description="Список алертов успешно получен", examples=[OpenApiExample( 'Успешный ответ', value=[{ "id": 1, "name": "Датчик 1", "object": "Объект 1", "metric_value": "12.5 °C", "sensor_type_name": "Инклинометр", "message": "alert message", "severity": "warning", "created_at": "2025-10-06T15:53:11.759725+03:00", "resolved": False }] )]) }) @action(detail=False, methods=['get']) @handle_exceptions def get_alerts(self, request): alerts = Alert.objects.all() # фильтрация по severity severity = request.query_params.get('severity') if severity and severity in ['warning', 'critical']: alerts = alerts.filter(severity=severity) # сортировка: warning первыми, потом critical alerts = alerts.annotate( severity_order=Case( When(severity='critical', then=1), When(severity='warning', then=2), default=3, output_field=IntegerField() ) ).order_by('severity_order', '-created_at') serializer = AlertSerializer(alerts, many=True) return Response(serializer.data) @extend_schema( summary="Изменение статуса алерта", description="Изменяет статус обработки алерта на противоположный", responses={ 200: OpenApiResponse(response=AlertSerializer, description="Статус алерта успешно изменен", examples=[OpenApiExample( 'Успешный ответ', value={ "message": "Статус алерта успешно изменен" } )]), 404: OpenApiResponse( response=ErrorResponseSerializer, description="Алерт не найден", examples=[ OpenApiExample( 'Алерт не найден', value={"error": "Алерт не найден"}, status_codes=['404'] ) ] ) }) @action(detail=True, methods=['patch']) @handle_exceptions def change_alert_status(self, request, pk=None): try: alert = Alert.objects.get(pk=pk) alert.resolved = not alert.resolved alert.save() return Response({"message": "Статус алерта успешно изменен"}, status=status.HTTP_200_OK) except Alert.DoesNotExist: return Response( {"error": "Алерт не найден"}, status=status.HTTP_404_NOT_FOUND) @extend_schema(tags=['Репорты']) class ReportView(ViewSet): permission_classes = [IsAuthenticated] @extend_schema( summary="Генерация отчета", description="Генерирует отчет в выбранном формате (PDF или CSV)", request={'application/json': {'type': 'object', 'properties': { 'report_format': {'type': 'string', 'enum': ['pdf', 'csv']}, 'hours': {'type': 'integer', 'description': 'Количество часов для фильтрации по времени'}, 'detector_ids': {'type': 'array', 'items': {'type': 'string'}, 'description': 'Список ID датчиков для фильтрации (может быть числом или строкой вида "GLE-79")'} }}}, methods=['POST'], responses={ 200: OpenApiResponse( response=inline_serializer( name='BinaryFile', fields={ 'file': serializers.FileField() } ), description="Файл отчета для скачивания в формате PDF или CSV" ), 400: OpenApiResponse( response=ErrorResponseSerializer, description="Неверный формат", examples=[OpenApiExample( 'Неверный формат', value={"error": "Неверный формат"}, status_codes=['400'] )] ) }) @action(detail=False, methods=['post']) @handle_exceptions def get_reports(self, request): """Генерация отчета в выбранном формате""" report_format = request.data.get('report_format', '').lower() if not report_format: report_format = request.query_params.get('format', '').lower() if report_format not in ["pdf", "csv"]: return Response( {"error": "Неверный формат. Допустимые значения: pdf, csv"}, status=status.HTTP_400_BAD_REQUEST ) # Получаем параметры фильтрации hours = request.data.get('hours') detector_ids = request.data.get('detector_ids', []) alerts = Alert.objects.select_related( 'sensor', 'sensor__signal_format', 'sensor_type', 'metric' ).prefetch_related( 'sensor__zones', 'sensor__zones__object' ).all() # Фильтрация по времени (если указано количество часов) if hours and isinstance(hours, (int, float)) and hours > 0: from datetime import timedelta cutoff_time = timezone.now() - timedelta(hours=hours) alerts = alerts.filter(created_at__gte=cutoff_time) # Фильтрация по датчикам (если указаны ID) if detector_ids and isinstance(detector_ids, list) and len(detector_ids) > 0: # Конвертируем строковые ID в числовые ID базы данных numeric_ids = [] for detector_id in detector_ids: if isinstance(detector_id, (int, float)): numeric_ids.append(int(detector_id)) elif isinstance(detector_id, str): # Парсим строковые ID вида "GLE-79" или "GA-123" try: if '-' in detector_id: # Формат "TYPE-ID" - извлекаем числовую часть numeric_ids.append(int(detector_id.split('-')[-1])) else: # Предполагаем, что это просто число в строковом формате numeric_ids.append(int(detector_id)) except (ValueError, IndexError): # Пропускаем некорректные ID continue if numeric_ids: alerts = alerts.filter(sensor_id__in=numeric_ids) # текущая дата для имени файла timestamp = timezone.now().strftime("%Y%m%d_%H%M%S") if report_format == "csv": response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = f'attachment; filename="alerts_report_{timestamp}.csv"' response.write(generate_csv_report(alerts)) return response else: # pdf response = HttpResponse(content_type='application/pdf') response['Content-Disposition'] = f'attachment; filename="alerts_report_{timestamp}.pdf"' response.write(generate_pdf_report(alerts)) return response