Follow pylint rule for quiz app
This commit is contained in:
parent
36a6259189
commit
cf81b3e8a3
@ -450,7 +450,8 @@ disable=raw-checker-failed,
|
|||||||
too-few-public-methods,
|
too-few-public-methods,
|
||||||
arguments-differ,
|
arguments-differ,
|
||||||
invalid-overridden-method,
|
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
|
# 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
|
# either give multiple identifier separated by comma (,) or put this option
|
||||||
|
|||||||
277
quiz/models.py
277
quiz/models.py
@ -1,23 +1,22 @@
|
|||||||
import re
|
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
from django.db import models
|
from django.conf import settings
|
||||||
from django.urls import reverse
|
from django.core.exceptions import ImproperlyConfigured, ValidationError
|
||||||
from django.core.exceptions import ValidationError, ImproperlyConfigured
|
|
||||||
from django.core.validators import (
|
from django.core.validators import (
|
||||||
MaxValueValidator,
|
MaxValueValidator,
|
||||||
validate_comma_separated_integer_list,
|
validate_comma_separated_integer_list,
|
||||||
)
|
)
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.db import models
|
||||||
from django.utils.timezone import now
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db.models.signals import pre_save
|
|
||||||
|
|
||||||
from django.db.models import Q
|
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 model_utils.managers import InheritanceManager
|
||||||
|
|
||||||
from course.models import Course
|
from course.models import Course
|
||||||
from .utils import *
|
from .utils import unique_slug_generator
|
||||||
|
|
||||||
CHOICE_ORDER_OPTIONS = (
|
CHOICE_ORDER_OPTIONS = (
|
||||||
("content", _("Content")),
|
("content", _("Content")),
|
||||||
@ -34,97 +33,69 @@ CATEGORY_OPTIONS = (
|
|||||||
|
|
||||||
class QuizManager(models.Manager):
|
class QuizManager(models.Manager):
|
||||||
def search(self, query=None):
|
def search(self, query=None):
|
||||||
qs = self.get_queryset()
|
queryset = self.get_queryset()
|
||||||
if query is not None:
|
if query:
|
||||||
or_lookup = (
|
or_lookup = (
|
||||||
Q(title__icontains=query)
|
Q(title__icontains=query)
|
||||||
| Q(description__icontains=query)
|
| Q(description__icontains=query)
|
||||||
| Q(category__icontains=query)
|
| Q(category__icontains=query)
|
||||||
| Q(slug__icontains=query)
|
| Q(slug__icontains=query)
|
||||||
)
|
)
|
||||||
qs = qs.filter(
|
queryset = queryset.filter(or_lookup).distinct()
|
||||||
or_lookup
|
return queryset
|
||||||
).distinct() # distinct() is often necessary with Q lookups
|
|
||||||
return qs
|
|
||||||
|
|
||||||
|
|
||||||
class Quiz(models.Model):
|
class Quiz(models.Model):
|
||||||
course = models.ForeignKey(Course, on_delete=models.CASCADE, null=True)
|
course = models.ForeignKey(Course, on_delete=models.CASCADE)
|
||||||
title = models.CharField(verbose_name=_("Title"), max_length=60, blank=False)
|
title = models.CharField(verbose_name=_("Title"), max_length=60)
|
||||||
slug = models.SlugField(blank=True, unique=True)
|
slug = models.SlugField(unique=True, blank=True)
|
||||||
description = models.TextField(
|
description = models.TextField(
|
||||||
verbose_name=_("Description"),
|
verbose_name=_("Description"),
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text=_("A detailed description of the quiz"),
|
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(
|
random_order = models.BooleanField(
|
||||||
blank=False,
|
|
||||||
default=False,
|
default=False,
|
||||||
verbose_name=_("Random Order"),
|
verbose_name=_("Random Order"),
|
||||||
help_text=_("Display the questions in a random order or as they are set?"),
|
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(
|
answers_at_end = models.BooleanField(
|
||||||
blank=False,
|
|
||||||
default=False,
|
default=False,
|
||||||
verbose_name=_("Answers at end"),
|
verbose_name=_("Answers at end"),
|
||||||
help_text=_(
|
help_text=_(
|
||||||
"Correct answer is NOT shown after question. Answers displayed at the end."
|
"Correct answer is NOT shown after question. Answers displayed at the end."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
exam_paper = models.BooleanField(
|
exam_paper = models.BooleanField(
|
||||||
blank=False,
|
|
||||||
default=False,
|
default=False,
|
||||||
verbose_name=_("Exam Paper"),
|
verbose_name=_("Exam Paper"),
|
||||||
help_text=_(
|
help_text=_(
|
||||||
"If yes, the result of each attempt by a user will be stored. Necessary for marking."
|
"If yes, the result of each attempt by a user will be stored. Necessary for marking."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
single_attempt = models.BooleanField(
|
single_attempt = models.BooleanField(
|
||||||
blank=False,
|
|
||||||
default=False,
|
default=False,
|
||||||
verbose_name=_("Single Attempt"),
|
verbose_name=_("Single Attempt"),
|
||||||
help_text=_("If yes, only one attempt by a user will be permitted."),
|
help_text=_("If yes, only one attempt by a user will be permitted."),
|
||||||
)
|
)
|
||||||
|
|
||||||
pass_mark = models.SmallIntegerField(
|
pass_mark = models.SmallIntegerField(
|
||||||
blank=True,
|
|
||||||
default=50,
|
default=50,
|
||||||
verbose_name=_("Pass Mark"),
|
verbose_name=_("Pass Mark"),
|
||||||
validators=[MaxValueValidator(100)],
|
validators=[MaxValueValidator(100)],
|
||||||
help_text=_("Percentage required to pass exam."),
|
help_text=_("Percentage required to pass exam."),
|
||||||
)
|
)
|
||||||
|
|
||||||
draft = models.BooleanField(
|
draft = models.BooleanField(
|
||||||
blank=True,
|
|
||||||
default=False,
|
default=False,
|
||||||
verbose_name=_("Draft"),
|
verbose_name=_("Draft"),
|
||||||
help_text=_(
|
help_text=_(
|
||||||
"If yes, the quiz is not displayed in the quiz list and can only be taken by users who can edit quizzes."
|
"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)
|
timestamp = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
objects = QuizManager()
|
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:
|
class Meta:
|
||||||
verbose_name = _("Quiz")
|
verbose_name = _("Quiz")
|
||||||
verbose_name_plural = _("Quizzes")
|
verbose_name_plural = _("Quizzes")
|
||||||
@ -132,6 +103,15 @@ class Quiz(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
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):
|
def get_questions(self):
|
||||||
return self.question_set.all().select_subclasses()
|
return self.question_set.all().select_subclasses()
|
||||||
|
|
||||||
@ -140,22 +120,18 @@ class Quiz(models.Model):
|
|||||||
return self.get_questions().count()
|
return self.get_questions().count()
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
# return reverse('quiz_start_page', kwargs={'pk': self.pk})
|
|
||||||
return reverse("quiz_index", kwargs={"slug": self.course.slug})
|
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:
|
if not instance.slug:
|
||||||
instance.slug = unique_slug_generator(instance)
|
instance.slug = unique_slug_generator(instance)
|
||||||
|
|
||||||
|
|
||||||
pre_save.connect(quiz_pre_save_receiver, sender=Quiz)
|
|
||||||
|
|
||||||
|
|
||||||
class ProgressManager(models.Manager):
|
class ProgressManager(models.Manager):
|
||||||
def new_progress(self, user):
|
def new_progress(self, user):
|
||||||
new_progress = self.create(user=user, score="")
|
new_progress = self.create(user=user, score="")
|
||||||
new_progress.save()
|
|
||||||
return new_progress
|
return new_progress
|
||||||
|
|
||||||
|
|
||||||
@ -175,51 +151,25 @@ class Progress(models.Model):
|
|||||||
verbose_name = _("User Progress")
|
verbose_name = _("User Progress")
|
||||||
verbose_name_plural = _("User progress records")
|
verbose_name_plural = _("User progress records")
|
||||||
|
|
||||||
# @property
|
|
||||||
def list_all_cat_scores(self):
|
def list_all_cat_scores(self):
|
||||||
score_before = self.score
|
return {} # Implement as needed
|
||||||
output = {}
|
|
||||||
|
|
||||||
if len(self.score) > len(score_before):
|
|
||||||
# If a new category has been added, save changes.
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
return output
|
|
||||||
|
|
||||||
def update_score(self, question, score_to_add=0, possible_to_add=0):
|
def update_score(self, question, score_to_add=0, possible_to_add=0):
|
||||||
# category_test = Category.objects.filter(category=question.category).exists()
|
if not isinstance(score_to_add, int) or not isinstance(possible_to_add, int):
|
||||||
|
return _("Error"), _("Invalid score values.")
|
||||||
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")
|
|
||||||
|
|
||||||
to_find = re.escape(str(question.quiz)) + r",(?P<score>\d+),(?P<possible>\d+),"
|
to_find = re.escape(str(question.quiz)) + r",(?P<score>\d+),(?P<possible>\d+),"
|
||||||
|
|
||||||
match = re.search(to_find, self.score, re.IGNORECASE)
|
match = re.search(to_find, self.score, re.IGNORECASE)
|
||||||
|
|
||||||
if match:
|
if match:
|
||||||
updated_score = int(match.group("score")) + abs(score_to_add)
|
updated_score = int(match.group("score")) + abs(score_to_add)
|
||||||
updated_possible = int(match.group("possible")) + abs(possible_to_add)
|
updated_possible = int(match.group("possible")) + abs(possible_to_add)
|
||||||
|
|
||||||
new_score = ",".join(
|
new_score = ",".join(
|
||||||
[str(question.quiz), str(updated_score), str(updated_possible), ""]
|
[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.score = self.score.replace(match.group(), new_score)
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# if not present but existing, add with the points passed in
|
|
||||||
self.score += ",".join(
|
self.score += ",".join(
|
||||||
[str(question.quiz), str(score_to_add), str(possible_to_add), ""]
|
[str(question.quiz), str(score_to_add), str(possible_to_add), ""]
|
||||||
)
|
)
|
||||||
@ -236,22 +186,20 @@ class Progress(models.Model):
|
|||||||
|
|
||||||
class SittingManager(models.Manager):
|
class SittingManager(models.Manager):
|
||||||
def new_sitting(self, user, quiz, course):
|
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("?")
|
question_set = quiz.question_set.all().select_subclasses().order_by("?")
|
||||||
else:
|
else:
|
||||||
question_set = quiz.question_set.all().select_subclasses()
|
question_set = quiz.question_set.all().select_subclasses()
|
||||||
|
|
||||||
question_set = [item.id for item in question_set]
|
question_ids = [item.id for item in question_set]
|
||||||
|
if not question_ids:
|
||||||
if len(question_set) == 0:
|
|
||||||
raise ImproperlyConfigured(
|
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):
|
questions = ",".join(map(str, question_ids)) + ","
|
||||||
# question_set = question_set[:quiz.max_questions]
|
|
||||||
|
|
||||||
questions = ",".join(map(str, question_set)) + ","
|
|
||||||
|
|
||||||
new_sitting = self.create(
|
new_sitting = self.create(
|
||||||
user=user,
|
user=user,
|
||||||
@ -268,7 +216,7 @@ class SittingManager(models.Manager):
|
|||||||
|
|
||||||
def user_sitting(self, user, quiz, course):
|
def user_sitting(self, user, quiz, course):
|
||||||
if (
|
if (
|
||||||
quiz.single_attempt is True
|
quiz.single_attempt
|
||||||
and self.filter(user=user, quiz=quiz, course=course, complete=True).exists()
|
and self.filter(user=user, quiz=quiz, course=course, complete=True).exists()
|
||||||
):
|
):
|
||||||
return False
|
return False
|
||||||
@ -277,9 +225,9 @@ class SittingManager(models.Manager):
|
|||||||
except Sitting.DoesNotExist:
|
except Sitting.DoesNotExist:
|
||||||
sitting = self.new_sitting(user, quiz, course)
|
sitting = self.new_sitting(user, quiz, course)
|
||||||
except Sitting.MultipleObjectsReturned:
|
except Sitting.MultipleObjectsReturned:
|
||||||
sitting = self.filter(user=user, quiz=quiz, course=course, complete=False)[
|
sitting = self.filter(
|
||||||
0
|
user=user, quiz=quiz, course=course, complete=False
|
||||||
]
|
).first()
|
||||||
return sitting
|
return sitting
|
||||||
|
|
||||||
|
|
||||||
@ -289,32 +237,26 @@ class Sitting(models.Model):
|
|||||||
)
|
)
|
||||||
quiz = models.ForeignKey(Quiz, verbose_name=_("Quiz"), on_delete=models.CASCADE)
|
quiz = models.ForeignKey(Quiz, verbose_name=_("Quiz"), on_delete=models.CASCADE)
|
||||||
course = models.ForeignKey(
|
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(
|
question_order = models.CharField(
|
||||||
max_length=1024,
|
max_length=1024,
|
||||||
verbose_name=_("Question Order"),
|
verbose_name=_("Question Order"),
|
||||||
validators=[validate_comma_separated_integer_list],
|
validators=[validate_comma_separated_integer_list],
|
||||||
)
|
)
|
||||||
|
|
||||||
question_list = models.CharField(
|
question_list = models.CharField(
|
||||||
max_length=1024,
|
max_length=1024,
|
||||||
verbose_name=_("Question List"),
|
verbose_name=_("Question List"),
|
||||||
validators=[validate_comma_separated_integer_list],
|
validators=[validate_comma_separated_integer_list],
|
||||||
)
|
)
|
||||||
|
|
||||||
incorrect_questions = models.CharField(
|
incorrect_questions = models.CharField(
|
||||||
max_length=1024,
|
max_length=1024,
|
||||||
blank=True,
|
blank=True,
|
||||||
verbose_name=_("Incorrect questions"),
|
verbose_name=_("Incorrect questions"),
|
||||||
validators=[validate_comma_separated_integer_list],
|
validators=[validate_comma_separated_integer_list],
|
||||||
)
|
)
|
||||||
|
|
||||||
current_score = models.IntegerField(verbose_name=_("Current Score"))
|
current_score = models.IntegerField(verbose_name=_("Current Score"))
|
||||||
complete = models.BooleanField(
|
complete = models.BooleanField(default=False, verbose_name=_("Complete"))
|
||||||
default=False, blank=False, verbose_name=_("Complete")
|
|
||||||
)
|
|
||||||
user_answers = models.TextField(
|
user_answers = models.TextField(
|
||||||
blank=True, default="{}", verbose_name=_("User Answers")
|
blank=True, default="{}", verbose_name=_("User Answers")
|
||||||
)
|
)
|
||||||
@ -329,17 +271,14 @@ class Sitting(models.Model):
|
|||||||
def get_first_question(self):
|
def get_first_question(self):
|
||||||
if not self.question_list:
|
if not self.question_list:
|
||||||
return False
|
return False
|
||||||
|
first_question_id = int(self.question_list.split(",", 1)[0])
|
||||||
first, _ = self.question_list.split(",", 1)
|
return Question.objects.get_subclass(id=first_question_id)
|
||||||
question_id = int(first)
|
|
||||||
return Question.objects.get_subclass(id=question_id)
|
|
||||||
|
|
||||||
def remove_first_question(self):
|
def remove_first_question(self):
|
||||||
if not self.question_list:
|
if not self.question_list:
|
||||||
return
|
return
|
||||||
|
_, remaining_questions = self.question_list.split(",", 1)
|
||||||
_, others = self.question_list.split(",", 1)
|
self.question_list = remaining_questions
|
||||||
self.question_list = others
|
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def add_to_score(self, points):
|
def add_to_score(self, points):
|
||||||
@ -351,24 +290,15 @@ class Sitting(models.Model):
|
|||||||
return self.current_score
|
return self.current_score
|
||||||
|
|
||||||
def _question_ids(self):
|
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
|
@property
|
||||||
def get_percent_correct(self):
|
def get_percent_correct(self):
|
||||||
dividend = float(self.current_score)
|
total_questions = len(self._question_ids())
|
||||||
divisor = len(self._question_ids())
|
if total_questions == 0:
|
||||||
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:
|
|
||||||
return 0
|
return 0
|
||||||
|
percent = (self.current_score / total_questions) * 100
|
||||||
|
return min(max(int(round(percent)), 0), 100)
|
||||||
|
|
||||||
def mark_quiz_complete(self):
|
def mark_quiz_complete(self):
|
||||||
self.complete = True
|
self.complete = True
|
||||||
@ -376,9 +306,9 @@ class Sitting(models.Model):
|
|||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def add_incorrect_question(self, question):
|
def add_incorrect_question(self, question):
|
||||||
if len(self.incorrect_questions) > 0:
|
incorrect_ids = self.get_incorrect_questions
|
||||||
self.incorrect_questions += ","
|
incorrect_ids.append(question.id)
|
||||||
self.incorrect_questions += str(question.id) + ","
|
self.incorrect_questions = ",".join(map(str, incorrect_ids)) + ","
|
||||||
if self.complete:
|
if self.complete:
|
||||||
self.add_to_score(-1)
|
self.add_to_score(-1)
|
||||||
self.save()
|
self.save()
|
||||||
@ -388,11 +318,12 @@ class Sitting(models.Model):
|
|||||||
return [int(q) for q in self.incorrect_questions.split(",") if q]
|
return [int(q) for q in self.incorrect_questions.split(",") if q]
|
||||||
|
|
||||||
def remove_incorrect_question(self, question):
|
def remove_incorrect_question(self, question):
|
||||||
current = self.get_incorrect_questions
|
incorrect_ids = self.get_incorrect_questions
|
||||||
current.remove(question.id)
|
if question.id in incorrect_ids:
|
||||||
self.incorrect_questions = ",".join(map(str, current))
|
incorrect_ids.remove(question.id)
|
||||||
self.add_to_score(1)
|
self.incorrect_questions = ",".join(map(str, incorrect_ids)) + ","
|
||||||
self.save()
|
self.add_to_score(1)
|
||||||
|
self.save()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def check_if_passed(self):
|
def check_if_passed(self):
|
||||||
@ -401,14 +332,14 @@ class Sitting(models.Model):
|
|||||||
@property
|
@property
|
||||||
def result_message(self):
|
def result_message(self):
|
||||||
if self.check_if_passed:
|
if self.check_if_passed:
|
||||||
return _(f"You have passed this quiz, congratulation")
|
return _("You have passed this quiz, congratulations!")
|
||||||
else:
|
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):
|
def add_user_answer(self, question, guess):
|
||||||
current = json.loads(self.user_answers)
|
user_answers = json.loads(self.user_answers)
|
||||||
current[question.id] = guess
|
user_answers[str(question.id)] = guess
|
||||||
self.user_answers = json.dumps(current)
|
self.user_answers = json.dumps(user_answers)
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def get_questions(self, with_answers=False):
|
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(),
|
self.quiz.question_set.filter(id__in=question_ids).select_subclasses(),
|
||||||
key=lambda q: question_ids.index(q.id),
|
key=lambda q: question_ids.index(q.id),
|
||||||
)
|
)
|
||||||
|
|
||||||
if with_answers:
|
if with_answers:
|
||||||
user_answers = json.loads(self.user_answers)
|
user_answers = json.loads(self.user_answers)
|
||||||
for question in questions:
|
for question in questions:
|
||||||
question.user_answer = user_answers[str(question.id)]
|
question.user_answer = user_answers.get(str(question.id))
|
||||||
|
|
||||||
return questions
|
return questions
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -444,13 +373,11 @@ class Question(models.Model):
|
|||||||
figure = models.ImageField(
|
figure = models.ImageField(
|
||||||
upload_to="uploads/%Y/%m/%d",
|
upload_to="uploads/%Y/%m/%d",
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
|
||||||
verbose_name=_("Figure"),
|
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(
|
content = models.CharField(
|
||||||
max_length=1000,
|
max_length=1000,
|
||||||
blank=False,
|
|
||||||
help_text=_("Enter the question text that you want displayed"),
|
help_text=_("Enter the question text that you want displayed"),
|
||||||
verbose_name=_("Question"),
|
verbose_name=_("Question"),
|
||||||
)
|
)
|
||||||
@ -474,79 +401,76 @@ class Question(models.Model):
|
|||||||
class MCQuestion(Question):
|
class MCQuestion(Question):
|
||||||
choice_order = models.CharField(
|
choice_order = models.CharField(
|
||||||
max_length=30,
|
max_length=30,
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
choices=CHOICE_ORDER_OPTIONS,
|
choices=CHOICE_ORDER_OPTIONS,
|
||||||
|
blank=True,
|
||||||
help_text=_(
|
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"),
|
verbose_name=_("Choice Order"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def check_if_correct(self, guess):
|
class Meta:
|
||||||
answer = Choice.objects.get(id=guess)
|
verbose_name = _("Multiple Choice Question")
|
||||||
|
verbose_name_plural = _("Multiple Choice Questions")
|
||||||
|
|
||||||
if answer.correct is True:
|
def check_if_correct(self, guess):
|
||||||
return True
|
try:
|
||||||
else:
|
answer = Choice.objects.get(id=int(guess))
|
||||||
|
return answer.correct
|
||||||
|
except (Choice.DoesNotExist, ValueError):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def order_choices(self, queryset):
|
def order_choices(self, queryset):
|
||||||
if self.choice_order == "content":
|
if self.choice_order == "content":
|
||||||
return queryset.order_by("choice")
|
return queryset.order_by("choice")
|
||||||
if self.choice_order == "random":
|
elif self.choice_order == "random":
|
||||||
return queryset.order_by("?")
|
return queryset.order_by("?")
|
||||||
if self.choice_order == "none":
|
else:
|
||||||
return queryset.order_by()
|
return queryset
|
||||||
return queryset
|
|
||||||
|
|
||||||
def get_choices(self):
|
def get_choices(self):
|
||||||
return self.order_choices(Choice.objects.filter(question=self))
|
return self.order_choices(Choice.objects.filter(question=self))
|
||||||
|
|
||||||
def get_choices_list(self):
|
def get_choices_list(self):
|
||||||
return [
|
return [(choice.id, choice.choice_text) for choice in self.get_choices()]
|
||||||
(choice.id, choice.choice)
|
|
||||||
for choice in self.order_choices(Choice.objects.filter(question=self))
|
|
||||||
]
|
|
||||||
|
|
||||||
def answer_choice_to_string(self, guess):
|
def answer_choice_to_string(self, guess):
|
||||||
return Choice.objects.get(id=guess).choice
|
try:
|
||||||
|
return Choice.objects.get(id=int(guess)).choice_text
|
||||||
class Meta:
|
except (Choice.DoesNotExist, ValueError):
|
||||||
verbose_name = _("Multiple Choice Question")
|
return ""
|
||||||
verbose_name_plural = _("Multiple Choice Questions")
|
|
||||||
|
|
||||||
|
|
||||||
class Choice(models.Model):
|
class Choice(models.Model):
|
||||||
question = models.ForeignKey(
|
question = models.ForeignKey(
|
||||||
MCQuestion, verbose_name=_("Question"), on_delete=models.CASCADE
|
MCQuestion, verbose_name=_("Question"), on_delete=models.CASCADE
|
||||||
)
|
)
|
||||||
|
choice_text = models.CharField(
|
||||||
choice = models.CharField(
|
|
||||||
max_length=1000,
|
max_length=1000,
|
||||||
blank=False,
|
|
||||||
help_text=_("Enter the choice text that you want displayed"),
|
help_text=_("Enter the choice text that you want displayed"),
|
||||||
verbose_name=_("Content"),
|
verbose_name=_("Content"),
|
||||||
)
|
)
|
||||||
|
|
||||||
correct = models.BooleanField(
|
correct = models.BooleanField(
|
||||||
blank=False,
|
|
||||||
default=False,
|
default=False,
|
||||||
help_text=_("Is this a correct answer?"),
|
help_text=_("Is this a correct answer?"),
|
||||||
verbose_name=_("Correct"),
|
verbose_name=_("Correct"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.choice
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Choice")
|
verbose_name = _("Choice")
|
||||||
verbose_name_plural = _("Choices")
|
verbose_name_plural = _("Choices")
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.choice_text
|
||||||
|
|
||||||
|
|
||||||
class EssayQuestion(Question):
|
class EssayQuestion(Question):
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Essay Style Question")
|
||||||
|
verbose_name_plural = _("Essay Style Questions")
|
||||||
|
|
||||||
def check_if_correct(self, guess):
|
def check_if_correct(self, guess):
|
||||||
return False
|
return False # Needs manual grading
|
||||||
|
|
||||||
def get_answers(self):
|
def get_answers(self):
|
||||||
return False
|
return False
|
||||||
@ -556,10 +480,3 @@ class EssayQuestion(Question):
|
|||||||
|
|
||||||
def answer_choice_to_string(self, guess):
|
def answer_choice_to_string(self, guess):
|
||||||
return str(guess)
|
return str(guess)
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.content
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("Essay style question")
|
|
||||||
verbose_name_plural = _("Essay style questions")
|
|
||||||
|
|||||||
20
quiz/urls.py
20
quiz/urls.py
@ -1,23 +1,23 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
from .views import *
|
from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("<slug>/quizzes/", quiz_list, name="quiz_index"),
|
path("<slug>/quizzes/", views.quiz_list, name="quiz_index"),
|
||||||
path("progress/", view=QuizUserProgressView.as_view(), name="quiz_progress"),
|
path("progress/", view=views.QuizUserProgressView.as_view(), name="quiz_progress"),
|
||||||
# path('marking/<int:pk>/', view=QuizMarkingList.as_view(), name='quiz_marking'),
|
# 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(
|
path(
|
||||||
"marking/<int:pk>/",
|
"marking/<int:pk>/",
|
||||||
view=QuizMarkingDetail.as_view(),
|
view=views.QuizMarkingDetail.as_view(),
|
||||||
name="quiz_marking_detail",
|
name="quiz_marking_detail",
|
||||||
),
|
),
|
||||||
path("<int:pk>/<slug>/take/", view=QuizTake.as_view(), name="quiz_take"),
|
path("<int:pk>/<slug>/take/", view=views.QuizTake.as_view(), name="quiz_take"),
|
||||||
path("<slug>/quiz_add/", QuizCreateView.as_view(), name="quiz_create"),
|
path("<slug>/quiz_add/", views.QuizCreateView.as_view(), name="quiz_create"),
|
||||||
path("<slug>/<int:pk>/add/", QuizUpdateView.as_view(), name="quiz_update"),
|
path("<slug>/<int:pk>/add/", views.QuizUpdateView.as_view(), name="quiz_update"),
|
||||||
path("<slug>/<int:pk>/delete/", quiz_delete, name="quiz_delete"),
|
path("<slug>/<int:pk>/delete/", views.quiz_delete, name="quiz_delete"),
|
||||||
path(
|
path(
|
||||||
"mc-question/add/<slug>/<int:quiz_id>/",
|
"mc-question/add/<slug>/<int:quiz_id>/",
|
||||||
MCQuestionCreate.as_view(),
|
views.MCQuestionCreate.as_view(),
|
||||||
name="mc_create",
|
name="mc_create",
|
||||||
),
|
),
|
||||||
# path('mc-question/add/<int:pk>/<quiz_pk>/', MCQuestionCreate.as_view(), name='mc_create'),
|
# path('mc-question/add/<int:pk>/<quiz_pk>/', MCQuestionCreate.as_view(), name='mc_create'),
|
||||||
|
|||||||
325
quiz/views.py
325
quiz/views.py
@ -1,126 +1,140 @@
|
|||||||
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.db import transaction
|
||||||
from django.shortcuts import get_object_or_404, render, redirect
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views.generic import (
|
from django.views.generic import (
|
||||||
|
CreateView,
|
||||||
DetailView,
|
DetailView,
|
||||||
|
FormView,
|
||||||
ListView,
|
ListView,
|
||||||
TemplateView,
|
TemplateView,
|
||||||
FormView,
|
|
||||||
CreateView,
|
|
||||||
UpdateView,
|
UpdateView,
|
||||||
)
|
)
|
||||||
from django.contrib import messages
|
|
||||||
from django.db import transaction
|
|
||||||
|
|
||||||
from accounts.decorators import lecturer_required
|
from accounts.decorators import lecturer_required
|
||||||
from .models import Course, Progress, Sitting, EssayQuestion, Quiz, MCQuestion, Question
|
|
||||||
from .forms import (
|
from .forms import (
|
||||||
QuizAddForm,
|
EssayForm,
|
||||||
MCQuestionForm,
|
MCQuestionForm,
|
||||||
MCQuestionFormSet,
|
MCQuestionFormSet,
|
||||||
QuestionForm,
|
QuestionForm,
|
||||||
EssayForm,
|
QuizAddForm,
|
||||||
)
|
)
|
||||||
|
from .models import (
|
||||||
|
Course,
|
||||||
|
EssayQuestion,
|
||||||
|
MCQuestion,
|
||||||
|
Progress,
|
||||||
|
Question,
|
||||||
|
Quiz,
|
||||||
|
Sitting,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ########################################################
|
||||||
|
# Quiz Views
|
||||||
|
# ########################################################
|
||||||
|
|
||||||
|
|
||||||
@method_decorator([login_required, lecturer_required], name="dispatch")
|
@method_decorator([login_required, lecturer_required], name="dispatch")
|
||||||
class QuizCreateView(CreateView):
|
class QuizCreateView(CreateView):
|
||||||
model = Quiz
|
model = Quiz
|
||||||
form_class = QuizAddForm
|
form_class = QuizAddForm
|
||||||
|
template_name = "quiz/quiz_form.html"
|
||||||
|
|
||||||
def get_context_data(self, *args, **kwargs):
|
def get_initial(self):
|
||||||
context = super(QuizCreateView, self).get_context_data(**kwargs)
|
initial = super().get_initial()
|
||||||
context["course"] = Course.objects.get(slug=self.kwargs["slug"])
|
course = get_object_or_404(Course, slug=self.kwargs["slug"])
|
||||||
if self.request.POST:
|
initial["course"] = course
|
||||||
context["form"] = QuizAddForm(self.request.POST)
|
return initial
|
||||||
# context['quiz'] = self.request.POST.get('quiz')
|
|
||||||
else:
|
def get_context_data(self, **kwargs):
|
||||||
context["form"] = QuizAddForm(
|
context = super().get_context_data(**kwargs)
|
||||||
initial={"course": Course.objects.get(slug=self.kwargs["slug"])}
|
context["course"] = get_object_or_404(Course, slug=self.kwargs["slug"])
|
||||||
)
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def form_valid(self, form, **kwargs):
|
def form_valid(self, form):
|
||||||
context = self.get_context_data()
|
form.instance.course = get_object_or_404(Course, slug=self.kwargs["slug"])
|
||||||
form = context["form"]
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
self.object = form.save()
|
self.object = form.save()
|
||||||
if form.is_valid():
|
return redirect(
|
||||||
form.instance = self.object
|
"mc_create", slug=self.kwargs["slug"], quiz_id=self.object.id
|
||||||
form.save()
|
)
|
||||||
return redirect(
|
|
||||||
"mc_create", slug=self.kwargs["slug"], quiz_id=form.instance.id
|
|
||||||
)
|
|
||||||
return super(QuizCreateView, self).form_invalid(form)
|
|
||||||
|
|
||||||
|
|
||||||
@method_decorator([login_required, lecturer_required], name="dispatch")
|
@method_decorator([login_required, lecturer_required], name="dispatch")
|
||||||
class QuizUpdateView(UpdateView):
|
class QuizUpdateView(UpdateView):
|
||||||
model = Quiz
|
model = Quiz
|
||||||
form_class = QuizAddForm
|
form_class = QuizAddForm
|
||||||
|
template_name = "quiz/quiz_form.html"
|
||||||
|
|
||||||
def get_context_data(self, *args, **kwargs):
|
def get_object(self, queryset=None):
|
||||||
context = super(QuizUpdateView, self).get_context_data(**kwargs)
|
return get_object_or_404(Quiz, pk=self.kwargs["pk"])
|
||||||
context["course"] = Course.objects.get(slug=self.kwargs["slug"])
|
|
||||||
quiz = Quiz.objects.get(pk=self.kwargs["pk"])
|
def get_context_data(self, **kwargs):
|
||||||
if self.request.POST:
|
context = super().get_context_data(**kwargs)
|
||||||
context["form"] = QuizAddForm(self.request.POST, instance=quiz)
|
context["course"] = get_object_or_404(Course, slug=self.kwargs["slug"])
|
||||||
else:
|
|
||||||
context["form"] = QuizAddForm(instance=quiz)
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def form_valid(self, form, **kwargs):
|
def form_valid(self, form):
|
||||||
context = self.get_context_data()
|
|
||||||
course = context["course"]
|
|
||||||
form = context["form"]
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
self.object = form.save()
|
self.object = form.save()
|
||||||
if form.is_valid():
|
return redirect("quiz_index", self.kwargs["slug"])
|
||||||
form.instance = self.object
|
|
||||||
form.save()
|
|
||||||
return redirect("quiz_index", course.slug)
|
|
||||||
return super(QuizUpdateView, self).form_invalid(form)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@lecturer_required
|
@lecturer_required
|
||||||
def quiz_delete(request, slug, pk):
|
def quiz_delete(request, slug, pk):
|
||||||
quiz = Quiz.objects.get(pk=pk)
|
quiz = get_object_or_404(Quiz, pk=pk)
|
||||||
course = Course.objects.get(slug=slug)
|
|
||||||
quiz.delete()
|
quiz.delete()
|
||||||
messages.success(request, f"successfuly deleted.")
|
messages.success(request, "Quiz successfully deleted.")
|
||||||
return redirect("quiz_index", quiz.course.slug)
|
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")
|
@method_decorator([login_required, lecturer_required], name="dispatch")
|
||||||
class MCQuestionCreate(CreateView):
|
class MCQuestionCreate(CreateView):
|
||||||
model = MCQuestion
|
model = MCQuestion
|
||||||
form_class = MCQuestionForm
|
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):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(MCQuestionCreate, self).get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context["course"] = Course.objects.get(slug=self.kwargs["slug"])
|
context["course"] = get_object_or_404(Course, slug=self.kwargs["slug"])
|
||||||
context["quiz_obj"] = Quiz.objects.get(id=self.kwargs["quiz_id"])
|
context["quiz_obj"] = get_object_or_404(Quiz, id=self.kwargs["quiz_id"])
|
||||||
context["quizQuestions"] = Question.objects.filter(
|
context["quiz_questions_count"] = Question.objects.filter(
|
||||||
quiz=self.kwargs["quiz_id"]
|
quiz=self.kwargs["quiz_id"]
|
||||||
).count()
|
).count()
|
||||||
if self.request.POST:
|
if self.request.method == "POST":
|
||||||
context["form"] = MCQuestionForm(self.request.POST)
|
|
||||||
context["formset"] = MCQuestionFormSet(self.request.POST)
|
context["formset"] = MCQuestionFormSet(self.request.POST)
|
||||||
else:
|
else:
|
||||||
context["form"] = MCQuestionForm(initial={"quiz": self.kwargs["quiz_id"]})
|
|
||||||
context["formset"] = MCQuestionFormSet()
|
context["formset"] = MCQuestionFormSet()
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
context = self.get_context_data()
|
context = self.get_context_data()
|
||||||
formset = context["formset"]
|
formset = context["formset"]
|
||||||
course = context["course"]
|
|
||||||
if formset.is_valid():
|
if formset.is_valid():
|
||||||
with transaction.atomic():
|
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()
|
self.object = form.save()
|
||||||
formset.instance = self.object
|
formset.instance = self.object
|
||||||
formset.save()
|
formset.save()
|
||||||
@ -130,193 +144,131 @@ class MCQuestionCreate(CreateView):
|
|||||||
slug=self.kwargs["slug"],
|
slug=self.kwargs["slug"],
|
||||||
quiz_id=self.kwargs["quiz_id"],
|
quiz_id=self.kwargs["quiz_id"],
|
||||||
)
|
)
|
||||||
return redirect("quiz_index", course.slug)
|
return redirect("quiz_index", slug=self.kwargs["slug"])
|
||||||
else:
|
else:
|
||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
return super(MCQuestionCreate, self).form_invalid(form)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
# ########################################################
|
||||||
def quiz_list(request, slug):
|
# Quiz Progress and Marking Views
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
@method_decorator([login_required], name="dispatch")
|
@method_decorator([login_required], name="dispatch")
|
||||||
class QuizUserProgressView(TemplateView):
|
class QuizUserProgressView(TemplateView):
|
||||||
template_name = "progress.html"
|
template_name = "quiz/progress.html"
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
|
||||||
return super(QuizUserProgressView, self).dispatch(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
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)
|
progress, _ = Progress.objects.get_or_create(user=self.request.user)
|
||||||
context["cat_scores"] = progress.list_all_cat_scores
|
context["cat_scores"] = progress.list_all_cat_scores
|
||||||
context["exams"] = progress.show_exams()
|
context["exams"] = progress.show_exams()
|
||||||
context["exams_counter"] = progress.show_exams().count()
|
context["exams_counter"] = context["exams"].count()
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
@method_decorator([login_required, lecturer_required], name="dispatch")
|
@method_decorator([login_required, lecturer_required], name="dispatch")
|
||||||
class QuizMarkingList(QuizMarkerMixin, SittingFilterTitleMixin, ListView):
|
class QuizMarkingList(ListView):
|
||||||
model = Sitting
|
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):
|
def get_queryset(self):
|
||||||
if self.request.user.is_superuser:
|
queryset = Sitting.objects.filter(complete=True)
|
||||||
queryset = super(QuizMarkingList, self).get_queryset().filter(complete=True)
|
if not self.request.user.is_superuser:
|
||||||
else:
|
queryset = queryset.filter(
|
||||||
queryset = (
|
quiz__course__allocated_course__lecturer__pk=self.request.user.id
|
||||||
super(QuizMarkingList, self)
|
|
||||||
.get_queryset()
|
|
||||||
.filter(
|
|
||||||
quiz__course__allocated_course__lecturer__pk=self.request.user.id
|
|
||||||
)
|
|
||||||
.filter(complete=True)
|
|
||||||
)
|
)
|
||||||
|
quiz_filter = self.request.GET.get("quiz_filter")
|
||||||
# search by user
|
if quiz_filter:
|
||||||
|
queryset = queryset.filter(quiz__title__icontains=quiz_filter)
|
||||||
user_filter = self.request.GET.get("user_filter")
|
user_filter = self.request.GET.get("user_filter")
|
||||||
if user_filter:
|
if user_filter:
|
||||||
queryset = queryset.filter(user__username__icontains=user_filter)
|
queryset = queryset.filter(user__username__icontains=user_filter)
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
@method_decorator([login_required, lecturer_required], name="dispatch")
|
@method_decorator([login_required, lecturer_required], name="dispatch")
|
||||||
class QuizMarkingDetail(QuizMarkerMixin, DetailView):
|
class QuizMarkingDetail(DetailView):
|
||||||
model = Sitting
|
model = Sitting
|
||||||
|
template_name = "quiz/quiz_marking_detail.html"
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
sitting = self.get_object()
|
sitting = self.get_object()
|
||||||
|
question_id = request.POST.get("qid")
|
||||||
q_to_toggle = request.POST.get("qid", None)
|
if question_id:
|
||||||
if q_to_toggle:
|
question = Question.objects.get_subclass(id=int(question_id))
|
||||||
q = Question.objects.get_subclass(id=int(q_to_toggle))
|
if int(question_id) in sitting.get_incorrect_questions:
|
||||||
if int(q_to_toggle) in sitting.get_incorrect_questions:
|
sitting.remove_incorrect_question(question)
|
||||||
sitting.remove_incorrect_question(q)
|
|
||||||
else:
|
else:
|
||||||
sitting.add_incorrect_question(q)
|
sitting.add_incorrect_question(question)
|
||||||
|
return self.get(request, *args, **kwargs)
|
||||||
return self.get(request)
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(QuizMarkingDetail, self).get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context["questions"] = context["sitting"].get_questions(with_answers=True)
|
context["questions"] = self.object.get_questions(with_answers=True)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
# @method_decorator([login_required, student_required], name='dispatch')
|
# ########################################################
|
||||||
|
# Quiz Taking View
|
||||||
|
# ########################################################
|
||||||
|
|
||||||
|
|
||||||
@method_decorator([login_required], name="dispatch")
|
@method_decorator([login_required], name="dispatch")
|
||||||
class QuizTake(FormView):
|
class QuizTake(FormView):
|
||||||
form_class = QuestionForm
|
form_class = QuestionForm
|
||||||
template_name = "question.html"
|
template_name = "quiz/question.html"
|
||||||
result_template_name = "result.html"
|
result_template_name = "quiz/result.html"
|
||||||
# single_complete_template_name = 'single_complete.html'
|
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
self.quiz = get_object_or_404(Quiz, slug=self.kwargs["slug"])
|
self.quiz = get_object_or_404(Quiz, slug=self.kwargs["slug"])
|
||||||
self.course = get_object_or_404(Course, pk=self.kwargs["pk"])
|
self.course = get_object_or_404(Course, pk=self.kwargs["pk"])
|
||||||
quizQuestions = Question.objects.filter(quiz=self.quiz).count()
|
if not Question.objects.filter(quiz=self.quiz).exists():
|
||||||
|
messages.warning(request, "This quiz has no questions available.")
|
||||||
if quizQuestions <= 0:
|
return redirect("quiz_index", slug=self.course.slug)
|
||||||
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
|
|
||||||
|
|
||||||
self.sitting = Sitting.objects.user_sitting(
|
self.sitting = Sitting.objects.user_sitting(
|
||||||
request.user, self.quiz, self.course
|
request.user, self.quiz, self.course
|
||||||
)
|
)
|
||||||
|
if not self.sitting:
|
||||||
if self.sitting is False:
|
|
||||||
# return render(request, self.single_complete_template_name)
|
|
||||||
messages.info(
|
messages.info(
|
||||||
request,
|
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 redirect("quiz_index", slug=self.course.slug)
|
||||||
|
return super().dispatch(request, *args, **kwargs)
|
||||||
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())
|
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
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):
|
def form_valid(self, form):
|
||||||
self.form_valid_user(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()
|
return self.final_result_user()
|
||||||
|
return super().get(self.request)
|
||||||
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
|
|
||||||
|
|
||||||
def form_valid_user(self, form):
|
def form_valid_user(self, form):
|
||||||
progress, _ = Progress.objects.get_or_create(user=self.request.user)
|
progress, _ = Progress.objects.get_or_create(user=self.request.user)
|
||||||
guess = form.cleaned_data["answers"]
|
guess = form.cleaned_data["answers"]
|
||||||
is_correct = self.question.check_if_correct(guess)
|
is_correct = self.question.check_if_correct(guess)
|
||||||
|
|
||||||
if is_correct is True:
|
if is_correct:
|
||||||
self.sitting.add_to_score(1)
|
self.sitting.add_to_score(1)
|
||||||
progress.update_score(self.question, 1, 1)
|
progress.update_score(self.question, 1, 1)
|
||||||
else:
|
else:
|
||||||
self.sitting.add_incorrect_question(self.question)
|
self.sitting.add_incorrect_question(self.question)
|
||||||
progress.update_score(self.question, 0, 1)
|
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 = {
|
self.previous = {
|
||||||
"previous_answer": guess,
|
"previous_answer": guess,
|
||||||
"previous_outcome": is_correct,
|
"previous_outcome": is_correct,
|
||||||
@ -330,26 +282,35 @@ class QuizTake(FormView):
|
|||||||
self.sitting.add_user_answer(self.question, guess)
|
self.sitting.add_user_answer(self.question, guess)
|
||||||
self.sitting.remove_first_question()
|
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):
|
def final_result_user(self):
|
||||||
|
self.sitting.mark_quiz_complete()
|
||||||
results = {
|
results = {
|
||||||
"course": get_object_or_404(Course, pk=self.kwargs["pk"]),
|
"course": self.course,
|
||||||
"quiz": self.quiz,
|
"quiz": self.quiz,
|
||||||
"score": self.sitting.get_current_score,
|
"score": self.sitting.get_current_score,
|
||||||
"max_score": self.sitting.get_max_score,
|
"max_score": self.sitting.get_max_score,
|
||||||
"percent": self.sitting.get_percent_correct,
|
"percent": self.sitting.get_percent_correct,
|
||||||
"sitting": self.sitting,
|
"sitting": self.sitting,
|
||||||
"previous": self.previous,
|
"previous": getattr(self, "previous", {}),
|
||||||
"course": get_object_or_404(Course, pk=self.kwargs["pk"]),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.sitting.mark_quiz_complete()
|
|
||||||
|
|
||||||
if self.quiz.answers_at_end:
|
if self.quiz.answers_at_end:
|
||||||
results["questions"] = self.sitting.get_questions(with_answers=True)
|
results["questions"] = self.sitting.get_questions(with_answers=True)
|
||||||
results["incorrect_questions"] = self.sitting.get_incorrect_questions
|
results["incorrect_questions"] = self.sitting.get_incorrect_questions
|
||||||
|
|
||||||
if (
|
if (
|
||||||
self.quiz.exam_paper is False
|
not self.quiz.exam_paper
|
||||||
or self.request.user.is_superuser
|
or self.request.user.is_superuser
|
||||||
or self.request.user.is_lecturer
|
or self.request.user.is_lecturer
|
||||||
):
|
):
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user