SkyLearn-Test/quiz/views.py
2024-10-05 18:59:48 +03:00

330 lines
11 KiB
Python

from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.db import transaction
from django.shortcuts import get_object_or_404, redirect, render
from django.utils.decorators import method_decorator
from django.views.generic import (
CreateView,
DetailView,
FormView,
ListView,
TemplateView,
UpdateView,
)
from accounts.decorators import lecturer_required
from .forms import (
EssayForm,
MCQuestionForm,
MCQuestionFormSet,
QuestionForm,
QuizAddForm,
)
from .models import (
Course,
EssayQuestion,
MCQuestion,
Progress,
Question,
Quiz,
Sitting,
)
# ########################################################
# Quiz Views
# ########################################################
@method_decorator([login_required, lecturer_required], name="dispatch")
class QuizCreateView(CreateView):
model = Quiz
form_class = QuizAddForm
template_name = "quiz/quiz_form.html"
def get_initial(self):
initial = super().get_initial()
course = get_object_or_404(Course, slug=self.kwargs["slug"])
initial["course"] = course
return initial
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["course"] = get_object_or_404(Course, slug=self.kwargs["slug"])
return context
def form_valid(self, form):
form.instance.course = get_object_or_404(Course, slug=self.kwargs["slug"])
with transaction.atomic():
self.object = form.save()
return redirect(
"mc_create", slug=self.kwargs["slug"], quiz_id=self.object.id
)
@method_decorator([login_required, lecturer_required], name="dispatch")
class QuizUpdateView(UpdateView):
model = Quiz
form_class = QuizAddForm
template_name = "quiz/quiz_form.html"
def get_object(self, queryset=None):
return get_object_or_404(Quiz, pk=self.kwargs["pk"])
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["course"] = get_object_or_404(Course, slug=self.kwargs["slug"])
return context
def form_valid(self, form):
with transaction.atomic():
self.object = form.save()
return redirect("quiz_index", self.kwargs["slug"])
@login_required
@lecturer_required
def quiz_delete(request, slug, pk):
quiz = get_object_or_404(Quiz, pk=pk)
quiz.delete()
messages.success(request, "Quiz successfully deleted.")
return redirect("quiz_index", slug=slug)
@login_required
def quiz_list(request, slug):
course = get_object_or_404(Course, slug=slug)
quizzes = Quiz.objects.filter(course=course).order_by("-timestamp")
return render(
request, "quiz/quiz_list.html", {"quizzes": quizzes, "course": course}
)
# ########################################################
# Multiple Choice Question Views
# ########################################################
@method_decorator([login_required, lecturer_required], name="dispatch")
class MCQuestionCreate(CreateView):
model = MCQuestion
form_class = MCQuestionForm
template_name = "quiz/mcquestion_form.html"
# def get_form_kwargs(self):
# kwargs = super().get_form_kwargs()
# kwargs["quiz"] = get_object_or_404(Quiz, id=self.kwargs["quiz_id"])
# return kwargs
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["course"] = get_object_or_404(Course, slug=self.kwargs["slug"])
context["quiz_obj"] = get_object_or_404(Quiz, id=self.kwargs["quiz_id"])
context["quiz_questions_count"] = Question.objects.filter(
quiz=self.kwargs["quiz_id"]
).count()
if self.request.method == "POST":
context["formset"] = MCQuestionFormSet(self.request.POST)
else:
context["formset"] = MCQuestionFormSet()
return context
def form_valid(self, form):
context = self.get_context_data()
formset = context["formset"]
if formset.is_valid():
with transaction.atomic():
# Save the MCQuestion instance without committing to the database yet
self.object = form.save(commit=False)
self.object.save()
# Retrieve the Quiz instance
quiz = get_object_or_404(Quiz, id=self.kwargs["quiz_id"])
# set the many-to-many relationship
self.object.quiz.add(quiz)
# Save the formset (choices for the question)
formset.instance = self.object
formset.save()
if "another" in self.request.POST:
return redirect(
"mc_create",
slug=self.kwargs["slug"],
quiz_id=self.kwargs["quiz_id"],
)
return redirect("quiz_index", slug=self.kwargs["slug"])
else:
return self.form_invalid(form)
# ########################################################
# Quiz Progress and Marking Views
# ########################################################
@method_decorator([login_required], name="dispatch")
class QuizUserProgressView(TemplateView):
template_name = "quiz/progress.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
progress, _ = Progress.objects.get_or_create(user=self.request.user)
context["cat_scores"] = progress.list_all_cat_scores
context["exams"] = progress.show_exams()
context["exams_counter"] = context["exams"].count()
return context
@method_decorator([login_required, lecturer_required], name="dispatch")
class QuizMarkingList(ListView):
model = Sitting
template_name = "quiz/quiz_marking_list.html"
def get_queryset(self):
queryset = Sitting.objects.filter(complete=True)
if not self.request.user.is_superuser:
queryset = queryset.filter(
quiz__course__allocated_course__lecturer__pk=self.request.user.id
)
quiz_filter = self.request.GET.get("quiz_filter")
if quiz_filter:
queryset = queryset.filter(quiz__title__icontains=quiz_filter)
user_filter = self.request.GET.get("user_filter")
if user_filter:
queryset = queryset.filter(user__username__icontains=user_filter)
return queryset
@method_decorator([login_required, lecturer_required], name="dispatch")
class QuizMarkingDetail(DetailView):
model = Sitting
template_name = "quiz/quiz_marking_detail.html"
def post(self, request, *args, **kwargs):
sitting = self.get_object()
question_id = request.POST.get("qid")
if question_id:
question = Question.objects.get_subclass(id=int(question_id))
if int(question_id) in sitting.get_incorrect_questions:
sitting.remove_incorrect_question(question)
else:
sitting.add_incorrect_question(question)
return self.get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["questions"] = self.object.get_questions(with_answers=True)
return context
# ########################################################
# Quiz Taking View
# ########################################################
@method_decorator([login_required], name="dispatch")
class QuizTake(FormView):
form_class = QuestionForm
template_name = "quiz/question.html"
result_template_name = "quiz/result.html"
def dispatch(self, request, *args, **kwargs):
self.quiz = get_object_or_404(Quiz, slug=self.kwargs["slug"])
self.course = get_object_or_404(Course, pk=self.kwargs["pk"])
if not Question.objects.filter(quiz=self.quiz).exists():
messages.warning(request, "This quiz has no questions available.")
return redirect("quiz_index", slug=self.course.slug)
self.sitting = Sitting.objects.user_sitting(
request.user, self.quiz, self.course
)
if not self.sitting:
messages.info(
request,
"You have already completed this quiz. Only one attempt is permitted.",
)
return redirect("quiz_index", slug=self.course.slug)
return super().dispatch(request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
self.question = self.sitting.get_first_question()
self.progress = self.sitting.progress()
kwargs["question"] = self.question
return kwargs
def get_form_class(self):
if isinstance(self.question, EssayQuestion):
return EssayForm
return self.form_class
def form_valid(self, form):
self.form_valid_user(form)
if not self.sitting.get_first_question():
return self.final_result_user()
return super().get(self.request)
def form_valid_user(self, form):
progress, _ = Progress.objects.get_or_create(user=self.request.user)
guess = form.cleaned_data["answers"]
is_correct = self.question.check_if_correct(guess)
if is_correct:
self.sitting.add_to_score(1)
progress.update_score(self.question, 1, 1)
else:
self.sitting.add_incorrect_question(self.question)
progress.update_score(self.question, 0, 1)
if not self.quiz.answers_at_end:
self.previous = {
"previous_answer": guess,
"previous_outcome": is_correct,
"previous_question": self.question,
"answers": self.question.get_choices(),
"question_type": {self.question.__class__.__name__: True},
}
else:
self.previous = {}
self.sitting.add_user_answer(self.question, guess)
self.sitting.remove_first_question()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["question"] = self.question
context["quiz"] = self.quiz
context["course"] = self.course
if hasattr(self, "previous"):
context["previous"] = self.previous
if hasattr(self, "progress"):
context["progress"] = self.progress
return context
def final_result_user(self):
self.sitting.mark_quiz_complete()
results = {
"course": self.course,
"quiz": self.quiz,
"score": self.sitting.get_current_score,
"max_score": self.sitting.get_max_score,
"percent": self.sitting.get_percent_correct,
"sitting": self.sitting,
"previous": getattr(self, "previous", {}),
}
if self.quiz.answers_at_end:
results["questions"] = self.sitting.get_questions(with_answers=True)
results["incorrect_questions"] = self.sitting.get_incorrect_questions
if (
not self.quiz.exam_paper
or self.request.user.is_superuser
or self.request.user.is_lecturer
):
self.sitting.delete()
return render(self.request, self.result_template_name, results)