4 changed files with 215 additions and 62 deletions
-
131apps/api/permissions.py
-
4apps/api/views/__init__.py
-
139apps/api/views/professor_dashboard.py
-
3config/urls.py
@ -1,60 +1,71 @@ |
|||||
from rest_framework import permissions |
|
||||
from rest_framework.authtoken.models import Token |
|
||||
from django.contrib.auth.models import AnonymousUser |
|
||||
|
|
||||
|
|
||||
class SwaggerTokenPermission(permissions.BasePermission): |
|
||||
""" |
|
||||
Custom permission for Swagger that allows access to authenticated users via token |
|
||||
or admin users via session authentication |
|
||||
""" |
|
||||
|
|
||||
def has_permission(self, request, view): |
|
||||
# Check if user is admin (for session-based access) |
|
||||
if request.user and request.user.is_authenticated and request.user.is_staff: |
|
||||
return True |
|
||||
|
|
||||
# Check for token in session (from our custom auth system) |
|
||||
swagger_token = request.session.get('swagger_token') |
|
||||
if swagger_token: |
|
||||
try: |
|
||||
token_obj = Token.objects.get(key=swagger_token) |
|
||||
if token_obj.user.is_active: |
|
||||
return True |
|
||||
except Token.DoesNotExist: |
|
||||
pass |
|
||||
|
|
||||
# Check for Authorization header |
|
||||
auth_header = request.META.get('HTTP_AUTHORIZATION', '') |
|
||||
if auth_header.startswith('Token '): |
|
||||
token = auth_header.split(' ')[1] |
|
||||
try: |
|
||||
token_obj = Token.objects.get(key=token) |
|
||||
if token_obj.user.is_active: |
|
||||
return True |
|
||||
except Token.DoesNotExist: |
|
||||
pass |
|
||||
|
|
||||
return False |
|
||||
|
|
||||
|
|
||||
class IsAdminOrSwaggerToken(permissions.BasePermission): |
|
||||
""" |
|
||||
Permission that allows access to admin users or users with valid swagger token |
|
||||
""" |
|
||||
|
|
||||
def has_permission(self, request, view): |
|
||||
# Allow admin users |
|
||||
if request.user and request.user.is_authenticated and request.user.is_staff: |
|
||||
return True |
|
||||
|
|
||||
# Check swagger token in session |
|
||||
swagger_token = request.session.get('swagger_token') |
|
||||
if swagger_token: |
|
||||
try: |
|
||||
token_obj = Token.objects.get(key=swagger_token) |
|
||||
return token_obj.user.is_active |
|
||||
except Token.DoesNotExist: |
|
||||
pass |
|
||||
|
|
||||
return False |
|
||||
|
from rest_framework import permissions |
||||
|
from rest_framework.authtoken.models import Token |
||||
|
from django.contrib.auth.models import AnonymousUser |
||||
|
|
||||
|
|
||||
|
class SwaggerTokenPermission(permissions.BasePermission): |
||||
|
""" |
||||
|
Custom permission for Swagger that allows access to authenticated users via token |
||||
|
or admin users via session authentication |
||||
|
""" |
||||
|
|
||||
|
def has_permission(self, request, view): |
||||
|
# Check if user is admin (for session-based access) |
||||
|
if request.user and request.user.is_authenticated and request.user.is_staff: |
||||
|
return True |
||||
|
|
||||
|
# Check for token in session (from our custom auth system) |
||||
|
swagger_token = request.session.get('swagger_token') |
||||
|
if swagger_token: |
||||
|
try: |
||||
|
token_obj = Token.objects.get(key=swagger_token) |
||||
|
if token_obj.user.is_active: |
||||
|
return True |
||||
|
except Token.DoesNotExist: |
||||
|
pass |
||||
|
|
||||
|
# Check for Authorization header |
||||
|
auth_header = request.META.get('HTTP_AUTHORIZATION', '') |
||||
|
if auth_header.startswith('Token '): |
||||
|
token = auth_header.split(' ')[1] |
||||
|
try: |
||||
|
token_obj = Token.objects.get(key=token) |
||||
|
if token_obj.user.is_active: |
||||
|
return True |
||||
|
except Token.DoesNotExist: |
||||
|
pass |
||||
|
|
||||
|
return False |
||||
|
|
||||
|
|
||||
|
class IsAdminOrSwaggerToken(permissions.BasePermission): |
||||
|
""" |
||||
|
Permission that allows access to admin users or users with valid swagger token |
||||
|
""" |
||||
|
|
||||
|
def has_permission(self, request, view): |
||||
|
# Allow admin users |
||||
|
if request.user and request.user.is_authenticated and request.user.is_staff: |
||||
|
return True |
||||
|
|
||||
|
# Check swagger token in session |
||||
|
swagger_token = request.session.get('swagger_token') |
||||
|
if swagger_token: |
||||
|
try: |
||||
|
token_obj = Token.objects.get(key=swagger_token) |
||||
|
return token_obj.user.is_active |
||||
|
except Token.DoesNotExist: |
||||
|
pass |
||||
|
|
||||
|
return False |
||||
|
|
||||
|
|
||||
|
class IsProfessorUser(permissions.BasePermission): |
||||
|
""" |
||||
|
Permission that allows access only to professors or admin users. |
||||
|
""" |
||||
|
|
||||
|
def has_permission(self, request, view): |
||||
|
if not (request.user and request.user.is_authenticated): |
||||
|
return False |
||||
|
return request.user.user_type == 'professor' or request.user.is_staff |
||||
@ -0,0 +1,139 @@ |
|||||
|
from rest_framework.views import APIView |
||||
|
from rest_framework.response import Response |
||||
|
from rest_framework.permissions import IsAuthenticated |
||||
|
from rest_framework.authentication import TokenAuthentication |
||||
|
from django.utils import timezone |
||||
|
from datetime import timedelta |
||||
|
from django.db.models import Count, Q |
||||
|
from django.db.models.functions import TruncDate |
||||
|
|
||||
|
from apps.account.models import User |
||||
|
from apps.course.models import Course, Participant |
||||
|
from apps.course.models.course import extract_text_from_json |
||||
|
from apps.quiz.models import Quiz, QuizParticipant |
||||
|
from apps.chat.models import ChatMessage, RoomMessage |
||||
|
from apps.api.permissions import IsProfessorUser |
||||
|
|
||||
|
|
||||
|
class ProfessorDashboardStatsView(APIView): |
||||
|
authentication_classes = [TokenAuthentication] |
||||
|
permission_classes = [IsAuthenticated, IsProfessorUser] |
||||
|
|
||||
|
def get(self, request): |
||||
|
now = timezone.now() |
||||
|
thirty_days_ago = now - timedelta(days=30) |
||||
|
|
||||
|
# --------------------------------------------------------- |
||||
|
# 1. CARDS & BASIC STATS |
||||
|
# --------------------------------------------------------- |
||||
|
# Total courses taught by this professor |
||||
|
courses_count = Course.objects.filter(professor=request.user).count() |
||||
|
|
||||
|
# Total unique active students enrolled in this professor's courses |
||||
|
students_count = Participant.objects.filter( |
||||
|
course__professor=request.user, |
||||
|
is_active=True |
||||
|
).values('student').distinct().count() |
||||
|
|
||||
|
# Total quizzes created in this professor's courses |
||||
|
quizzes_count = Quiz.objects.filter(course__professor=request.user).count() |
||||
|
|
||||
|
# Chat rooms count (group rooms for professor's courses + private chats with professor) |
||||
|
chat_rooms_count = RoomMessage.objects.filter( |
||||
|
Q(course__professor=request.user) | |
||||
|
(Q(room_type=RoomMessage.RoomTypeChoices.PRIVATE) & (Q(initiator=request.user) | Q(recipient=request.user))) |
||||
|
).distinct().count() |
||||
|
|
||||
|
# --------------------------------------------------------- |
||||
|
# 2. CHARTS DATA |
||||
|
# --------------------------------------------------------- |
||||
|
# Chat Volume Trend (30 days) |
||||
|
professor_rooms = RoomMessage.objects.filter( |
||||
|
Q(course__professor=request.user) | |
||||
|
(Q(room_type=RoomMessage.RoomTypeChoices.PRIVATE) & (Q(initiator=request.user) | Q(recipient=request.user))) |
||||
|
) |
||||
|
chat_volume = ChatMessage.objects.filter( |
||||
|
room__in=professor_rooms, |
||||
|
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 Efficacy: Completed vs Enrolled (Bar Chart for Top 5 Courses) |
||||
|
top_courses_qs = Course.objects.filter(professor=request.user).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() |
||||
|
title_text = extract_text_from_json(course.title) |
||||
|
course_efficacy.append({ |
||||
|
"title": title_text[:20] + "..." if len(title_text) > 20 else title_text, |
||||
|
"enrolled": enrolled, |
||||
|
"completed": completed |
||||
|
}) |
||||
|
|
||||
|
# Quiz Participation statistics (all quizzes of the professor) |
||||
|
quizzes = Quiz.objects.filter(course__professor=request.user) |
||||
|
quizzes_stats = [] |
||||
|
for quiz in quizzes: |
||||
|
course = quiz.course |
||||
|
if not course: |
||||
|
continue |
||||
|
total_enrolled = Participant.objects.filter(course=course, is_active=True).count() |
||||
|
participated = QuizParticipant.objects.filter(quiz=quiz).values('user').distinct().count() |
||||
|
not_participated = max(0, total_enrolled - participated) |
||||
|
quizzes_stats.append({ |
||||
|
"id": quiz.id, |
||||
|
"title": quiz.title, |
||||
|
"course_title": extract_text_from_json(course.title), |
||||
|
"participated": participated, |
||||
|
"not_participated": not_participated, |
||||
|
}) |
||||
|
|
||||
|
# --------------------------------------------------------- |
||||
|
# 3. LISTS & LEADERBOARDS |
||||
|
# --------------------------------------------------------- |
||||
|
# Top Courses List |
||||
|
top_courses_list = [ |
||||
|
{ |
||||
|
"id": c.id, |
||||
|
"title": extract_text_from_json(c.title), |
||||
|
"participants": c.participant_count |
||||
|
} for c in top_courses_qs |
||||
|
] |
||||
|
|
||||
|
# Leaderboard (Top 5 Performing Students in professor's quizzes) |
||||
|
top_students = QuizParticipant.objects.filter( |
||||
|
quiz__course__professor=request.user |
||||
|
).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({ |
||||
|
"cards": { |
||||
|
"courses_count": courses_count, |
||||
|
"students_count": students_count, |
||||
|
"quizzes_count": quizzes_count, |
||||
|
"chat_rooms_count": chat_rooms_count, |
||||
|
}, |
||||
|
"charts": { |
||||
|
"chat_volume": chat_chart, |
||||
|
"course_efficacy": course_efficacy, |
||||
|
"quizzes_stats": quizzes_stats |
||||
|
}, |
||||
|
"lists": { |
||||
|
"top_courses": top_courses_list, |
||||
|
"leaderboard": leaderboard |
||||
|
} |
||||
|
}) |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue