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

526 lines
21 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, 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)