This commit is contained in:
Ross
2021-12-18 09:14:41 +00:00
parent 61c806594a
commit 2cc73e62ff
6 changed files with 296 additions and 158 deletions
+1 -1
View File
@@ -78,7 +78,7 @@ urlpatterns = [
), ),
path( path(
"exam/<int:pk>/scores", "exam/<int:pk>/scores",
views.exam_scores_cid, views.AnatomyExamViews.exam_scores_cid,
# cache_page(60 * 1)(views.exam_scores_cid), # cache_page(60 * 1)(views.exam_scores_cid),
name="exam_scores_cid", name="exam_scores_cid",
), ),
+118 -118
View File
@@ -475,124 +475,124 @@ def exam_scores_refresh(request, pk):
}, },
) )
@login_required #@login_required
def exam_scores_cid(request, pk): #def exam_scores_cid(request, pk):
exam = get_object_or_404(Exam, pk=pk) # exam = get_object_or_404(Exam, pk=pk)
#
if not exam.exam_mode: # if not exam.exam_mode:
raise Http404("Packet not in exam mode") # raise Http404("Packet not in exam mode")
#
questions = exam.exam_questions.all() # questions = exam.exam_questions.all()
#
cids = ( # cids = (
CidUserAnswer.objects.filter(question__in=questions, exam__id=pk) # CidUserAnswer.objects.filter(question__in=questions, exam__id=pk)
.order_by("cid") # .order_by("cid")
.values_list("cid", flat=True) # .values_list("cid", flat=True)
.distinct() # .distinct()
) # )
#
user_answers_and_marks = defaultdict(list) # user_answers_and_marks = defaultdict(list)
user_answers_marks = defaultdict(list) # user_answers_marks = defaultdict(list)
user_answers = defaultdict(list) # user_answers = defaultdict(list)
user_names = {} # user_names = {}
#
by_question = defaultdict(list) # by_question = defaultdict(list)
unmarked = set() # unmarked = set()
#
# Loop through all candidates # # Loop through all candidates
for cid in cids: # for cid in cids:
# Convoluted (probably...) # # Convoluted (probably...)
user_names[cid] = cid # user_names[cid] = cid
for q in questions: # for q in questions:
# Get user answer # # Get user answer
s = q.cid_user_answers.filter(cid=cid, exam__id=pk).first() # s = q.cid_user_answers.filter(cid=cid, exam__id=pk).first()
if not s: # if not s:
# skip if no answer # # skip if no answer
user_answers_marks[cid].append(0) # user_answers_marks[cid].append(0)
user_answers[cid].append("") # user_answers[cid].append("")
by_question[q].append(("", 0)) # by_question[q].append(("", 0))
continue # continue
#
ans = s.answer # ans = s.answer
answer_score = s.get_answer_score() # answer_score = s.get_answer_score()
if answer_score == "unmarked": # if answer_score == "unmarked":
index = exam.get_question_index(q) # index = exam.get_question_index(q)
unmarked.add(index) # unmarked.add(index)
user_answers[cid].append(ans) # user_answers[cid].append(ans)
user_answers_marks[cid].append(answer_score) # user_answers_marks[cid].append(answer_score)
user_answers_and_marks[cid].append((ans, answer_score)) # user_answers_and_marks[cid].append((ans, answer_score))
#
by_question[q].append((ans, answer_score)) # by_question[q].append((ans, answer_score))
#
user_scores = {} # user_scores = {}
for user in user_answers_marks: # for user in user_answers_marks:
user_scores[user] = sum( # user_scores[user] = sum(
[i for i in user_answers_marks[user] if i != "unmarked"] # [i for i in user_answers_marks[user] if i != "unmarked"]
) # )
#
user_scores_list = list(user_scores.values()) # user_scores_list = list(user_scores.values())
#
if len(user_scores_list) < 1: # if len(user_scores_list) < 1:
mean = 0 # mean = 0
median = 0 # median = 0
mode = 0 # mode = 0
fig_html = "" # fig_html = ""
else: # else:
mean = statistics.mean(user_scores_list) # mean = statistics.mean(user_scores_list)
median = statistics.median(user_scores_list) # median = statistics.median(user_scores_list)
try: # try:
mode = statistics.mode(user_scores_list) # mode = statistics.mode(user_scores_list)
except statistics.StatisticsError: # except statistics.StatisticsError:
mode = "No unique mode" # mode = "No unique mode"
#
df = user_scores_list # df = user_scores_list
fig = px.histogram( # fig = px.histogram(
df, # df,
x=0, # x=0,
title="{}: distribution of scores".format(exam), # title="{}: distribution of scores".format(exam),
labels={"0": "Score"}, # labels={"0": "Score"},
height=400, # height=400,
width=600, # width=600,
) # )
fig_html = fig.to_html() # fig_html = fig.to_html()
#
max_score = len(questions) * 2 # max_score = len(questions) * 2
#
exam.stats_mean = mean # exam.stats_mean = mean
exam.stats_median = median # exam.stats_median = median
exam.stats_mode = mode # exam.stats_mode = mode
#
exam.stats_candidates = len(user_scores_list) # exam.stats_candidates = len(user_scores_list)
exam.stats_max_possible = max_score # exam.stats_max_possible = max_score
#
exam.stats_min = min(user_scores_list) # exam.stats_min = min(user_scores_list)
exam.stats_max = max(user_scores_list) # exam.stats_max = max(user_scores_list)
#
exam.stats_graph = fig_html # exam.stats_graph = fig_html
exam.save() # exam.save()
#
return render( # return render(
request, # request,
"anatomy/exam_scores.html", # "anatomy/exam_scores.html",
{ # {
"cids": cids, # "cids": cids,
"exam": exam, # "exam": exam,
"unmarked": unmarked, # "unmarked": unmarked,
"questions": questions, # "questions": questions,
"by_question": by_question, # "by_question": by_question,
"user_answers": dict(user_answers), # "user_answers": dict(user_answers),
"user_answers_marks": dict(user_answers_marks), # "user_answers_marks": dict(user_answers_marks),
"user_scores": user_scores, # "user_scores": user_scores,
"user_scores_list": user_scores_list, # "user_scores_list": user_scores_list,
"user_names": user_names, # "user_names": user_names,
"user_answers_and_marks": user_answers_and_marks, # "user_answers_and_marks": user_answers_and_marks,
"max_score": max_score, # "max_score": max_score,
"mean": mean, # "mean": mean,
"median": median, # "median": median,
"mode": mode, # "mode": mode,
"plot": fig_html, # "plot": fig_html,
}, # },
) # )
def exam_scores_cid_user(request, pk, sk, passcode): def exam_scores_cid_user(request, pk, sk, passcode):
+126
View File
@@ -1,3 +1,5 @@
from collections import defaultdict
import statistics
from dal import autocomplete from dal import autocomplete
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
@@ -54,6 +56,7 @@ from datetime import datetime
import os import os
import string import string
import random import random
import plotly.express as px
# from rad.views import get_question_and_content_type # from rad.views import get_question_and_content_type
@@ -833,6 +836,129 @@ class ExamViews(View, LoginRequiredMixin):
return JsonResponse(question_json) return JsonResponse(question_json)
@login_required
def exam_scores_cid(self, request, pk):
exam = get_object_or_404(self.Exam, pk=pk)
if request.user not in exam.author.all():
if not self.check_user_access(request.user, pk):
raise PermissionDenied
if not exam.exam_mode:
raise Http404("Packet not in exam mode")
questions = exam.exam_questions.all()
cids = (
self.CidUserAnswer.objects.filter(question__in=questions, exam__id=pk)
.order_by("cid")
.values_list("cid", flat=True)
.distinct()
)
user_answers_and_marks = defaultdict(list)
user_answers_marks = defaultdict(list)
user_answers = defaultdict(list)
user_names = {}
by_question = defaultdict(list)
unmarked = set()
# Loop through all candidates
for cid in cids:
# Convoluted (probably...)
user_names[cid] = cid
for q in questions:
# Get user answer
s = q.cid_user_answers.filter(cid=cid, exam__id=pk).first()
if not s:
# skip if no answer
user_answers_marks[cid].append(0)
user_answers[cid].append("")
by_question[q].append(("", 0))
continue
ans = s.answer
answer_score = s.get_answer_score()
if answer_score == "unmarked":
index = exam.get_question_index(q)
unmarked.add(index)
user_answers[cid].append(ans)
user_answers_marks[cid].append(answer_score)
user_answers_and_marks[cid].append((ans, answer_score))
by_question[q].append((ans, answer_score))
user_scores = {}
for user in user_answers_marks:
user_scores[user] = sum(
[i for i in user_answers_marks[user] if i != "unmarked"]
)
user_scores_list = list(user_scores.values())
if len(user_scores_list) < 1:
mean = 0
median = 0
mode = 0
fig_html = ""
else:
mean = statistics.mean(user_scores_list)
median = statistics.median(user_scores_list)
try:
mode = statistics.mode(user_scores_list)
except statistics.StatisticsError:
mode = "No unique mode"
df = user_scores_list
fig = px.histogram(
df,
x=0,
title="{}: distribution of scores".format(exam),
labels={"0": "Score"},
height=400,
width=600,
)
fig_html = fig.to_html()
max_score = len(questions) * 2
exam.stats_mean = mean
exam.stats_median = median
exam.stats_mode = mode
exam.stats_candidates = len(user_scores_list)
exam.stats_max_possible = max_score
exam.stats_min = min(user_scores_list)
exam.stats_max = max(user_scores_list)
exam.stats_graph = fig_html
exam.save()
return render(
request,
f"{self.app_name}/exam_scores.html",
{
"cids": cids,
"exam": exam,
"unmarked": unmarked,
"questions": questions,
"by_question": by_question,
"user_answers": dict(user_answers),
"user_answers_marks": dict(user_answers_marks),
"user_scores": user_scores,
"user_scores_list": user_scores_list,
"user_names": user_names,
"user_answers_and_marks": user_answers_and_marks,
"max_score": max_score,
"mean": mean,
"median": median,
"mode": mode,
"plot": fig_html,
},
)
class GenericViewBase: class GenericViewBase:
def __init__(self, app_name, QuestionObject, CidUserAnswerObject, ExamObject): def __init__(self, app_name, QuestionObject, CidUserAnswerObject, ExamObject):
+27 -27
View File
@@ -16,12 +16,14 @@ from generic.models import CidUser, ExamBase, QuestionNote
import reversion import reversion
class Category(models.Model): class Category(models.Model):
category = models.CharField(max_length=200) category = models.CharField(max_length=200)
def __str__(self): def __str__(self):
return self.category return self.category
class Question(models.Model): class Question(models.Model):
stem = models.TextField( stem = models.TextField(
blank=False, blank=False,
@@ -34,9 +36,7 @@ class Question(models.Model):
blank=False, blank=False,
help_text="The question text", help_text="The question text",
) )
a_answer = models.BooleanField( a_answer = models.BooleanField(help_text=answer_help_text, default=True)
help_text=answer_help_text, default=True
)
a_feedback = models.TextField( a_feedback = models.TextField(
blank=True, blank=True,
help_text="Feedback for answer", help_text="Feedback for answer",
@@ -46,9 +46,7 @@ class Question(models.Model):
blank=False, blank=False,
help_text="The question text", help_text="The question text",
) )
b_answer = models.BooleanField( b_answer = models.BooleanField(help_text=answer_help_text, default=True)
help_text=answer_help_text, default=True
)
b_feedback = models.TextField( b_feedback = models.TextField(
blank=True, blank=True,
help_text="Feedback for answer", help_text="Feedback for answer",
@@ -58,9 +56,7 @@ class Question(models.Model):
blank=False, blank=False,
help_text="The question text", help_text="The question text",
) )
c_answer = models.BooleanField( c_answer = models.BooleanField(help_text=answer_help_text, default=True)
help_text=answer_help_text, default=True
)
c_feedback = models.TextField( c_feedback = models.TextField(
blank=True, blank=True,
help_text="Feedback for answer", help_text="Feedback for answer",
@@ -70,9 +66,7 @@ class Question(models.Model):
blank=False, blank=False,
help_text="The question text", help_text="The question text",
) )
d_answer = models.BooleanField( d_answer = models.BooleanField(help_text=answer_help_text, default=True)
help_text=answer_help_text, default=True
)
d_feedback = models.TextField( d_feedback = models.TextField(
blank=True, blank=True,
help_text="Feedback for answer", help_text="Feedback for answer",
@@ -82,9 +76,7 @@ class Question(models.Model):
blank=False, blank=False,
help_text="The question text", help_text="The question text",
) )
e_answer = models.BooleanField( e_answer = models.BooleanField(help_text=answer_help_text, default=True)
help_text=answer_help_text, default=True
)
e_feedback = models.TextField( e_feedback = models.TextField(
blank=True, blank=True,
help_text="Feedback for answer", help_text="Feedback for answer",
@@ -124,7 +116,13 @@ class Question(models.Model):
return reverse("physics:question_detail", kwargs={"pk": self.pk}) return reverse("physics:question_detail", kwargs={"pk": self.pk})
def get_answers(self): def get_answers(self):
return [self.a_answer, self.b_answer, self.c_answer, self.d_answer, self.e_answer] return [
self.a_answer,
self.b_answer,
self.c_answer,
self.d_answer,
self.e_answer,
]
def get_answers_js(self): def get_answers_js(self):
return str(self.get_answers()).lower() return str(self.get_answers()).lower()
@@ -136,10 +134,8 @@ class Question(models.Model):
@reversion.register @reversion.register
class Exam(ExamBase): class Exam(ExamBase):
app_name = "physics" app_name = "physics"
exam_questions = SortedManyToManyField( exam_questions = SortedManyToManyField(Question, related_name="exams", blank="true")
Question, related_name="exams", blank="true"
)
time_limit = models.IntegerField( time_limit = models.IntegerField(
help_text="Exam time limit (in seconds)", help_text="Exam time limit (in seconds)",
@@ -149,12 +145,16 @@ class Exam(ExamBase):
recreate_json = None recreate_json = None
author = models.ManyToManyField(settings.AUTH_USER_MODEL, author = models.ManyToManyField(
blank=True, settings.AUTH_USER_MODEL,
help_text='Author of exam', blank=True,
related_name="physics_exam_author") help_text="Author of exam",
related_name="physics_exam_author",
)
valid_users = models.ManyToManyField(CidUser, blank=True, related_name="physics_exams") valid_users = models.ManyToManyField(
CidUser, blank=True, related_name="physics_exams"
)
def get_take_url(self): def get_take_url(self):
return reverse("physics:exam_start", kwargs={"pk": self.pk}) return reverse("physics:exam_start", kwargs={"pk": self.pk})
@@ -189,7 +189,7 @@ class CidUserAnswer(models.Model):
def get_answers(self): def get_answers(self):
return (self.a, self.b, self.c, self.d, self.e) return (self.a, self.b, self.c, self.d, self.e)
def get_answer_scores(self): def get_answer_score(self):
q = self.question q = self.question
if self.a == q.a_answer: if self.a == q.a_answer:
@@ -217,4 +217,4 @@ class CidUserAnswer(models.Model):
else: else:
score_e = 0 score_e = 0
return (score_a, score_b, score_c, score_d, score_e) return (score_a, score_b, score_c, score_d, score_e)
@@ -1,11 +1,10 @@
{% extends 'physics/base.html' %} {% extends 'physics/base.html' %}
{% block content %} {% block content %}
CID: {{ciduseranswer.cid}}<br/> CID: {{ciduseranswer.cid}}<br />
Exam: {{ciduseranswer.exam}}<br/> Exam: {{ciduseranswer.exam}}<br />
{{ciduseranswer.question}}<br/> {{ciduseranswer.question}}<br />
Answers: <br/> Answers: <br />
<ol> <ol>
<li>{{ciduseranswer.a}}</li> <li>{{ciduseranswer.a}}</li>
<li>{{ciduseranswer.b}}</li> <li>{{ciduseranswer.b}}</li>
@@ -13,6 +12,6 @@ Answers: <br/>
<li>{{ciduseranswer.d}}</li> <li>{{ciduseranswer.d}}</li>
<li>{{ciduseranswer.e}}</li> <li>{{ciduseranswer.e}}</li>
</ol> </ol>
Score: {{ciduseranswer.get_answer_scores}}<br/> Score: {{ciduseranswer.get_answer_score}}<br />
<a href="{% url 'physics:user_answer_delete' ciduseranswer.id %}">Delete answer</a> <a href="{% url 'physics:user_answer_delete' ciduseranswer.id %}">Delete answer</a>
{% endblock content %} {% endblock content %}
+19 -6
View File
@@ -91,6 +91,9 @@ def active_exams(request):
def exam_scores_cid(request, pk): def exam_scores_cid(request, pk):
exam = get_object_or_404(Exam, pk=pk) exam = get_object_or_404(Exam, pk=pk)
if not exam.exam_mode:
raise Http404("Packet not in exam mode")
questions = exam.exam_questions.all() questions = exam.exam_questions.all()
cids = ( cids = (
@@ -117,13 +120,21 @@ def exam_scores_cid(request, pk):
s = q.cid_user_answers.filter(cid=cid, exam__id=pk).first() s = q.cid_user_answers.filter(cid=cid, exam__id=pk).first()
if not s: if not s:
# skip if no answer # skip if no answer
user_answers_marks[cid].append((0,0,0,0,0)) user_answers_marks[cid].append((0, 0, 0, 0, 0))
user_answers[cid].append(("", "", "", "", "")) user_answers[cid].append(("", "", "", "", ""))
by_question[q].append((("", 0),("", 0),("", 0),("", 0),("", 0),)) by_question[q].append(
(
("", 0),
("", 0),
("", 0),
("", 0),
("", 0),
)
)
continue continue
ans = s.get_answers() ans = s.get_answers()
answer_scores = s.get_answer_scores() answer_scores = s.get_answer_score()
user_answers[cid].append(ans) user_answers[cid].append(ans)
user_answers_marks[cid].append(answer_scores) user_answers_marks[cid].append(answer_scores)
@@ -212,7 +223,7 @@ def exam_scores_cid_user(request, pk, sk, passcode):
ans = user_answer.get_answers() ans = user_answer.get_answers()
answer_scores = user_answer.get_answer_scores() answer_scores = user_answer.get_answer_score()
correct_answers = q.get_answers() correct_answers = q.get_answers()
@@ -235,7 +246,7 @@ def exam_scores_cid_user(request, pk, sk, passcode):
{ {
"exam": exam, "exam": exam,
"cid": cid, "cid": cid,
"passcode" : passcode, "passcode": passcode,
"questions": questions, "questions": questions,
"answers": answers, "answers": answers,
"answers_marks": answers_marks, "answers_marks": answers_marks,
@@ -452,7 +463,9 @@ def loadJsonAnswer(answer):
return True, None return True, None
PhysicsExamViews = ExamViews(Exam, Question, CidUserAnswer, "physics", "physics", loadJsonAnswer) PhysicsExamViews = ExamViews(
Exam, Question, CidUserAnswer, "physics", "physics", loadJsonAnswer
)
GenericViews = GenericViewBase("physics", Question, CidUserAnswer, Exam) GenericViews = GenericViewBase("physics", Question, CidUserAnswer, Exam)