update
This commit is contained in:
0
analytics/__init__.py
Normal file
0
analytics/__init__.py
Normal file
32
analytics/admin.py
Normal file
32
analytics/admin.py
Normal file
@@ -0,0 +1,32 @@
|
||||
"""
|
||||
Admin configuration for analytics app.
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from .models import ReportStatistic, UserStatistic, OSINTStatistic
|
||||
|
||||
|
||||
@admin.register(ReportStatistic)
|
||||
class ReportStatisticAdmin(admin.ModelAdmin):
|
||||
"""Report statistic admin."""
|
||||
list_display = ('date', 'total_reports', 'verified_reports', 'pending_reports')
|
||||
list_filter = ('date',)
|
||||
date_hierarchy = 'date'
|
||||
readonly_fields = ('created_at', 'updated_at')
|
||||
|
||||
|
||||
@admin.register(UserStatistic)
|
||||
class UserStatisticAdmin(admin.ModelAdmin):
|
||||
"""User statistic admin."""
|
||||
list_display = ('date', 'total_users', 'new_users', 'active_users')
|
||||
list_filter = ('date',)
|
||||
date_hierarchy = 'date'
|
||||
readonly_fields = ('created_at', 'updated_at')
|
||||
|
||||
|
||||
@admin.register(OSINTStatistic)
|
||||
class OSINTStatisticAdmin(admin.ModelAdmin):
|
||||
"""OSINT statistic admin."""
|
||||
list_display = ('date', 'total_tasks', 'completed_tasks', 'average_confidence')
|
||||
list_filter = ('date',)
|
||||
date_hierarchy = 'date'
|
||||
readonly_fields = ('created_at', 'updated_at')
|
||||
6
analytics/apps.py
Normal file
6
analytics/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AnalyticsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'analytics'
|
||||
82
analytics/migrations/0001_initial.py
Normal file
82
analytics/migrations/0001_initial.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-26 13:41
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='OSINTStatistic',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('date', models.DateField(unique=True)),
|
||||
('total_tasks', models.IntegerField(default=0)),
|
||||
('completed_tasks', models.IntegerField(default=0)),
|
||||
('failed_tasks', models.IntegerField(default=0)),
|
||||
('average_confidence', models.FloatField(default=0.0, help_text='Average confidence score')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'OSINT Statistic',
|
||||
'verbose_name_plural': 'OSINT Statistics',
|
||||
'db_table': 'analytics_osintstatistic',
|
||||
'ordering': ['-date'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserStatistic',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('date', models.DateField(unique=True)),
|
||||
('total_users', models.IntegerField(default=0)),
|
||||
('new_users', models.IntegerField(default=0)),
|
||||
('active_users', models.IntegerField(default=0, help_text='Users who logged in')),
|
||||
('moderators', models.IntegerField(default=0)),
|
||||
('admins', models.IntegerField(default=0)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'User Statistic',
|
||||
'verbose_name_plural': 'User Statistics',
|
||||
'db_table': 'analytics_userstatistic',
|
||||
'ordering': ['-date'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ReportStatistic',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('date', models.DateField(unique=True)),
|
||||
('total_reports', models.IntegerField(default=0)),
|
||||
('pending_reports', models.IntegerField(default=0)),
|
||||
('verified_reports', models.IntegerField(default=0)),
|
||||
('rejected_reports', models.IntegerField(default=0)),
|
||||
('phishing_count', models.IntegerField(default=0)),
|
||||
('fake_website_count', models.IntegerField(default=0)),
|
||||
('romance_scam_count', models.IntegerField(default=0)),
|
||||
('investment_scam_count', models.IntegerField(default=0)),
|
||||
('tech_support_scam_count', models.IntegerField(default=0)),
|
||||
('identity_theft_count', models.IntegerField(default=0)),
|
||||
('fake_product_count', models.IntegerField(default=0)),
|
||||
('advance_fee_count', models.IntegerField(default=0)),
|
||||
('other_count', models.IntegerField(default=0)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Report Statistic',
|
||||
'verbose_name_plural': 'Report Statistics',
|
||||
'db_table': 'analytics_reportstatistic',
|
||||
'ordering': ['-date'],
|
||||
'indexes': [models.Index(fields=['date'], name='analytics_r_date_0bacbd_idx')],
|
||||
},
|
||||
),
|
||||
]
|
||||
0
analytics/migrations/__init__.py
Normal file
0
analytics/migrations/__init__.py
Normal file
98
analytics/models.py
Normal file
98
analytics/models.py
Normal file
@@ -0,0 +1,98 @@
|
||||
"""
|
||||
Analytics and statistics models.
|
||||
"""
|
||||
from django.db import models
|
||||
from django.contrib.auth import get_user_model
|
||||
from reports.models import ScamReport
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class ReportStatistic(models.Model):
|
||||
"""
|
||||
Aggregated statistics for reports.
|
||||
"""
|
||||
date = models.DateField(unique=True)
|
||||
total_reports = models.IntegerField(default=0)
|
||||
pending_reports = models.IntegerField(default=0)
|
||||
verified_reports = models.IntegerField(default=0)
|
||||
rejected_reports = models.IntegerField(default=0)
|
||||
|
||||
# By scam type
|
||||
phishing_count = models.IntegerField(default=0)
|
||||
fake_website_count = models.IntegerField(default=0)
|
||||
romance_scam_count = models.IntegerField(default=0)
|
||||
investment_scam_count = models.IntegerField(default=0)
|
||||
tech_support_scam_count = models.IntegerField(default=0)
|
||||
identity_theft_count = models.IntegerField(default=0)
|
||||
fake_product_count = models.IntegerField(default=0)
|
||||
advance_fee_count = models.IntegerField(default=0)
|
||||
other_count = models.IntegerField(default=0)
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'analytics_reportstatistic'
|
||||
verbose_name = 'Report Statistic'
|
||||
verbose_name_plural = 'Report Statistics'
|
||||
ordering = ['-date']
|
||||
indexes = [
|
||||
models.Index(fields=['date']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"Statistics for {self.date}"
|
||||
|
||||
|
||||
class UserStatistic(models.Model):
|
||||
"""
|
||||
User activity statistics.
|
||||
"""
|
||||
date = models.DateField(unique=True)
|
||||
total_users = models.IntegerField(default=0)
|
||||
new_users = models.IntegerField(default=0)
|
||||
active_users = models.IntegerField(
|
||||
default=0,
|
||||
help_text='Users who logged in'
|
||||
)
|
||||
moderators = models.IntegerField(default=0)
|
||||
admins = models.IntegerField(default=0)
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'analytics_userstatistic'
|
||||
verbose_name = 'User Statistic'
|
||||
verbose_name_plural = 'User Statistics'
|
||||
ordering = ['-date']
|
||||
|
||||
def __str__(self):
|
||||
return f"User Statistics for {self.date}"
|
||||
|
||||
|
||||
class OSINTStatistic(models.Model):
|
||||
"""
|
||||
OSINT task and result statistics.
|
||||
"""
|
||||
date = models.DateField(unique=True)
|
||||
total_tasks = models.IntegerField(default=0)
|
||||
completed_tasks = models.IntegerField(default=0)
|
||||
failed_tasks = models.IntegerField(default=0)
|
||||
average_confidence = models.FloatField(
|
||||
default=0.0,
|
||||
help_text='Average confidence score'
|
||||
)
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'analytics_osintstatistic'
|
||||
verbose_name = 'OSINT Statistic'
|
||||
verbose_name_plural = 'OSINT Statistics'
|
||||
ordering = ['-date']
|
||||
|
||||
def __str__(self):
|
||||
return f"OSINT Statistics for {self.date}"
|
||||
3
analytics/tests.py
Normal file
3
analytics/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
14
analytics/urls.py
Normal file
14
analytics/urls.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""
|
||||
URL configuration for analytics app.
|
||||
"""
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
app_name = 'analytics'
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.AnalyticsDashboardView.as_view(), name='dashboard'),
|
||||
path('reports/', views.ReportAnalyticsView.as_view(), name='reports'),
|
||||
path('users/', views.UserAnalyticsView.as_view(), name='users'),
|
||||
]
|
||||
|
||||
174
analytics/views.py
Normal file
174
analytics/views.py
Normal file
@@ -0,0 +1,174 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user