175 lines
7.5 KiB
Python
175 lines
7.5 KiB
Python
"""
|
|
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
|