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

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)