3160 lines
108 KiB
Python
3160 lines
108 KiB
Python
"""
|
|
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) |