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