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, Avg, Sum from django.utils import timezone from datetime import timedelta import json from ..models import ( Dashboard, DashboardWidget, DataSource, Report, ReportExecution, MetricDefinition, MetricValue ) from .serializers import ( DashboardSerializer, DashboardWidgetSerializer, DataSourceSerializer, ReportSerializer, ReportExecutionSerializer, MetricDefinitionSerializer, MetricValueSerializer, AnalyticsStatsSerializer, DashboardCreateSerializer, ReportExecuteSerializer, MetricCalculateSerializer, DataSourceTestSerializer, WidgetDataSerializer ) from core.utils import AuditLogger 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 DataSourceViewSet(BaseViewSet): """ViewSet for DataSource model""" queryset = DataSource.objects.all() serializer_class = DataSourceSerializer filterset_fields = ['source_type', 'is_active'] search_fields = ['name', 'description'] ordering_fields = ['name', 'last_updated'] ordering = ['name'] @action(detail=False, methods=['get']) def active(self, request): """Get active data sources""" 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_connection(self, request, pk=None): """Test data source connection""" data_source = self.get_object() serializer = DataSourceTestSerializer(data={'source_id': pk}) if serializer.is_valid(): try: # Mock connection test - in real implementation, would test actual connection test_result = { 'success': True, 'message': 'Connection successful', 'response_time': 0.15, 'records_available': 1000 } # Update last_updated timestamp data_source.last_updated = timezone.now() data_source.save() # Log the action AuditLogger.log_action( user=request.user, action='DATA_SOURCE_TESTED', model='DataSource', object_id=str(data_source.source_id), details={ 'source_name': data_source.name, 'test_result': 'SUCCESS' } ) return Response(test_result) except Exception as e: return Response({ 'success': False, 'message': f'Connection failed: {str(e)}', 'error': str(e) }, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) class MetricDefinitionViewSet(BaseViewSet): """ViewSet for MetricDefinition model""" queryset = MetricDefinition.objects.all() serializer_class = MetricDefinitionSerializer filterset_fields = ['metric_type', 'data_source', 'is_active'] search_fields = ['name', 'description'] ordering_fields = ['name', 'metric_type'] ordering = ['name'] @action(detail=False, methods=['get']) def active(self, request): """Get active metrics""" 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 calculate(self, request, pk=None): """Calculate metric value""" metric = self.get_object() serializer = MetricCalculateSerializer(data=request.data) if serializer.is_valid(): try: # Mock calculation - in real implementation, would execute actual calculation calculated_value = 85.5 # Mock value # Determine status based on thresholds status_value = 'NORMAL' if metric.threshold_critical and calculated_value >= metric.threshold_critical: status_value = 'CRITICAL' elif metric.threshold_warning and calculated_value >= metric.threshold_warning: status_value = 'WARNING' # Create metric value record metric_value = MetricValue.objects.create( metric=metric, value=calculated_value, timestamp=timezone.now(), status=status_value, notes=f"Calculated via API by {request.user.get_full_name()}", tenant=getattr(request.user, 'tenant', None) ) # Log the action AuditLogger.log_action( user=request.user, action='METRIC_CALCULATED', model='MetricValue', object_id=str(metric_value.value_id), details={ 'metric_name': metric.name, 'calculated_value': calculated_value, 'status': status_value } ) return Response({ 'message': 'Metric calculated successfully', 'metric_value': MetricValueSerializer(metric_value).data }) except Exception as e: return Response({ 'error': f'Calculation failed: {str(e)}' }, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) class MetricValueViewSet(viewsets.ReadOnlyModelViewSet): """ViewSet for MetricValue model (read-only)""" queryset = MetricValue.objects.all() serializer_class = MetricValueSerializer permission_classes = [permissions.IsAuthenticated] filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] filterset_fields = ['metric', 'status'] search_fields = ['metric__name', 'notes'] ordering_fields = ['timestamp', 'value'] ordering = ['-timestamp'] 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 @action(detail=False, methods=['get']) def latest(self, request): """Get latest metric values""" queryset = self.get_queryset().order_by('metric', '-timestamp').distinct('metric') serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) class DashboardViewSet(BaseViewSet): """ViewSet for Dashboard model""" queryset = Dashboard.objects.all() serializer_class = DashboardSerializer filterset_fields = ['is_public', 'is_default', 'created_by'] search_fields = ['name', 'description'] ordering_fields = ['name', 'created_at'] ordering = ['name'] @action(detail=False, methods=['post']) def create_dashboard(self, request): """Create a dashboard with widgets""" serializer = DashboardCreateSerializer(data=request.data) if serializer.is_valid(): # Create dashboard dashboard = Dashboard.objects.create( name=serializer.validated_data['name'], description=serializer.validated_data.get('description', ''), layout=serializer.validated_data.get('layout', {}), is_public=serializer.validated_data.get('is_public', False), created_by=request.user, tenant=getattr(request.user, 'tenant', None) ) # Create widgets if provided widgets_data = serializer.validated_data.get('widgets', []) for widget_data in widgets_data: data_source = DataSource.objects.get(id=widget_data['data_source_id']) DashboardWidget.objects.create( dashboard=dashboard, title=widget_data['title'], description=widget_data.get('description', ''), widget_type=widget_data['widget_type'], data_source=data_source, configuration=widget_data.get('configuration', {}), position_x=widget_data.get('position_x', 0), position_y=widget_data.get('position_y', 0), width=widget_data.get('width', 4), height=widget_data.get('height', 3), tenant=getattr(request.user, 'tenant', None) ) # Log the action AuditLogger.log_action( user=request.user, action='DASHBOARD_CREATED', model='Dashboard', object_id=str(dashboard.dashboard_id), details={ 'dashboard_name': dashboard.name, 'widgets_count': len(widgets_data) } ) return Response({ 'message': 'Dashboard created successfully', 'dashboard': DashboardSerializer(dashboard).data }) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @action(detail=False, methods=['get']) def public(self, request): """Get public dashboards""" queryset = self.get_queryset().filter(is_public=True) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) @action(detail=False, methods=['get']) def my_dashboards(self, request): """Get user's dashboards""" queryset = self.get_queryset().filter(created_by=request.user) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) class DashboardWidgetViewSet(BaseViewSet): """ViewSet for DashboardWidget model""" queryset = DashboardWidget.objects.all() serializer_class = DashboardWidgetSerializer filterset_fields = ['dashboard', 'widget_type', 'data_source', 'is_visible'] search_fields = ['title', 'description'] ordering_fields = ['title', 'position_x', 'position_y'] ordering = ['position_y', 'position_x'] @action(detail=True, methods=['get']) def data(self, request, pk=None): """Get widget data""" widget = self.get_object() serializer = WidgetDataSerializer(data={'widget_id': pk}) if serializer.is_valid(): try: # Mock data generation - in real implementation, would query actual data source mock_data = { 'CHART': { 'labels': ['Jan', 'Feb', 'Mar', 'Apr', 'May'], 'datasets': [{ 'label': 'Sample Data', 'data': [65, 59, 80, 81, 56] }] }, 'TABLE': { 'headers': ['Name', 'Value', 'Status'], 'rows': [ ['Metric 1', '85.5', 'Normal'], ['Metric 2', '92.1', 'Warning'], ['Metric 3', '78.3', 'Normal'] ] }, 'KPI': { 'value': 85.5, 'target': 90.0, 'trend': 'up', 'change': '+2.3%' }, 'GAUGE': { 'value': 75, 'min': 0, 'max': 100, 'thresholds': [50, 80] } } widget_data = mock_data.get(widget.widget_type, {}) return Response({ 'widget_id': widget.widget_id, 'title': widget.title, 'widget_type': widget.widget_type, 'data': widget_data, 'last_updated': timezone.now() }) except Exception as e: return Response({ 'error': f'Failed to load widget data: {str(e)}' }, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) class ReportViewSet(BaseViewSet): """ViewSet for Report model""" queryset = Report.objects.all() serializer_class = ReportSerializer filterset_fields = ['report_type', 'data_source', 'is_active', 'created_by'] search_fields = ['name', 'description'] ordering_fields = ['name', 'created_at'] ordering = ['name'] @action(detail=False, methods=['get']) def active(self, request): """Get active reports""" 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 execute(self, request, pk=None): """Execute a report""" report = self.get_object() serializer = ReportExecuteSerializer(data=request.data) if serializer.is_valid(): try: # Create execution record execution = ReportExecution.objects.create( report=report, executed_by=request.user, start_time=timezone.now(), status='RUNNING', parameters=serializer.validated_data.get('parameters', {}), tenant=getattr(request.user, 'tenant', None) ) # Mock execution - in real implementation, would execute actual report import time time.sleep(0.1) # Simulate processing time # Update execution with results execution.end_time = timezone.now() execution.status = 'COMPLETED' execution.result_count = 150 # Mock result count execution.file_path = f'/reports/{execution.execution_id}.json' execution.save() # Log the action AuditLogger.log_action( user=request.user, action='REPORT_EXECUTED', model='ReportExecution', object_id=str(execution.execution_id), details={ 'report_name': report.name, 'result_count': execution.result_count } ) return Response({ 'message': 'Report executed successfully', 'execution': ReportExecutionSerializer(execution).data }) except Exception as e: # Update execution with error if 'execution' in locals(): execution.end_time = timezone.now() execution.status = 'FAILED' execution.error_message = str(e) execution.save() return Response({ 'error': f'Report execution failed: {str(e)}' }, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) class ReportExecutionViewSet(viewsets.ReadOnlyModelViewSet): """ViewSet for ReportExecution model (read-only)""" queryset = ReportExecution.objects.all() serializer_class = ReportExecutionSerializer permission_classes = [permissions.IsAuthenticated] filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] filterset_fields = ['report', 'executed_by', 'status'] search_fields = ['report__name', 'executed_by__first_name', 'executed_by__last_name'] ordering_fields = ['start_time', 'end_time'] ordering = ['-start_time'] 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 @action(detail=False, methods=['get']) def recent(self, request): """Get recent executions""" queryset = self.get_queryset().order_by('-start_time')[:20] serializer = self.get_serializer(queryset, many=True) return Response(serializer.data) class AnalyticsStatsViewSet(viewsets.ViewSet): """ViewSet for analytics statistics""" permission_classes = [permissions.IsAuthenticated] @action(detail=False, methods=['get']) def dashboard(self, request): """Get analytics dashboard statistics""" tenant_filter = {} if hasattr(request.user, 'tenant') and request.user.tenant: tenant_filter['tenant'] = request.user.tenant today = timezone.now().date() # Dashboard statistics dashboards = Dashboard.objects.filter(**tenant_filter) total_dashboards = dashboards.count() # Report statistics reports = Report.objects.filter(**tenant_filter) total_reports = reports.count() # Metric statistics metrics = MetricDefinition.objects.filter(**tenant_filter) total_metrics = metrics.count() # Data source statistics data_sources = DataSource.objects.filter(**tenant_filter) active_data_sources = data_sources.filter(is_active=True).count() # Execution statistics executions = ReportExecution.objects.filter(**tenant_filter) reports_executed_today = executions.filter(start_time__date=today).count() failed_executions = executions.filter( start_time__date=today, status='FAILED' ).count() # Average execution time completed_executions = executions.filter( status='COMPLETED', start_time__date=today ) avg_execution_time = 0 if completed_executions.exists(): total_time = sum([ (exec.end_time - exec.start_time).total_seconds() for exec in completed_executions if exec.end_time ]) avg_execution_time = total_time / completed_executions.count() # Popular reports (top 5) popular_reports = list( executions.filter(start_time__gte=today - timedelta(days=30)) .values('report__name') .annotate(execution_count=Count('id')) .order_by('-execution_count')[:5] ) # Metric status breakdown metric_values = MetricValue.objects.filter(**tenant_filter) latest_values = metric_values.order_by('metric', '-timestamp').distinct('metric') metric_status_breakdown = latest_values.values('status').annotate( count=Count('id') ).order_by('-count') stats = { 'total_dashboards': total_dashboards, 'total_reports': total_reports, 'total_metrics': total_metrics, 'active_data_sources': active_data_sources, 'reports_executed_today': reports_executed_today, 'failed_executions': failed_executions, 'avg_execution_time': round(avg_execution_time, 2), 'popular_reports': popular_reports, 'metric_status_breakdown': { item['status']: item['count'] for item in metric_status_breakdown } } serializer = AnalyticsStatsSerializer(stats) return Response(serializer.data)