Follow pylint rule for quiz app

This commit is contained in:
Adil Mohak 2024-10-05 01:17:28 +03:00
parent 36a6259189
commit cf81b3e8a3
4 changed files with 252 additions and 373 deletions

View File

@ -450,7 +450,8 @@ disable=raw-checker-failed,
too-few-public-methods,
arguments-differ,
invalid-overridden-method,
unsupported-binary-operation
unsupported-binary-operation,
attribute-defined-outside-init
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option

View File

@ -1,23 +1,22 @@
import re
import json
import re
from django.db import models
from django.urls import reverse
from django.core.exceptions import ValidationError, ImproperlyConfigured
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.core.validators import (
MaxValueValidator,
validate_comma_separated_integer_list,
)
from django.utils.translation import gettext_lazy as _
from django.utils.timezone import now
from django.conf import settings
from django.db.models.signals import pre_save
from django.db import models
from django.db.models import Q
from django.db.models.signals import pre_save
from django.urls import reverse
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from model_utils.managers import InheritanceManager
from course.models import Course
from .utils import *
from .utils import unique_slug_generator
CHOICE_ORDER_OPTIONS = (
("content", _("Content")),
@ -34,97 +33,69 @@ CATEGORY_OPTIONS = (
class QuizManager(models.Manager):
def search(self, query=None):
qs = self.get_queryset()
if query is not None:
queryset = self.get_queryset()
if query:
or_lookup = (
Q(title__icontains=query)
| Q(description__icontains=query)
| Q(category__icontains=query)
| Q(slug__icontains=query)
)
qs = qs.filter(
or_lookup
).distinct() # distinct() is often necessary with Q lookups
return qs
queryset = queryset.filter(or_lookup).distinct()
return queryset
class Quiz(models.Model):
course = models.ForeignKey(Course, on_delete=models.CASCADE, null=True)
title = models.CharField(verbose_name=_("Title"), max_length=60, blank=False)
slug = models.SlugField(blank=True, unique=True)
course = models.ForeignKey(Course, on_delete=models.CASCADE)
title = models.CharField(verbose_name=_("Title"), max_length=60)
slug = models.SlugField(unique=True, blank=True)
description = models.TextField(
verbose_name=_("Description"),
blank=True,
help_text=_("A detailed description of the quiz"),
)
category = models.TextField(choices=CATEGORY_OPTIONS, blank=True)
category = models.CharField(max_length=20, choices=CATEGORY_OPTIONS, blank=True)
random_order = models.BooleanField(
blank=False,
default=False,
verbose_name=_("Random Order"),
help_text=_("Display the questions in a random order or as they are set?"),
)
# max_questions = models.PositiveIntegerField(blank=True, null=True, verbose_name=_("Max Questions"),
# help_text=_("Number of questions to be answered on each attempt."))
answers_at_end = models.BooleanField(
blank=False,
default=False,
verbose_name=_("Answers at end"),
help_text=_(
"Correct answer is NOT shown after question. Answers displayed at the end."
),
)
exam_paper = models.BooleanField(
blank=False,
default=False,
verbose_name=_("Exam Paper"),
help_text=_(
"If yes, the result of each attempt by a user will be stored. Necessary for marking."
),
)
single_attempt = models.BooleanField(
blank=False,
default=False,
verbose_name=_("Single Attempt"),
help_text=_("If yes, only one attempt by a user will be permitted."),
)
pass_mark = models.SmallIntegerField(
blank=True,
default=50,
verbose_name=_("Pass Mark"),
validators=[MaxValueValidator(100)],
help_text=_("Percentage required to pass exam."),
)
draft = models.BooleanField(
blank=True,
default=False,
verbose_name=_("Draft"),
help_text=_(
"If yes, the quiz is not displayed in the quiz list and can only be taken by users who can edit quizzes."
),
)
timestamp = models.DateTimeField(auto_now=True)
objects = QuizManager()
def save(self, force_insert=False, force_update=False, *args, **kwargs):
if self.single_attempt is True:
self.exam_paper = True
if self.pass_mark > 100:
raise ValidationError("%s is above 100" % self.pass_mark)
if self.pass_mark < 0:
raise ValidationError("%s is below 0" % self.pass_mark)
super(Quiz, self).save(force_insert, force_update, *args, **kwargs)
class Meta:
verbose_name = _("Quiz")
verbose_name_plural = _("Quizzes")
@ -132,6 +103,15 @@ class Quiz(models.Model):
def __str__(self):
return self.title
def save(self, *args, **kwargs):
if self.single_attempt:
self.exam_paper = True
if not (0 <= self.pass_mark <= 100):
raise ValidationError(_("Pass mark must be between 0 and 100."))
super().save(*args, **kwargs)
def get_questions(self):
return self.question_set.all().select_subclasses()
@ -140,22 +120,18 @@ class Quiz(models.Model):
return self.get_questions().count()
def get_absolute_url(self):
# return reverse('quiz_start_page', kwargs={'pk': self.pk})
return reverse("quiz_index", kwargs={"slug": self.course.slug})
def quiz_pre_save_receiver(sender, instance, *args, **kwargs):
@receiver(pre_save, sender=Quiz)
def quiz_pre_save_receiver(sender, instance, **kwargs):
if not instance.slug:
instance.slug = unique_slug_generator(instance)
pre_save.connect(quiz_pre_save_receiver, sender=Quiz)
class ProgressManager(models.Manager):
def new_progress(self, user):
new_progress = self.create(user=user, score="")
new_progress.save()
return new_progress
@ -175,51 +151,25 @@ class Progress(models.Model):
verbose_name = _("User Progress")
verbose_name_plural = _("User progress records")
# @property
def list_all_cat_scores(self):
score_before = self.score
output = {}
if len(self.score) > len(score_before):
# If a new category has been added, save changes.
self.save()
return output
return {} # Implement as needed
def update_score(self, question, score_to_add=0, possible_to_add=0):
# category_test = Category.objects.filter(category=question.category).exists()
if any(
[
item is False
for item in [
score_to_add,
possible_to_add,
isinstance(score_to_add, int),
isinstance(possible_to_add, int),
]
]
):
return _("error"), _("category does not exist or invalid score")
if not isinstance(score_to_add, int) or not isinstance(possible_to_add, int):
return _("Error"), _("Invalid score values.")
to_find = re.escape(str(question.quiz)) + r",(?P<score>\d+),(?P<possible>\d+),"
match = re.search(to_find, self.score, re.IGNORECASE)
if match:
updated_score = int(match.group("score")) + abs(score_to_add)
updated_possible = int(match.group("possible")) + abs(possible_to_add)
new_score = ",".join(
[str(question.quiz), str(updated_score), str(updated_possible), ""]
)
# swap old score for the new one
self.score = self.score.replace(match.group(), new_score)
self.save()
else:
# if not present but existing, add with the points passed in
self.score += ",".join(
[str(question.quiz), str(score_to_add), str(possible_to_add), ""]
)
@ -236,22 +186,20 @@ class Progress(models.Model):
class SittingManager(models.Manager):
def new_sitting(self, user, quiz, course):
if quiz.random_order is True:
if quiz.random_order:
question_set = quiz.question_set.all().select_subclasses().order_by("?")
else:
question_set = quiz.question_set.all().select_subclasses()
question_set = [item.id for item in question_set]
if len(question_set) == 0:
question_ids = [item.id for item in question_set]
if not question_ids:
raise ImproperlyConfigured(
_("Question set of the quiz is empty. Please configure questions properly")
_(
"Question set of the quiz is empty. Please configure questions properly."
)
)
# if quiz.max_questions and quiz.max_questions < len(question_set):
# question_set = question_set[:quiz.max_questions]
questions = ",".join(map(str, question_set)) + ","
questions = ",".join(map(str, question_ids)) + ","
new_sitting = self.create(
user=user,
@ -268,7 +216,7 @@ class SittingManager(models.Manager):
def user_sitting(self, user, quiz, course):
if (
quiz.single_attempt is True
quiz.single_attempt
and self.filter(user=user, quiz=quiz, course=course, complete=True).exists()
):
return False
@ -277,9 +225,9 @@ class SittingManager(models.Manager):
except Sitting.DoesNotExist:
sitting = self.new_sitting(user, quiz, course)
except Sitting.MultipleObjectsReturned:
sitting = self.filter(user=user, quiz=quiz, course=course, complete=False)[
0
]
sitting = self.filter(
user=user, quiz=quiz, course=course, complete=False
).first()
return sitting
@ -289,32 +237,26 @@ class Sitting(models.Model):
)
quiz = models.ForeignKey(Quiz, verbose_name=_("Quiz"), on_delete=models.CASCADE)
course = models.ForeignKey(
Course, null=True, verbose_name=_("Course"), on_delete=models.CASCADE
Course, verbose_name=_("Course"), on_delete=models.CASCADE
)
question_order = models.CharField(
max_length=1024,
verbose_name=_("Question Order"),
validators=[validate_comma_separated_integer_list],
)
question_list = models.CharField(
max_length=1024,
verbose_name=_("Question List"),
validators=[validate_comma_separated_integer_list],
)
incorrect_questions = models.CharField(
max_length=1024,
blank=True,
verbose_name=_("Incorrect questions"),
validators=[validate_comma_separated_integer_list],
)
current_score = models.IntegerField(verbose_name=_("Current Score"))
complete = models.BooleanField(
default=False, blank=False, verbose_name=_("Complete")
)
complete = models.BooleanField(default=False, verbose_name=_("Complete"))
user_answers = models.TextField(
blank=True, default="{}", verbose_name=_("User Answers")
)
@ -329,17 +271,14 @@ class Sitting(models.Model):
def get_first_question(self):
if not self.question_list:
return False
first, _ = self.question_list.split(",", 1)
question_id = int(first)
return Question.objects.get_subclass(id=question_id)
first_question_id = int(self.question_list.split(",", 1)[0])
return Question.objects.get_subclass(id=first_question_id)
def remove_first_question(self):
if not self.question_list:
return
_, others = self.question_list.split(",", 1)
self.question_list = others
_, remaining_questions = self.question_list.split(",", 1)
self.question_list = remaining_questions
self.save()
def add_to_score(self, points):
@ -351,24 +290,15 @@ class Sitting(models.Model):
return self.current_score
def _question_ids(self):
return [int(n) for n in self.question_order.split(",") if n]
return [int(q) for q in self.question_order.split(",") if q]
@property
def get_percent_correct(self):
dividend = float(self.current_score)
divisor = len(self._question_ids())
if divisor < 1:
return 0 # prevent divide by zero error
if dividend > divisor:
return 100
correct = int(round((dividend / divisor) * 100))
if correct >= 1:
return correct
else:
total_questions = len(self._question_ids())
if total_questions == 0:
return 0
percent = (self.current_score / total_questions) * 100
return min(max(int(round(percent)), 0), 100)
def mark_quiz_complete(self):
self.complete = True
@ -376,9 +306,9 @@ class Sitting(models.Model):
self.save()
def add_incorrect_question(self, question):
if len(self.incorrect_questions) > 0:
self.incorrect_questions += ","
self.incorrect_questions += str(question.id) + ","
incorrect_ids = self.get_incorrect_questions
incorrect_ids.append(question.id)
self.incorrect_questions = ",".join(map(str, incorrect_ids)) + ","
if self.complete:
self.add_to_score(-1)
self.save()
@ -388,9 +318,10 @@ class Sitting(models.Model):
return [int(q) for q in self.incorrect_questions.split(",") if q]
def remove_incorrect_question(self, question):
current = self.get_incorrect_questions
current.remove(question.id)
self.incorrect_questions = ",".join(map(str, current))
incorrect_ids = self.get_incorrect_questions
if question.id in incorrect_ids:
incorrect_ids.remove(question.id)
self.incorrect_questions = ",".join(map(str, incorrect_ids)) + ","
self.add_to_score(1)
self.save()
@ -401,14 +332,14 @@ class Sitting(models.Model):
@property
def result_message(self):
if self.check_if_passed:
return _(f"You have passed this quiz, congratulation")
return _("You have passed this quiz, congratulations!")
else:
return _(f"You failed this quiz, give it one chance again.")
return _("You failed this quiz, try again.")
def add_user_answer(self, question, guess):
current = json.loads(self.user_answers)
current[question.id] = guess
self.user_answers = json.dumps(current)
user_answers = json.loads(self.user_answers)
user_answers[str(question.id)] = guess
self.user_answers = json.dumps(user_answers)
self.save()
def get_questions(self, with_answers=False):
@ -417,12 +348,10 @@ class Sitting(models.Model):
self.quiz.question_set.filter(id__in=question_ids).select_subclasses(),
key=lambda q: question_ids.index(q.id),
)
if with_answers:
user_answers = json.loads(self.user_answers)
for question in questions:
question.user_answer = user_answers[str(question.id)]
question.user_answer = user_answers.get(str(question.id))
return questions
@property
@ -444,13 +373,11 @@ class Question(models.Model):
figure = models.ImageField(
upload_to="uploads/%Y/%m/%d",
blank=True,
null=True,
verbose_name=_("Figure"),
help_text=_("Add an image for the question if it's necessary."),
help_text=_("Add an image for the question if necessary."),
)
content = models.CharField(
max_length=1000,
blank=False,
help_text=_("Enter the question text that you want displayed"),
verbose_name=_("Question"),
)
@ -474,79 +401,76 @@ class Question(models.Model):
class MCQuestion(Question):
choice_order = models.CharField(
max_length=30,
null=True,
blank=True,
choices=CHOICE_ORDER_OPTIONS,
blank=True,
help_text=_(
"The order in which multichoice choice options are displayed to the user"
"The order in which multiple-choice options are displayed to the user"
),
verbose_name=_("Choice Order"),
)
def check_if_correct(self, guess):
answer = Choice.objects.get(id=guess)
class Meta:
verbose_name = _("Multiple Choice Question")
verbose_name_plural = _("Multiple Choice Questions")
if answer.correct is True:
return True
else:
def check_if_correct(self, guess):
try:
answer = Choice.objects.get(id=int(guess))
return answer.correct
except (Choice.DoesNotExist, ValueError):
return False
def order_choices(self, queryset):
if self.choice_order == "content":
return queryset.order_by("choice")
if self.choice_order == "random":
elif self.choice_order == "random":
return queryset.order_by("?")
if self.choice_order == "none":
return queryset.order_by()
else:
return queryset
def get_choices(self):
return self.order_choices(Choice.objects.filter(question=self))
def get_choices_list(self):
return [
(choice.id, choice.choice)
for choice in self.order_choices(Choice.objects.filter(question=self))
]
return [(choice.id, choice.choice_text) for choice in self.get_choices()]
def answer_choice_to_string(self, guess):
return Choice.objects.get(id=guess).choice
class Meta:
verbose_name = _("Multiple Choice Question")
verbose_name_plural = _("Multiple Choice Questions")
try:
return Choice.objects.get(id=int(guess)).choice_text
except (Choice.DoesNotExist, ValueError):
return ""
class Choice(models.Model):
question = models.ForeignKey(
MCQuestion, verbose_name=_("Question"), on_delete=models.CASCADE
)
choice = models.CharField(
choice_text = models.CharField(
max_length=1000,
blank=False,
help_text=_("Enter the choice text that you want displayed"),
verbose_name=_("Content"),
)
correct = models.BooleanField(
blank=False,
default=False,
help_text=_("Is this a correct answer?"),
verbose_name=_("Correct"),
)
def __str__(self):
return self.choice
class Meta:
verbose_name = _("Choice")
verbose_name_plural = _("Choices")
def __str__(self):
return self.choice_text
class EssayQuestion(Question):
class Meta:
verbose_name = _("Essay Style Question")
verbose_name_plural = _("Essay Style Questions")
def check_if_correct(self, guess):
return False
return False # Needs manual grading
def get_answers(self):
return False
@ -556,10 +480,3 @@ class EssayQuestion(Question):
def answer_choice_to_string(self, guess):
return str(guess)
def __str__(self):
return self.content
class Meta:
verbose_name = _("Essay style question")
verbose_name_plural = _("Essay style questions")

View File

@ -1,23 +1,23 @@
from django.urls import path
from .views import *
from . import views
urlpatterns = [
path("<slug>/quizzes/", quiz_list, name="quiz_index"),
path("progress/", view=QuizUserProgressView.as_view(), name="quiz_progress"),
path("<slug>/quizzes/", views.quiz_list, name="quiz_index"),
path("progress/", view=views.QuizUserProgressView.as_view(), name="quiz_progress"),
# path('marking/<int:pk>/', view=QuizMarkingList.as_view(), name='quiz_marking'),
path("marking_list/", view=QuizMarkingList.as_view(), name="quiz_marking"),
path("marking_list/", view=views.QuizMarkingList.as_view(), name="quiz_marking"),
path(
"marking/<int:pk>/",
view=QuizMarkingDetail.as_view(),
view=views.QuizMarkingDetail.as_view(),
name="quiz_marking_detail",
),
path("<int:pk>/<slug>/take/", view=QuizTake.as_view(), name="quiz_take"),
path("<slug>/quiz_add/", QuizCreateView.as_view(), name="quiz_create"),
path("<slug>/<int:pk>/add/", QuizUpdateView.as_view(), name="quiz_update"),
path("<slug>/<int:pk>/delete/", quiz_delete, name="quiz_delete"),
path("<int:pk>/<slug>/take/", view=views.QuizTake.as_view(), name="quiz_take"),
path("<slug>/quiz_add/", views.QuizCreateView.as_view(), name="quiz_create"),
path("<slug>/<int:pk>/add/", views.QuizUpdateView.as_view(), name="quiz_update"),
path("<slug>/<int:pk>/delete/", views.quiz_delete, name="quiz_delete"),
path(
"mc-question/add/<slug>/<int:quiz_id>/",
MCQuestionCreate.as_view(),
views.MCQuestionCreate.as_view(),
name="mc_create",
),
# path('mc-question/add/<int:pk>/<quiz_pk>/', MCQuestionCreate.as_view(), name='mc_create'),

View File

@ -1,126 +1,140 @@
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404, render, redirect
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,
FormView,
CreateView,
UpdateView,
)
from django.contrib import messages
from django.db import transaction
from accounts.decorators import lecturer_required
from .models import Course, Progress, Sitting, EssayQuestion, Quiz, MCQuestion, Question
from .forms import (
QuizAddForm,
EssayForm,
MCQuestionForm,
MCQuestionFormSet,
QuestionForm,
EssayForm,
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_context_data(self, *args, **kwargs):
context = super(QuizCreateView, self).get_context_data(**kwargs)
context["course"] = Course.objects.get(slug=self.kwargs["slug"])
if self.request.POST:
context["form"] = QuizAddForm(self.request.POST)
# context['quiz'] = self.request.POST.get('quiz')
else:
context["form"] = QuizAddForm(
initial={"course": Course.objects.get(slug=self.kwargs["slug"])}
)
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, **kwargs):
context = self.get_context_data()
form = context["form"]
def form_valid(self, form):
form.instance.course = get_object_or_404(Course, slug=self.kwargs["slug"])
with transaction.atomic():
self.object = form.save()
if form.is_valid():
form.instance = self.object
form.save()
return redirect(
"mc_create", slug=self.kwargs["slug"], quiz_id=form.instance.id
"mc_create", slug=self.kwargs["slug"], quiz_id=self.object.id
)
return super(QuizCreateView, self).form_invalid(form)
@method_decorator([login_required, lecturer_required], name="dispatch")
class QuizUpdateView(UpdateView):
model = Quiz
form_class = QuizAddForm
template_name = "quiz/quiz_form.html"
def get_context_data(self, *args, **kwargs):
context = super(QuizUpdateView, self).get_context_data(**kwargs)
context["course"] = Course.objects.get(slug=self.kwargs["slug"])
quiz = Quiz.objects.get(pk=self.kwargs["pk"])
if self.request.POST:
context["form"] = QuizAddForm(self.request.POST, instance=quiz)
else:
context["form"] = QuizAddForm(instance=quiz)
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, **kwargs):
context = self.get_context_data()
course = context["course"]
form = context["form"]
def form_valid(self, form):
with transaction.atomic():
self.object = form.save()
if form.is_valid():
form.instance = self.object
form.save()
return redirect("quiz_index", course.slug)
return super(QuizUpdateView, self).form_invalid(form)
return redirect("quiz_index", self.kwargs["slug"])
@login_required
@lecturer_required
def quiz_delete(request, slug, pk):
quiz = Quiz.objects.get(pk=pk)
course = Course.objects.get(slug=slug)
quiz = get_object_or_404(Quiz, pk=pk)
quiz.delete()
messages.success(request, f"successfuly deleted.")
return redirect("quiz_index", quiz.course.slug)
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(MCQuestionCreate, self).get_context_data(**kwargs)
context["course"] = Course.objects.get(slug=self.kwargs["slug"])
context["quiz_obj"] = Quiz.objects.get(id=self.kwargs["quiz_id"])
context["quizQuestions"] = Question.objects.filter(
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.POST:
context["form"] = MCQuestionForm(self.request.POST)
if self.request.method == "POST":
context["formset"] = MCQuestionFormSet(self.request.POST)
else:
context["form"] = MCQuestionForm(initial={"quiz": self.kwargs["quiz_id"]})
context["formset"] = MCQuestionFormSet()
return context
def form_valid(self, form):
context = self.get_context_data()
formset = context["formset"]
course = context["course"]
if formset.is_valid():
with transaction.atomic():
form.instance.question = self.request.POST.get("content")
form.instance.quiz = get_object_or_404(Quiz, id=self.kwargs["quiz_id"])
self.object = form.save()
formset.instance = self.object
formset.save()
@ -130,193 +144,131 @@ class MCQuestionCreate(CreateView):
slug=self.kwargs["slug"],
quiz_id=self.kwargs["quiz_id"],
)
return redirect("quiz_index", course.slug)
return redirect("quiz_index", slug=self.kwargs["slug"])
else:
return self.form_invalid(form)
return super(MCQuestionCreate, self).form_invalid(form)
@login_required
def quiz_list(request, slug):
quizzes = Quiz.objects.filter(course__slug=slug).order_by("-timestamp")
course = Course.objects.get(slug=slug)
return render(
request, "quiz/quiz_list.html", {"quizzes": quizzes, "course": course}
)
# return render(request, 'quiz/quiz_list.html', {'quizzes': quizzes})
@method_decorator([login_required, lecturer_required], name="dispatch")
class QuizMarkerMixin(object):
@method_decorator(login_required)
# @method_decorator(permission_required('quiz.view_sittings'))
def dispatch(self, *args, **kwargs):
return super(QuizMarkerMixin, self).dispatch(*args, **kwargs)
# @method_decorator([login_required, lecturer_required], name='get_queryset')
class SittingFilterTitleMixin(object):
def get_queryset(self):
queryset = super(SittingFilterTitleMixin, self).get_queryset()
quiz_filter = self.request.GET.get("quiz_filter")
if quiz_filter:
queryset = queryset.filter(quiz__title__icontains=quiz_filter)
return queryset
# ########################################################
# Quiz Progress and Marking Views
# ########################################################
@method_decorator([login_required], name="dispatch")
class QuizUserProgressView(TemplateView):
template_name = "progress.html"
def dispatch(self, request, *args, **kwargs):
return super(QuizUserProgressView, self).dispatch(request, *args, **kwargs)
template_name = "quiz/progress.html"
def get_context_data(self, **kwargs):
context = super(QuizUserProgressView, self).get_context_data(**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"] = progress.show_exams().count()
context["exams_counter"] = context["exams"].count()
return context
@method_decorator([login_required, lecturer_required], name="dispatch")
class QuizMarkingList(QuizMarkerMixin, SittingFilterTitleMixin, ListView):
class QuizMarkingList(ListView):
model = Sitting
template_name = "quiz/quiz_marking_list.html"
# def get_context_data(self, **kwargs):
# context = super(QuizMarkingList, self).get_context_data(**kwargs)
# context['queryset_counter'] = super(QuizMarkingList, self).get_queryset().filter(complete=True).filter(course__allocated_course__lecturer__pk=self.request.user.id).count()
# context['marking_list'] = super(QuizMarkingList, self).get_queryset().filter(complete=True).filter(course__allocated_course__lecturer__pk=self.request.user.id)
# return context
def get_queryset(self):
if self.request.user.is_superuser:
queryset = super(QuizMarkingList, self).get_queryset().filter(complete=True)
else:
queryset = (
super(QuizMarkingList, self)
.get_queryset()
.filter(
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
)
.filter(complete=True)
)
# search by user
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(QuizMarkerMixin, DetailView):
class QuizMarkingDetail(DetailView):
model = Sitting
template_name = "quiz/quiz_marking_detail.html"
def post(self, request, *args, **kwargs):
sitting = self.get_object()
q_to_toggle = request.POST.get("qid", None)
if q_to_toggle:
q = Question.objects.get_subclass(id=int(q_to_toggle))
if int(q_to_toggle) in sitting.get_incorrect_questions:
sitting.remove_incorrect_question(q)
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(q)
return self.get(request)
sitting.add_incorrect_question(question)
return self.get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(QuizMarkingDetail, self).get_context_data(**kwargs)
context["questions"] = context["sitting"].get_questions(with_answers=True)
context = super().get_context_data(**kwargs)
context["questions"] = self.object.get_questions(with_answers=True)
return context
# @method_decorator([login_required, student_required], name='dispatch')
# ########################################################
# Quiz Taking View
# ########################################################
@method_decorator([login_required], name="dispatch")
class QuizTake(FormView):
form_class = QuestionForm
template_name = "question.html"
result_template_name = "result.html"
# single_complete_template_name = 'single_complete.html'
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"])
quizQuestions = Question.objects.filter(quiz=self.quiz).count()
if quizQuestions <= 0:
messages.warning(request, f"Question set of the quiz is empty. try later!")
return redirect("quiz_index", self.course.slug)
# if self.quiz.draft and not request.user.has_perm("quiz.change_quiz"):
# raise PermissionDenied
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 self.sitting is False:
# return render(request, self.single_complete_template_name)
if not self.sitting:
messages.info(
request,
f"You have already sat this exam and only one sitting is permitted",
"You have already completed this quiz. Only one attempt is permitted.",
)
return redirect("quiz_index", self.course.slug)
return super(QuizTake, self).dispatch(request, *args, **kwargs)
def get_form(self, *args, **kwargs):
self.question = self.sitting.get_first_question()
self.progress = self.sitting.progress()
if self.question.__class__ is EssayQuestion:
form_class = EssayForm
else:
form_class = self.form_class
return form_class(**self.get_form_kwargs())
return redirect("quiz_index", slug=self.course.slug)
return super().dispatch(request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super(QuizTake, self).get_form_kwargs()
kwargs = super().get_form_kwargs()
self.question = self.sitting.get_first_question()
self.progress = self.sitting.progress()
kwargs["question"] = self.question
return kwargs
return dict(kwargs, question=self.question)
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 self.sitting.get_first_question() is False:
if not self.sitting.get_first_question():
return self.final_result_user()
self.request.POST = {}
return super(QuizTake, self).get(self, self.request)
def get_context_data(self, **kwargs):
context = super(QuizTake, self).get_context_data(**kwargs)
context["question"] = self.question
context["quiz"] = self.quiz
context["course"] = get_object_or_404(Course, pk=self.kwargs["pk"])
if hasattr(self, "previous"):
context["previous"] = self.previous
if hasattr(self, "progress"):
context["progress"] = self.progress
return context
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 is True:
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 self.quiz.answers_at_end is not True:
if not self.quiz.answers_at_end:
self.previous = {
"previous_answer": guess,
"previous_outcome": is_correct,
@ -330,26 +282,35 @@ class QuizTake(FormView):
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": get_object_or_404(Course, pk=self.kwargs["pk"]),
"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": self.previous,
"course": get_object_or_404(Course, pk=self.kwargs["pk"]),
"previous": getattr(self, "previous", {}),
}
self.sitting.mark_quiz_complete()
if self.quiz.answers_at_end:
results["questions"] = self.sitting.get_questions(with_answers=True)
results["incorrect_questions"] = self.sitting.get_incorrect_questions
if (
self.quiz.exam_paper is False
not self.quiz.exam_paper
or self.request.user.is_superuser
or self.request.user.is_lecturer
):