""" Views for analytics app. """ from django.views.generic import TemplateView from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin from django.db.models import Count, Q, Avg, Max, Min from django.utils import timezone from django.utils.safestring import mark_safe import json from datetime import timedelta from reports.models import ScamReport from accounts.models import User, ActivityLog from osint.models import OSINTTask from moderation.models import ModerationAction class AdminRequiredMixin(UserPassesTestMixin): """Mixin to require admin role.""" def test_func(self): return self.request.user.is_authenticated and self.request.user.is_administrator() class AnalyticsDashboardView(LoginRequiredMixin, AdminRequiredMixin, TemplateView): """Analytics dashboard.""" template_name = 'analytics/dashboard.html' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Report statistics context['total_reports'] = ScamReport.objects.count() context['pending_reports'] = ScamReport.objects.filter(status='pending').count() context['verified_reports'] = ScamReport.objects.filter(status='verified').count() context['rejected_reports'] = ScamReport.objects.filter(status='rejected').count() # Scam type breakdown with display names and percentages scam_types_data = ScamReport.objects.values('scam_type').annotate( count=Count('id') ).order_by('-count') scam_types_list = [] total_reports = context.get('total_reports', 0) for item in scam_types_data: scam_type_key = item['scam_type'] display_name = dict(ScamReport.SCAM_TYPE_CHOICES).get(scam_type_key, scam_type_key) percentage = (item['count'] / total_reports * 100) if total_reports > 0 else 0 scam_types_list.append({ 'scam_type': scam_type_key, 'display_name': display_name, 'count': item['count'], 'percentage': round(percentage, 1) }) context['scam_types'] = scam_types_list # User statistics context['total_users'] = User.objects.count() context['moderators'] = User.objects.filter(role__in=['moderator', 'admin']).count() # OSINT statistics context['osint_tasks'] = OSINTTask.objects.count() context['completed_tasks'] = OSINTTask.objects.filter(status='completed').count() return context class ReportAnalyticsView(LoginRequiredMixin, AdminRequiredMixin, TemplateView): """Report analytics.""" template_name = 'analytics/reports.html' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Overall statistics context['total_reports'] = ScamReport.objects.count() context['pending_reports'] = ScamReport.objects.filter(status='pending').count() context['verified_reports'] = ScamReport.objects.filter(status='verified').count() context['rejected_reports'] = ScamReport.objects.filter(status='rejected').count() context['under_review_reports'] = ScamReport.objects.filter(status='under_review').count() # Scam type distribution scam_types_data = ScamReport.objects.values('scam_type').annotate( count=Count('id') ).order_by('-count') scam_types_list = [] for item in scam_types_data: scam_type_key = item['scam_type'] display_name = dict(ScamReport.SCAM_TYPE_CHOICES).get(scam_type_key, scam_type_key) percentage = (item['count'] / context['total_reports'] * 100) if context['total_reports'] > 0 else 0 scam_types_list.append({ 'scam_type': scam_type_key, 'display_name': display_name, 'count': item['count'], 'percentage': round(percentage, 1) }) context['scam_types'] = scam_types_list # Time-based statistics now = timezone.now() last_7_days = now - timedelta(days=7) last_30_days = now - timedelta(days=30) last_90_days = now - timedelta(days=90) context['reports_last_7_days'] = ScamReport.objects.filter(created_at__gte=last_7_days).count() context['reports_last_30_days'] = ScamReport.objects.filter(created_at__gte=last_30_days).count() context['reports_last_90_days'] = ScamReport.objects.filter(created_at__gte=last_90_days).count() # Daily reports for the last 30 days daily_reports = [] for i in range(29, -1, -1): # From 29 days ago to today date = now - timedelta(days=i) count = ScamReport.objects.filter( created_at__date=date.date() ).count() daily_reports.append({ 'date': date.date().isoformat(), 'count': count }) context['daily_reports'] = mark_safe(json.dumps(daily_reports)) # Average moderation time verified_reports = ScamReport.objects.filter( status='verified', verified_at__isnull=False ) if verified_reports.exists(): moderation_times = [] for report in verified_reports: if report.created_at and report.verified_at: time_diff = report.verified_at - report.created_at moderation_times.append(time_diff.total_seconds() / 3600) # Convert to hours if moderation_times: context['avg_moderation_time_hours'] = round(sum(moderation_times) / len(moderation_times), 2) context['min_moderation_time_hours'] = round(min(moderation_times), 2) context['max_moderation_time_hours'] = round(max(moderation_times), 2) # Top reporters top_reporters = ScamReport.objects.values( 'reporter__username', 'reporter__email' ).annotate( report_count=Count('id') ).order_by('-report_count')[:10] context['top_reporters'] = top_reporters # Moderation statistics context['total_moderations'] = ModerationAction.objects.count() context['approvals'] = ModerationAction.objects.filter(action_type='approve').count() context['rejections'] = ModerationAction.objects.filter(action_type='reject').count() # Reports by status over time status_over_time = [] for i in range(6, -1, -1): # From 6 days ago to today date = now - timedelta(days=i) status_over_time.append({ 'date': date.date().isoformat(), 'pending': ScamReport.objects.filter(status='pending', created_at__date=date.date()).count(), 'verified': ScamReport.objects.filter(status='verified', created_at__date=date.date()).count(), 'rejected': ScamReport.objects.filter(status='rejected', created_at__date=date.date()).count(), }) context['status_over_time'] = mark_safe(json.dumps(status_over_time)) return context class UserAnalyticsView(LoginRequiredMixin, AdminRequiredMixin, TemplateView): """User analytics.""" template_name = 'analytics/users.html' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Add detailed user analytics return context