Files
testpoint/core/admin_dashboard.py
T
2026-07-29 10:47:40 +00:00

81 lines
3.3 KiB
Python

from datetime import datetime, time, timedelta
from django.contrib.auth import get_user_model
from django.db.models import Count
from django.utils import timezone
from exams.models import ExamSet, StudentAttempt
def _chart_points(values, maximum, width=680, height=170, padding=18):
"""Return stable SVG points for a seven-day chart, including an empty state."""
maximum = max(maximum, 1)
usable_width = width - padding * 2
usable_height = height - padding * 2
points = []
for index, value in enumerate(values):
x = padding + usable_width * index / max(len(values) - 1, 1)
y = padding + usable_height - (value / maximum * usable_height)
points.append(f"{x:.1f},{y:.1f}")
return " ".join(points)
def dashboard_callback(request, context):
today = timezone.localdate()
days = [today - timedelta(days=offset) for offset in range(6, -1, -1)]
starts = [timezone.make_aware(datetime.combine(day, time.min)) for day in days]
ends = [start + timedelta(days=1) for start in starts]
attempt_counts = []
active_student_counts = []
for start, end in zip(starts, ends):
attempts = StudentAttempt.objects.filter(started_at__gte=start, started_at__lt=end)
attempt_counts.append(attempts.count())
active_student_counts.append(attempts.values("student_id").distinct().count())
total_attempts = StudentAttempt.objects.count()
seven_day_attempts = sum(attempt_counts)
active_students = StudentAttempt.objects.filter(started_at__gte=starts[0]).values("student_id").distinct().count()
recent_tests = (
ExamSet.objects.annotate(
question_count=Count("sections__questions", distinct=True),
attempt_count=Count("studentattempt", distinct=True),
)
.order_by("-created_at")[:6]
)
chart_maximum = max([*attempt_counts, *active_student_counts, 1])
context.update(
{
"dashboard_metrics": [
{
"label": "Published tests",
"value": ExamSet.objects.filter(is_published=True).count(),
"detail": "Tests available to students",
"icon": "description",
"link": "/admin/exams/examset/?is_published__exact=1",
},
{
"label": "Student attempts",
"value": total_attempts,
"detail": f"{seven_day_attempts} started in the last 7 days",
"icon": "monitoring",
"link": "/admin/exams/studentattempt/",
},
{
"label": "Active students",
"value": active_students,
"detail": "Students active in the last 7 days",
"icon": "group",
"link": "/admin/auth/user/",
},
],
"chart_labels": [f"{day.strftime('%b')} {day.day}" for day in days],
"attempt_points": _chart_points(attempt_counts, chart_maximum),
"active_points": _chart_points(active_student_counts, chart_maximum),
"has_chart_activity": any(attempt_counts),
"recent_tests": recent_tests,
"total_users": get_user_model().objects.count(),
}
)
return context