""" Analytics app views with comprehensive CRUD operations following healthcare best practices. """ from django.shortcuts import render, get_object_or_404, redirect from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib import messages from django.views.generic import ( ListView, DetailView, CreateView, UpdateView, DeleteView, TemplateView ) from django.urls import reverse_lazy, reverse from django.http import JsonResponse, HttpResponse from django.db.models import Q, Count, Avg, Sum, Prefetch from django.utils import timezone from django.core.paginator import Paginator from django.db import transaction from datetime import datetime, timedelta, date import json from .models import ( Dashboard, DashboardWidget, DataSource, Report, ReportExecution, MetricDefinition, MetricValue ) from .forms import ( DashboardForm, DashboardWidgetForm, DataSourceForm, ReportForm, MetricDefinitionForm ) # ============================================================================ # DASHBOARD AND OVERVIEW VIEWS # ============================================================================ class AnalyticsDashboardView(LoginRequiredMixin, TemplateView): """ Main analytics dashboard with comprehensive statistics and recent activity. """ template_name = 'analytics/dashboard.html' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Basic statistics context.update({ 'total_dashboards': Dashboard.objects.filter(tenant=self.request.user.tenant).count(), 'total_reports': Report.objects.filter(tenant=self.request.user.tenant).count(), 'total_data_sources': DataSource.objects.filter( tenant=self.request.user.tenant, is_active=True ).count(), 'total_metrics': MetricDefinition.objects.filter(tenant=self.request.user.tenant).count(), }) # Recent activity context.update({ 'recent_dashboards': Dashboard.objects.filter( tenant=self.request.user.tenant ).order_by('-created_at')[:5], 'recent_reports': Report.objects.filter( tenant=self.request.user.tenant ).order_by('-created_at')[:5], 'recent_executions': ReportExecution.objects.filter( report__tenant=self.request.user.tenant ).order_by('-started_at')[:10], }) # Execution statistics today = timezone.now().date() context.update({ 'executions_today': ReportExecution.objects.filter( report__tenant=self.request.user.tenant, started_at__date=today ).count(), 'successful_executions': ReportExecution.objects.filter( report__tenant=self.request.user.tenant, started_at__date=today, status='SUCCESS' ).count(), 'failed_executions': ReportExecution.objects.filter( report__tenant=self.request.user.tenant, started_at__date=today, status='FAILED' ).count(), }) # Data source health context.update({ 'healthy_data_sources': DataSource.objects.filter( tenant=self.request.user.tenant, is_active=True, last_test_status='SUCCESS' ).count(), 'unhealthy_data_sources': DataSource.objects.filter( tenant=self.request.user.tenant, is_active=True, last_test_status='FAILED' ).count(), }) return context # ============================================================================ # DASHBOARD VIEWS (FULL CRUD - Master Data) # ============================================================================ class DashboardListView(LoginRequiredMixin, ListView): """ List all dashboards with filtering and search capabilities. """ model = Dashboard template_name = 'analytics/dashboard_list.html' context_object_name = 'dashboards' paginate_by = 20 def get_queryset(self): queryset = Dashboard.objects.filter(tenant=self.request.user.tenant) # Search functionality search = self.request.GET.get('search') if search: queryset = queryset.filter( Q(dashboard_name__icontains=search) | Q(description__icontains=search) ) # Filter by category category = self.request.GET.get('category') if category: queryset = queryset.filter(category=category) # Filter by visibility visibility = self.request.GET.get('visibility') if visibility: queryset = queryset.filter(visibility=visibility) # Filter by status is_active = self.request.GET.get('is_active') if is_active: queryset = queryset.filter(is_active=is_active == 'true') return queryset.order_by('-created_at') def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['search'] = self.request.GET.get('search', '') context['selected_category'] = self.request.GET.get('category', '') context['selected_visibility'] = self.request.GET.get('visibility', '') context['selected_is_active'] = self.request.GET.get('is_active', '') return context class DashboardDetailView(LoginRequiredMixin, DetailView): """ Display detailed information about a specific dashboard. """ model = Dashboard template_name = 'analytics/dashboard_detail.html' context_object_name = 'dashboard' def get_queryset(self): return Dashboard.objects.filter(tenant=self.request.user.tenant) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) dashboard = self.get_object() # Dashboard widgets context['widgets'] = DashboardWidget.objects.filter( dashboard=dashboard ).order_by('position_x', 'position_y') # Usage statistics context['widget_count'] = DashboardWidget.objects.filter( dashboard=dashboard ).count() return context class DashboardCreateView(LoginRequiredMixin, CreateView): """ Create a new dashboard. """ model = Dashboard form_class = DashboardForm template_name = 'analytics/dashboard_form.html' success_url = reverse_lazy('analytics:dashboard_list') def form_valid(self, form): form.instance.tenant = self.request.user.tenant form.instance.created_by = self.request.user messages.success(self.request, 'Dashboard created successfully.') return super().form_valid(form) class DashboardUpdateView(LoginRequiredMixin, UpdateView): """ Update an existing dashboard. """ model = Dashboard form_class = DashboardForm template_name = 'analytics/dashboard_form.html' def get_queryset(self): return Dashboard.objects.filter(tenant=self.request.user.tenant) def get_success_url(self): return reverse('analytics:dashboard_detail', kwargs={'pk': self.object.pk}) def form_valid(self, form): messages.success(self.request, 'Dashboard updated successfully.') return super().form_valid(form) class DashboardDeleteView(LoginRequiredMixin, DeleteView): """ Delete a dashboard. """ model = Dashboard template_name = 'analytics/dashboard_confirm_delete.html' success_url = reverse_lazy('analytics:dashboard_list') def get_queryset(self): return Dashboard.objects.filter(tenant=self.request.user.tenant) def delete(self, request, *args, **kwargs): messages.success(request, 'Dashboard deleted successfully.') return super().delete(request, *args, **kwargs) # ============================================================================ # DASHBOARD WIDGET VIEWS (FULL CRUD - Operational Data) # ============================================================================ class DashboardWidgetListView(LoginRequiredMixin, ListView): """ List all dashboard widgets. """ model = DashboardWidget template_name = 'analytics/dashboard_widget_list.html' context_object_name = 'widgets' paginate_by = 20 def get_queryset(self): queryset = DashboardWidget.objects.filter(tenant=self.request.user.tenant) # Filter by dashboard dashboard = self.request.GET.get('dashboard') if dashboard: queryset = queryset.filter(dashboard_id=dashboard) # Filter by widget type widget_type = self.request.GET.get('widget_type') if widget_type: queryset = queryset.filter(widget_type=widget_type) return queryset.select_related('dashboard').order_by('dashboard__dashboard_name', 'position_x', 'position_y') def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['dashboards'] = Dashboard.objects.filter( tenant=self.request.user.tenant ).order_by('dashboard_name') context['selected_dashboard'] = self.request.GET.get('dashboard', '') context['selected_widget_type'] = self.request.GET.get('widget_type', '') return context class DashboardWidgetDetailView(LoginRequiredMixin, DetailView): """ Display detailed information about a specific dashboard widget. """ model = DashboardWidget template_name = 'analytics/dashboard_widget_detail.html' context_object_name = 'widget' def get_queryset(self): return DashboardWidget.objects.filter(tenant=self.request.user.tenant) class DashboardWidgetCreateView(LoginRequiredMixin, CreateView): """ Create a new dashboard widget. """ model = DashboardWidget form_class = DashboardWidgetForm template_name = 'analytics/dashboard_widget_form.html' success_url = reverse_lazy('analytics:dashboard_widget_list') def form_valid(self, form): form.instance.tenant = self.request.user.tenant messages.success(self.request, 'Dashboard widget created successfully.') return super().form_valid(form) def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['user'] = self.request.user return kwargs class DashboardWidgetUpdateView(LoginRequiredMixin, UpdateView): """ Update an existing dashboard widget. """ model = DashboardWidget form_class = DashboardWidgetForm template_name = 'analytics/dashboard_widget_form.html' def get_queryset(self): return DashboardWidget.objects.filter(tenant=self.request.user.tenant) def get_success_url(self): return reverse('analytics:dashboard_widget_detail', kwargs={'pk': self.object.pk}) def form_valid(self, form): messages.success(self.request, 'Dashboard widget updated successfully.') return super().form_valid(form) def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['user'] = self.request.user return kwargs class DashboardWidgetDeleteView(LoginRequiredMixin, DeleteView): """ Delete a dashboard widget. """ model = DashboardWidget template_name = 'analytics/dashboard_widget_confirm_delete.html' success_url = reverse_lazy('analytics:dashboard_widget_list') def get_queryset(self): return DashboardWidget.objects.filter(tenant=self.request.user.tenant) def delete(self, request, *args, **kwargs): messages.success(request, 'Dashboard widget deleted successfully.') return super().delete(request, *args, **kwargs) # ============================================================================ # DATA SOURCE VIEWS (FULL CRUD - Master Data) # ============================================================================ class DataSourceListView(LoginRequiredMixin, ListView): """ List all data sources with filtering capabilities. """ model = DataSource template_name = 'analytics/data_source_list.html' context_object_name = 'data_sources' paginate_by = 20 def get_queryset(self): queryset = DataSource.objects.filter(tenant=self.request.user.tenant) # Search functionality search = self.request.GET.get('search') if search: queryset = queryset.filter( Q(source_name__icontains=search) | Q(description__icontains=search) ) # Filter by source type source_type = self.request.GET.get('source_type') if source_type: queryset = queryset.filter(source_type=source_type) # Filter by status is_active = self.request.GET.get('is_active') if is_active: queryset = queryset.filter(is_active=is_active == 'true') # Filter by test status test_status = self.request.GET.get('test_status') if test_status: queryset = queryset.filter(last_test_status=test_status) return queryset.order_by('source_name') def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['search'] = self.request.GET.get('search', '') context['selected_source_type'] = self.request.GET.get('source_type', '') context['selected_is_active'] = self.request.GET.get('is_active', '') context['selected_test_status'] = self.request.GET.get('test_status', '') return context class DataSourceDetailView(LoginRequiredMixin, DetailView): """ Display detailed information about a specific data source. """ model = DataSource template_name = 'analytics/data_source_detail.html' context_object_name = 'data_source' def get_queryset(self): return DataSource.objects.filter(tenant=self.request.user.tenant) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) data_source = self.get_object() # Related reports context['related_reports'] = Report.objects.filter( data_source=data_source ).order_by('report_name') # Related metrics context['related_metrics'] = MetricDefinition.objects.filter( data_source=data_source ).order_by('metric_name') return context class DataSourceCreateView(LoginRequiredMixin, CreateView): """ Create a new data source. """ model = DataSource form_class = DataSourceForm template_name = 'analytics/data_source_form.html' success_url = reverse_lazy('analytics:data_source_list') def form_valid(self, form): form.instance.tenant = self.request.user.tenant messages.success(self.request, 'Data source created successfully.') return super().form_valid(form) class DataSourceUpdateView(LoginRequiredMixin, UpdateView): """ Update an existing data source. """ model = DataSource form_class = DataSourceForm template_name = 'analytics/data_source_form.html' def get_queryset(self): return DataSource.objects.filter(tenant=self.request.user.tenant) def get_success_url(self): return reverse('analytics:data_source_detail', kwargs={'pk': self.object.pk}) def form_valid(self, form): messages.success(self.request, 'Data source updated successfully.') return super().form_valid(form) class DataSourceDeleteView(LoginRequiredMixin, DeleteView): """ Delete a data source (only if not used by reports/metrics). """ model = DataSource template_name = 'analytics/data_source_confirm_delete.html' success_url = reverse_lazy('analytics:data_source_list') def get_queryset(self): return DataSource.objects.filter(tenant=self.request.user.tenant) def delete(self, request, *args, **kwargs): data_source = self.get_object() # Check if data source is used by reports if Report.objects.filter(data_source=data_source).exists(): messages.error(request, 'Cannot delete data source that is used by reports.') return redirect('analytics:data_source_detail', pk=data_source.pk) # Check if data source is used by metrics if MetricDefinition.objects.filter(data_source=data_source).exists(): messages.error(request, 'Cannot delete data source that is used by metrics.') return redirect('analytics:data_source_detail', pk=data_source.pk) messages.success(request, f'Data source {data_source.source_name} deleted successfully.') return super().delete(request, *args, **kwargs) # ============================================================================ # REPORT VIEWS (FULL CRUD - Operational Data) # ============================================================================ class ReportListView(LoginRequiredMixin, ListView): """ List all reports with filtering capabilities. """ model = Report template_name = 'analytics/report_list.html' context_object_name = 'reports' paginate_by = 20 def get_queryset(self): queryset = Report.objects.filter(tenant=self.request.user.tenant) # Search functionality search = self.request.GET.get('search') if search: queryset = queryset.filter( Q(report_name__icontains=search) | Q(description__icontains=search) ) # Filter by category category = self.request.GET.get('category') if category: queryset = queryset.filter(category=category) # Filter by data source data_source = self.request.GET.get('data_source') if data_source: queryset = queryset.filter(data_source_id=data_source) # Filter by status is_active = self.request.GET.get('is_active') if is_active: queryset = queryset.filter(is_active=is_active == 'true') return queryset.select_related('data_source').order_by('name') def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['data_sources'] = DataSource.objects.filter( tenant=self.request.user.tenant, is_active=True ).order_by('name') context['search'] = self.request.GET.get('search', '') context['selected_category'] = self.request.GET.get('category', '') context['selected_data_source'] = self.request.GET.get('data_source', '') context['selected_is_active'] = self.request.GET.get('is_active', '') return context class ReportDetailView(LoginRequiredMixin, DetailView): """ Display detailed information about a specific report. """ model = Report template_name = 'analytics/report_detail.html' context_object_name = 'report' def get_queryset(self): return Report.objects.filter(tenant=self.request.user.tenant) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) report = self.get_object() # Recent executions context['recent_executions'] = ReportExecution.objects.filter( report=report ).order_by('-execution_time')[:10] # Execution statistics context['total_executions'] = ReportExecution.objects.filter( report=report ).count() context['successful_executions'] = ReportExecution.objects.filter( report=report, status='SUCCESS' ).count() return context class ReportCreateView(LoginRequiredMixin, CreateView): """ Create a new report. """ model = Report form_class = ReportForm template_name = 'analytics/report_form.html' success_url = reverse_lazy('analytics:report_list') def form_valid(self, form): form.instance.tenant = self.request.user.tenant form.instance.created_by = self.request.user messages.success(self.request, 'Report created successfully.') return super().form_valid(form) def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['user'] = self.request.user return kwargs class ReportUpdateView(LoginRequiredMixin, UpdateView): """ Update an existing report. """ model = Report form_class = ReportForm template_name = 'analytics/report_form.html' def get_queryset(self): return Report.objects.filter(tenant=self.request.user.tenant) def get_success_url(self): return reverse('analytics:report_detail', kwargs={'pk': self.object.pk}) def form_valid(self, form): messages.success(self.request, 'Report updated successfully.') return super().form_valid(form) def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['user'] = self.request.user return kwargs class ReportDeleteView(LoginRequiredMixin, DeleteView): """ Delete a report. """ model = Report template_name = 'analytics/report_confirm_delete.html' success_url = reverse_lazy('analytics:report_list') def get_queryset(self): return Report.objects.filter(tenant=self.request.user.tenant) def delete(self, request, *args, **kwargs): messages.success(request, 'Report deleted successfully.') return super().delete(request, *args, **kwargs) # ============================================================================ # REPORT EXECUTION VIEWS (READ-ONLY - System Generated) # ============================================================================ class ReportExecutionListView(LoginRequiredMixin, ListView): """ List all report executions (read-only). """ model = ReportExecution template_name = 'analytics/report_execution_list.html' context_object_name = 'executions' paginate_by = 20 def get_queryset(self): queryset = ReportExecution.objects.filter(tenant=self.request.user.tenant) # Filter by report report = self.request.GET.get('report') if report: queryset = queryset.filter(report_id=report) # Filter by status status = self.request.GET.get('status') if status: queryset = queryset.filter(status=status) # Filter by date range start_date = self.request.GET.get('start_date') end_date = self.request.GET.get('end_date') if start_date: queryset = queryset.filter(execution_time__date__gte=start_date) if end_date: queryset = queryset.filter(execution_time__date__lte=end_date) return queryset.select_related('report').order_by('-execution_time') def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['reports'] = Report.objects.filter( tenant=self.request.user.tenant ).order_by('report_name') context['selected_report'] = self.request.GET.get('report', '') context['selected_status'] = self.request.GET.get('status', '') context['start_date'] = self.request.GET.get('start_date', '') context['end_date'] = self.request.GET.get('end_date', '') return context class ReportExecutionDetailView(LoginRequiredMixin, DetailView): """ Display detailed information about a specific report execution. """ model = ReportExecution template_name = 'analytics/report_execution_detail.html' context_object_name = 'execution' def get_queryset(self): return ReportExecution.objects.filter(tenant=self.request.user.tenant) # ============================================================================ # METRIC DEFINITION VIEWS (FULL CRUD - Master Data) # ============================================================================ class MetricDefinitionListView(LoginRequiredMixin, ListView): """ List all metric definitions with filtering capabilities. """ model = MetricDefinition template_name = 'analytics/metric_list.html' context_object_name = 'metrics' paginate_by = 20 def get_queryset(self): queryset = MetricDefinition.objects.filter(tenant=self.request.user.tenant) # Search functionality search = self.request.GET.get('search') if search: queryset = queryset.filter( Q(name__icontains=search) | Q(description__icontains=search) ) # Filter by category category = self.request.GET.get('category') if category: queryset = queryset.filter(category=category) # Filter by data source data_source = self.request.GET.get('data_source') if data_source: queryset = queryset.filter(data_source_id=data_source) # Filter by status is_active = self.request.GET.get('is_active') if is_active: queryset = queryset.filter(is_active=is_active == 'true') return queryset.select_related('data_source').order_by('name') def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['data_sources'] = DataSource.objects.filter( tenant=self.request.user.tenant, is_active=True ).order_by('name') context['search'] = self.request.GET.get('search', '') context['selected_category'] = self.request.GET.get('category', '') context['selected_data_source'] = self.request.GET.get('data_source', '') context['selected_is_active'] = self.request.GET.get('is_active', '') return context class MetricDefinitionDetailView(LoginRequiredMixin, DetailView): """ Display detailed information about a specific metric definition. """ model = MetricDefinition template_name = 'analytics/metric_definition_detail.html' context_object_name = 'metric' def get_queryset(self): return MetricDefinition.objects.filter(tenant=self.request.user.tenant) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) metric = self.get_object() # Recent values context['recent_values'] = MetricValue.objects.filter( metric=metric ).order_by('-measurement_date')[:10] # Value statistics context['total_values'] = MetricValue.objects.filter( metric=metric ).count() return context class MetricDefinitionCreateView(LoginRequiredMixin, CreateView): """ Create a new metric definition. """ model = MetricDefinition form_class = MetricDefinitionForm template_name = 'analytics/metric_definition_form.html' success_url = reverse_lazy('analytics:metric_definition_list') def form_valid(self, form): form.instance.tenant = self.request.user.tenant messages.success(self.request, 'Metric definition created successfully.') return super().form_valid(form) def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['user'] = self.request.user return kwargs class MetricDefinitionUpdateView(LoginRequiredMixin, UpdateView): """ Update an existing metric definition. """ model = MetricDefinition form_class = MetricDefinitionForm template_name = 'analytics/metric_definition_form.html' def get_queryset(self): return MetricDefinition.objects.filter(tenant=self.request.user.tenant) def get_success_url(self): return reverse('analytics:metric_definition_detail', kwargs={'pk': self.object.pk}) def form_valid(self, form): messages.success(self.request, 'Metric definition updated successfully.') return super().form_valid(form) def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs['user'] = self.request.user return kwargs class MetricDefinitionDeleteView(LoginRequiredMixin, DeleteView): """ Delete a metric definition. """ model = MetricDefinition template_name = 'analytics/metric_definition_confirm_delete.html' success_url = reverse_lazy('analytics:metric_definition_list') def get_queryset(self): return MetricDefinition.objects.filter(tenant=self.request.user.tenant) def delete(self, request, *args, **kwargs): messages.success(request, 'Metric definition deleted successfully.') return super().delete(request, *args, **kwargs) # ============================================================================ # METRIC VALUE VIEWS (READ-ONLY - System Generated) # ============================================================================ class MetricValueListView(LoginRequiredMixin, ListView): """ List all metric values (read-only). """ model = MetricValue template_name = 'analytics/metric_value_list.html' context_object_name = 'metric_values' paginate_by = 20 def get_queryset(self): queryset = MetricValue.objects.filter(tenant=self.request.user.tenant) # Filter by metric metric = self.request.GET.get('metric') if metric: queryset = queryset.filter(metric_id=metric) # Filter by date range start_date = self.request.GET.get('start_date') end_date = self.request.GET.get('end_date') if start_date: queryset = queryset.filter(measurement_date__gte=start_date) if end_date: queryset = queryset.filter(measurement_date__lte=end_date) return queryset.select_related('metric').order_by('-measurement_date') def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['metrics'] = MetricDefinition.objects.filter( tenant=self.request.user.tenant ).order_by('metric_name') context['selected_metric'] = self.request.GET.get('metric', '') context['start_date'] = self.request.GET.get('start_date', '') context['end_date'] = self.request.GET.get('end_date', '') return context # ============================================================================ # HTMX VIEWS FOR REAL-TIME UPDATES # ============================================================================ @login_required def analytics_stats(request): """ Return analytics statistics for dashboard updates. """ context = { 'total_dashboards': Dashboard.objects.filter(tenant=request.user.tenant).count(), 'total_reports': Report.objects.filter(tenant=request.user.tenant).count(), 'total_data_sources': DataSource.objects.filter( tenant=request.user.tenant, is_active=True ).count(), 'total_metrics': MetricDefinition.objects.filter(tenant=request.user.tenant).count(), 'executions_today': ReportExecution.objects.filter( report__tenant=request.user.tenant, started_at__date=timezone.now().date() ).count(), 'successful_executions': ReportExecution.objects.filter( report__tenant=request.user.tenant, started_at__date=timezone.now().date(), status='SUCCESS' ).count(), } return render(request, 'analytics/partials/analytics_stats.html', context) @login_required def dashboard_search(request): """ Search dashboards for HTMX updates. """ search = request.GET.get('search', '') dashboards = Dashboard.objects.filter( tenant=request.user.tenant ) if search: dashboards = dashboards.filter( Q(dashboard_name__icontains=search) | Q(description__icontains=search) ) dashboards = dashboards[:20] return render(request, 'analytics/dashboard_list.html', { 'dashboards': dashboards }) # ============================================================================ # ACTION VIEWS FOR WORKFLOW OPERATIONS # ============================================================================ @login_required def test_data_source(request, data_source_id): """ Test a data source connection. """ try: data_source = DataSource.objects.get( id=data_source_id, tenant=request.user.tenant ) # Simulate connection test (implement actual logic based on source type) data_source.last_test_time = timezone.now() data_source.last_test_status = 'SUCCESS' data_source.last_test_message = 'Connection successful' data_source.save() messages.success(request, f'Data source {data_source.name} tested successfully.') except DataSource.DoesNotExist: messages.error(request, 'Data source not found.') return redirect('analytics:data_source_detail', pk=data_source_id) @login_required def execute_report(request, report_id): """ Execute a report. """ try: report = Report.objects.get( id=report_id, tenant=request.user.tenant ) # Create report execution record execution = ReportExecution.objects.create( tenant=request.user.tenant, report=report, execution_time=timezone.now(), executed_by=request.user, status='RUNNING' ) # Simulate report execution (implement actual logic) execution.status = 'SUCCESS' execution.completion_time = timezone.now() execution.save() messages.success(request, f'Report {report.name} executed successfully.') except Report.DoesNotExist: messages.error(request, 'Report not found.') return redirect('analytics:report_detail', pk=report_id) @login_required def calculate_metric(request, metric_id): """ Calculate a metric value. """ try: metric = MetricDefinition.objects.get( id=metric_id, tenant=request.user.tenant ) # Simulate metric calculation (implement actual logic) metric_value = MetricValue.objects.create( tenant=request.user.tenant, metric=metric, measurement_date=timezone.now().date(), value=100.0, # Placeholder value calculated_at=timezone.now() ) messages.success(request, f'Metric {metric.name} calculated successfully.') except MetricDefinition.DoesNotExist: messages.error(request, 'Metric definition not found.') return redirect('analytics:metric_definition_detail', pk=metric_id) @login_required def metric_stats(request): """ AJAX endpoint to return metric statistics for dashboard widgets. Returns JSON data with metric counts and statistics. """ try: # Get current tenant tenant = request.user.tenant today = timezone.now().date() # Calculate total metrics total_metrics = MetricDefinition.objects.filter( tenant=tenant ).count() # Calculate active metrics (metrics that have been calculated recently) active_metrics = MetricDefinition.objects.filter( tenant=tenant, is_active=True ).count() # Calculate alerts triggered (metrics with values above threshold) alerts_triggered = MetricValue.objects.filter( tenant=tenant, calculated_at__date=today, # Add your alert logic here - example: value above certain threshold ).count() # Calculate metrics updated today updated_today = MetricValue.objects.filter( tenant=tenant, calculated_at__date=today ).count() # Additional statistics that might be useful recent_calculations = MetricValue.objects.filter( tenant=tenant, calculated_at__gte=timezone.now() - timedelta(hours=24) ).count() failed_calculations = MetricValue.objects.filter( tenant=tenant, calculated_at__date=today, # Add status field check if you have one ).count() # Return JSON response return JsonResponse({ 'success': True, 'total_metrics': total_metrics, 'active_metrics': active_metrics, 'alerts_triggered': alerts_triggered, 'updated_today': updated_today, 'recent_calculations': recent_calculations, 'failed_calculations': failed_calculations, 'last_updated': timezone.now().isoformat() }) except Exception as e: # Log the error in production return JsonResponse({ 'success': False, 'error': str(e), 'total_metrics': 0, 'active_metrics': 0, 'alerts_triggered': 0, 'updated_today': 0 }, status=500) @login_required def metric_list(request): """ AJAX endpoint for DataTable to return paginated metric data. Supports server-side processing with search, filtering, and sorting. """ try: # Get DataTable parameters draw = int(request.GET.get('draw', 1)) start = int(request.GET.get('start', 0)) length = int(request.GET.get('length', 25)) search_value = request.GET.get('search[value]', '') # Custom filter parameters category_filter = request.GET.get('category', '') status_filter = request.GET.get('status', '') frequency_filter = request.GET.get('frequency', '') search_input = request.GET.get('search_value', '') # Get ordering parameters order_column_index = int(request.GET.get('order[0][column]', 6)) # Default to last_updated order_direction = request.GET.get('order[0][dir]', 'desc') # Column mapping for ordering columns = [ 'id', # 0 'name', # 1 'category', # 2 'current_value', # 3 'target_value', # 4 'status', # 5 'updated_at', # 6 'update_frequency', # 7 'id' # 8 - actions column ] order_column = columns[order_column_index] if order_column_index < len(columns) else 'updated_at' if order_direction == 'desc': order_column = f'-{order_column}' # Base queryset queryset = MetricDefinition.objects.filter( tenant=request.user.tenant ).select_related('created_by').prefetch_related('metricvalue_set') # Apply search filters if search_value or search_input: search_term = search_value or search_input queryset = queryset.filter( Q(name__icontains=search_term) | Q(description__icontains=search_term) | Q(formula__icontains=search_term) ) # Apply category filter if category_filter: queryset = queryset.filter(category=category_filter) # Apply status filter if status_filter: queryset = queryset.filter(status=status_filter) # Apply frequency filter if frequency_filter: queryset = queryset.filter(update_frequency=frequency_filter) # Get total count before pagination total_records = queryset.count() # Apply ordering queryset = queryset.order_by(order_column) # Apply pagination page_number = (start // length) + 1 paginator = Paginator(queryset, length) page_obj = paginator.get_page(page_number) # Prepare data for DataTable data = [] for metric in page_obj: # Get latest metric value latest_value = metric.metricvalue_set.order_by('-calculated_at').first() current_value = latest_value.value if latest_value else None last_updated = latest_value.calculated_at if latest_value else None # Get display values for choices category_display = metric.get_category_display() if hasattr(metric, 'get_category_display') else metric.category status_display = metric.get_status_display() if hasattr(metric, 'get_status_display') else metric.status frequency_display = metric.get_update_frequency_display() if hasattr(metric, 'get_update_frequency_display') else metric.update_frequency row_data = { 'id': metric.id, 'name': metric.name, 'description': metric.description or '', 'category': metric.category, 'category_display': category_display, 'current_value': float(current_value) if current_value is not None else None, 'target_value': float(metric.target_value) if metric.target_value is not None else None, 'target_direction': getattr(metric, 'target_direction', 'HIGHER'), 'unit': getattr(metric, 'unit', ''), 'status': metric.status if hasattr(metric, 'status') else 'ACTIVE', 'status_display': status_display, 'last_updated': last_updated.isoformat() if last_updated else None, 'update_frequency': metric.update_frequency if hasattr(metric, 'update_frequency') else 'DAILY', 'frequency_display': frequency_display, 'created_by': metric.created_by.get_full_name() if metric.created_by else '', 'created_at': metric.created_at.isoformat() if hasattr(metric, 'created_at') else None, 'is_active': getattr(metric, 'is_active', True) } data.append(row_data) # Return DataTable response response_data = { 'draw': draw, 'recordsTotal': MetricDefinition.objects.filter(tenant=request.user.tenant).count(), 'recordsFiltered': total_records, 'data': data } return JsonResponse(response_data) except Exception as e: # Return error response for DataTable return JsonResponse({ 'draw': int(request.GET.get('draw', 1)), 'recordsTotal': 0, 'recordsFiltered': 0, 'data': [], 'error': str(e) }, status=500) from django.contrib.auth.decorators import login_required from django.http import JsonResponse from analytics.models import Report @login_required def report_list(request): """ Return full report data for front-end DataTables client-side processing. """ latest_exec_qs = ReportExecution.objects.order_by('-started_at') reports = ( Report.objects .filter(tenant=request.user.tenant) .select_related('created_by', 'data_source') .prefetch_related(Prefetch('executions', queryset=latest_exec_qs, to_attr='prefetched_execs')) .annotate(execution_count=Count('executions')) ) data = [] for report in reports: latest_exec = report.prefetched_execs[0] if getattr(report, 'prefetched_execs', None) else None data.append({ 'id': report.report_id, 'name': report.name, 'description': report.description or '', # If "category" is different from report_type, change this: 'category': report.report_type, 'category_display': report.get_report_type_display(), 'type': report.report_type, 'type_display': report.get_report_type_display(), 'status': latest_exec.status if latest_exec else None, 'status_display': latest_exec.get_status_display() if latest_exec else None, 'last_generated': latest_exec.started_at.isoformat() if latest_exec else None, 'next_run': None, 'created_by': ({ 'id': report.created_by.id, 'username': report.created_by.username, 'full_name': report.created_by.get_full_name() or report.created_by.username, 'email': report.created_by.email } if report.created_by else None), 'created_at': report.created_at.isoformat(), 'updated_at': report.updated_at.isoformat(), 'data_source': ({ 'id': report.data_source.source_id, 'name': report.data_source.name } if report.data_source else None), # 'execution_count': report.execution_count, 'is_active': report.is_active, 'output_format': report.output_format, 'parameters': latest_exec.execution_parameters if latest_exec and latest_exec.execution_parameters else {}, # 'schedule_enabled': report.schedule_enabled, }) return JsonResponse({'data': data}) # # """ # Views for the analytics app. # """ # # from django.shortcuts import render, redirect, get_object_or_404 # from django.urls import reverse, reverse_lazy # from django.http import HttpResponse, JsonResponse # from django.views.generic import ( # ListView, DetailView, CreateView, UpdateView, DeleteView, # TemplateView # ) # from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin # from django.contrib.auth.decorators import login_required, permission_required # from django.contrib import messages # from django.db.models import Q, Count, Avg, Sum, F # from django.utils import timezone # from django.utils.translation import gettext_lazy as _ # from django.views.decorators.http import require_http_methods # from django.template.loader import render_to_string # import json # from datetime import datetime, timedelta # # from core.models import Tenant, AuditLogEntry # from core.utils import AuditLogger # from .models import ( # Dashboard, DashboardWidget, DataSource, Report, # ReportExecution, MetricDefinition, MetricValue # ) # from .forms import ( # DashboardForm, DashboardWidgetForm, DataSourceForm, ReportForm, # ReportExecutionForm, MetricDefinitionForm, MetricValueForm, # AnalyticsSearchForm, DashboardWidgetPositionForm # ) # # # class AnalyticsMixin: # """ # Mixin for analytics views that ensures tenant-specific data access. # """ # def get_queryset(self): # """ # Filter queryset to only include items from the user's tenant. # """ # queryset = super().get_queryset() # if hasattr(self.request, 'tenant') and self.request.tenant: # return queryset.filter(tenant=self.request.tenant) # return queryset.none() # # def get_form_kwargs(self): # """ # Add tenant and user to form kwargs. # """ # kwargs = super().get_form_kwargs() # kwargs['tenant'] = getattr(self.request, 'tenant', None) # kwargs['user'] = self.request.user # return kwargs # # def form_valid(self, form): # """ # Set tenant on the object if not already set. # """ # obj = form.instance # if not obj.tenant_id and hasattr(self.request, 'tenant'): # obj.tenant = self.request.tenant # # # Set created_by for new objects # if not obj.pk and not obj.created_by_id: # obj.created_by = self.request.user # # return super().form_valid(form) # # # class AnalyticsDashboardView(LoginRequiredMixin, TemplateView): # """ # Main analytics dashboard view showing key metrics and links. # """ # template_name = 'analytics/dashboard.html' # # def get_context_data(self, **kwargs): # context = super().get_context_data(**kwargs) # tenant = getattr(self.request, 'tenant', None) # # if tenant: # # Get default dashboard if it exists # default_dashboard = Dashboard.objects.filter( # tenant=tenant, # is_default=True, # is_active=True # ).first() # # # Get recent dashboards # recent_dashboards = Dashboard.objects.filter( # tenant=tenant, # is_active=True # ).order_by('-updated_at')[:5] # # # Get recent reports # recent_reports = Report.objects.filter( # tenant=tenant, # is_active=True # ).order_by('-updated_at')[:5] # # # Get recent report executions # recent_executions = ReportExecution.objects.filter( # report__tenant=tenant # ).order_by('-executed_at')[:5] # # # Get metrics in critical or warning state # critical_metrics = MetricDefinition.objects.filter( # tenant=tenant, # is_active=True # ).filter( # Q(values__value__gte=F('threshold_critical')) | # Q(values__value__gte=F('threshold_warning')) # ).distinct()[:5] # # context.update({ # 'default_dashboard': default_dashboard, # 'recent_dashboards': recent_dashboards, # 'recent_reports': recent_reports, # 'recent_executions': recent_executions, # 'critical_metrics': critical_metrics, # 'dashboard_count': Dashboard.objects.filter(tenant=tenant).count(), # 'report_count': Report.objects.filter(tenant=tenant).count(), # 'metric_count': MetricDefinition.objects.filter(tenant=tenant).count(), # 'data_source_count': DataSource.objects.filter(tenant=tenant).count() # }) # # return context # # # # ============================================================================ # # DASHBOARD VIEWS # # ============================================================================ # # class DashboardListView(LoginRequiredMixin, AnalyticsMixin, ListView): # """ # List all dashboards for the current tenant. # """ # model = Dashboard # template_name = 'analytics/dashboard_list.html' # context_object_name = 'dashboards' # paginate_by = 10 # # def get_queryset(self): # queryset = super().get_queryset().order_by('-updated_at') # search_form = AnalyticsSearchForm(self.request.GET) # # if search_form.is_valid(): # search = search_form.cleaned_data.get('search') # status = search_form.cleaned_data.get('status') # date_from = search_form.cleaned_data.get('date_from') # date_to = search_form.cleaned_data.get('date_to') # # if search: # queryset = queryset.filter( # Q(name__icontains=search) | # Q(description__icontains=search) | # Q(dashboard_type__icontains=search) # ) # # if status == 'active': # queryset = queryset.filter(is_active=True) # elif status == 'inactive': # queryset = queryset.filter(is_active=False) # # if date_from: # queryset = queryset.filter(created_at__gte=date_from) # # if date_to: # queryset = queryset.filter(created_at__lte=date_to) # # return queryset # # def get_context_data(self, **kwargs): # context = super().get_context_data(**kwargs) # context['search_form'] = AnalyticsSearchForm(self.request.GET) # context['dashboard_types'] = Dashboard.DASHBOARD_TYPES # return context # # # class DashboardDetailView(LoginRequiredMixin, AnalyticsMixin, DetailView): # """ # Display a single dashboard with its widgets. # """ # model = Dashboard # template_name = 'analytics/dashboard_detail.html' # context_object_name = 'dashboard' # # def get_object(self, queryset=None): # """ # Get the dashboard and verify the user has access. # """ # obj = super().get_object(queryset) # if not obj.user_has_access(self.request.user): # messages.error(self.request, _("You do not have access to this dashboard.")) # return redirect('analytics:dashboard_list') # return obj # # def get_context_data(self, **kwargs): # context = super().get_context_data(**kwargs) # dashboard = self.object # # # Get all widgets for this dashboard # widgets = DashboardWidget.objects.filter( # dashboard=dashboard, # is_active=True # ).order_by('position_y', 'position_x') # # context['widgets'] = widgets # context['layout_config'] = json.dumps(dashboard.layout_config) # context['refresh_interval'] = dashboard.refresh_interval # # # Add user permission context # context['can_edit'] = self.request.user.has_perm('analytics.change_dashboard') # context['can_delete'] = self.request.user.has_perm('analytics.delete_dashboard') # # return context # # # class DashboardCreateView(LoginRequiredMixin, PermissionRequiredMixin, AnalyticsMixin, CreateView): # """ # Create a new dashboard. # """ # model = Dashboard # form_class = DashboardForm # template_name = 'analytics/dashboard_form.html' # permission_required = 'analytics.add_dashboard' # # def get_success_url(self): # return reverse('analytics:dashboard_detail', kwargs={'pk': self.object.pk}) # # def form_valid(self, form): # """ # Set tenant and created_by on the dashboard. # """ # response = super().form_valid(form) # # # Log the action # AuditLogger.log_action( # user=self.request.user, # action='DASHBOARD_CREATED', # model='Dashboard', # object_id=str(self.object.dashboard_id), # details={ # 'name': self.object.name, # 'dashboard_type': self.object.dashboard_type # } # ) # # messages.success(self.request, _("Dashboard created successfully.")) # return response # # # class DashboardUpdateView(LoginRequiredMixin, PermissionRequiredMixin, AnalyticsMixin, UpdateView): # """ # Update an existing dashboard. # """ # model = Dashboard # form_class = DashboardForm # template_name = 'analytics/dashboard_form.html' # permission_required = 'analytics.change_dashboard' # # def get_success_url(self): # return reverse('analytics:dashboard_detail', kwargs={'pk': self.object.pk}) # # def form_valid(self, form): # """ # Update the dashboard and log the action. # """ # response = super().form_valid(form) # # # Log the action # AuditLogger.log_action( # user=self.request.user, # action='DASHBOARD_UPDATED', # model='Dashboard', # object_id=str(self.object.dashboard_id), # details={ # 'name': self.object.name, # 'dashboard_type': self.object.dashboard_type # } # ) # # messages.success(self.request, _("Dashboard updated successfully.")) # return response # # # class DashboardDeleteView(LoginRequiredMixin, PermissionRequiredMixin, AnalyticsMixin, DeleteView): # """ # Delete a dashboard. # """ # model = Dashboard # template_name = 'analytics/dashboard_confirm_delete.html' # permission_required = 'analytics.delete_dashboard' # success_url = reverse_lazy('analytics:dashboard_list') # # def delete(self, request, *args, **kwargs): # """ # Delete the dashboard and log the action. # """ # dashboard = self.get_object() # dashboard_id = dashboard.dashboard_id # dashboard_name = dashboard.name # # # Delete the dashboard # response = super().delete(request, *args, **kwargs) # # # Log the action # AuditLogger.log_action( # user=self.request.user, # action='DASHBOARD_DELETED', # model='Dashboard', # object_id=str(dashboard_id), # details={ # 'name': dashboard_name # } # ) # # messages.success(request, _("Dashboard deleted successfully.")) # return response # # # # ============================================================================ # # DASHBOARD WIDGET VIEWS # # ============================================================================ # # class DashboardWidgetListView(LoginRequiredMixin, AnalyticsMixin, ListView): # """ # List all widgets, optionally filtered by dashboard. # """ # model = DashboardWidget # template_name = 'analytics/widget_list.html' # context_object_name = 'widgets' # paginate_by = 20 # # def get_queryset(self): # queryset = super().get_queryset().order_by('-updated_at') # # # Filter by dashboard if specified # dashboard_id = self.request.GET.get('dashboard_id') # if dashboard_id: # queryset = queryset.filter(dashboard_id=dashboard_id) # # # Filter by widget type if specified # widget_type = self.request.GET.get('widget_type') # if widget_type: # queryset = queryset.filter(widget_type=widget_type) # # # Search filter # search = self.request.GET.get('search') # if search: # queryset = queryset.filter( # Q(title__icontains=search) | # Q(description__icontains=search) | # Q(dashboard__name__icontains=search) # ) # # return queryset # # def get_context_data(self, **kwargs): # context = super().get_context_data(**kwargs) # tenant = getattr(self.request, 'tenant', None) # # # Add dashboards for filter dropdown # if tenant: # context['dashboards'] = Dashboard.objects.filter( # tenant=tenant, # is_active=True # ).order_by('name') # # context['widget_types'] = DashboardWidget.WIDGET_TYPES # context['dashboard_id'] = self.request.GET.get('dashboard_id') # context['widget_type'] = self.request.GET.get('widget_type') # context['search'] = self.request.GET.get('search') # # return context # # # class DashboardWidgetDetailView(LoginRequiredMixin, AnalyticsMixin, DetailView): # """ # Display a single widget with its configuration. # """ # model = DashboardWidget # template_name = 'analytics/widget_detail.html' # context_object_name = 'widget' # # def get_context_data(self, **kwargs): # context = super().get_context_data(**kwargs) # widget = self.object # # # Add preview data if available # if widget.data_source: # try: # preview_data = widget.get_data() # context['preview_data'] = preview_data # except Exception as e: # context['preview_error'] = str(e) # # # Add user permission context # context['can_edit'] = self.request.user.has_perm('analytics.change_dashboardwidget') # context['can_delete'] = self.request.user.has_perm('analytics.delete_dashboardwidget') # # return context # # # class DashboardWidgetCreateView(LoginRequiredMixin, PermissionRequiredMixin, AnalyticsMixin, CreateView): # """ # Create a new dashboard widget. # """ # model = DashboardWidget # form_class = DashboardWidgetForm # template_name = 'analytics/widget_form.html' # permission_required = 'analytics.add_dashboardwidget' # # def get_initial(self): # """ # Pre-populate dashboard if specified in URL. # """ # initial = super().get_initial() # dashboard_id = self.request.GET.get('dashboard_id') # if dashboard_id: # initial['dashboard'] = dashboard_id # return initial # # def get_success_url(self): # """ # Redirect to widget detail or back to dashboard. # """ # if 'dashboard_id' in self.request.GET: # return reverse('analytics:dashboard_detail', kwargs={'pk': self.request.GET['dashboard_id']}) # return reverse('analytics:widget_detail', kwargs={'pk': self.object.pk}) # # def form_valid(self, form): # """ # Set tenant and created_by on the widget. # """ # response = super().form_valid(form) # # # Log the action # AuditLogger.log_action( # user=self.request.user, # action='DASHBOARD_WIDGET_CREATED', # model='DashboardWidget', # object_id=str(self.object.widget_id), # details={ # 'title': self.object.title, # 'widget_type': self.object.widget_type, # 'dashboard': str(self.object.dashboard.dashboard_id) # } # ) # # messages.success(self.request, _("Widget created successfully.")) # return response # # # class DashboardWidgetUpdateView(LoginRequiredMixin, PermissionRequiredMixin, AnalyticsMixin, UpdateView): # """ # Update an existing dashboard widget. # """ # model = DashboardWidget # form_class = DashboardWidgetForm # template_name = 'analytics/widget_form.html' # permission_required = 'analytics.change_dashboardwidget' # # def get_success_url(self): # """ # Redirect to widget detail or back to dashboard. # """ # if 'next' in self.request.GET: # return self.request.GET['next'] # return reverse('analytics:widget_detail', kwargs={'pk': self.object.pk}) # # def form_valid(self, form): # """ # Update the widget and log the action. # """ # response = super().form_valid(form) # # # Log the action # AuditLogger.log_action( # user=self.request.user, # action='DASHBOARD_WIDGET_UPDATED', # model='DashboardWidget', # object_id=str(self.object.widget_id), # details={ # 'title': self.object.title, # 'widget_type': self.object.widget_type, # 'dashboard': str(self.object.dashboard.dashboard_id) # } # ) # # messages.success(self.request, _("Widget updated successfully.")) # return response # # # class DashboardWidgetDeleteView(LoginRequiredMixin, PermissionRequiredMixin, AnalyticsMixin, DeleteView): # """ # Delete a dashboard widget. # """ # model = DashboardWidget # template_name = 'analytics/widget_confirm_delete.html' # permission_required = 'analytics.delete_dashboardwidget' # # def get_success_url(self): # """ # Redirect back to dashboard or widget list. # """ # if self.object.dashboard: # return reverse('analytics:dashboard_detail', kwargs={'pk': self.object.dashboard.pk}) # return reverse_lazy('analytics:widget_list') # # def delete(self, request, *args, **kwargs): # """ # Delete the widget and log the action. # """ # widget = self.get_object() # widget_id = widget.widget_id # widget_title = widget.title # dashboard_id = widget.dashboard.dashboard_id if widget.dashboard else None # # # Delete the widget # response = super().delete(request, *args, **kwargs) # # # Log the action # AuditLogger.log_action( # user=self.request.user, # action='DASHBOARD_WIDGET_DELETED', # model='DashboardWidget', # object_id=str(widget_id), # details={ # 'title': widget_title, # 'dashboard': str(dashboard_id) if dashboard_id else None # } # ) # # messages.success(request, _("Widget deleted successfully.")) # return response # # # # ============================================================================ # # DATA SOURCE VIEWS # # ============================================================================ # # class DataSourceListView(LoginRequiredMixin, AnalyticsMixin, ListView): # """ # List all data sources for the current tenant. # """ # model = DataSource # template_name = 'analytics/data_source_list.html' # context_object_name = 'data_sources' # paginate_by = 10 # # def get_queryset(self): # queryset = super().get_queryset().order_by('-updated_at') # search_form = AnalyticsSearchForm(self.request.GET) # # if search_form.is_valid(): # search = search_form.cleaned_data.get('search') # status = search_form.cleaned_data.get('status') # date_from = search_form.cleaned_data.get('date_from') # date_to = search_form.cleaned_data.get('date_to') # # if search: # queryset = queryset.filter( # Q(name__icontains=search) | # Q(description__icontains=search) | # Q(source_type__icontains=search) # ) # # if status == 'active': # queryset = queryset.filter(is_active=True) # elif status == 'inactive': # queryset = queryset.filter(is_active=False) # # if date_from: # queryset = queryset.filter(created_at__gte=date_from) # # if date_to: # queryset = queryset.filter(created_at__lte=date_to) # # return queryset # # def get_context_data(self, **kwargs): # context = super().get_context_data(**kwargs) # context['search_form'] = AnalyticsSearchForm(self.request.GET) # context['source_types'] = DataSource.SOURCE_TYPES # # # Add counts # tenant = getattr(self.request, 'tenant', None) # if tenant: # data_sources = context['data_sources'] # data_source_ids = [ds.data_source_id for ds in data_sources] # # # Get widget counts per data source # widget_counts = DashboardWidget.objects.filter( # data_source_id__in=data_source_ids # ).values('data_source_id').annotate(count=Count('widget_id')) # # widget_count_dict = { # str(item['data_source_id']): item['count'] # for item in widget_counts # } # # # Get report counts per data source # report_counts = Report.objects.filter( # data_source_id__in=data_source_ids # ).values('data_source_id').annotate(count=Count('report_id')) # # report_count_dict = { # str(item['data_source_id']): item['count'] # for item in report_counts # } # # # Get metric counts per data source # metric_counts = MetricDefinition.objects.filter( # data_source_id__in=data_source_ids # ).values('data_source_id').annotate(count=Count('metric_id')) # # metric_count_dict = { # str(item['data_source_id']): item['count'] # for item in metric_counts # } # # # Add to context # context['widget_counts'] = widget_count_dict # context['report_counts'] = report_count_dict # context['metric_counts'] = metric_count_dict # # return context # # # class DataSourceDetailView(LoginRequiredMixin, AnalyticsMixin, DetailView): # """ # Display a single data source with its details and usage. # """ # model = DataSource # template_name = 'analytics/data_source_detail.html' # context_object_name = 'data_source' # # def get_context_data(self, **kwargs): # context = super().get_context_data(**kwargs) # data_source = self.object # # # Get widgets using this data source # widgets = DashboardWidget.objects.filter( # data_source=data_source # ).order_by('-updated_at')[:10] # # # Get reports using this data source # reports = Report.objects.filter( # data_source=data_source # ).order_by('-updated_at')[:10] # # # Get metrics using this data source # metrics = MetricDefinition.objects.filter( # data_source=data_source # ).order_by('-updated_at')[:10] # # context['widgets'] = widgets # context['reports'] = reports # context['metrics'] = metrics # context['widget_count'] = DashboardWidget.objects.filter(data_source=data_source).count() # context['report_count'] = Report.objects.filter(data_source=data_source).count() # context['metric_count'] = MetricDefinition.objects.filter(data_source=data_source).count() # # # Add user permission context # context['can_edit'] = self.request.user.has_perm('analytics.change_datasource') # context['can_delete'] = self.request.user.has_perm('analytics.delete_datasource') # # return context # # # class DataSourceCreateView(LoginRequiredMixin, PermissionRequiredMixin, AnalyticsMixin, CreateView): # """ # Create a new data source. # """ # model = DataSource # form_class = DataSourceForm # template_name = 'analytics/data_source_form.html' # permission_required = 'analytics.add_datasource' # # def get_success_url(self): # return reverse('analytics:data_source_detail', kwargs={'pk': self.object.pk}) # # def form_valid(self, form): # """ # Set tenant and created_by on the data source. # """ # response = super().form_valid(form) # # # Log the action # AuditLogger.log_action( # user=self.request.user, # action='DATA_SOURCE_CREATED', # model='DataSource', # object_id=str(self.object.data_source_id), # details={ # 'name': self.object.name, # 'source_type': self.object.source_type # } # ) # # messages.success(self.request, _("Data source created successfully.")) # return response # # # class DataSourceUpdateView(LoginRequiredMixin, PermissionRequiredMixin, AnalyticsMixin, UpdateView): # """ # Update an existing data source. # """ # model = DataSource # form_class = DataSourceForm # template_name = 'analytics/data_source_form.html' # permission_required = 'analytics.change_datasource' # # def get_success_url(self): # return reverse('analytics:data_source_detail', kwargs={'pk': self.object.pk}) # # def form_valid(self, form): # """ # Update the data source and log the action. # """ # response = super().form_valid(form) # # # Log the action # AuditLogger.log_action( # user=self.request.user, # action='DATA_SOURCE_UPDATED', # model='DataSource', # object_id=str(self.object.data_source_id), # details={ # 'name': self.object.name, # 'source_type': self.object.source_type # } # ) # # messages.success(self.request, _("Data source updated successfully.")) # return response # # # class DataSourceDeleteView(LoginRequiredMixin, PermissionRequiredMixin, AnalyticsMixin, DeleteView): # """ # Delete a data source. # """ # model = DataSource # template_name = 'analytics/data_source_confirm_delete.html' # permission_required = 'analytics.delete_datasource' # success_url = reverse_lazy('analytics:data_source_list') # # def get_context_data(self, **kwargs): # context = super().get_context_data(**kwargs) # data_source = self.object # # # Get counts of related objects # widget_count = DashboardWidget.objects.filter(data_source=data_source).count() # report_count = Report.objects.filter(data_source=data_source).count() # metric_count = MetricDefinition.objects.filter(data_source=data_source).count() # # context['widget_count'] = widget_count # context['report_count'] = report_count # context['metric_count'] = metric_count # context['can_delete'] = (widget_count + report_count + metric_count) == 0 # # return context # # def delete(self, request, *args, **kwargs): # """ # Delete the data source and log the action. # """ # data_source = self.get_object() # # # Check if data source is in use # widget_count = DashboardWidget.objects.filter(data_source=data_source).count() # report_count = Report.objects.filter(data_source=data_source).count() # metric_count = MetricDefinition.objects.filter(data_source=data_source).count() # # if widget_count > 0 or report_count > 0 or metric_count > 0: # messages.error(request, _( # "Cannot delete data source because it is in use. " # f"It is used by {widget_count} widgets, {report_count} reports, and {metric_count} metrics." # )) # return redirect('analytics:data_source_detail', pk=data_source.pk) # # data_source_id = data_source.data_source_id # data_source_name = data_source.name # # # Delete the data source # response = super().delete(request, *args, **kwargs) # # # Log the action # AuditLogger.log_action( # user=self.request.user, # action='DATA_SOURCE_DELETED', # model='DataSource', # object_id=str(data_source_id), # details={ # 'name': data_source_name # } # ) # # messages.success(request, _("Data source deleted successfully.")) # return response # # # # ============================================================================ # # REPORT VIEWS # # ============================================================================ # # class ReportListView(LoginRequiredMixin, AnalyticsMixin, ListView): # """ # List all reports for the current tenant. # """ # model = Report # template_name = 'analytics/report_list.html' # context_object_name = 'reports' # paginate_by = 10 # # def get_queryset(self): # queryset = super().get_queryset().order_by('-updated_at') # search_form = AnalyticsSearchForm(self.request.GET) # # if search_form.is_valid(): # search = search_form.cleaned_data.get('search') # status = search_form.cleaned_data.get('status') # date_from = search_form.cleaned_data.get('date_from') # date_to = search_form.cleaned_data.get('date_to') # # if search: # queryset = queryset.filter( # Q(name__icontains=search) | # Q(description__icontains=search) | # Q(report_type__icontains=search) # ) # # if status == 'active': # queryset = queryset.filter(is_active=True) # elif status == 'inactive': # queryset = queryset.filter(is_active=False) # # if date_from: # queryset = queryset.filter(created_at__gte=date_from) # # if date_to: # queryset = queryset.filter(created_at__lte=date_to) # # return queryset # # def get_context_data(self, **kwargs): # context = super().get_context_data(**kwargs) # context['search_form'] = AnalyticsSearchForm(self.request.GET) # context['report_types'] = Report.REPORT_TYPES # # # Add execution counts and last execution # tenant = getattr(self.request, 'tenant', None) # if tenant: # reports = context['reports'] # report_ids = [report.report_id for report in reports] # # # Get execution counts per report # execution_counts = ReportExecution.objects.filter( # report_id__in=report_ids # ).values('report_id').annotate(count=Count('execution_id')) # # execution_count_dict = { # str(item['report_id']): item['count'] # for item in execution_counts # } # # # Get latest execution per report # latest_executions = {} # for report_id in report_ids: # latest = ReportExecution.objects.filter( # report_id=report_id # ).order_by('-executed_at').first() # # if latest: # latest_executions[str(report_id)] = { # 'status': latest.status, # 'executed_at': latest.executed_at, # 'execution_id': latest.execution_id # } # # # Add to context # context['execution_counts'] = execution_count_dict # context['latest_executions'] = latest_executions # # return context # # # class ReportDetailView(LoginRequiredMixin, AnalyticsMixin, DetailView): # """ # Display a single report with its details and execution history. # """ # model = Report # template_name = 'analytics/report_detail.html' # context_object_name = 'report' # # def get_context_data(self, **kwargs): # context = super().get_context_data(**kwargs) # report = self.object # # # Get recent executions # executions = ReportExecution.objects.filter( # report=report # ).order_by('-executed_at')[:10] # # # Get execution stats # execution_count = ReportExecution.objects.filter(report=report).count() # completed_count = ReportExecution.objects.filter(report=report, status='COMPLETED').count() # failed_count = ReportExecution.objects.filter(report=report, status='FAILED').count() # # # Get average execution time # avg_time = ReportExecution.objects.filter( # report=report, # status='COMPLETED' # ).aggregate(avg_time=Avg('execution_time')) # # context['executions'] = executions # context['execution_count'] = execution_count # context['completed_count'] = completed_count # context['failed_count'] = failed_count # context['avg_execution_time'] = avg_time['avg_time'] # # # Add execution form # context['execution_form'] = ReportExecutionForm(report=report) # # # Add user permission context # context['can_edit'] = self.request.user.has_perm('analytics.change_report') # context['can_delete'] = self.request.user.has_perm('analytics.delete_report') # context['can_execute'] = self.request.user.has_perm('analytics.add_reportexecution') # # return context # # # class ReportCreateView(LoginRequiredMixin, PermissionRequiredMixin, AnalyticsMixin, CreateView): # """ # Create a new report. # """ # model = Report # form_class = ReportForm # template_name = 'analytics/report_form.html' # permission_required = 'analytics.add_report' # # def get_success_url(self): # return reverse('analytics:report_detail', kwargs={'pk': self.object.pk}) # # def form_valid(self, form): # """ # Set tenant and created_by on the report. # """ # response = super().form_valid(form) # # # Log the action # AuditLogger.log_action( # user=self.request.user, # action='REPORT_CREATED', # model='Report', # object_id=str(self.object.report_id), # details={ # 'name': self.object.name, # 'report_type': self.object.report_type # } # ) # # messages.success(self.request, _("Report created successfully.")) # return response # # # class ReportUpdateView(LoginRequiredMixin, PermissionRequiredMixin, AnalyticsMixin, UpdateView): # """ # Update an existing report. # """ # model = Report # form_class = ReportForm # template_name = 'analytics/report_form.html' # permission_required = 'analytics.change_report' # # def get_success_url(self): # return reverse('analytics:report_detail', kwargs={'pk': self.object.pk}) # # def form_valid(self, form): # """ # Update the report and log the action. # """ # response = super().form_valid(form) # # # Log the action # AuditLogger.log_action( # user=self.request.user, # action='REPORT_UPDATED', # model='Report', # object_id=str(self.object.report_id), # details={ # 'name': self.object.name, # 'report_type': self.object.report_type # } # ) # # messages.success(self.request, _("Report updated successfully.")) # return response # # # class ReportDeleteView(LoginRequiredMixin, PermissionRequiredMixin, AnalyticsMixin, DeleteView): # """ # Delete a report. # """ # model = Report # template_name = 'analytics/report_confirm_delete.html' # permission_required = 'analytics.delete_report' # success_url = reverse_lazy('analytics:report_list') # # def get_context_data(self, **kwargs): # context = super().get_context_data(**kwargs) # report = self.object # # # Get execution count # execution_count = ReportExecution.objects.filter(report=report).count() # context['execution_count'] = execution_count # # return context # # def delete(self, request, *args, **kwargs): # """ # Delete the report and log the action. # """ # report = self.get_object() # report_id = report.report_id # report_name = report.name # # # Delete the report (will cascade delete executions) # response = super().delete(request, *args, **kwargs) # # # Log the action # AuditLogger.log_action( # user=self.request.user, # action='REPORT_DELETED', # model='Report', # object_id=str(report_id), # details={ # 'name': report_name # } # ) # # messages.success(request, _("Report deleted successfully.")) # return response # # # class ReportExecutionDetailView(LoginRequiredMixin, AnalyticsMixin, DetailView): # """ # Display the details of a report execution. # """ # model = ReportExecution # template_name = 'analytics/report_execution_detail.html' # context_object_name = 'execution' # # def get_queryset(self): # """ # Filter executions to those for reports in the user's tenant. # """ # queryset = super().get_queryset() # tenant = getattr(self.request, 'tenant', None) # if tenant: # return queryset.filter(report__tenant=tenant) # return queryset.none() # # def get_context_data(self, **kwargs): # context = super().get_context_data(**kwargs) # execution = self.object # # # Add report context # context['report'] = execution.report # # # Add user permission context # context['can_view_report'] = self.request.user.has_perm('analytics.view_report') # context['can_cancel'] = ( # execution.status in ['PENDING', 'RUNNING'] and # self.request.user.has_perm('analytics.change_reportexecution') # ) # # return context # # # # ============================================================================ # # METRIC VIEWS # # ============================================================================ # # class MetricListView(LoginRequiredMixin, AnalyticsMixin, ListView): # """ # List all metrics for the current tenant. # """ # model = MetricDefinition # template_name = 'analytics/metric_list.html' # context_object_name = 'metrics' # paginate_by = 10 # # def get_queryset(self): # queryset = super().get_queryset().order_by('-updated_at') # search_form = AnalyticsSearchForm(self.request.GET) # # if search_form.is_valid(): # search = search_form.cleaned_data.get('search') # status = search_form.cleaned_data.get('status') # date_from = search_form.cleaned_data.get('date_from') # date_to = search_form.cleaned_data.get('date_to') # # if search: # queryset = queryset.filter( # Q(name__icontains=search) | # Q(description__icontains=search) | # Q(metric_type__icontains=search) | # Q(unit__icontains=search) # ) # # if status == 'active': # queryset = queryset.filter(is_active=True) # elif status == 'inactive': # queryset = queryset.filter(is_active=False) # # if date_from: # queryset = queryset.filter(created_at__gte=date_from) # # if date_to: # queryset = queryset.filter(created_at__lte=date_to) # # return queryset # # def get_context_data(self, **kwargs): # context = super().get_context_data(**kwargs) # context['search_form'] = AnalyticsSearchForm(self.request.GET) # context['metric_types'] = MetricDefinition.METRIC_TYPES # # # Add current values and status # metrics = context['metrics'] # metric_ids = [metric.metric_id for metric in metrics] # # # Get latest value for each metric # latest_values = {} # for metric_id in metric_ids: # latest = MetricValue.objects.filter( # metric_id=metric_id # ).order_by('-timestamp').first() # # if latest: # latest_values[str(metric_id)] = { # 'value': latest.value, # 'timestamp': latest.timestamp, # } # # # Add statuses based on thresholds # statuses = {} # for metric in metrics: # if str(metric.metric_id) in latest_values: # value = latest_values[str(metric.metric_id)]['value'] # # if metric.threshold_critical is not None and value >= metric.threshold_critical: # statuses[str(metric.metric_id)] = 'CRITICAL' # elif metric.threshold_warning is not None and value >= metric.threshold_warning: # statuses[str(metric.metric_id)] = 'WARNING' # else: # statuses[str(metric.metric_id)] = 'NORMAL' # else: # statuses[str(metric.metric_id)] = 'UNKNOWN' # # # Add to context # context['latest_values'] = latest_values # context['statuses'] = statuses # # return context # # # class MetricDetailView(LoginRequiredMixin, AnalyticsMixin, DetailView): # """ # Display a single metric with its details and values. # """ # model = MetricDefinition # template_name = 'analytics/metric_detail.html' # context_object_name = 'metric' # # def get_context_data(self, **kwargs): # context = super().get_context_data(**kwargs) # metric = self.object # # # Get recent values # values = MetricValue.objects.filter( # metric=metric # ).order_by('-timestamp')[:20] # # # Get value stats # value_count = MetricValue.objects.filter(metric=metric).count() # if value_count > 0: # latest_value = MetricValue.objects.filter(metric=metric).order_by('-timestamp').first() # earliest_value = MetricValue.objects.filter(metric=metric).order_by('timestamp').first() # # # Calculate average, min, max # value_stats = MetricValue.objects.filter(metric=metric).aggregate( # avg_value=Avg('value'), # min_value=models.Min('value'), # max_value=models.Max('value') # ) # # context['latest_value'] = latest_value # context['earliest_value'] = earliest_value # context['avg_value'] = value_stats['avg_value'] # context['min_value'] = value_stats['min_value'] # context['max_value'] = value_stats['max_value'] # # # Get status # context['status'] = metric.get_status() # # # Add to context # context['values'] = values # context['value_count'] = value_count # # # Prepare chart data # chart_values = MetricValue.objects.filter(metric=metric).order_by('timestamp')[:100] # chart_data = { # 'labels': [value.timestamp.strftime('%Y-%m-%d %H:%M') for value in chart_values], # 'values': [float(value.value) for value in chart_values], # } # # if metric.threshold_warning is not None: # chart_data['threshold_warning'] = float(metric.threshold_warning) # # if metric.threshold_critical is not None: # chart_data['threshold_critical'] = float(metric.threshold_critical) # # context['chart_data'] = json.dumps(chart_data) # # # Add user permission context # context['can_edit'] = self.request.user.has_perm('analytics.change_metricdefinition') # context['can_delete'] = self.request.user.has_perm('analytics.delete_metricdefinition') # context['can_add_value'] = self.request.user.has_perm('analytics.add_metricvalue') # # return context # # # class MetricCreateView(LoginRequiredMixin, PermissionRequiredMixin, AnalyticsMixin, CreateView): # """ # Create a new metric. # """ # model = MetricDefinition # form_class = MetricDefinitionForm # template_name = 'analytics/metric_form.html' # permission_required = 'analytics.add_metricdefinition' # # def get_success_url(self): # return reverse('analytics:metric_detail', kwargs={'pk': self.object.pk}) # # def form_valid(self, form): # """ # Set tenant and created_by on the metric. # """ # response = super().form_valid(form) # # # Log the action # AuditLogger.log_action( # user=self.request.user, # action='METRIC_CREATED', # model='MetricDefinition', # object_id=str(self.object.metric_id), # details={ # 'name': self.object.name, # 'metric_type': self.object.metric_type # } # ) # # messages.success(self.request, _("Metric created successfully.")) # return response # # # class MetricUpdateView(LoginRequiredMixin, PermissionRequiredMixin, AnalyticsMixin, UpdateView): # """ # Update an existing metric. # """ # model = MetricDefinition # form_class = MetricDefinitionForm # template_name = 'analytics/metric_form.html' # permission_required = 'analytics.change_metricdefinition' # # def get_success_url(self): # return reverse('analytics:metric_detail', kwargs={'pk': self.object.pk}) # # def form_valid(self, form): # """ # Update the metric and log the action. # """ # response = super().form_valid(form) # # # Log the action # AuditLogger.log_action( # user=self.request.user, # action='METRIC_UPDATED', # model='MetricDefinition', # object_id=str(self.object.metric_id), # details={ # 'name': self.object.name, # 'metric_type': self.object.metric_type # } # ) # # messages.success(self.request, _("Metric updated successfully.")) # return response # # # class MetricDeleteView(LoginRequiredMixin, PermissionRequiredMixin, AnalyticsMixin, DeleteView): # """ # Delete a metric. # """ # model = MetricDefinition # template_name = 'analytics/metric_confirm_delete.html' # permission_required = 'analytics.delete_metricdefinition' # success_url = reverse_lazy('analytics:metric_list') # # def get_context_data(self, **kwargs): # context = super().get_context_data(**kwargs) # metric = self.object # # # Get value count # value_count = MetricValue.objects.filter(metric=metric).count() # context['value_count'] = value_count # # return context # # def delete(self, request, *args, **kwargs): # """ # Delete the metric and log the action. # """ # metric = self.get_object() # metric_id = metric.metric_id # metric_name = metric.name # # # Delete the metric (will cascade delete values) # response = super().delete(request, *args, **kwargs) # # # Log the action # AuditLogger.log_action( # user=self.request.user, # action='METRIC_DELETED', # model='MetricDefinition', # object_id=str(metric_id), # details={ # 'name': metric_name # } # ) # # messages.success(request, _("Metric deleted successfully.")) # return response # # # class MetricValueCreateView(LoginRequiredMixin, PermissionRequiredMixin, AnalyticsMixin, CreateView): # """ # Create a new metric value. # """ # model = MetricValue # form_class = MetricValueForm # template_name = 'analytics/metric_value_form.html' # permission_required = 'analytics.add_metricvalue' # # def get_initial(self): # """ # Pre-populate metric if specified in URL. # """ # initial = super().get_initial() # metric_id = self.request.GET.get('metric_id') # if metric_id: # initial['metric'] = metric_id # # # Default timestamp to now # initial['timestamp'] = timezone.now() # initial['collection_method'] = 'MANUAL' # # return initial # # def get_success_url(self): # """ # Redirect to metric detail or value list. # """ # if self.object.metric: # return reverse('analytics:metric_detail', kwargs={'pk': self.object.metric.pk}) # return reverse_lazy('analytics:metric_list') # # def form_valid(self, form): # """ # Save the metric value and log the action. # """ # response = super().form_valid(form) # # # Log the action # AuditLogger.log_action( # user=self.request.user, # action='METRIC_VALUE_CREATED', # model='MetricValue', # object_id=str(self.object.value_id), # details={ # 'metric_name': self.object.metric.name, # 'value': str(self.object.value), # 'timestamp': self.object.timestamp.isoformat() # } # ) # # messages.success(self.request, _("Metric value added successfully.")) # return response # # # # ============================================================================ # # HTMX VIEWS # # ============================================================================ # # @login_required # @require_http_methods(["GET"]) # def analytics_stats(request): # """ # HTMX endpoint for analytics dashboard stats. # """ # tenant = getattr(request, 'tenant', None) # if not tenant: # return HttpResponse(_("Tenant not found.")) # # # Get dashboard count # dashboard_count = Dashboard.objects.filter(tenant=tenant).count() # active_dashboard_count = Dashboard.objects.filter(tenant=tenant, is_active=True).count() # # # Get report count # report_count = Report.objects.filter(tenant=tenant).count() # active_report_count = Report.objects.filter(tenant=tenant, is_active=True).count() # # # Get data source count # data_source_count = DataSource.objects.filter(tenant=tenant).count() # # # Get metric count # metric_count = MetricDefinition.objects.filter(tenant=tenant).count() # # # Get critical metrics # critical_metrics = MetricDefinition.objects.filter( # tenant=tenant, # is_active=True # ).filter( # Q(values__value__gte=F('threshold_critical')) # ).distinct().count() # # # Get warning metrics # warning_metrics = MetricDefinition.objects.filter( # tenant=tenant, # is_active=True # ).filter( # Q(values__value__gte=F('threshold_warning')) & # ~Q(values__value__gte=F('threshold_critical')) # ).distinct().count() # # # Get recent report executions # recent_executions = ReportExecution.objects.filter( # report__tenant=tenant # ).order_by('-executed_at')[:5] # # context = { # 'dashboard_count': dashboard_count, # 'active_dashboard_count': active_dashboard_count, # 'report_count': report_count, # 'active_report_count': active_report_count, # 'data_source_count': data_source_count, # 'metric_count': metric_count, # 'critical_metrics': critical_metrics, # 'warning_metrics': warning_metrics, # 'recent_executions': recent_executions # } # # return render(request, 'analytics/partials/analytics_stats.html', context) # # # @login_required # @require_http_methods(["GET"]) # def widget_preview(request, widget_id): # """ # HTMX endpoint for widget preview. # """ # tenant = getattr(request, 'tenant', None) # if not tenant: # return HttpResponse(_("Tenant not found.")) # # widget = get_object_or_404( # DashboardWidget, # widget_id=widget_id, # dashboard__tenant=tenant # ) # # try: # data = widget.get_data() # # context = { # 'widget': widget, # 'data': data, # 'error': None # } # except Exception as e: # context = { # 'widget': widget, # 'data': None, # 'error': str(e) # } # # return render(request, 'analytics/partials/widget_preview.html', context) # # # @login_required # @require_http_methods(["GET"]) # def metric_chart(request, metric_id): # """ # HTMX endpoint for metric chart. # """ # tenant = getattr(request, 'tenant', None) # if not tenant: # return HttpResponse(_("Tenant not found.")) # # metric = get_object_or_404( # MetricDefinition, # metric_id=metric_id, # tenant=tenant # ) # # # Get time range from request # time_range = request.GET.get('range', '7d') # # # Calculate date range # now = timezone.now() # if time_range == '24h': # start_date = now - timedelta(days=1) # elif time_range == '7d': # start_date = now - timedelta(days=7) # elif time_range == '30d': # start_date = now - timedelta(days=30) # elif time_range == '90d': # start_date = now - timedelta(days=90) # elif time_range == '1y': # start_date = now - timedelta(days=365) # elif time_range == 'all': # start_date = None # else: # start_date = now - timedelta(days=7) # # # Get values for the time range # if start_date: # values = MetricValue.objects.filter( # metric=metric, # timestamp__gte=start_date # ).order_by('timestamp') # else: # values = MetricValue.objects.filter( # metric=metric # ).order_by('timestamp') # # # Prepare chart data # chart_data = { # 'labels': [value.timestamp.strftime('%Y-%m-%d %H:%M') for value in values], # 'values': [float(value.value) for value in values], # } # # if metric.threshold_warning is not None: # chart_data['threshold_warning'] = float(metric.threshold_warning) # # if metric.threshold_critical is not None: # chart_data['threshold_critical'] = float(metric.threshold_critical) # # context = { # 'metric': metric, # 'values': values, # 'chart_data': json.dumps(chart_data), # 'time_range': time_range # } # # return render(request, 'analytics/partials/metric_chart.html', context) # # # @login_required # @permission_required('analytics.add_reportexecution') # @require_http_methods(["POST"]) # def execute_report(request, report_id): # """ # HTMX endpoint to execute a report. # """ # tenant = getattr(request, 'tenant', None) # if not tenant: # return HttpResponse(_("Tenant not found.")) # # report = get_object_or_404( # Report, # report_id=report_id, # tenant=tenant # ) # # # Process execution form # form = ReportExecutionForm(request.POST, report=report) # if form.is_valid(): # execution = form.save(commit=False) # execution.report = report # execution.executed_by = request.user # execution.status = 'PENDING' # execution.save() # # # # # Log the action # AuditLogger.log_action( # user=request.user, # action='REPORT_EXECUTION_CREATED', # model='ReportExecution', # object_id=str(execution.execution_id), # details={ # 'report_name': report.name, # 'format': execution.format # } # ) # # messages.success(request, _("Report execution started.")) # # # Return updated executions list # executions = ReportExecution.objects.filter( # report=report # ).order_by('-executed_at')[:10] # # context = { # 'report': report, # 'executions': executions, # 'execution_form': ReportExecutionForm(report=report) # } # # return render(request, 'analytics/partials/report_executions.html', context) # else: # # Return form with errors # context = { # 'report': report, # 'execution_form': form # } # # return render(request, 'analytics/partials/report_execution_form.html', context) # # # @login_required # @permission_required('analytics.change_reportexecution') # @require_http_methods(["POST"]) # def cancel_report_execution(request, execution_id): # """ # HTMX endpoint to cancel a report execution. # """ # tenant = getattr(request, 'tenant', None) # if not tenant: # return HttpResponse(_("Tenant not found.")) # # execution = get_object_or_404( # ReportExecution, # execution_id=execution_id, # report__tenant=tenant # ) # # # Cancel the execution # if execution.status in ['PENDING', 'RUNNING']: # execution.status = 'CANCELLED' # execution.save(update_fields=['status']) # # # Log the action # AuditLogger.log_action( # user=request.user, # action='REPORT_EXECUTION_CANCELLED', # model='ReportExecution', # object_id=str(execution.execution_id), # details={ # 'report_name': execution.report.name # } # ) # # messages.success(request, _("Report execution cancelled.")) # else: # messages.error(request, _("Cannot cancel execution with status: {}").format(execution.get_status_display())) # # # Return updated execution details # context = { # 'execution': execution, # 'report': execution.report, # 'can_cancel': False # } # # return render(request, 'analytics/partials/execution_details.html', context) # # # @login_required # @permission_required('analytics.change_dashboardwidget') # @require_http_methods(["POST"]) # def update_widget_positions(request, dashboard_id): # """ # HTMX endpoint to update widget positions on a dashboard. # """ # tenant = getattr(request, 'tenant', None) # if not tenant: # return HttpResponse(_("Tenant not found.")) # # dashboard = get_object_or_404( # Dashboard, # dashboard_id=dashboard_id, # tenant=tenant # ) # # form = DashboardWidgetPositionForm(request.POST) # if form.is_valid(): # positions = form.cleaned_data['positions'] # # # Update widget positions # for pos in positions: # try: # widget = DashboardWidget.objects.get( # widget_id=pos['widget_id'], # dashboard=dashboard # ) # # widget.position_x = pos['x'] # widget.position_y = pos['y'] # widget.width = pos['w'] # widget.height = pos['h'] # widget.save(update_fields=['position_x', 'position_y', 'width', 'height']) # # except DashboardWidget.DoesNotExist: # continue # # # Log the action # AuditLogger.log_action( # user=request.user, # action='DASHBOARD_LAYOUT_UPDATED', # model='Dashboard', # object_id=str(dashboard.dashboard_id), # details={ # 'name': dashboard.name # } # ) # # messages.success(request, _("Dashboard layout updated successfully.")) # # return JsonResponse({'success': True}) # else: # return JsonResponse({'success': False, 'errors': form.errors}) # # # @login_required # @require_http_methods(["GET"]) # def data_source_test(request, data_source_id): # """ # HTMX endpoint to test a data source connection. # """ # tenant = getattr(request, 'tenant', None) # if not tenant: # return HttpResponse(_("Tenant not found.")) # # data_source = get_object_or_404( # DataSource, # data_source_id=data_source_id, # tenant=tenant # ) # # # Try to connect to the data source # try: # # Actual implementation would depend on the source type # result = { # 'success': True, # 'message': _("Connection successful."), # 'details': { # 'connection_time': '0.5s', # 'status': 'OK' # } # } # except Exception as e: # result = { # 'success': False, # 'message': _("Connection failed: {}").format(str(e)), # 'details': None # } # # context = { # 'data_source': data_source, # 'result': result # } # # return render(request, 'analytics/partials/data_source_test_result.html', context) # # # @login_required # @require_http_methods(["GET"]) # def search_analytics(request): # """ # HTMX endpoint for searching analytics entities. # """ # tenant = getattr(request, 'tenant', None) # if not tenant: # return HttpResponse(_("Tenant not found.")) # # search_term = request.GET.get('search', '') # entity_type = request.GET.get('type', '') # # if not search_term: # return HttpResponse(_("Please enter a search term.")) # # results = [] # # # Search dashboards # if not entity_type or entity_type == 'dashboard': # dashboards = Dashboard.objects.filter( # tenant=tenant, # name__icontains=search_term # )[:5] # # for dashboard in dashboards: # results.append({ # 'type': 'dashboard', # 'id': dashboard.dashboard_id, # 'name': dashboard.name, # 'description': dashboard.description, # 'url': reverse('analytics:dashboard_detail', kwargs={'pk': dashboard.pk}) # }) # # # Search reports # if not entity_type or entity_type == 'report': # reports = Report.objects.filter( # tenant=tenant, # name__icontains=search_term # )[:5] # # for report in reports: # results.append({ # 'type': 'report', # 'id': report.report_id, # 'name': report.name, # 'description': report.description, # 'url': reverse('analytics:report_detail', kwargs={'pk': report.pk}) # }) # # # Search metrics # if not entity_type or entity_type == 'metric': # metrics = MetricDefinition.objects.filter( # tenant=tenant, # name__icontains=search_term # )[:5] # # for metric in metrics: # results.append({ # 'type': 'metric', # 'id': metric.metric_id, # 'name': metric.name, # 'description': metric.description, # 'url': reverse('analytics:metric_detail', kwargs={'pk': metric.pk}) # }) # # # Search data sources # if not entity_type or entity_type == 'data_source': # data_sources = DataSource.objects.filter( # tenant=tenant, # name__icontains=search_term # )[:5] # # for data_source in data_sources: # results.append({ # 'type': 'data_source', # 'id': data_source.data_source_id, # 'name': data_source.name, # 'description': data_source.description, # 'url': reverse('analytics:data_source_detail', kwargs={'pk': data_source.pk}) # }) # # context = { # 'results': results, # 'search_term': search_term, # 'count': len(results) # } # # return render(request, 'analytics/partials/search_results.html', context)