|
|
|
@ -3,13 +3,16 @@ from rest_framework.response import Response |
|
|
|
from rest_framework.permissions import IsAuthenticated, IsAdminUser |
|
|
|
from django.utils import timezone |
|
|
|
from datetime import timedelta |
|
|
|
from django.db.models import Count |
|
|
|
from django.db.models import Count, Sum |
|
|
|
from django.db.models.functions import TruncDate |
|
|
|
|
|
|
|
from apps.account.models import User |
|
|
|
from apps.course.models import Course, Participant |
|
|
|
from apps.account.models import User, LoginHistory |
|
|
|
from apps.course.models import Course, Participant, CourseLiveSession |
|
|
|
from apps.quiz.models import QuizParticipant |
|
|
|
from apps.blog.models import Blog |
|
|
|
from apps.chat.models import ChatMessage, RoomMessage |
|
|
|
from apps.transaction.models import TransactionParticipant |
|
|
|
from apps.certificate.models import Certificate |
|
|
|
# Import your Transaction model here |
|
|
|
|
|
|
|
class AdminDashboardStatsView(APIView): |
|
|
|
permission_classes = [IsAuthenticated, IsAdminUser] |
|
|
|
@ -18,37 +21,107 @@ class AdminDashboardStatsView(APIView): |
|
|
|
now = timezone.now() |
|
|
|
thirty_days_ago = now - timedelta(days=30) |
|
|
|
|
|
|
|
# 1. Basic Counts |
|
|
|
# --------------------------------------------------------- |
|
|
|
# 1. CARDS & BASIC STATS |
|
|
|
# --------------------------------------------------------- |
|
|
|
active_students = User.objects.filter(groups__name="Student Group", is_active=True).count() |
|
|
|
professors = User.objects.filter(groups__name="Professor Group", is_active=True).count() |
|
|
|
active_courses = Course.objects.filter(status=Course.StatusChoices.ONGOING).count() |
|
|
|
total_blogs = Blog.objects.count() |
|
|
|
pending_certificates = Certificate.objects.filter(status='pending').count() |
|
|
|
active_live_sessions = CourseLiveSession.objects.filter(ended_at__isnull=True).count() |
|
|
|
|
|
|
|
# 2. Revenue (Mocked - replace with your actual Transaction logic) |
|
|
|
# revenue_30d = Transaction.objects.filter(status='success', created_at__gte=thirty_days_ago).aggregate(Sum('amount'))['amount__sum'] or 0 |
|
|
|
# Revenue (Mocked for 30d sum) |
|
|
|
revenue_30d = 12500.00 # Dummy data |
|
|
|
|
|
|
|
# 3. Transaction Chart Data (Mocked - replace with actual grouped query) |
|
|
|
transactions_data = [ |
|
|
|
{"name": "Success", "value": 450, "fill": "var(--color-success)"}, |
|
|
|
{"name": "Pending", "value": 85, "fill": "var(--color-pending)"}, |
|
|
|
{"name": "Waiting", "value": 40, "fill": "var(--color-waiting)"}, |
|
|
|
{"name": "Failed", "value": 25, "fill": "var(--color-failed)"}, |
|
|
|
# Private vs Group Chat Usage |
|
|
|
chat_types = RoomMessage.objects.values('room_type').annotate(count=Count('id')) |
|
|
|
chat_type_stats = {item['room_type']: item['count'] for item in chat_types} |
|
|
|
|
|
|
|
# --------------------------------------------------------- |
|
|
|
# 2. CHARTS DATA |
|
|
|
# --------------------------------------------------------- |
|
|
|
|
|
|
|
# Payment Methods (Pie Chart) |
|
|
|
payment_methods = TransactionParticipant.objects.values('payment_method').annotate(total=Count('id')) |
|
|
|
payment_method_chart = [ |
|
|
|
{"name": item['payment_method'], "value": item['total']} |
|
|
|
for item in payment_methods |
|
|
|
] |
|
|
|
|
|
|
|
# Transaction Status (Donut Chart) |
|
|
|
transaction_status = TransactionParticipant.objects.values('status').annotate(total=Count('id')) |
|
|
|
transaction_chart = [ |
|
|
|
{"name": item['status'], "value": item['total']} |
|
|
|
for item in transaction_status |
|
|
|
] |
|
|
|
|
|
|
|
# 4. Top 5 Popular Courses |
|
|
|
top_courses = Course.objects.annotate( |
|
|
|
participant_count=Count('participants') |
|
|
|
).order_by('-participant_count')[:5] |
|
|
|
# Global Student Distribution (Bar Chart) |
|
|
|
top_countries = LoginHistory.objects.exclude(country__isnull=True).exclude(country="").values('country').annotate( |
|
|
|
count=Count('id') |
|
|
|
).order_by('-count')[:5] |
|
|
|
country_chart = [{"country": item['country'], "students": item['count']} for item in top_countries] |
|
|
|
|
|
|
|
# New User Registrations Trend (Line Chart) |
|
|
|
registrations = User.objects.filter(date_joined__gte=thirty_days_ago).annotate( |
|
|
|
date=TruncDate('date_joined') |
|
|
|
).values('date').annotate(count=Count('id')).order_by('date') |
|
|
|
registration_chart = [{"date": item['date'].strftime('%Y-%m-%d'), "users": item['count']} for item in registrations] |
|
|
|
|
|
|
|
# Community Chat Volume Trend (Area Chart) |
|
|
|
chat_volume = ChatMessage.objects.filter(sent_at__gte=thirty_days_ago).annotate( |
|
|
|
date=TruncDate('sent_at') |
|
|
|
).values('date').annotate(count=Count('id')).order_by('date') |
|
|
|
chat_chart = [{"date": item['date'].strftime('%Y-%m-%d'), "messages": item['count']} for item in chat_volume] |
|
|
|
|
|
|
|
# Course Pipeline (Stacked Bar) |
|
|
|
course_statuses = Course.objects.values('status').annotate(count=Count('id')) |
|
|
|
course_pipeline = {"name": "Courses"} |
|
|
|
for status in course_statuses: |
|
|
|
course_pipeline[status['status']] = status['count'] |
|
|
|
|
|
|
|
top_courses_data = [ |
|
|
|
# Course Efficacy: Completed vs Enrolled (Bar Chart) |
|
|
|
# We take the top 5 courses, check how many users are enrolled, and how many have at least 1 completed lesson |
|
|
|
top_courses_qs = Course.objects.annotate(participant_count=Count('participants')).order_by('-participant_count')[:5] |
|
|
|
course_efficacy = [] |
|
|
|
for course in top_courses_qs: |
|
|
|
enrolled = course.participant_count |
|
|
|
completed = Participant.objects.filter( |
|
|
|
course=course, |
|
|
|
student__lesson_completions__course_lesson__course=course |
|
|
|
).distinct().count() |
|
|
|
course_efficacy.append({ |
|
|
|
"title": course.title[:20] + "..." if len(course.title) > 20 else course.title, |
|
|
|
"enrolled": enrolled, |
|
|
|
"completed": completed |
|
|
|
}) |
|
|
|
|
|
|
|
# --------------------------------------------------------- |
|
|
|
# 3. LISTS & LEADERBOARDS |
|
|
|
# --------------------------------------------------------- |
|
|
|
|
|
|
|
# Top 5 Courses (List) |
|
|
|
top_courses_list = [ |
|
|
|
{ |
|
|
|
"id": c.id, |
|
|
|
"title": c.title, |
|
|
|
"professor": c.professor.fullname if c.professor else "Unknown", |
|
|
|
"participants": c.participant_count |
|
|
|
} for c in top_courses |
|
|
|
} for c in top_courses_qs |
|
|
|
] |
|
|
|
|
|
|
|
# Top 5 Blogs (List) |
|
|
|
top_blogs = Blog.objects.order_by('-views_count')[:5] |
|
|
|
top_blogs_list = [ |
|
|
|
{"id": b.id, "title": b._extract_text_from_json(b.title), "views": b.views_count} |
|
|
|
for b in top_blogs |
|
|
|
] |
|
|
|
|
|
|
|
# Top Performing Students (Leaderboard) |
|
|
|
top_students = QuizParticipant.objects.select_related('user').order_by('-total_score', 'total_timing')[:5] |
|
|
|
leaderboard = [ |
|
|
|
{"id": s.user.id, "name": s.user.fullname, "score": s.total_score, "time": s.total_timing} |
|
|
|
for s in top_students |
|
|
|
] |
|
|
|
|
|
|
|
return Response({ |
|
|
|
@ -58,8 +131,23 @@ class AdminDashboardStatsView(APIView): |
|
|
|
"active_courses": active_courses, |
|
|
|
"total_blogs": total_blogs, |
|
|
|
"revenue_30d": revenue_30d, |
|
|
|
"pending_certificates": pending_certificates |
|
|
|
"pending_certificates": pending_certificates, |
|
|
|
"active_live_sessions": active_live_sessions, |
|
|
|
"group_rooms": chat_type_stats.get('group', 0), |
|
|
|
"private_rooms": chat_type_stats.get('private', 0), |
|
|
|
}, |
|
|
|
"charts": { |
|
|
|
"transactions_status": transaction_chart, |
|
|
|
"payment_methods": payment_method_chart, |
|
|
|
"countries": country_chart, |
|
|
|
"registrations": registration_chart, |
|
|
|
"chat_volume": chat_chart, |
|
|
|
"course_pipeline": [course_pipeline], |
|
|
|
"course_efficacy": course_efficacy |
|
|
|
}, |
|
|
|
"transactions_chart": transactions_data, |
|
|
|
"top_courses": top_courses_data |
|
|
|
"lists": { |
|
|
|
"top_courses": top_courses_list, |
|
|
|
"top_blogs": top_blogs_list, |
|
|
|
"leaderboard": leaderboard |
|
|
|
} |
|
|
|
}) |