Browse Source
dashboard quiz page endpoints added
dashboard quiz page endpoints added
created views and serializers specially for dashboard , crud operations on quizez , filtering and ...master
4 changed files with 287 additions and 3 deletions
-
119apps/quiz/serializers/admin.py
-
12apps/quiz/urls.py
-
3apps/quiz/views/__init__.py
-
156apps/quiz/views/admin.py
@ -0,0 +1,119 @@ |
|||||
|
from rest_framework import serializers |
||||
|
from apps.quiz.models import Quiz, Question, QuizParticipant |
||||
|
from apps.course.models import Course, CourseLesson |
||||
|
from apps.account.models import User |
||||
|
|
||||
|
|
||||
|
class AdminCourseSerializer(serializers.ModelSerializer): |
||||
|
class Meta: |
||||
|
model = Course |
||||
|
fields = ['id', 'title'] |
||||
|
|
||||
|
|
||||
|
class AdminCourseLessonSerializer(serializers.ModelSerializer): |
||||
|
title = serializers.SerializerMethodField() |
||||
|
|
||||
|
class Meta: |
||||
|
model = CourseLesson |
||||
|
fields = ['id', 'title'] |
||||
|
|
||||
|
def get_title(self, obj): |
||||
|
if obj.title: |
||||
|
return obj.title |
||||
|
if obj.lesson: |
||||
|
return obj.lesson.title |
||||
|
return f"Lesson {obj.id}" |
||||
|
|
||||
|
|
||||
|
class AdminQuizListSerializer(serializers.ModelSerializer): |
||||
|
questions_count = serializers.SerializerMethodField() |
||||
|
participants_count = serializers.SerializerMethodField() |
||||
|
course_title = serializers.SerializerMethodField() |
||||
|
lesson_title = serializers.SerializerMethodField() |
||||
|
|
||||
|
class Meta: |
||||
|
model = Quiz |
||||
|
fields = [ |
||||
|
'id', |
||||
|
'title', |
||||
|
'description', |
||||
|
'each_question_timing', |
||||
|
'status', |
||||
|
'course', |
||||
|
'course_title', |
||||
|
'lesson', |
||||
|
'lesson_title', |
||||
|
'questions_count', |
||||
|
'participants_count' |
||||
|
] |
||||
|
|
||||
|
def get_questions_count(self, obj): |
||||
|
return obj.questions.count() |
||||
|
|
||||
|
def get_participants_count(self, obj): |
||||
|
return obj.participants.count() |
||||
|
|
||||
|
def get_course_title(self, obj): |
||||
|
return obj.course.title if obj.course else None |
||||
|
|
||||
|
def get_lesson_title(self, obj): |
||||
|
if not obj.lesson: |
||||
|
return None |
||||
|
return obj.lesson.title or (obj.lesson.lesson.title if obj.lesson.lesson else None) |
||||
|
|
||||
|
|
||||
|
class AdminQuizDetailSerializer(serializers.ModelSerializer): |
||||
|
class Meta: |
||||
|
model = Quiz |
||||
|
fields = [ |
||||
|
'id', |
||||
|
'title', |
||||
|
'description', |
||||
|
'each_question_timing', |
||||
|
'status', |
||||
|
'course', |
||||
|
'lesson' |
||||
|
] |
||||
|
read_only_fields = ['course'] # Set automatically on save based on lesson.course |
||||
|
|
||||
|
|
||||
|
class AdminQuestionSerializer(serializers.ModelSerializer): |
||||
|
class Meta: |
||||
|
model = Question |
||||
|
fields = [ |
||||
|
'id', |
||||
|
'quiz', |
||||
|
'question', |
||||
|
'option1', |
||||
|
'option2', |
||||
|
'option3', |
||||
|
'option4', |
||||
|
'correct_answer', |
||||
|
'priority' |
||||
|
] |
||||
|
|
||||
|
|
||||
|
class AdminQuizParticipantSerializer(serializers.ModelSerializer): |
||||
|
user_name = serializers.CharField(source='user.fullname', read_only=True) |
||||
|
user_email = serializers.EmailField(source='user.email', read_only=True) |
||||
|
|
||||
|
class Meta: |
||||
|
model = QuizParticipant |
||||
|
fields = [ |
||||
|
'id', |
||||
|
'user_id', |
||||
|
'user_name', |
||||
|
'user_email', |
||||
|
'started_at', |
||||
|
'ended_at', |
||||
|
'total_timing', |
||||
|
'question_score', |
||||
|
'timing_score', |
||||
|
'total_score' |
||||
|
] |
||||
|
|
||||
|
|
||||
|
class AdminQuizAbsenteeSerializer(serializers.ModelSerializer): |
||||
|
class Meta: |
||||
|
model = User |
||||
|
fields = ['id', 'fullname', 'email'] |
||||
@ -1,13 +1,21 @@ |
|||||
from django.urls import path |
|
||||
|
from django.urls import path, include |
||||
|
from rest_framework.routers import DefaultRouter |
||||
|
|
||||
from . import views |
from . import views |
||||
|
|
||||
|
router = DefaultRouter() |
||||
|
router.register(r'admin/quizzes', views.AdminQuizViewSet, basename='admin-quizzes') |
||||
|
router.register(r'admin/questions', views.AdminQuestionViewSet, basename='admin-questions') |
||||
|
|
||||
urlpatterns = [ |
urlpatterns = [ |
||||
|
path('', include(router.urls)), |
||||
|
path('admin/courses/', views.AdminCourseListView.as_view(), name='admin-courses-list'), |
||||
|
path('admin/lessons/', views.AdminLessonListView.as_view(), name='admin-lessons-list'), |
||||
|
|
||||
# path('prizes/', PrizeListAPIView.as_view()), |
# path('prizes/', PrizeListAPIView.as_view()), |
||||
# path('ranked-list/', RankedListAPIView.as_view()), |
# path('ranked-list/', RankedListAPIView.as_view()), |
||||
# path('self-rank/', SelfRankAPIView.as_view()), |
# path('self-rank/', SelfRankAPIView.as_view()), |
||||
# path('my-quizzes/', UserQuizScores.as_view()), |
# path('my-quizzes/', UserQuizScores.as_view()), |
||||
path('submit-quiz/', views.QuizParticipantCreateAPIView.as_view()), |
path('submit-quiz/', views.QuizParticipantCreateAPIView.as_view()), |
||||
path('<int:quiz_id>/', views.QuizDetailAPIView.as_view()), |
path('<int:quiz_id>/', views.QuizDetailAPIView.as_view()), |
||||
|
|
||||
] |
] |
||||
@ -1,2 +1,3 @@ |
|||||
from .quiz import * |
from .quiz import * |
||||
from .participant import * |
|
||||
|
from .participant import * |
||||
|
from .admin import * |
||||
@ -0,0 +1,156 @@ |
|||||
|
from django.db.models import Q |
||||
|
from rest_framework.viewsets import ModelViewSet |
||||
|
from rest_framework.generics import ListAPIView |
||||
|
from rest_framework.permissions import IsAuthenticated, IsAdminUser |
||||
|
from rest_framework.authentication import TokenAuthentication |
||||
|
from rest_framework.decorators import action |
||||
|
from rest_framework.response import Response |
||||
|
from django.shortcuts import get_object_or_404 |
||||
|
|
||||
|
from apps.quiz.models import Quiz, Question, QuizParticipant |
||||
|
from apps.course.models import Course, CourseLesson, Participant |
||||
|
from apps.account.models import User |
||||
|
|
||||
|
from apps.quiz.serializers.admin import ( |
||||
|
AdminCourseSerializer, |
||||
|
AdminCourseLessonSerializer, |
||||
|
AdminQuizListSerializer, |
||||
|
AdminQuizDetailSerializer, |
||||
|
AdminQuestionSerializer, |
||||
|
AdminQuizParticipantSerializer, |
||||
|
AdminQuizAbsenteeSerializer |
||||
|
) |
||||
|
|
||||
|
|
||||
|
class AdminQuizViewSet(ModelViewSet): |
||||
|
""" |
||||
|
Admin CRUD ViewSet for Quiz management. |
||||
|
Includes searching, filtering, and retrieving participants/absentees list. |
||||
|
""" |
||||
|
permission_classes = [IsAuthenticated, IsAdminUser] |
||||
|
authentication_classes = [TokenAuthentication] |
||||
|
|
||||
|
def get_serializer_class(self): |
||||
|
if self.action == 'list': |
||||
|
return AdminQuizListSerializer |
||||
|
return AdminQuizDetailSerializer |
||||
|
|
||||
|
def get_queryset(self): |
||||
|
queryset = Quiz.objects.all().select_related('course', 'lesson') |
||||
|
|
||||
|
# Handle Search |
||||
|
search_query = self.request.query_params.get('search', None) |
||||
|
if search_query: |
||||
|
queryset = queryset.filter( |
||||
|
Q(title__icontains=search_query) | |
||||
|
Q(description__icontains=search_query) |
||||
|
) |
||||
|
|
||||
|
# Handle Course Filter |
||||
|
course_id = self.request.query_params.get('course', None) |
||||
|
if course_id: |
||||
|
queryset = queryset.filter(course_id=course_id) |
||||
|
|
||||
|
# Handle Lesson Filter |
||||
|
lesson_id = self.request.query_params.get('lesson', None) |
||||
|
if lesson_id: |
||||
|
queryset = queryset.filter(lesson_id=lesson_id) |
||||
|
|
||||
|
# Handle Status Filter |
||||
|
status_param = self.request.query_params.get('status', None) |
||||
|
if status_param is not None: |
||||
|
if status_param.lower() == 'true': |
||||
|
queryset = queryset.filter(status=True) |
||||
|
elif status_param.lower() == 'false': |
||||
|
queryset = queryset.filter(status=False) |
||||
|
|
||||
|
return queryset.order_by('-id') |
||||
|
|
||||
|
@action(detail=True, methods=['get']) |
||||
|
def participants(self, request, pk=None): |
||||
|
""" |
||||
|
Returns two lists: |
||||
|
1. Participants: users who have participated in the quiz. |
||||
|
2. Absentees: users who are enrolled in the course but have not participated. |
||||
|
""" |
||||
|
quiz = self.get_object() |
||||
|
|
||||
|
# 1. Fetch participants |
||||
|
participants_qs = QuizParticipant.objects.filter(quiz=quiz).select_related('user') |
||||
|
participants_serializer = AdminQuizParticipantSerializer(participants_qs, many=True) |
||||
|
|
||||
|
# 2. Fetch absentees |
||||
|
absentees_data = [] |
||||
|
if quiz.course: |
||||
|
# Enrolled course student IDs |
||||
|
enrolled_student_ids = Participant.objects.filter( |
||||
|
course=quiz.course, |
||||
|
is_active=True |
||||
|
).values_list('student_id', flat=True) |
||||
|
|
||||
|
# Participated user IDs |
||||
|
participated_user_ids = participants_qs.values_list('user_id', flat=True) |
||||
|
|
||||
|
# Absentee IDs |
||||
|
absentee_ids = set(enrolled_student_ids) - set(participated_user_ids) |
||||
|
|
||||
|
# Fetch User details for absentees |
||||
|
absentees_qs = User.objects.filter(id__in=absentee_ids) |
||||
|
absentees_serializer = AdminQuizAbsenteeSerializer(absentees_qs, many=True) |
||||
|
absentees_data = absentees_serializer.data |
||||
|
|
||||
|
return Response({ |
||||
|
'participants': participants_serializer.data, |
||||
|
'absentees': absentees_data |
||||
|
}) |
||||
|
|
||||
|
|
||||
|
class AdminQuestionViewSet(ModelViewSet): |
||||
|
""" |
||||
|
Admin CRUD ViewSet for Question management. |
||||
|
Usually filtered by quiz_id. |
||||
|
""" |
||||
|
serializer_class = AdminQuestionSerializer |
||||
|
permission_classes = [IsAuthenticated, IsAdminUser] |
||||
|
authentication_classes = [TokenAuthentication] |
||||
|
pagination_class = None |
||||
|
|
||||
|
def get_queryset(self): |
||||
|
queryset = Question.objects.all() |
||||
|
|
||||
|
quiz_id = self.request.query_params.get('quiz_id', None) |
||||
|
if not quiz_id: |
||||
|
# Fallback parameter name |
||||
|
quiz_id = self.request.query_params.get('quiz', None) |
||||
|
|
||||
|
if quiz_id: |
||||
|
queryset = queryset.filter(quiz_id=quiz_id) |
||||
|
|
||||
|
return queryset.order_by('priority', 'id') |
||||
|
|
||||
|
|
||||
|
class AdminCourseListView(ListAPIView): |
||||
|
""" |
||||
|
Dropdown listing helper for all courses. |
||||
|
""" |
||||
|
queryset = Course.objects.all().order_by('-id') |
||||
|
serializer_class = AdminCourseSerializer |
||||
|
permission_classes = [IsAuthenticated, IsAdminUser] |
||||
|
authentication_classes = [TokenAuthentication] |
||||
|
pagination_class = None # Return all courses for dropdown usage |
||||
|
|
||||
|
|
||||
|
class AdminLessonListView(ListAPIView): |
||||
|
""" |
||||
|
Dropdown listing helper for lessons. Filtered by course_id. |
||||
|
""" |
||||
|
serializer_class = AdminCourseLessonSerializer |
||||
|
permission_classes = [IsAuthenticated, IsAdminUser] |
||||
|
authentication_classes = [TokenAuthentication] |
||||
|
pagination_class = None # Return all matching lessons for dropdown usage |
||||
|
|
||||
|
def get_queryset(self): |
||||
|
course_id = self.request.query_params.get('course_id', None) |
||||
|
if course_id: |
||||
|
return CourseLesson.objects.filter(course_id=course_id).select_related('lesson').order_by('priority') |
||||
|
return CourseLesson.objects.all().select_related('lesson').order_by('-id') |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue